diff --git a/analytics/views/support.py b/analytics/views/support.py index 5cbdcede41..ccc5eaf9f4 100644 --- a/analytics/views/support.py +++ b/analytics/views/support.py @@ -36,6 +36,7 @@ from zerver.models import ( MultiuseInvite, PreregistrationUser, Realm, + RealmReactivationStatus, UserProfile, get_org_type_display_name, get_realm, @@ -313,8 +314,9 @@ def support( ] confirmations += get_confirmations([Confirmation.MULTIUSE_INVITE], multiuse_invite_ids) + realm_reactivation_status_objects = RealmReactivationStatus.objects.filter(realm__in=realms) 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 diff --git a/zerver/actions/realm_settings.py b/zerver/actions/realm_settings.py index f61e30d2aa..7a8d7776b1 100644 --- a/zerver/actions/realm_settings.py +++ b/zerver/actions/realm_settings.py @@ -21,6 +21,7 @@ from zerver.models import ( Attachment, Realm, RealmAuditLog, + RealmReactivationStatus, RealmUserDefault, ScheduledEmail, Stream, @@ -460,7 +461,9 @@ def do_change_realm_plan_type( 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( realm=realm, acting_user=acting_user, diff --git a/zerver/lib/export.py b/zerver/lib/export.py index f36f468640..7b57dd8a13 100644 --- a/zerver/lib/export.py +++ b/zerver/lib/export.py @@ -145,6 +145,7 @@ ALL_ZULIP_TABLES = { "zerver_realmemoji", "zerver_realmfilter", "zerver_realmplayground", + "zerver_realmreactivationstatus", "zerver_realmuserdefault", "zerver_recipient", "zerver_scheduledemail", @@ -184,6 +185,7 @@ NON_EXPORTED_TABLES = { "zerver_multiuseinvite_streams", "zerver_preregistrationuser", "zerver_preregistrationuser_streams", + "zerver_realmreactivationstatus", # Missed message addresses are low value to export since # missed-message email addresses include the server's hostname and # expire after a few days. diff --git a/zerver/migrations/0400_realmreactivationstatus.py b/zerver/migrations/0400_realmreactivationstatus.py new file mode 100644 index 0000000000..d22bf5cc78 --- /dev/null +++ b/zerver/migrations/0400_realmreactivationstatus.py @@ -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" + ), + ), + ], + ), + ] diff --git a/zerver/models.py b/zerver/models.py index 0bf1232d10..98cd9dba12 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -2315,6 +2315,15 @@ class EmailChangeStatus(models.Model): 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): APNS = 1 GCM = 2 diff --git a/zerver/tests/test_realm.py b/zerver/tests/test_realm.py index a7656da60e..24e3f8bcba 100644 --- a/zerver/tests/test_realm.py +++ b/zerver/tests/test_realm.py @@ -31,6 +31,7 @@ from zerver.models import ( Message, Realm, RealmAuditLog, + RealmReactivationStatus, RealmUserDefault, ScheduledEmail, Stream, @@ -354,7 +355,9 @@ class RealmTest(ZulipTestCase): realm = get_realm("zulip") do_deactivate_realm(realm, acting_user=None) 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) self.assert_in_success_response( ["Your organization has been successfully reactivated"], response @@ -362,6 +365,11 @@ class RealmTest(ZulipTestCase): realm = get_realm("zulip") 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: realm = get_realm("zulip") do_deactivate_realm(realm, acting_user=None) diff --git a/zerver/views/realm.py b/zerver/views/realm.py index 756dc20bca..28ae2c1a43 100644 --- a/zerver/views/realm.py +++ b/zerver/views/realm.py @@ -38,7 +38,7 @@ from zerver.lib.validator import ( check_string_or_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 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: try: - realm = get_object_from_key( - confirmation_key, [Confirmation.REALM_REACTIVATION], mark_object_used=False + obj = get_object_from_key( + confirmation_key, [Confirmation.REALM_REACTIVATION], mark_object_used=True ) except ConfirmationKeyException: 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) - # TODO: After reactivating the realm, the confirmation link needs to be revoked in some way. context = {"realm": realm} return render(request, "zerver/realm_reactivation.html", context)