2012-09-28 22:29:48 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
# Copyright: (c) 2008, Jarek Zgoda <jarek.zgoda@gmail.com>
|
|
|
|
|
|
|
|
__revision__ = '$Id: models.py 28 2009-10-22 15:03:02Z jarek.zgoda $'
|
|
|
|
|
2017-07-08 06:25:05 +02:00
|
|
|
import datetime
|
2012-09-28 22:29:48 +02:00
|
|
|
|
|
|
|
from django.db import models
|
|
|
|
from django.core.urlresolvers import reverse
|
|
|
|
from django.conf import settings
|
|
|
|
from django.contrib.contenttypes.models import ContentType
|
2016-10-10 14:52:01 +02:00
|
|
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
2017-07-22 00:25:41 +02:00
|
|
|
from django.http import HttpRequest, HttpResponse
|
|
|
|
from django.shortcuts import render
|
2017-04-15 04:03:56 +02:00
|
|
|
from django.utils.timezone import now as timezone_now
|
2012-09-28 22:29:48 +02:00
|
|
|
|
2017-05-02 01:15:58 +02:00
|
|
|
from zerver.lib.send_email import send_email
|
2013-08-08 16:50:58 +02:00
|
|
|
from zerver.lib.utils import generate_random_token
|
2017-11-01 21:16:23 +01:00
|
|
|
from zerver.models import PreregistrationUser, EmailChangeStatus, MultiuseInvite, UserProfile
|
2017-07-11 20:52:27 +02:00
|
|
|
from random import SystemRandom
|
|
|
|
import string
|
2017-03-03 19:01:52 +01:00
|
|
|
from typing import Any, Dict, Optional, Text, Union
|
2012-09-28 22:29:48 +02:00
|
|
|
|
2017-07-22 00:24:42 +02:00
|
|
|
class ConfirmationKeyException(Exception):
|
|
|
|
WRONG_LENGTH = 1
|
|
|
|
EXPIRED = 2
|
|
|
|
DOES_NOT_EXIST = 3
|
|
|
|
|
2017-10-27 10:52:58 +02:00
|
|
|
def __init__(self, error_type: int) -> None:
|
2017-10-27 08:28:23 +02:00
|
|
|
super().__init__()
|
2017-07-22 00:24:42 +02:00
|
|
|
self.error_type = error_type
|
|
|
|
|
2017-10-27 10:52:58 +02:00
|
|
|
def render_confirmation_key_error(request: HttpRequest, exception: ConfirmationKeyException) -> HttpResponse:
|
2017-07-22 00:25:41 +02:00
|
|
|
if exception.error_type == ConfirmationKeyException.WRONG_LENGTH:
|
|
|
|
return render(request, 'confirmation/link_malformed.html')
|
|
|
|
if exception.error_type == ConfirmationKeyException.EXPIRED:
|
|
|
|
return render(request, 'confirmation/link_expired.html')
|
|
|
|
return render(request, 'confirmation/link_does_not_exist.html')
|
|
|
|
|
2017-10-27 10:52:58 +02:00
|
|
|
def generate_key() -> str:
|
2017-07-11 20:52:27 +02:00
|
|
|
generator = SystemRandom()
|
|
|
|
# 24 characters * 5 bits of entropy/character = 120 bits of entropy
|
|
|
|
return ''.join(generator.choice(string.ascii_lowercase + string.digits) for _ in range(24))
|
2013-02-28 20:07:04 +01:00
|
|
|
|
2017-11-08 03:36:44 +01:00
|
|
|
ConfirmationObjT = Union[MultiuseInvite, PreregistrationUser, EmailChangeStatus]
|
2017-10-27 10:52:58 +02:00
|
|
|
def get_object_from_key(confirmation_key: str,
|
2017-11-08 03:36:44 +01:00
|
|
|
confirmation_type: int) -> ConfirmationObjT:
|
2017-07-22 00:27:45 +02:00
|
|
|
# Confirmation keys used to be 40 characters
|
|
|
|
if len(confirmation_key) not in (24, 40):
|
|
|
|
raise ConfirmationKeyException(ConfirmationKeyException.WRONG_LENGTH)
|
2017-07-08 06:36:39 +02:00
|
|
|
try:
|
2017-11-01 21:07:39 +01:00
|
|
|
confirmation = Confirmation.objects.get(confirmation_key=confirmation_key,
|
|
|
|
type=confirmation_type)
|
2017-07-08 06:36:39 +02:00
|
|
|
except Confirmation.DoesNotExist:
|
2017-07-22 00:27:45 +02:00
|
|
|
raise ConfirmationKeyException(ConfirmationKeyException.DOES_NOT_EXIST)
|
2017-07-08 06:36:39 +02:00
|
|
|
|
|
|
|
time_elapsed = timezone_now() - confirmation.date_sent
|
2017-07-08 06:50:57 +02:00
|
|
|
if time_elapsed.total_seconds() > _properties[confirmation.type].validity_in_days * 24 * 3600:
|
2017-07-22 00:27:45 +02:00
|
|
|
raise ConfirmationKeyException(ConfirmationKeyException.EXPIRED)
|
2017-07-08 06:36:39 +02:00
|
|
|
|
|
|
|
obj = confirmation.content_object
|
2017-08-10 22:27:57 +02:00
|
|
|
if hasattr(obj, "status"):
|
|
|
|
obj.status = getattr(settings, 'STATUS_ACTIVE', 1)
|
|
|
|
obj.save(update_fields=['status'])
|
2017-07-08 06:36:39 +02:00
|
|
|
return obj
|
2017-07-08 04:18:58 +02:00
|
|
|
|
2017-10-27 10:52:58 +02:00
|
|
|
def create_confirmation_link(obj: Union[ContentType, int], host: str,
|
|
|
|
confirmation_type: int,
|
|
|
|
url_args: Optional[Dict[str, str]]=None) -> str:
|
2017-07-08 04:38:13 +02:00
|
|
|
key = generate_key()
|
2017-07-08 06:25:05 +02:00
|
|
|
Confirmation.objects.create(content_object=obj, date_sent=timezone_now(), confirmation_key=key,
|
|
|
|
type=confirmation_type)
|
2017-07-08 08:43:25 +02:00
|
|
|
return confirmation_url(key, host, confirmation_type, url_args)
|
2017-07-08 04:38:13 +02:00
|
|
|
|
2017-10-27 10:52:58 +02:00
|
|
|
def confirmation_url(confirmation_key: str, host: str,
|
|
|
|
confirmation_type: int,
|
|
|
|
url_args: Optional[Dict[str, str]]=None) -> str:
|
2017-07-08 08:43:25 +02:00
|
|
|
if url_args is None:
|
|
|
|
url_args = {}
|
|
|
|
url_args['confirmation_key'] = confirmation_key
|
|
|
|
return '%s%s%s' % (settings.EXTERNAL_URI_SCHEME, host,
|
|
|
|
reverse(_properties[confirmation_type].url_name, kwargs=url_args))
|
2017-01-17 11:11:51 +01:00
|
|
|
|
2012-09-28 22:29:48 +02:00
|
|
|
class Confirmation(models.Model):
|
|
|
|
content_type = models.ForeignKey(ContentType)
|
2017-07-08 06:25:05 +02:00
|
|
|
object_id = models.PositiveIntegerField() # type: int
|
2016-10-10 14:52:01 +02:00
|
|
|
content_object = GenericForeignKey('content_type', 'object_id')
|
2017-07-08 06:25:05 +02:00
|
|
|
date_sent = models.DateTimeField() # type: datetime.datetime
|
|
|
|
confirmation_key = models.CharField(max_length=40) # type: str
|
2012-09-28 22:29:48 +02:00
|
|
|
|
2017-07-08 06:25:05 +02:00
|
|
|
# The following list is the set of valid types
|
2017-07-08 04:38:13 +02:00
|
|
|
USER_REGISTRATION = 1
|
|
|
|
INVITATION = 2
|
|
|
|
EMAIL_CHANGE = 3
|
|
|
|
UNSUBSCRIBE = 4
|
|
|
|
SERVER_REGISTRATION = 5
|
2017-08-10 22:34:17 +02:00
|
|
|
MULTIUSE_INVITE = 6
|
2017-11-30 01:06:25 +01:00
|
|
|
REALM_CREATION = 7
|
2017-07-08 06:25:05 +02:00
|
|
|
type = models.PositiveSmallIntegerField() # type: int
|
2012-09-28 22:29:48 +02:00
|
|
|
|
2017-10-27 10:52:58 +02:00
|
|
|
def __str__(self) -> Text:
|
2017-07-08 06:25:05 +02:00
|
|
|
return '<Confirmation: %s>' % (self.content_object,)
|
2017-01-20 12:27:38 +01:00
|
|
|
|
2017-11-05 11:57:15 +01:00
|
|
|
class ConfirmationType:
|
2017-10-27 10:52:58 +02:00
|
|
|
def __init__(self, url_name: str,
|
|
|
|
validity_in_days: int=settings.CONFIRMATION_LINK_DEFAULT_VALIDITY_DAYS) -> None:
|
2017-07-08 04:38:13 +02:00
|
|
|
self.url_name = url_name
|
2017-07-08 06:50:57 +02:00
|
|
|
self.validity_in_days = validity_in_days
|
2017-07-08 04:38:13 +02:00
|
|
|
|
|
|
|
_properties = {
|
|
|
|
Confirmation.USER_REGISTRATION: ConfirmationType('confirmation.views.confirm'),
|
2017-07-08 06:50:57 +02:00
|
|
|
Confirmation.INVITATION: ConfirmationType('confirmation.views.confirm',
|
|
|
|
validity_in_days=settings.INVITATION_LINK_VALIDITY_DAYS),
|
2017-07-08 04:38:13 +02:00
|
|
|
Confirmation.EMAIL_CHANGE: ConfirmationType('zerver.views.user_settings.confirm_email_change'),
|
2017-07-08 08:43:25 +02:00
|
|
|
Confirmation.UNSUBSCRIBE: ConfirmationType('zerver.views.unsubscribe.email_unsubscribe',
|
|
|
|
validity_in_days=1000000), # should never expire
|
2017-11-10 04:33:28 +01:00
|
|
|
Confirmation.MULTIUSE_INVITE: ConfirmationType(
|
|
|
|
'zerver.views.registration.accounts_home_from_multiuse_invite',
|
2017-11-30 01:06:25 +01:00
|
|
|
validity_in_days=settings.INVITATION_LINK_VALIDITY_DAYS),
|
|
|
|
Confirmation.REALM_CREATION: ConfirmationType('confirmation.views.confirm'),
|
2017-07-08 04:38:13 +02:00
|
|
|
}
|
|
|
|
|
2017-11-30 00:48:34 +01:00
|
|
|
# Functions related to links generated by the generate_realm_creation_link.py
|
|
|
|
# management command.
|
|
|
|
# Note that being validated here will just allow the user to access the create_realm
|
|
|
|
# form, where they will enter their email and go through the regular
|
|
|
|
# Confirmation.REALM_CREATION pathway.
|
|
|
|
# Arguably RealmCreationKey should just be another ConfirmationObjT and we should
|
|
|
|
# add another Confirmation.type for this; it's this way for historical reasons.
|
2017-07-06 06:59:17 +02:00
|
|
|
|
2017-10-27 10:52:58 +02:00
|
|
|
def check_key_is_valid(creation_key: Text) -> bool:
|
2017-07-06 06:59:17 +02:00
|
|
|
if not RealmCreationKey.objects.filter(creation_key=creation_key).exists():
|
|
|
|
return False
|
2017-11-30 00:19:01 +01:00
|
|
|
time_elapsed = timezone_now() - RealmCreationKey.objects.get(creation_key=creation_key).date_created
|
|
|
|
if time_elapsed.total_seconds() > settings.REALM_CREATION_LINK_VALIDITY_DAYS * 24 * 3600:
|
|
|
|
return False
|
|
|
|
return True
|
2017-07-06 06:59:17 +02:00
|
|
|
|
2017-10-27 10:52:58 +02:00
|
|
|
def generate_realm_creation_url() -> Text:
|
2017-07-06 06:59:17 +02:00
|
|
|
key = generate_key()
|
|
|
|
RealmCreationKey.objects.create(creation_key=key, date_created=timezone_now())
|
|
|
|
return u'%s%s%s' % (settings.EXTERNAL_URI_SCHEME,
|
|
|
|
settings.EXTERNAL_HOST,
|
|
|
|
reverse('zerver.views.create_realm',
|
|
|
|
kwargs={'creation_key': key}))
|
|
|
|
|
2016-06-22 21:16:02 +02:00
|
|
|
class RealmCreationKey(models.Model):
|
2017-03-09 09:11:43 +01:00
|
|
|
creation_key = models.CharField('activation key', max_length=40)
|
2017-04-15 04:03:56 +02:00
|
|
|
date_created = models.DateTimeField('created', default=timezone_now)
|