mirror of https://github.com/zulip/zulip.git
realm_reactivation: Prevent realm reactivation link reuse.
This uses the approach analogical to EmailChangeStatus for email change confirmation links.
This commit is contained in:
parent
46c6f33b10
commit
cf74d7d140
|
@ -36,6 +36,7 @@ from zerver.models import (
|
||||||
MultiuseInvite,
|
MultiuseInvite,
|
||||||
PreregistrationUser,
|
PreregistrationUser,
|
||||||
Realm,
|
Realm,
|
||||||
|
RealmReactivationStatus,
|
||||||
UserProfile,
|
UserProfile,
|
||||||
get_org_type_display_name,
|
get_org_type_display_name,
|
||||||
get_realm,
|
get_realm,
|
||||||
|
@ -313,8 +314,9 @@ def support(
|
||||||
]
|
]
|
||||||
confirmations += get_confirmations([Confirmation.MULTIUSE_INVITE], multiuse_invite_ids)
|
confirmations += get_confirmations([Confirmation.MULTIUSE_INVITE], multiuse_invite_ids)
|
||||||
|
|
||||||
|
realm_reactivation_status_objects = RealmReactivationStatus.objects.filter(realm__in=realms)
|
||||||
confirmations += get_confirmations(
|
confirmations += get_confirmations(
|
||||||
[Confirmation.REALM_REACTIVATION], [realm.id for realm in realms]
|
[Confirmation.REALM_REACTIVATION], [obj.id for obj in realm_reactivation_status_objects]
|
||||||
)
|
)
|
||||||
|
|
||||||
context["confirmations"] = confirmations
|
context["confirmations"] = confirmations
|
||||||
|
|
|
@ -21,6 +21,7 @@ from zerver.models import (
|
||||||
Attachment,
|
Attachment,
|
||||||
Realm,
|
Realm,
|
||||||
RealmAuditLog,
|
RealmAuditLog,
|
||||||
|
RealmReactivationStatus,
|
||||||
RealmUserDefault,
|
RealmUserDefault,
|
||||||
ScheduledEmail,
|
ScheduledEmail,
|
||||||
Stream,
|
Stream,
|
||||||
|
@ -460,7 +461,9 @@ def do_change_realm_plan_type(
|
||||||
|
|
||||||
|
|
||||||
def do_send_realm_reactivation_email(realm: Realm, *, acting_user: Optional[UserProfile]) -> None:
|
def do_send_realm_reactivation_email(realm: Realm, *, acting_user: Optional[UserProfile]) -> None:
|
||||||
url = create_confirmation_link(realm, Confirmation.REALM_REACTIVATION)
|
obj = RealmReactivationStatus.objects.create(realm=realm)
|
||||||
|
|
||||||
|
url = create_confirmation_link(obj, Confirmation.REALM_REACTIVATION)
|
||||||
RealmAuditLog.objects.create(
|
RealmAuditLog.objects.create(
|
||||||
realm=realm,
|
realm=realm,
|
||||||
acting_user=acting_user,
|
acting_user=acting_user,
|
||||||
|
|
|
@ -145,6 +145,7 @@ ALL_ZULIP_TABLES = {
|
||||||
"zerver_realmemoji",
|
"zerver_realmemoji",
|
||||||
"zerver_realmfilter",
|
"zerver_realmfilter",
|
||||||
"zerver_realmplayground",
|
"zerver_realmplayground",
|
||||||
|
"zerver_realmreactivationstatus",
|
||||||
"zerver_realmuserdefault",
|
"zerver_realmuserdefault",
|
||||||
"zerver_recipient",
|
"zerver_recipient",
|
||||||
"zerver_scheduledemail",
|
"zerver_scheduledemail",
|
||||||
|
@ -184,6 +185,7 @@ NON_EXPORTED_TABLES = {
|
||||||
"zerver_multiuseinvite_streams",
|
"zerver_multiuseinvite_streams",
|
||||||
"zerver_preregistrationuser",
|
"zerver_preregistrationuser",
|
||||||
"zerver_preregistrationuser_streams",
|
"zerver_preregistrationuser_streams",
|
||||||
|
"zerver_realmreactivationstatus",
|
||||||
# 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.
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Generated by Django 4.0.6 on 2022-07-25 20:31
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("zerver", "0399_preregistrationuser_multiuse_invite"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="RealmReactivationStatus",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("status", models.IntegerField(default=0)),
|
||||||
|
(
|
||||||
|
"realm",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE, to="zerver.realm"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -2315,6 +2315,15 @@ class EmailChangeStatus(models.Model):
|
||||||
realm: Realm = models.ForeignKey(Realm, on_delete=CASCADE)
|
realm: Realm = models.ForeignKey(Realm, on_delete=CASCADE)
|
||||||
|
|
||||||
|
|
||||||
|
class RealmReactivationStatus(models.Model):
|
||||||
|
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name="ID")
|
||||||
|
# status: whether an object has been confirmed.
|
||||||
|
# if confirmed, set to confirmation.settings.STATUS_USED
|
||||||
|
status: int = models.IntegerField(default=0)
|
||||||
|
|
||||||
|
realm: Realm = models.ForeignKey(Realm, on_delete=CASCADE)
|
||||||
|
|
||||||
|
|
||||||
class AbstractPushDeviceToken(models.Model):
|
class AbstractPushDeviceToken(models.Model):
|
||||||
APNS = 1
|
APNS = 1
|
||||||
GCM = 2
|
GCM = 2
|
||||||
|
|
|
@ -31,6 +31,7 @@ from zerver.models import (
|
||||||
Message,
|
Message,
|
||||||
Realm,
|
Realm,
|
||||||
RealmAuditLog,
|
RealmAuditLog,
|
||||||
|
RealmReactivationStatus,
|
||||||
RealmUserDefault,
|
RealmUserDefault,
|
||||||
ScheduledEmail,
|
ScheduledEmail,
|
||||||
Stream,
|
Stream,
|
||||||
|
@ -354,7 +355,9 @@ class RealmTest(ZulipTestCase):
|
||||||
realm = get_realm("zulip")
|
realm = get_realm("zulip")
|
||||||
do_deactivate_realm(realm, acting_user=None)
|
do_deactivate_realm(realm, acting_user=None)
|
||||||
self.assertTrue(realm.deactivated)
|
self.assertTrue(realm.deactivated)
|
||||||
confirmation_url = create_confirmation_link(realm, Confirmation.REALM_REACTIVATION)
|
|
||||||
|
obj = RealmReactivationStatus.objects.create(realm=realm)
|
||||||
|
confirmation_url = create_confirmation_link(obj, Confirmation.REALM_REACTIVATION)
|
||||||
response = self.client_get(confirmation_url)
|
response = self.client_get(confirmation_url)
|
||||||
self.assert_in_success_response(
|
self.assert_in_success_response(
|
||||||
["Your organization has been successfully reactivated"], response
|
["Your organization has been successfully reactivated"], response
|
||||||
|
@ -362,6 +365,11 @@ class RealmTest(ZulipTestCase):
|
||||||
realm = get_realm("zulip")
|
realm = get_realm("zulip")
|
||||||
self.assertFalse(realm.deactivated)
|
self.assertFalse(realm.deactivated)
|
||||||
|
|
||||||
|
# Make sure the link can't be reused.
|
||||||
|
do_deactivate_realm(realm, acting_user=None)
|
||||||
|
response = self.client_get(confirmation_url)
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
def test_realm_reactivation_confirmation_object(self) -> None:
|
def test_realm_reactivation_confirmation_object(self) -> None:
|
||||||
realm = get_realm("zulip")
|
realm = get_realm("zulip")
|
||||||
do_deactivate_realm(realm, acting_user=None)
|
do_deactivate_realm(realm, acting_user=None)
|
||||||
|
|
|
@ -38,7 +38,7 @@ from zerver.lib.validator import (
|
||||||
check_string_or_int,
|
check_string_or_int,
|
||||||
to_non_negative_int,
|
to_non_negative_int,
|
||||||
)
|
)
|
||||||
from zerver.models import Realm, RealmUserDefault, UserProfile
|
from zerver.models import Realm, RealmReactivationStatus, RealmUserDefault, UserProfile
|
||||||
from zerver.views.user_settings import check_settings_values
|
from zerver.views.user_settings import check_settings_values
|
||||||
|
|
||||||
ORG_TYPE_IDS: List[int] = [t["id"] for t in Realm.ORG_TYPES.values()]
|
ORG_TYPE_IDS: List[int] = [t["id"] for t in Realm.ORG_TYPES.values()]
|
||||||
|
@ -327,14 +327,16 @@ def check_subdomain_available(request: HttpRequest, subdomain: str) -> HttpRespo
|
||||||
|
|
||||||
def realm_reactivation(request: HttpRequest, confirmation_key: str) -> HttpResponse:
|
def realm_reactivation(request: HttpRequest, confirmation_key: str) -> HttpResponse:
|
||||||
try:
|
try:
|
||||||
realm = get_object_from_key(
|
obj = get_object_from_key(
|
||||||
confirmation_key, [Confirmation.REALM_REACTIVATION], mark_object_used=False
|
confirmation_key, [Confirmation.REALM_REACTIVATION], mark_object_used=True
|
||||||
)
|
)
|
||||||
except ConfirmationKeyException:
|
except ConfirmationKeyException:
|
||||||
return render(request, "zerver/realm_reactivation_link_error.html", status=404)
|
return render(request, "zerver/realm_reactivation_link_error.html", status=404)
|
||||||
assert isinstance(realm, Realm)
|
|
||||||
|
assert isinstance(obj, RealmReactivationStatus)
|
||||||
|
realm = obj.realm
|
||||||
|
|
||||||
do_reactivate_realm(realm)
|
do_reactivate_realm(realm)
|
||||||
# TODO: After reactivating the realm, the confirmation link needs to be revoked in some way.
|
|
||||||
|
|
||||||
context = {"realm": realm}
|
context = {"realm": realm}
|
||||||
return render(request, "zerver/realm_reactivation.html", context)
|
return render(request, "zerver/realm_reactivation.html", context)
|
||||||
|
|
Loading…
Reference in New Issue