mirror of
https://github.com/zulip/zulip.git
synced 2025-10-28 10:33:54 +00:00
confirmation: Replace RealmCreationKey - use Confirmation instead.
Fixes #20028. There's no reason to have a special `RealmCreationKey` class - the `Confirmation` system already does this job. This is somewhat complicated by the need to write a migration for `RealmCreationKey`->`Confirmation` for pre-existing, valid objects, to avoid breaking realm creation links that haven't been used yet.
This commit is contained in:
committed by
Tim Abbott
parent
072f234269
commit
40b1f6eb4e
@@ -0,0 +1,112 @@
|
|||||||
|
# Generated by Django 5.2.3 on 2025-07-04 20:33
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, transaction
|
||||||
|
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||||
|
from django.db.migrations.state import StateApps
|
||||||
|
from django.db.models import Max
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_realmcreationkey_to_realmcreationstatus(
|
||||||
|
apps: StateApps, schema_editor: BaseDatabaseSchemaEditor
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
This migration is for switching from using a separate RealmCreationKey class
|
||||||
|
for realm creation keys to just using the Confirmation system.
|
||||||
|
|
||||||
|
The aim is to iterate through all the existing RealmCreationKey and create
|
||||||
|
a corresponding Confirmation+RealmCreationStatus.
|
||||||
|
|
||||||
|
For validity of these objects, we only need to worry about RealmCreationKeys
|
||||||
|
expired due to time. This is taken care of by making sure we set expiry_date
|
||||||
|
on the Confirmation we're creating.
|
||||||
|
The way the RealmCreationKey system worked was to .delete() the RealmCreationKey
|
||||||
|
objects when they were used - so those simply no longer exist and we don't need to
|
||||||
|
worry about this case.
|
||||||
|
"""
|
||||||
|
CAN_CREATE_REALM = 11
|
||||||
|
|
||||||
|
Confirmation = apps.get_model("confirmation", "Confirmation")
|
||||||
|
ContentType = apps.get_model("contenttypes", "ContentType")
|
||||||
|
RealmCreationKey = apps.get_model("confirmation", "RealmCreationKey")
|
||||||
|
RealmCreationStatus = apps.get_model("zerver", "RealmCreationStatus")
|
||||||
|
realm_creation_status_content_type, created = ContentType.objects.get_or_create(
|
||||||
|
model="realmcreationstatus", app_label="zerver"
|
||||||
|
)
|
||||||
|
|
||||||
|
BATCH_SIZE = 10000
|
||||||
|
max_id = RealmCreationKey.objects.aggregate(Max("id"))["id__max"]
|
||||||
|
if max_id is None:
|
||||||
|
# Nothing to do.
|
||||||
|
return
|
||||||
|
|
||||||
|
lower_bound = 1
|
||||||
|
while lower_bound <= max_id + (BATCH_SIZE / 2):
|
||||||
|
upper_bound = lower_bound + BATCH_SIZE - 1
|
||||||
|
creation_keys = RealmCreationKey.objects.filter(id__range=(lower_bound, upper_bound))
|
||||||
|
creation_statuses_to_create = []
|
||||||
|
confirmations_to_create = []
|
||||||
|
for creation_key in creation_keys:
|
||||||
|
# These keys were generated in the same way as keys for Confirmation objects,
|
||||||
|
# so we can copy them over without breaking anything.
|
||||||
|
key = creation_key.creation_key
|
||||||
|
date_created = creation_key.date_created
|
||||||
|
presume_email_valid = creation_key.presume_email_valid
|
||||||
|
|
||||||
|
creation_status = RealmCreationStatus(
|
||||||
|
status=0, date_created=date_created, presume_email_valid=presume_email_valid
|
||||||
|
)
|
||||||
|
confirmation = Confirmation(
|
||||||
|
content_type=realm_creation_status_content_type,
|
||||||
|
type=CAN_CREATE_REALM,
|
||||||
|
confirmation_key=key,
|
||||||
|
date_sent=date_created,
|
||||||
|
expiry_date=date_created
|
||||||
|
+ timedelta(days=settings.CAN_CREATE_REALM_LINK_VALIDITY_DAYS),
|
||||||
|
)
|
||||||
|
|
||||||
|
# To attach the Confirmations to RealmCreationStatus objects we need to set the
|
||||||
|
# confirmation.object_id to their respective ids. But we haven't saved
|
||||||
|
# the RealmCreationStatus objs to the database yet - so we don't have ids.
|
||||||
|
#
|
||||||
|
# After we .bulk_create() them, their .id attributes will be populated.
|
||||||
|
# So for now we just link the RealmCreationStatus to the Confirmation
|
||||||
|
# via a temporary ._object attribute - which we'll use later to set
|
||||||
|
# the .object_id as intended.
|
||||||
|
confirmation._object = creation_status
|
||||||
|
|
||||||
|
creation_statuses_to_create.append(creation_status)
|
||||||
|
confirmations_to_create.append(confirmation)
|
||||||
|
with transaction.atomic():
|
||||||
|
RealmCreationStatus.objects.bulk_create(creation_statuses_to_create)
|
||||||
|
|
||||||
|
# Now the objects in creation_statuses_to_create have had their .id
|
||||||
|
# attrs populated. For every confirmation, confirmation._object
|
||||||
|
# points to its corresponding RealmCreationStatus - so now we can
|
||||||
|
# set the confirmation.object_id values and clear out the temporary
|
||||||
|
# ._object attr.
|
||||||
|
for confirmation in confirmations_to_create:
|
||||||
|
confirmation.object_id = confirmation._object.id
|
||||||
|
delattr(confirmation, "_object")
|
||||||
|
Confirmation.objects.bulk_create(confirmations_to_create)
|
||||||
|
|
||||||
|
lower_bound += BATCH_SIZE
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
atomic = False
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("confirmation", "0015_alter_confirmation_object_id"),
|
||||||
|
("zerver", "0743_realmcreationstatus"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(
|
||||||
|
migrate_realmcreationkey_to_realmcreationstatus,
|
||||||
|
reverse_code=migrations.RunPython.noop,
|
||||||
|
elidable=True,
|
||||||
|
),
|
||||||
|
]
|
||||||
15
confirmation/migrations/0017_delete_realmcreationkey.py
Normal file
15
confirmation/migrations/0017_delete_realmcreationkey.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Generated by Django 5.2.3 on 2025-07-04 19:08
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("confirmation", "0016_realmcreationkey_to_realmcreationstatus"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name="RealmCreationKey",
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -5,7 +5,7 @@ import secrets
|
|||||||
from base64 import b32encode
|
from base64 import b32encode
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import Optional, TypeAlias, Union, cast
|
from typing import TypeAlias, Union, cast
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@@ -30,6 +30,7 @@ from zerver.models import (
|
|||||||
RealmReactivationStatus,
|
RealmReactivationStatus,
|
||||||
UserProfile,
|
UserProfile,
|
||||||
)
|
)
|
||||||
|
from zerver.models.prereg_users import RealmCreationStatus
|
||||||
|
|
||||||
if settings.ZILENCER_ENABLED:
|
if settings.ZILENCER_ENABLED:
|
||||||
from zilencer.models import (
|
from zilencer.models import (
|
||||||
@@ -70,6 +71,7 @@ NoZilencerConfirmationObjT: TypeAlias = (
|
|||||||
| EmailChangeStatus
|
| EmailChangeStatus
|
||||||
| UserProfile
|
| UserProfile
|
||||||
| RealmReactivationStatus
|
| RealmReactivationStatus
|
||||||
|
| RealmCreationStatus
|
||||||
)
|
)
|
||||||
ZilencerConfirmationObjT: TypeAlias = Union[
|
ZilencerConfirmationObjT: TypeAlias = Union[
|
||||||
NoZilencerConfirmationObjT,
|
NoZilencerConfirmationObjT,
|
||||||
@@ -153,7 +155,7 @@ def create_confirmation_object(
|
|||||||
realm = None
|
realm = None
|
||||||
else:
|
else:
|
||||||
obj = cast(NoZilencerConfirmationObjT, obj)
|
obj = cast(NoZilencerConfirmationObjT, obj)
|
||||||
assert not isinstance(obj, PreregistrationRealm)
|
assert not isinstance(obj, PreregistrationRealm | RealmCreationStatus)
|
||||||
realm = obj.realm
|
realm = obj.realm
|
||||||
|
|
||||||
current_time = timezone_now()
|
current_time = timezone_now()
|
||||||
@@ -185,15 +187,17 @@ def create_confirmation_link(
|
|||||||
url_args: Mapping[str, str] = {},
|
url_args: Mapping[str, str] = {},
|
||||||
no_associated_realm_object: bool = False,
|
no_associated_realm_object: bool = False,
|
||||||
) -> str:
|
) -> str:
|
||||||
return confirmation_url_for(
|
conf = create_confirmation_object(
|
||||||
create_confirmation_object(
|
obj,
|
||||||
obj,
|
confirmation_type,
|
||||||
confirmation_type,
|
validity_in_minutes=validity_in_minutes,
|
||||||
validity_in_minutes=validity_in_minutes,
|
no_associated_realm_object=no_associated_realm_object,
|
||||||
no_associated_realm_object=no_associated_realm_object,
|
)
|
||||||
),
|
result = confirmation_url_for(
|
||||||
|
conf,
|
||||||
url_args=url_args,
|
url_args=url_args,
|
||||||
)
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def confirmation_url_for(confirmation_obj: "Confirmation", url_args: Mapping[str, str] = {}) -> str:
|
def confirmation_url_for(confirmation_obj: "Confirmation", url_args: Mapping[str, str] = {}) -> str:
|
||||||
@@ -236,6 +240,7 @@ class Confirmation(models.Model):
|
|||||||
REALM_REACTIVATION = 8
|
REALM_REACTIVATION = 8
|
||||||
REMOTE_SERVER_BILLING_LEGACY_LOGIN = 9
|
REMOTE_SERVER_BILLING_LEGACY_LOGIN = 9
|
||||||
REMOTE_REALM_BILLING_LEGACY_LOGIN = 10
|
REMOTE_REALM_BILLING_LEGACY_LOGIN = 10
|
||||||
|
CAN_CREATE_REALM = 11
|
||||||
type = models.PositiveSmallIntegerField()
|
type = models.PositiveSmallIntegerField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -272,6 +277,9 @@ _properties = {
|
|||||||
Confirmation.MULTIUSE_INVITE: ConfirmationType(
|
Confirmation.MULTIUSE_INVITE: ConfirmationType(
|
||||||
"join", validity_in_days=settings.INVITATION_LINK_VALIDITY_DAYS
|
"join", validity_in_days=settings.INVITATION_LINK_VALIDITY_DAYS
|
||||||
),
|
),
|
||||||
|
Confirmation.CAN_CREATE_REALM: ConfirmationType(
|
||||||
|
"create_realm", validity_in_days=settings.CAN_CREATE_REALM_LINK_VALIDITY_DAYS
|
||||||
|
),
|
||||||
Confirmation.NEW_REALM_USER_REGISTRATION: ConfirmationType("get_prereg_key_and_redirect"),
|
Confirmation.NEW_REALM_USER_REGISTRATION: ConfirmationType("get_prereg_key_and_redirect"),
|
||||||
Confirmation.REALM_REACTIVATION: ConfirmationType("realm_reactivation_get"),
|
Confirmation.REALM_REACTIVATION: ConfirmationType("realm_reactivation_get"),
|
||||||
}
|
}
|
||||||
@@ -294,47 +302,7 @@ def one_click_unsubscribe_link(user_profile: UserProfile, email_type: str) -> st
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# 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.NEW_REALM_USER_REGISTRATION pathway.
|
|
||||||
# Arguably RealmCreationKey should just be another ConfirmationObjT and we should
|
|
||||||
# add another Confirmation.type for this; it's this way for historical reasons.
|
|
||||||
|
|
||||||
|
|
||||||
def validate_key(creation_key: str | None) -> Optional["RealmCreationKey"]:
|
|
||||||
"""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.InvalidError
|
|
||||||
time_elapsed = timezone_now() - key_record.date_created
|
|
||||||
if time_elapsed.total_seconds() > settings.REALM_CREATION_LINK_VALIDITY_DAYS * 24 * 3600:
|
|
||||||
raise RealmCreationKey.InvalidError
|
|
||||||
return key_record
|
|
||||||
|
|
||||||
|
|
||||||
def generate_realm_creation_url(by_admin: bool = False) -> str:
|
def generate_realm_creation_url(by_admin: bool = False) -> str:
|
||||||
key = generate_key()
|
from zerver.views.registration import prepare_realm_creation_url
|
||||||
RealmCreationKey.objects.create(
|
|
||||||
creation_key=key, date_created=timezone_now(), presume_email_valid=by_admin
|
|
||||||
)
|
|
||||||
return urljoin(
|
|
||||||
settings.ROOT_DOMAIN_URI,
|
|
||||||
reverse("create_realm", kwargs={"creation_key": key}),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
return prepare_realm_creation_url(presume_email_valid=by_admin)
|
||||||
class RealmCreationKey(models.Model):
|
|
||||||
creation_key = models.CharField("activation key", db_index=True, max_length=40)
|
|
||||||
date_created = models.DateTimeField("created", default=timezone_now)
|
|
||||||
|
|
||||||
# True just if we should presume the email address the user enters
|
|
||||||
# is theirs, and skip sending mail to it to confirm that.
|
|
||||||
presume_email_valid = models.BooleanField(default=False)
|
|
||||||
|
|
||||||
class InvalidError(Exception):
|
|
||||||
pass
|
|
||||||
|
|||||||
@@ -217,6 +217,8 @@ _Released 2025-07-29_
|
|||||||
preserve and update your `postfix` configuration.
|
preserve and update your `postfix` configuration.
|
||||||
- The `SOCIAL_AUTH_SYNC_CUSTOM_ATTRS_DICT` setting has been removed.
|
- The `SOCIAL_AUTH_SYNC_CUSTOM_ATTRS_DICT` setting has been removed.
|
||||||
It was deprecated in favor of `SOCIAL_AUTH_SYNC_ATTRS_DICT` in 10.0.
|
It was deprecated in favor of `SOCIAL_AUTH_SYNC_ATTRS_DICT` in 10.0.
|
||||||
|
- The obscure `REALM_CREATION_LINK_VALIDITY_DAYS` setting was renamed to
|
||||||
|
`CAN_CREATE_REALM_LINK_VALIDITY_DAYS`.
|
||||||
|
|
||||||
## Zulip Server 10.x series
|
## Zulip Server 10.x series
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ The above command will output a URL which can be used for creating a
|
|||||||
new realm and an administrator user for that realm. The link expires
|
new realm and an administrator user for that realm. The link expires
|
||||||
after the creation of the realm. The link also expires if not used
|
after the creation of the realm. The link also expires if not used
|
||||||
within 7 days. The expiration period can be changed by modifying
|
within 7 days. The expiration period can be changed by modifying
|
||||||
`REALM_CREATION_LINK_VALIDITY_DAYS` in settings.py.
|
`CAN_CREATE_REALM_LINK_VALIDITY_DAYS` in settings.py.
|
||||||
|
|
||||||
## Subdomains
|
## Subdomains
|
||||||
|
|
||||||
|
|||||||
@@ -182,6 +182,7 @@ ALL_ZULIP_TABLES = {
|
|||||||
"zerver_realm",
|
"zerver_realm",
|
||||||
"zerver_realmauditlog",
|
"zerver_realmauditlog",
|
||||||
"zerver_realmauthenticationmethod",
|
"zerver_realmauthenticationmethod",
|
||||||
|
"zerver_realmcreationstatus",
|
||||||
"zerver_realmdomain",
|
"zerver_realmdomain",
|
||||||
"zerver_realmemoji",
|
"zerver_realmemoji",
|
||||||
"zerver_realmexport",
|
"zerver_realmexport",
|
||||||
@@ -230,6 +231,7 @@ NON_EXPORTED_TABLES = {
|
|||||||
"zerver_preregistrationuser_streams",
|
"zerver_preregistrationuser_streams",
|
||||||
"zerver_preregistrationuser_groups",
|
"zerver_preregistrationuser_groups",
|
||||||
"zerver_realmreactivationstatus",
|
"zerver_realmreactivationstatus",
|
||||||
|
"zerver_realmcreationstatus",
|
||||||
# Missed message addresses are low value to export since
|
# Missed message addresses are low value to export since
|
||||||
# missed-message email addresses include the server's hostname and
|
# missed-message email addresses include the server's hostname and
|
||||||
# expire after a few days.
|
# expire after a few days.
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class Command(ZulipBaseCommand):
|
|||||||
Outputs a randomly generated, 1-time-use link for Organization creation.
|
Outputs a randomly generated, 1-time-use link for Organization creation.
|
||||||
Whoever visits the link can create a new organization on this server, regardless of whether
|
Whoever visits the link can create a new organization on this server, regardless of whether
|
||||||
settings.OPEN_REALM_CREATION is enabled. The link would expire automatically after
|
settings.OPEN_REALM_CREATION is enabled. The link would expire automatically after
|
||||||
settings.REALM_CREATION_LINK_VALIDITY_DAYS.
|
settings.CAN_CREATE_REALM_LINK_VALIDITY_DAYS.
|
||||||
|
|
||||||
Usage: ./manage.py generate_realm_creation_link """
|
Usage: ./manage.py generate_realm_creation_link """
|
||||||
|
|
||||||
|
|||||||
27
zerver/migrations/0747_realmcreationstatus.py
Normal file
27
zerver/migrations/0747_realmcreationstatus.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Generated by Django 5.2.3 on 2025-07-04 19:08
|
||||||
|
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("zerver", "0746_alter_channelfolder_unique_together_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="RealmCreationStatus",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("status", models.IntegerField(default=0)),
|
||||||
|
("date_created", models.DateTimeField(default=django.utils.timezone.now)),
|
||||||
|
("presume_email_valid", models.BooleanField(default=False)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -170,3 +170,14 @@ class RealmReactivationStatus(models.Model):
|
|||||||
status = models.IntegerField(default=0)
|
status = models.IntegerField(default=0)
|
||||||
|
|
||||||
realm = models.ForeignKey(Realm, on_delete=CASCADE)
|
realm = models.ForeignKey(Realm, on_delete=CASCADE)
|
||||||
|
|
||||||
|
|
||||||
|
class RealmCreationStatus(models.Model):
|
||||||
|
# status: whether an object has been confirmed.
|
||||||
|
# if confirmed, set to confirmation.settings.STATUS_USED
|
||||||
|
status = models.IntegerField(default=0)
|
||||||
|
date_created = models.DateTimeField(default=timezone_now)
|
||||||
|
|
||||||
|
# True just if we should presume the email address the user enters
|
||||||
|
# is theirs, and skip sending mail to it to confirm that.
|
||||||
|
presume_email_valid = models.BooleanField(default=False)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from django.core.management.base import CommandError
|
|||||||
from django.test import override_settings
|
from django.test import override_settings
|
||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
|
|
||||||
from confirmation.models import RealmCreationKey, generate_realm_creation_url
|
from confirmation.models import Confirmation, generate_realm_creation_url
|
||||||
from zerver.actions.create_user import do_create_user
|
from zerver.actions.create_user import do_create_user
|
||||||
from zerver.actions.user_settings import do_change_user_setting
|
from zerver.actions.user_settings import do_change_user_setting
|
||||||
from zerver.lib.management import ZulipBaseCommand, check_config
|
from zerver.lib.management import ZulipBaseCommand, check_config
|
||||||
@@ -360,11 +360,13 @@ class TestGenerateRealmCreationLink(ZulipTestCase):
|
|||||||
@override_settings(OPEN_REALM_CREATION=False)
|
@override_settings(OPEN_REALM_CREATION=False)
|
||||||
def test_realm_creation_with_expired_link(self) -> None:
|
def test_realm_creation_with_expired_link(self) -> None:
|
||||||
generated_link = generate_realm_creation_url(by_admin=True)
|
generated_link = generate_realm_creation_url(by_admin=True)
|
||||||
key = generated_link[-24:]
|
key = generated_link.split("/")[-1]
|
||||||
# Manually expire the link by changing the date of creation
|
# Manually expire the link by changing the date of expiry.
|
||||||
obj = RealmCreationKey.objects.get(creation_key=key)
|
confirmation = Confirmation.objects.get(confirmation_key=key)
|
||||||
obj.date_created -= timedelta(days=settings.REALM_CREATION_LINK_VALIDITY_DAYS + 1)
|
assert confirmation.expiry_date is not None
|
||||||
obj.save()
|
|
||||||
|
confirmation.expiry_date -= timedelta(days=settings.CAN_CREATE_REALM_LINK_VALIDITY_DAYS + 1)
|
||||||
|
confirmation.save()
|
||||||
|
|
||||||
result = self.client_get(generated_link)
|
result = self.client_get(generated_link)
|
||||||
self.assert_in_success_response(["Organization creation link expired or invalid"], result)
|
self.assert_in_success_response(["Organization creation link expired or invalid"], result)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from typing import Annotated, Any
|
from typing import Annotated, Any, cast
|
||||||
from urllib.parse import urlencode, urljoin
|
from urllib.parse import urlencode, urljoin
|
||||||
|
|
||||||
import orjson
|
import orjson
|
||||||
@@ -27,11 +27,9 @@ from confirmation import settings as confirmation_settings
|
|||||||
from confirmation.models import (
|
from confirmation.models import (
|
||||||
Confirmation,
|
Confirmation,
|
||||||
ConfirmationKeyError,
|
ConfirmationKeyError,
|
||||||
RealmCreationKey,
|
|
||||||
create_confirmation_link,
|
create_confirmation_link,
|
||||||
get_object_from_key,
|
get_object_from_key,
|
||||||
render_confirmation_key_error,
|
render_confirmation_key_error,
|
||||||
validate_key,
|
|
||||||
)
|
)
|
||||||
from zerver.actions.create_realm import do_create_realm
|
from zerver.actions.create_realm import do_create_realm
|
||||||
from zerver.actions.create_user import do_activate_mirror_dummy_user, do_create_user
|
from zerver.actions.create_user import do_activate_mirror_dummy_user, do_create_user
|
||||||
@@ -101,6 +99,7 @@ from zerver.models import (
|
|||||||
UserProfile,
|
UserProfile,
|
||||||
)
|
)
|
||||||
from zerver.models.constants import MAX_LANGUAGE_ID_LENGTH
|
from zerver.models.constants import MAX_LANGUAGE_ID_LENGTH
|
||||||
|
from zerver.models.prereg_users import RealmCreationStatus
|
||||||
from zerver.models.realm_audit_logs import AuditLogEventType, RealmAuditLog
|
from zerver.models.realm_audit_logs import AuditLogEventType, RealmAuditLog
|
||||||
from zerver.models.realms import (
|
from zerver.models.realms import (
|
||||||
DisposableEmailError,
|
DisposableEmailError,
|
||||||
@@ -1006,6 +1005,19 @@ def prepare_realm_activation_url(
|
|||||||
return activation_url
|
return activation_url
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_realm_creation_url(
|
||||||
|
presume_email_valid: bool = False,
|
||||||
|
) -> str:
|
||||||
|
realm_creation_status = RealmCreationStatus.objects.create(
|
||||||
|
presume_email_valid=presume_email_valid
|
||||||
|
)
|
||||||
|
confirmation_url = create_confirmation_link(
|
||||||
|
realm_creation_status, Confirmation.CAN_CREATE_REALM, no_associated_realm_object=True
|
||||||
|
)
|
||||||
|
|
||||||
|
return confirmation_url
|
||||||
|
|
||||||
|
|
||||||
def send_confirm_registration_email(
|
def send_confirm_registration_email(
|
||||||
email: str,
|
email: str,
|
||||||
activation_url: str,
|
activation_url: str,
|
||||||
@@ -1246,15 +1258,9 @@ def realm_import_post_process(
|
|||||||
|
|
||||||
|
|
||||||
@add_google_analytics
|
@add_google_analytics
|
||||||
def create_realm(request: HttpRequest, creation_key: str | None = None) -> HttpResponse:
|
def create_realm(request: HttpRequest, confirmation_key: str | None = None) -> HttpResponse:
|
||||||
try:
|
if confirmation_key is None:
|
||||||
key_record = validate_key(creation_key)
|
realm_creation_obj: RealmCreationStatus | None = None
|
||||||
except RealmCreationKey.InvalidError:
|
|
||||||
return TemplateResponse(
|
|
||||||
request,
|
|
||||||
"zerver/portico_error_pages/realm_creation_link_invalid.html",
|
|
||||||
)
|
|
||||||
if key_record is None:
|
|
||||||
if not settings.OPEN_REALM_CREATION:
|
if not settings.OPEN_REALM_CREATION:
|
||||||
return TemplateResponse(
|
return TemplateResponse(
|
||||||
request,
|
request,
|
||||||
@@ -1265,6 +1271,19 @@ def create_realm(request: HttpRequest, creation_key: str | None = None) -> HttpR
|
|||||||
request,
|
request,
|
||||||
"zerver/portico_error_pages/realm_creation_disabled.html",
|
"zerver/portico_error_pages/realm_creation_disabled.html",
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
realm_creation_obj = cast(
|
||||||
|
RealmCreationStatus,
|
||||||
|
get_object_from_key(
|
||||||
|
confirmation_key, [Confirmation.CAN_CREATE_REALM], mark_object_used=False
|
||||||
|
),
|
||||||
|
)
|
||||||
|
except ConfirmationKeyError:
|
||||||
|
return TemplateResponse(
|
||||||
|
request,
|
||||||
|
"zerver/portico_error_pages/realm_creation_link_invalid.html",
|
||||||
|
)
|
||||||
|
|
||||||
# When settings.OPEN_REALM_CREATION is enabled, anyone can create a new realm,
|
# When settings.OPEN_REALM_CREATION is enabled, anyone can create a new realm,
|
||||||
# with a few restrictions on their email address.
|
# with a few restrictions on their email address.
|
||||||
@@ -1300,12 +1319,13 @@ def create_realm(request: HttpRequest, creation_key: str | None = None) -> HttpR
|
|||||||
realm_default_language,
|
realm_default_language,
|
||||||
import_from,
|
import_from,
|
||||||
)
|
)
|
||||||
if key_record is not None and key_record.presume_email_valid:
|
if realm_creation_obj is not None and realm_creation_obj.presume_email_valid:
|
||||||
# The user has a token created from the server command line;
|
# The user has a token created from the server command line;
|
||||||
# skip confirming the email is theirs, taking their word for it.
|
# skip confirming the email is theirs, taking their word for it.
|
||||||
# This is essential on first install if the admin hasn't stopped
|
# This is essential on first install if the admin hasn't stopped
|
||||||
# to configure outbound email up front, or it isn't working yet.
|
# to configure outbound email up front, or it isn't working yet.
|
||||||
key_record.delete()
|
realm_creation_obj.status = getattr(settings, "STATUS_USED", 1)
|
||||||
|
realm_creation_obj.save(update_fields=["status"])
|
||||||
return HttpResponseRedirect(activation_url)
|
return HttpResponseRedirect(activation_url)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -1322,8 +1342,10 @@ def create_realm(request: HttpRequest, creation_key: str | None = None) -> HttpR
|
|||||||
return render(request, "500.html", status=500)
|
return render(request, "500.html", status=500)
|
||||||
return config_error(request, "smtp")
|
return config_error(request, "smtp")
|
||||||
|
|
||||||
if key_record is not None:
|
if realm_creation_obj is not None:
|
||||||
key_record.delete()
|
realm_creation_obj.status = getattr(settings, "STATUS_USED", 1)
|
||||||
|
realm_creation_obj.save(update_fields=["status"])
|
||||||
|
|
||||||
url = reverse(
|
url = reverse(
|
||||||
"new_realm_send_confirm",
|
"new_realm_send_confirm",
|
||||||
query={
|
query={
|
||||||
|
|||||||
@@ -516,7 +516,7 @@ PERSONAL_ZMIRROR_SERVER: str | None = None
|
|||||||
# When security-relevant links in emails expire.
|
# When security-relevant links in emails expire.
|
||||||
CONFIRMATION_LINK_DEFAULT_VALIDITY_DAYS = 1
|
CONFIRMATION_LINK_DEFAULT_VALIDITY_DAYS = 1
|
||||||
INVITATION_LINK_VALIDITY_DAYS = 10
|
INVITATION_LINK_VALIDITY_DAYS = 10
|
||||||
REALM_CREATION_LINK_VALIDITY_DAYS = 7
|
CAN_CREATE_REALM_LINK_VALIDITY_DAYS = 7
|
||||||
|
|
||||||
# Version number for ToS. Change this if you want to force every
|
# Version number for ToS. Change this if you want to force every
|
||||||
# user to click through to re-accept terms of service before using
|
# user to click through to re-accept terms of service before using
|
||||||
|
|||||||
@@ -698,7 +698,7 @@ i18n_urls = [
|
|||||||
# Realm creation
|
# Realm creation
|
||||||
path("json/antispam_challenge", get_challenge),
|
path("json/antispam_challenge", get_challenge),
|
||||||
path("new/", create_realm),
|
path("new/", create_realm),
|
||||||
path("new/<creation_key>", create_realm, name="create_realm"),
|
path("new/<confirmation_key>", create_realm, name="create_realm"),
|
||||||
# Realm reactivation
|
# Realm reactivation
|
||||||
path("reactivate/", realm_reactivation, name="realm_reactivation"),
|
path("reactivate/", realm_reactivation, name="realm_reactivation"),
|
||||||
path("reactivate/<confirmation_key>", realm_reactivation_get, name="realm_reactivation_get"),
|
path("reactivate/<confirmation_key>", realm_reactivation_get, name="realm_reactivation_get"),
|
||||||
|
|||||||
Reference in New Issue
Block a user