2012-09-28 22:29:48 +02:00
|
|
|
# Copyright: (c) 2008, Jarek Zgoda <jarek.zgoda@gmail.com>
|
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
__revision__ = "$Id: models.py 28 2009-10-22 15:03:02Z jarek.zgoda $"
|
2017-07-08 06:25:05 +02:00
|
|
|
import datetime
|
2020-09-05 04:02:13 +02:00
|
|
|
import secrets
|
|
|
|
from base64 import b32encode
|
2020-06-13 03:34:01 +02:00
|
|
|
from typing import Mapping, Optional, Union
|
2020-06-14 01:36:12 +02:00
|
|
|
from urllib.parse import urljoin
|
2012-09-28 22:29:48 +02:00
|
|
|
|
|
|
|
from django.conf import settings
|
2016-10-10 14:52:01 +02:00
|
|
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
2020-06-11 00:54:34 +02:00
|
|
|
from django.contrib.contenttypes.models import ContentType
|
|
|
|
from django.db import models
|
|
|
|
from django.db.models import CASCADE
|
2017-07-22 00:25:41 +02:00
|
|
|
from django.http import HttpRequest, HttpResponse
|
|
|
|
from django.shortcuts import render
|
2020-06-11 00:54:34 +02:00
|
|
|
from django.urls import reverse
|
2017-04-15 04:03:56 +02:00
|
|
|
from django.utils.timezone import now as timezone_now
|
2021-07-24 19:40:01 +02:00
|
|
|
from typing_extensions import Protocol
|
2012-09-28 22:29:48 +02:00
|
|
|
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.models import EmailChangeStatus, MultiuseInvite, PreregistrationUser, Realm, UserProfile
|
|
|
|
|
2012-09-28 22:29:48 +02:00
|
|
|
|
2021-07-24 19:40:01 +02:00
|
|
|
class HasRealmObject(Protocol):
|
|
|
|
realm: Realm
|
|
|
|
|
|
|
|
|
|
|
|
class OptionalHasRealmObject(Protocol):
|
|
|
|
realm: Optional[Realm]
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
2021-02-12 08:19:30 +01: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:
|
2021-02-12 08:20:45 +01:00
|
|
|
return render(request, "confirmation/link_malformed.html")
|
2017-07-22 00:25:41 +02:00
|
|
|
if exception.error_type == ConfirmationKeyException.EXPIRED:
|
2021-02-12 08:20:45 +01:00
|
|
|
return render(request, "confirmation/link_expired.html")
|
|
|
|
return render(request, "confirmation/link_does_not_exist.html")
|
2017-07-22 00:25:41 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2017-10-27 10:52:58 +02:00
|
|
|
def generate_key() -> str:
|
2017-07-11 20:52:27 +02:00
|
|
|
# 24 characters * 5 bits of entropy/character = 120 bits of entropy
|
2020-09-05 04:02:13 +02:00
|
|
|
return b32encode(secrets.token_bytes(15)).decode().lower()
|
2013-02-28 20:07:04 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2017-11-08 03:36:44 +01:00
|
|
|
ConfirmationObjT = Union[MultiuseInvite, PreregistrationUser, EmailChangeStatus]
|
2021-02-12 08:19:30 +01:00
|
|
|
|
|
|
|
|
|
|
|
def get_object_from_key(
|
|
|
|
confirmation_key: str, confirmation_type: int, activate_object: bool = True
|
|
|
|
) -> 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:
|
2021-02-12 08:19:30 +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
|
2021-07-24 18:16:48 +02:00
|
|
|
assert obj is not None
|
2020-02-15 07:06:48 +01:00
|
|
|
if activate_object and hasattr(obj, "status"):
|
2021-02-12 08:20:45 +01:00
|
|
|
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
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
|
|
|
def create_confirmation_link(
|
2021-07-24 19:40:01 +02:00
|
|
|
obj: Union[Realm, HasRealmObject, OptionalHasRealmObject],
|
|
|
|
confirmation_type: int,
|
2021-08-02 20:45:55 +02:00
|
|
|
validity_in_days: Optional[int] = None,
|
2021-07-24 19:40:01 +02:00
|
|
|
url_args: Mapping[str, str] = {},
|
2021-02-12 08:19:30 +01:00
|
|
|
) -> str:
|
2017-07-08 04:38:13 +02:00
|
|
|
key = generate_key()
|
2018-11-12 14:15:49 +01:00
|
|
|
realm = None
|
2021-07-24 19:40:01 +02:00
|
|
|
if isinstance(obj, Realm):
|
2019-09-17 14:04:48 +02:00
|
|
|
realm = obj
|
2021-07-24 19:40:01 +02:00
|
|
|
elif hasattr(obj, "realm"):
|
|
|
|
realm = obj.realm
|
2019-09-17 14:04:48 +02:00
|
|
|
|
2021-08-02 20:45:55 +02:00
|
|
|
expiry_date = None
|
|
|
|
if validity_in_days:
|
|
|
|
expiry_date = timezone_now() + datetime.timedelta(days=validity_in_days)
|
|
|
|
else:
|
|
|
|
expiry_date = timezone_now() + datetime.timedelta(
|
|
|
|
days=_properties[confirmation_type].validity_in_days
|
|
|
|
)
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
Confirmation.objects.create(
|
|
|
|
content_object=obj,
|
|
|
|
date_sent=timezone_now(),
|
|
|
|
confirmation_key=key,
|
|
|
|
realm=realm,
|
2021-08-02 20:45:55 +02:00
|
|
|
expiry_date=expiry_date,
|
2021-02-12 08:19:30 +01:00
|
|
|
type=confirmation_type,
|
|
|
|
)
|
2020-06-14 01:36:12 +02:00
|
|
|
return confirmation_url(key, realm, confirmation_type, url_args)
|
2017-07-08 04:38:13 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
|
|
|
def confirmation_url(
|
|
|
|
confirmation_key: str,
|
|
|
|
realm: Optional[Realm],
|
|
|
|
confirmation_type: int,
|
|
|
|
url_args: Mapping[str, str] = {},
|
|
|
|
) -> str:
|
2020-06-13 03:34:01 +02:00
|
|
|
url_args = dict(url_args)
|
2021-02-12 08:20:45 +01:00
|
|
|
url_args["confirmation_key"] = confirmation_key
|
2020-06-14 01:36:12 +02:00
|
|
|
return urljoin(
|
|
|
|
settings.ROOT_DOMAIN_URI if realm is None else realm.uri,
|
|
|
|
reverse(_properties[confirmation_type].url_name, kwargs=url_args),
|
|
|
|
)
|
2017-01-17 11:11:51 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2012-09-28 22:29:48 +02:00
|
|
|
class Confirmation(models.Model):
|
2018-01-29 08:17:31 +01:00
|
|
|
content_type = models.ForeignKey(ContentType, on_delete=CASCADE)
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
object_id: int = models.PositiveIntegerField(db_index=True)
|
2021-02-12 08:20:45 +01:00
|
|
|
content_object = GenericForeignKey("content_type", "object_id")
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
date_sent: datetime.datetime = models.DateTimeField(db_index=True)
|
|
|
|
confirmation_key: str = models.CharField(max_length=40, db_index=True)
|
2021-08-02 20:45:55 +02:00
|
|
|
expiry_date: datetime.datetime = models.DateTimeField(db_index=True, null=True)
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
realm: Optional[Realm] = models.ForeignKey(Realm, null=True, on_delete=CASCADE)
|
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
|
2018-11-12 14:15:49 +01:00
|
|
|
REALM_REACTIVATION = 8
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
type: int = models.PositiveSmallIntegerField()
|
2012-09-28 22:29:48 +02:00
|
|
|
|
2018-05-11 01:38:30 +02:00
|
|
|
def __str__(self) -> str:
|
2021-02-12 08:20:45 +01:00
|
|
|
return f"<Confirmation: {self.content_object}>"
|
2017-01-20 12:27:38 +01:00
|
|
|
|
2020-03-27 10:03:05 +01:00
|
|
|
class Meta:
|
|
|
|
unique_together = ("type", "confirmation_key")
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2017-11-05 11:57:15 +01:00
|
|
|
class ConfirmationType:
|
2021-02-12 08:19:30 +01: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
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2017-07-08 04:38:13 +02:00
|
|
|
_properties = {
|
2021-02-12 08:20:45 +01:00
|
|
|
Confirmation.USER_REGISTRATION: ConfirmationType("check_prereg_key_and_redirect"),
|
2021-02-12 08:19:30 +01:00
|
|
|
Confirmation.INVITATION: ConfirmationType(
|
2021-02-12 08:20:45 +01:00
|
|
|
"check_prereg_key_and_redirect", validity_in_days=settings.INVITATION_LINK_VALIDITY_DAYS
|
2021-02-12 08:19:30 +01:00
|
|
|
),
|
2021-02-12 08:20:45 +01:00
|
|
|
Confirmation.EMAIL_CHANGE: ConfirmationType("confirm_email_change"),
|
2020-09-02 02:50:08 +02:00
|
|
|
Confirmation.UNSUBSCRIBE: ConfirmationType(
|
2021-02-12 08:20:45 +01:00
|
|
|
"unsubscribe",
|
2020-09-02 02:50:08 +02:00
|
|
|
validity_in_days=1000000, # should never expire
|
|
|
|
),
|
2017-11-10 04:33:28 +01:00
|
|
|
Confirmation.MULTIUSE_INVITE: ConfirmationType(
|
2021-02-12 08:20:45 +01:00
|
|
|
"join", validity_in_days=settings.INVITATION_LINK_VALIDITY_DAYS
|
2021-02-12 08:19:30 +01:00
|
|
|
),
|
2021-02-12 08:20:45 +01:00
|
|
|
Confirmation.REALM_CREATION: ConfirmationType("check_prereg_key_and_redirect"),
|
|
|
|
Confirmation.REALM_REACTIVATION: ConfirmationType("realm_reactivation"),
|
2017-07-08 04:38:13 +02:00
|
|
|
}
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2018-11-08 22:40:27 +01:00
|
|
|
def one_click_unsubscribe_link(user_profile: UserProfile, email_type: str) -> str:
|
|
|
|
"""
|
|
|
|
Generate a unique link that a logged-out user can visit to unsubscribe from
|
|
|
|
Zulip e-mails without having to first log in.
|
|
|
|
"""
|
2021-02-12 08:19:30 +01:00
|
|
|
return create_confirmation_link(
|
2021-02-12 08:20:45 +01:00
|
|
|
user_profile, Confirmation.UNSUBSCRIBE, url_args={"email_type": email_type}
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
|
|
|
|
2018-11-08 22:40:27 +01: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
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
def validate_key(creation_key: Optional[str]) -> Optional["RealmCreationKey"]:
|
2018-01-29 20:54:49 +01:00
|
|
|
"""Get the record for this key, raising InvalidCreationKey if non-None but invalid."""
|
|
|
|
if creation_key is None:
|
|
|
|
return None
|
|
|
|
try:
|
|
|
|
key_record = RealmCreationKey.objects.get(creation_key=creation_key)
|
|
|
|
except RealmCreationKey.DoesNotExist:
|
|
|
|
raise RealmCreationKey.Invalid()
|
|
|
|
time_elapsed = timezone_now() - key_record.date_created
|
2017-11-30 00:19:01 +01:00
|
|
|
if time_elapsed.total_seconds() > settings.REALM_CREATION_LINK_VALIDITY_DAYS * 24 * 3600:
|
2018-01-29 20:54:49 +01:00
|
|
|
raise RealmCreationKey.Invalid()
|
|
|
|
return key_record
|
2017-07-06 06:59:17 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
|
|
|
def generate_realm_creation_url(by_admin: bool = False) -> str:
|
2017-07-06 06:59:17 +02:00
|
|
|
key = generate_key()
|
2021-02-12 08:19:30 +01:00
|
|
|
RealmCreationKey.objects.create(
|
|
|
|
creation_key=key, date_created=timezone_now(), presume_email_valid=by_admin
|
|
|
|
)
|
2020-06-14 01:36:12 +02:00
|
|
|
return urljoin(
|
|
|
|
settings.ROOT_DOMAIN_URI,
|
2021-02-12 08:20:45 +01:00
|
|
|
reverse("create_realm", kwargs={"creation_key": key}),
|
2020-06-14 01:36:12 +02:00
|
|
|
)
|
2017-07-06 06:59:17 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2016-06-22 21:16:02 +02:00
|
|
|
class RealmCreationKey(models.Model):
|
2021-02-12 08:20:45 +01:00
|
|
|
creation_key = models.CharField("activation key", db_index=True, max_length=40)
|
|
|
|
date_created = models.DateTimeField("created", default=timezone_now)
|
2018-01-29 19:58:00 +01:00
|
|
|
|
|
|
|
# True just if we should presume the email address the user enters
|
|
|
|
# is theirs, and skip sending mail to it to confirm that.
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
presume_email_valid: bool = models.BooleanField(default=False)
|
2018-01-29 20:54:49 +01:00
|
|
|
|
|
|
|
class Invalid(Exception):
|
|
|
|
pass
|