diff --git a/analytics/tests/test_counts.py b/analytics/tests/test_counts.py index f81b1353cb..4c73366bd9 100644 --- a/analytics/tests/test_counts.py +++ b/analytics/tests/test_counts.py @@ -1371,12 +1371,12 @@ class TestLoggingCountStats(AnalyticsTestCase): user = self.create_user(email="first@domain.tld") stream, _ = self.create_stream_with_recipient() - invite_expires_in_days = 2 + invite_expires_in_minutes = 2 * 24 * 60 do_invite_users( user, ["user1@domain.tld", "user2@domain.tld"], [stream], - invite_expires_in_days=invite_expires_in_days, + invite_expires_in_minutes=invite_expires_in_minutes, ) assertInviteCountEquals(2) @@ -1386,7 +1386,7 @@ class TestLoggingCountStats(AnalyticsTestCase): user, ["user1@domain.tld", "user2@domain.tld"], [stream], - invite_expires_in_days=invite_expires_in_days, + invite_expires_in_minutes=invite_expires_in_minutes, ) assertInviteCountEquals(4) @@ -1396,7 +1396,7 @@ class TestLoggingCountStats(AnalyticsTestCase): user, ["user3@domain.tld", "malformed"], [stream], - invite_expires_in_days=invite_expires_in_days, + invite_expires_in_minutes=invite_expires_in_minutes, ) except InvitationError: pass @@ -1408,7 +1408,7 @@ class TestLoggingCountStats(AnalyticsTestCase): user, ["first@domain.tld", "user4@domain.tld"], [stream], - invite_expires_in_days=invite_expires_in_days, + invite_expires_in_minutes=invite_expires_in_minutes, ) except InvitationError: pass diff --git a/analytics/tests/test_support_views.py b/analytics/tests/test_support_views.py index af08f9fe64..a2370961d1 100644 --- a/analytics/tests/test_support_views.py +++ b/analytics/tests/test_support_views.py @@ -258,7 +258,7 @@ class TestSupportEndpoint(ZulipTestCase): check_preregistration_user_query_result(result, self.nonreg_email("test")) check_zulip_realm_query_result(result) - invite_expires_in_days = 10 + invite_expires_in_minutes = 10 * 24 * 60 stream_ids = [self.get_stream_id("Denmark")] invitee_emails = [self.nonreg_email("test1")] self.client_post( @@ -266,7 +266,7 @@ class TestSupportEndpoint(ZulipTestCase): { "invitee_emails": invitee_emails, "stream_ids": orjson.dumps(stream_ids).decode(), - "invite_expires_in_days": invite_expires_in_days, + "invite_expires_in_minutes": invite_expires_in_minutes, "invite_as": PreregistrationUser.INVITE_AS["MEMBER"], }, ) @@ -282,7 +282,7 @@ class TestSupportEndpoint(ZulipTestCase): do_create_multiuse_invite_link( self.example_user("hamlet"), invited_as=1, - invite_expires_in_days=invite_expires_in_days, + invite_expires_in_minutes=invite_expires_in_minutes, ) result = self.client_get("/activity/support", {"q": "zulip"}) check_multiuse_invite_link_query_result(result) diff --git a/confirmation/models.py b/confirmation/models.py index 03e6fdce35..ac5d66c590 100644 --- a/confirmation/models.py +++ b/confirmation/models.py @@ -86,10 +86,10 @@ def create_confirmation_link( obj: Union[Realm, HasRealmObject, OptionalHasRealmObject], confirmation_type: int, *, - validity_in_days: Union[Optional[int], UnspecifiedValue] = UnspecifiedValue(), + validity_in_minutes: Union[Optional[int], UnspecifiedValue] = UnspecifiedValue(), url_args: Mapping[str, str] = {}, ) -> str: - # validity_in_days is an override for the default values which are + # validity_in_minutes is an override for the default values which are # determined by the confirmation_type - its main purpose is for use # in tests which may want to have control over the exact expiration time. key = generate_key() @@ -101,12 +101,12 @@ def create_confirmation_link( current_time = timezone_now() expiry_date = None - if not isinstance(validity_in_days, UnspecifiedValue): - if validity_in_days is None: + if not isinstance(validity_in_minutes, UnspecifiedValue): + if validity_in_minutes is None: expiry_date = None else: - assert validity_in_days is not None - expiry_date = current_time + datetime.timedelta(days=validity_in_days) + assert validity_in_minutes is not None + expiry_date = current_time + datetime.timedelta(minutes=validity_in_minutes) else: expiry_date = current_time + datetime.timedelta( days=_properties[confirmation_type].validity_in_days diff --git a/static/js/invite.js b/static/js/invite.js index bd6304c0c1..04df06e50f 100644 --- a/static/js/invite.js +++ b/static/js/invite.js @@ -1,6 +1,6 @@ import autosize from "autosize"; import ClipboardJS from "clipboard"; -import {addDays} from "date-fns"; +import {add} from "date-fns"; import $ from "jquery"; import copy_invite_link from "../templates/copy_invite_link.hbs"; @@ -52,7 +52,7 @@ function get_common_invitation_data() { csrfmiddlewaretoken: $('input[name="csrfmiddlewaretoken"]').attr("value"), invite_as, stream_ids: JSON.stringify(stream_ids), - invite_expires_in_days: expires_in, + invite_expires_in_minutes: expires_in, }; return data; } @@ -208,7 +208,7 @@ function valid_to() { if (!time_valid) { return $t({defaultMessage: "Never expires"}); } - const valid_to = addDays(new Date(), time_valid); + const valid_to = add(new Date(), {minutes: time_valid}); return $t({defaultMessage: "Expires on {date}"}, {date: valid_to.toLocaleDateString()}); } diff --git a/static/js/settings_config.ts b/static/js/settings_config.ts index 98a2031c44..63a191e76a 100644 --- a/static/js/settings_config.ts +++ b/static/js/settings_config.ts @@ -449,22 +449,22 @@ export const expires_in_values = { // default: false, // }, day: { - value: 1, + value: 24 * 60, description: $t({defaultMessage: "1 day"}), default: false, }, threeDays: { - value: 3, + value: 3 * 24 * 60, description: $t({defaultMessage: "3 days"}), default: false, }, tenDays: { - value: 10, + value: 10 * 24 * 60, description: $t({defaultMessage: "10 days"}), default: true, }, thirtyDays: { - value: 30, + value: 30 * 24 * 60, description: $t({defaultMessage: "30 days"}), default: false, }, diff --git a/templates/zerver/api/changelog.md b/templates/zerver/api/changelog.md index d68b58d138..a889aeb491 100644 --- a/templates/zerver/api/changelog.md +++ b/templates/zerver/api/changelog.md @@ -20,6 +20,11 @@ format used by the Zulip server that they are interacting with. ## Changes in Zulip 6.0 +**Feature level 126** + +* `POST /invites`, `POST /invites/multiuse`: Replaced `invite_expires_in_days` + parameter with `invite_expires_in_minutes`. + **Feature level 125** * [`POST /register`](/api/register-queue), [`PATCH diff --git a/version.py b/version.py index 6eabf0aa7f..db8f1a0a18 100644 --- a/version.py +++ b/version.py @@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.4.3" # Changes should be accompanied by documentation explaining what the # new level means in templates/zerver/api/changelog.md, as well as # "**Changes**" entries in the endpoint's documentation in `zulip.yaml`. -API_FEATURE_LEVEL = 125 +API_FEATURE_LEVEL = 126 # Bump the minor PROVISION_VERSION to indicate that folks should provision # only when going from an old version of the code to a newer version. Bump diff --git a/zerver/actions/invites.py b/zerver/actions/invites.py index 774c743398..9c56ca1402 100644 --- a/zerver/actions/invites.py +++ b/zerver/actions/invites.py @@ -41,13 +41,13 @@ def do_send_confirmation_email( invitee: PreregistrationUser, referrer: UserProfile, email_language: str, - invite_expires_in_days: Union[Optional[int], UnspecifiedValue] = UnspecifiedValue(), + invite_expires_in_minutes: Union[Optional[int], UnspecifiedValue] = UnspecifiedValue(), ) -> str: """ Send the confirmation/welcome e-mail to an invited user. """ activation_url = create_confirmation_link( - invitee, Confirmation.INVITATION, validity_in_days=invite_expires_in_days + invitee, Confirmation.INVITATION, validity_in_minutes=invite_expires_in_minutes ) context = { "referrer_full_name": referrer.full_name, @@ -129,7 +129,7 @@ def do_invite_users( invitee_emails: Collection[str], streams: Collection[Stream], *, - invite_expires_in_days: Optional[int], + invite_expires_in_minutes: Optional[int], invite_as: int = PreregistrationUser.INVITE_AS["MEMBER"], ) -> None: num_invites = len(invitee_emails) @@ -224,7 +224,7 @@ def do_invite_users( "prereg_id": prereg_user.id, "referrer_id": user_profile.id, "email_language": user_profile.realm.default_language, - "invite_expires_in_days": invite_expires_in_days, + "invite_expires_in_minutes": invite_expires_in_minutes, } queue_json_publish("invites", event) @@ -343,7 +343,7 @@ def revoke_invites_generated_by_user(user_profile: UserProfile) -> None: def do_create_multiuse_invite_link( referred_by: UserProfile, invited_as: int, - invite_expires_in_days: Optional[int], + invite_expires_in_minutes: Optional[int], streams: Sequence[Stream] = [], ) -> str: realm = referred_by.realm @@ -354,7 +354,7 @@ def do_create_multiuse_invite_link( invite.save() notify_invites_changed(referred_by.realm) return create_confirmation_link( - invite, Confirmation.MULTIUSE_INVITE, validity_in_days=invite_expires_in_days + invite, Confirmation.MULTIUSE_INVITE, validity_in_minutes=invite_expires_in_minutes ) @@ -395,11 +395,11 @@ def do_resend_user_invite_email(prereg_user: PreregistrationUser) -> int: expiry_date = prereg_user.confirmation.get().expiry_date if expiry_date is None: - invite_expires_in_days = None + invite_expires_in_minutes = None else: # The resent invitation is reset to expire as long after the # reminder is sent as it lasted originally. - invite_expires_in_days = (expiry_date - prereg_user.invited_at).days + invite_expires_in_minutes = (expiry_date - prereg_user.invited_at).total_seconds() / 60 prereg_user.confirmation.clear() do_increment_logging_stat( @@ -412,7 +412,7 @@ def do_resend_user_invite_email(prereg_user: PreregistrationUser) -> int: "prereg_id": prereg_user.id, "referrer_id": prereg_user.referred_by.id, "email_language": prereg_user.referred_by.realm.default_language, - "invite_expires_in_days": invite_expires_in_days, + "invite_expires_in_minutes": invite_expires_in_minutes, } queue_json_publish("invites", event) diff --git a/zerver/models.py b/zerver/models.py index 3d08ad3c78..61de70be4d 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -2246,7 +2246,7 @@ class PreregistrationUser(models.Model): def filter_to_valid_prereg_users( query: QuerySet, - invite_expires_in_days: Union[Optional[int], UnspecifiedValue] = UnspecifiedValue(), + invite_expires_in_minutes: Union[Optional[int], UnspecifiedValue] = UnspecifiedValue(), ) -> QuerySet: """ If invite_expires_in_days is specified, we return only those PreregistrationUser @@ -2256,15 +2256,15 @@ def filter_to_valid_prereg_users( revoked_value = confirmation_settings.STATUS_REVOKED query = query.exclude(status__in=[active_value, revoked_value]) - if invite_expires_in_days is None: - # Since invite_expires_in_days is None, we're invitation will never + if invite_expires_in_minutes is None: + # Since invite_expires_in_minutes is None, we're invitation will never # expire, we do not need to check anything else and can simply return # after excluding objects with active and revoked status. return query - assert invite_expires_in_days is not None - if not isinstance(invite_expires_in_days, UnspecifiedValue): - lowest_datetime = timezone_now() - datetime.timedelta(days=invite_expires_in_days) + assert invite_expires_in_minutes is not None + if not isinstance(invite_expires_in_minutes, UnspecifiedValue): + lowest_datetime = timezone_now() - datetime.timedelta(minutes=invite_expires_in_minutes) return query.filter(invited_at__gte=lowest_datetime) else: return query.filter( diff --git a/zerver/tests/test_auth_backends.py b/zerver/tests/test_auth_backends.py index 5e2a7f922d..2349b3f98b 100644 --- a/zerver/tests/test_auth_backends.py +++ b/zerver/tests/test_auth_backends.py @@ -1505,7 +1505,7 @@ class SocialAuthBase(DesktopFlowTestingLib, ZulipTestCase, ABC): realm = get_realm("zulip") iago = self.example_user("iago") - do_invite_users(iago, [email], [], invite_expires_in_days=2) + do_invite_users(iago, [email], [], invite_expires_in_minutes=2 * 24 * 60) account_data_dict = self.get_account_data_dict(email=email, name=name) result = self.social_auth_test( @@ -1555,9 +1555,9 @@ class SocialAuthBase(DesktopFlowTestingLib, ZulipTestCase, ABC): referrer = self.example_user("hamlet") multiuse_obj = MultiuseInvite.objects.create(realm=realm, referred_by=referrer) multiuse_obj.streams.set(streams) - validity_in_days = 2 + validity_in_minutes = 2 * 24 * 60 create_confirmation_link( - multiuse_obj, Confirmation.MULTIUSE_INVITE, validity_in_days=validity_in_days + multiuse_obj, Confirmation.MULTIUSE_INVITE, validity_in_minutes=validity_in_minutes ) multiuse_confirmation = Confirmation.objects.all().last() assert multiuse_confirmation is not None @@ -1602,9 +1602,9 @@ class SocialAuthBase(DesktopFlowTestingLib, ZulipTestCase, ABC): realm=lear_realm, referred_by=UserProfile.objects.filter(realm=lear_realm).first() ) multiuse_obj.streams.set(streams) - validity_in_days = 2 + validity_in_minutes = 2 * 24 * 60 create_confirmation_link( - multiuse_obj, Confirmation.MULTIUSE_INVITE, validity_in_days=validity_in_days + multiuse_obj, Confirmation.MULTIUSE_INVITE, validity_in_minutes=validity_in_minutes ) multiuse_confirmation = Confirmation.objects.all().last() assert multiuse_confirmation is not None @@ -1840,15 +1840,15 @@ class SocialAuthBase(DesktopFlowTestingLib, ZulipTestCase, ABC): email = self.nonreg_email("alice") name = "Alice Jones" - invite_expires_in_days = 2 + invite_expires_in_minutes = 2 * 24 * 60 do_invite_users( iago, [email], [], - invite_expires_in_days=invite_expires_in_days, + invite_expires_in_minutes=invite_expires_in_minutes, invite_as=PreregistrationUser.INVITE_AS["REALM_ADMIN"], ) - now = timezone_now() + datetime.timedelta(days=invite_expires_in_days + 1) + now = timezone_now() + datetime.timedelta(days=3) subdomain = "zulip" realm = get_realm("zulip") @@ -4320,9 +4320,9 @@ class GoogleAuthBackendTest(SocialAuthBase): referrer = self.example_user("hamlet") multiuse_obj = MultiuseInvite.objects.create(realm=realm, referred_by=referrer) multiuse_obj.streams.set(streams) - validity_in_days = 2 + validity_in_minutes = 2 * 24 * 60 create_confirmation_link( - multiuse_obj, Confirmation.MULTIUSE_INVITE, validity_in_days=validity_in_days + multiuse_obj, Confirmation.MULTIUSE_INVITE, validity_in_minutes=validity_in_minutes ) multiuse_confirmation = Confirmation.objects.all().last() assert multiuse_confirmation is not None diff --git a/zerver/tests/test_events.py b/zerver/tests/test_events.py index 0a2d5af0f2..6c6d6dd6c9 100644 --- a/zerver/tests/test_events.py +++ b/zerver/tests/test_events.py @@ -773,13 +773,13 @@ class NormalActionsTest(BaseAction): for stream_name in ["Denmark", "Scotland"]: streams.append(get_stream(stream_name, self.user_profile.realm)) - invite_expires_in_days = 2 + invite_expires_in_minutes = 2 * 24 * 60 events = self.verify_action( lambda: do_invite_users( self.user_profile, ["foo@zulip.com"], streams, - invite_expires_in_days=invite_expires_in_days, + invite_expires_in_minutes=invite_expires_in_minutes, ), state_change_expected=False, ) @@ -791,12 +791,12 @@ class NormalActionsTest(BaseAction): for stream_name in ["Denmark", "Verona"]: streams.append(get_stream(stream_name, self.user_profile.realm)) - invite_expires_in_days = 2 + invite_expires_in_minutes = 2 * 24 * 60 events = self.verify_action( lambda: do_create_multiuse_invite_link( self.user_profile, PreregistrationUser.INVITE_AS["MEMBER"], - invite_expires_in_days, + invite_expires_in_minutes, streams, ), state_change_expected=False, @@ -806,12 +806,12 @@ class NormalActionsTest(BaseAction): def test_deactivate_user_invites_changed_event(self) -> None: self.user_profile = self.example_user("iago") user_profile = self.example_user("cordelia") - invite_expires_in_days = 2 + invite_expires_in_minutes = 2 * 24 * 60 do_invite_users( user_profile, ["foo@zulip.com"], [], - invite_expires_in_days=invite_expires_in_days, + invite_expires_in_minutes=invite_expires_in_minutes, ) events = self.verify_action( @@ -827,12 +827,12 @@ class NormalActionsTest(BaseAction): for stream_name in ["Denmark", "Verona"]: streams.append(get_stream(stream_name, self.user_profile.realm)) - invite_expires_in_days = 2 + invite_expires_in_minutes = 2 * 24 * 60 do_invite_users( self.user_profile, ["foo@zulip.com"], streams, - invite_expires_in_days=invite_expires_in_days, + invite_expires_in_minutes=invite_expires_in_minutes, ) prereg_users = PreregistrationUser.objects.filter( referred_by__realm=self.user_profile.realm @@ -849,11 +849,11 @@ class NormalActionsTest(BaseAction): for stream_name in ["Denmark", "Verona"]: streams.append(get_stream(stream_name, self.user_profile.realm)) - invite_expires_in_days = 2 + invite_expires_in_minutes = 2 * 24 * 60 do_create_multiuse_invite_link( self.user_profile, PreregistrationUser.INVITE_AS["MEMBER"], - invite_expires_in_days, + invite_expires_in_minutes, streams, ) @@ -872,12 +872,12 @@ class NormalActionsTest(BaseAction): for stream_name in ["Denmark", "Scotland"]: streams.append(get_stream(stream_name, self.user_profile.realm)) - invite_expires_in_days = 2 + invite_expires_in_minutes = 2 * 24 * 60 do_invite_users( self.user_profile, ["foo@zulip.com"], streams, - invite_expires_in_days=invite_expires_in_days, + invite_expires_in_minutes=invite_expires_in_minutes, ) prereg_user = PreregistrationUser.objects.get(email="foo@zulip.com") diff --git a/zerver/tests/test_i18n.py b/zerver/tests/test_i18n.py index 3149fa54d3..93e026e68c 100644 --- a/zerver/tests/test_i18n.py +++ b/zerver/tests/test_i18n.py @@ -35,7 +35,7 @@ class EmailTranslationTestCase(ZulipTestCase): realm.default_language = "de" realm.save() stream = get_realm_stream("Denmark", realm.id) - invite_expires_in_days = 2 + invite_expires_in_minutes = 2 * 24 * 60 self.login_user(hamlet) # TODO: Uncomment and replace with translation once we have German translations for the strings @@ -59,7 +59,7 @@ class EmailTranslationTestCase(ZulipTestCase): { "invitee_emails": "new-email@zulip.com", "stream_ids": orjson.dumps([stream.id]).decode(), - "invite_expires_in_days": invite_expires_in_days, + "invite_expires_in_minutes": invite_expires_in_minutes, }, ) diff --git a/zerver/tests/test_queue_worker.py b/zerver/tests/test_queue_worker.py index eb7751ae63..1d1e9bbc8d 100644 --- a/zerver/tests/test_queue_worker.py +++ b/zerver/tests/test_queue_worker.py @@ -613,24 +613,24 @@ class WorkerTest(ZulipTestCase): PreregistrationUser.objects.create( email=self.nonreg_email("bob"), referred_by=inviter, realm=inviter.realm ) - invite_expires_in_days = 4 + invite_expires_in_minutes = 4 * 24 * 60 data: List[Dict[str, Any]] = [ dict( prereg_id=prereg_alice.id, referrer_id=inviter.id, - invite_expires_in_days=invite_expires_in_days, + invite_expires_in_minutes=invite_expires_in_minutes, ), dict( prereg_id=prereg_alice.id, referrer_id=inviter.id, email_language="en", - invite_expires_in_days=invite_expires_in_days, + invite_expires_in_minutes=invite_expires_in_minutes, ), # Nonexistent prereg_id, as if the invitation was deleted dict( prereg_id=-1, referrer_id=inviter.id, - invite_expires_in_days=invite_expires_in_days, + invite_expires_in_minutes=invite_expires_in_minutes, ), ] for element in data: diff --git a/zerver/tests/test_signup.py b/zerver/tests/test_signup.py index f6db878f30..d227114b8f 100644 --- a/zerver/tests/test_signup.py +++ b/zerver/tests/test_signup.py @@ -1050,7 +1050,7 @@ class InviteUserBase(ZulipTestCase): self, invitee_emails: str, stream_names: Sequence[str], - invite_expires_in_days: Optional[int] = settings.INVITATION_LINK_VALIDITY_DAYS, + invite_expires_in_minutes: Optional[int] = settings.INVITATION_LINK_VALIDITY_MINUTES, body: str = "", invite_as: int = PreregistrationUser.INVITE_AS["MEMBER"], ) -> HttpResponse: @@ -1066,7 +1066,7 @@ class InviteUserBase(ZulipTestCase): for stream_name in stream_names: stream_ids.append(self.get_stream_id(stream_name)) - invite_expires_in: Union[str, Optional[int]] = invite_expires_in_days + invite_expires_in: Union[str, Optional[int]] = invite_expires_in_minutes if invite_expires_in is None: invite_expires_in = orjson.dumps(None).decode() @@ -1074,7 +1074,7 @@ class InviteUserBase(ZulipTestCase): "/json/invites", { "invitee_emails": invitee_emails, - "invite_expires_in_days": invite_expires_in, + "invite_expires_in_minutes": invite_expires_in, "stream_ids": orjson.dumps(stream_ids).decode(), "invite_as": invite_as, }, @@ -1981,9 +1981,9 @@ so we didn't send them an invitation. We did send invitations to everyone else!" data = {"email": invitee_email, "referrer_email": current_user.email} invitee = PreregistrationUser.objects.get(email=data["email"]) referrer = self.example_user(referrer_name) - validity_in_days = 2 + validity_in_minutes = 2 * 24 * 60 link = create_confirmation_link( - invitee, Confirmation.INVITATION, validity_in_days=validity_in_days + invitee, Confirmation.INVITATION, validity_in_minutes=validity_in_minutes ) context = common_context(referrer) context.update( @@ -2037,12 +2037,12 @@ so we didn't send them an invitation. We did send invitations to everyone else!" def test_no_invitation_reminder_when_link_expires_quickly(self) -> None: self.login("hamlet") # Check invitation reminder email is scheduled with 4 day link expiry - self.invite("alice@zulip.com", ["Denmark"], invite_expires_in_days=4) + self.invite("alice@zulip.com", ["Denmark"], invite_expires_in_minutes=4 * 24 * 60) self.assertEqual( ScheduledEmail.objects.filter(type=ScheduledEmail.INVITATION_REMINDER).count(), 1 ) # Check invitation reminder email is not scheduled with 3 day link expiry - self.invite("bob@zulip.com", ["Denmark"], invite_expires_in_days=3) + self.invite("bob@zulip.com", ["Denmark"], invite_expires_in_minutes=3 * 24 * 60) self.assertEqual( ScheduledEmail.objects.filter(type=ScheduledEmail.INVITATION_REMINDER).count(), 1 ) @@ -2108,7 +2108,7 @@ so we didn't send them an invitation. We did send invitations to everyone else!" email=email, referred_by=inviter, realm=realm ) activation_url = create_confirmation_link( - prereg_user, Confirmation.INVITATION, validity_in_days=None + prereg_user, Confirmation.INVITATION, validity_in_minutes=None ) confirmation = Confirmation.objects.last() assert confirmation is not None @@ -2126,32 +2126,32 @@ so we didn't send them an invitation. We did send invitations to everyone else!" for stream_name in ["Denmark", "Scotland"]: streams.append(get_stream(stream_name, self.user_profile.realm)) - invite_expires_in_days = 2 + invite_expires_in_minutes = 2 * 24 * 60 do_invite_users( self.user_profile, ["foo@zulip.com"], streams, - invite_expires_in_days=invite_expires_in_days, + invite_expires_in_minutes=invite_expires_in_minutes, ) prereg_user = PreregistrationUser.objects.get(email="foo@zulip.com") do_invite_users( self.user_profile, ["foo@zulip.com"], streams, - invite_expires_in_days=invite_expires_in_days, + invite_expires_in_minutes=invite_expires_in_minutes, ) do_invite_users( self.user_profile, ["foo@zulip.com"], streams, - invite_expires_in_days=invite_expires_in_days, + invite_expires_in_minutes=invite_expires_in_minutes, ) # Also send an invite from a different realm. lear = get_realm("lear") lear_user = self.lear_user("cordelia") do_invite_users( - lear_user, ["foo@zulip.com"], [], invite_expires_in_days=invite_expires_in_days + lear_user, ["foo@zulip.com"], [], invite_expires_in_minutes=invite_expires_in_minutes ) invites = PreregistrationUser.objects.filter(email__iexact="foo@zulip.com") @@ -2294,33 +2294,39 @@ class InvitationsTestCase(InviteUserBase): for stream_name in ["Denmark", "Scotland"]: streams.append(get_stream(stream_name, user_profile.realm)) - invite_expires_in_days = 2 + invite_expires_in_minutes = 2 * 24 * 60 do_invite_users( user_profile, ["TestOne@zulip.com"], streams, - invite_expires_in_days=invite_expires_in_days, + invite_expires_in_minutes=invite_expires_in_minutes, ) do_invite_users( user_profile, ["TestTwo@zulip.com"], streams, - invite_expires_in_days=invite_expires_in_days, + invite_expires_in_minutes=invite_expires_in_minutes, ) do_invite_users( - hamlet, ["TestThree@zulip.com"], streams, invite_expires_in_days=invite_expires_in_days + hamlet, + ["TestThree@zulip.com"], + streams, + invite_expires_in_minutes=invite_expires_in_minutes, ) do_invite_users( - othello, ["TestFour@zulip.com"], streams, invite_expires_in_days=invite_expires_in_days + othello, + ["TestFour@zulip.com"], + streams, + invite_expires_in_minutes=invite_expires_in_minutes, ) do_invite_users( self.mit_user("sipbtest"), ["TestOne@mit.edu"], [], - invite_expires_in_days=invite_expires_in_days, + invite_expires_in_minutes=invite_expires_in_minutes, ) do_create_multiuse_invite_link( - user_profile, PreregistrationUser.INVITE_AS["MEMBER"], invite_expires_in_days + user_profile, PreregistrationUser.INVITE_AS["MEMBER"], invite_expires_in_minutes ) self.assert_length(do_get_invites_controlled_by_user(user_profile), 5) self.assert_length(do_get_invites_controlled_by_user(hamlet), 1) @@ -2344,26 +2350,26 @@ class InvitationsTestCase(InviteUserBase): for stream_name in ["Denmark", "Scotland"]: streams.append(get_stream(stream_name, user_profile.realm)) - invite_expires_in_days = 2 + invite_expires_in_minutes = 2 * 24 * 60 do_invite_users( user_profile, ["TestOne@zulip.com"], streams, - invite_expires_in_days=invite_expires_in_days, + invite_expires_in_minutes=invite_expires_in_minutes, ) with patch( "confirmation.models.timezone_now", - return_value=timezone_now() - datetime.timedelta(days=invite_expires_in_days + 1), + return_value=timezone_now() - datetime.timedelta(days=3), ): do_invite_users( user_profile, ["TestTwo@zulip.com"], streams, - invite_expires_in_days=invite_expires_in_days, + invite_expires_in_minutes=invite_expires_in_minutes, ) do_create_multiuse_invite_link( - othello, PreregistrationUser.INVITE_AS["MEMBER"], invite_expires_in_days + othello, PreregistrationUser.INVITE_AS["MEMBER"], invite_expires_in_minutes ) prereg_user_three = PreregistrationUser( @@ -2371,11 +2377,13 @@ class InvitationsTestCase(InviteUserBase): ) prereg_user_three.save() create_confirmation_link( - prereg_user_three, Confirmation.INVITATION, validity_in_days=invite_expires_in_days + prereg_user_three, + Confirmation.INVITATION, + validity_in_minutes=invite_expires_in_minutes, ) do_create_multiuse_invite_link( - hamlet, PreregistrationUser.INVITE_AS["MEMBER"], invite_expires_in_days + hamlet, PreregistrationUser.INVITE_AS["MEMBER"], invite_expires_in_minutes ) result = self.client_get("/json/invites") @@ -2406,13 +2414,13 @@ class InvitationsTestCase(InviteUserBase): user_profile, ["TestOne@zulip.com"], streams, - invite_expires_in_days=None, + invite_expires_in_minutes=None, ) do_invite_users( user_profile, ["TestTwo@zulip.com"], streams, - invite_expires_in_days=100, + invite_expires_in_minutes=100 * 24 * 60, ) do_create_multiuse_invite_link( user_profile, PreregistrationUser.INVITE_AS["MEMBER"], None @@ -2537,9 +2545,9 @@ class InvitationsTestCase(InviteUserBase): multiuse_invite = MultiuseInvite.objects.create( referred_by=self.example_user("hamlet"), realm=zulip_realm ) - validity_in_days = 2 + validity_in_minutes = 2 * 24 * 60 create_confirmation_link( - multiuse_invite, Confirmation.MULTIUSE_INVITE, validity_in_days=validity_in_days + multiuse_invite, Confirmation.MULTIUSE_INVITE, validity_in_minutes=validity_in_minutes ) result = self.client_delete("/json/invites/multiuse/" + str(multiuse_invite.id)) self.assertEqual(result.status_code, 200) @@ -2554,9 +2562,9 @@ class InvitationsTestCase(InviteUserBase): realm=zulip_realm, invited_as=PreregistrationUser.INVITE_AS["REALM_OWNER"], ) - validity_in_days = 2 + validity_in_minutes = 2 create_confirmation_link( - multiuse_invite, Confirmation.MULTIUSE_INVITE, validity_in_days=validity_in_days + multiuse_invite, Confirmation.MULTIUSE_INVITE, validity_in_minutes=validity_in_minutes ) error_result = self.client_delete("/json/invites/multiuse/" + str(multiuse_invite.id)) self.assert_json_error(error_result, "Must be an organization owner") @@ -2571,9 +2579,11 @@ class InvitationsTestCase(InviteUserBase): multiuse_invite_in_mit = MultiuseInvite.objects.create( referred_by=self.mit_user("sipbtest"), realm=mit_realm ) - validity_in_days = 2 + validity_in_minutes = 2 * 24 * 60 create_confirmation_link( - multiuse_invite_in_mit, Confirmation.MULTIUSE_INVITE, validity_in_days=validity_in_days + multiuse_invite_in_mit, + Confirmation.MULTIUSE_INVITE, + validity_in_minutes=validity_in_minutes, ) error_result = self.client_delete( "/json/invites/multiuse/" + str(multiuse_invite_in_mit.id) @@ -2830,10 +2840,10 @@ class MultiuseInviteTest(ZulipTestCase): if date_sent is None: date_sent = timezone_now() - validity_in_days = 2 + validity_in_minutes = 2 * 24 * 60 with patch("confirmation.models.timezone_now", return_value=date_sent): return create_confirmation_link( - invite, Confirmation.MULTIUSE_INVITE, validity_in_days=validity_in_days + invite, Confirmation.MULTIUSE_INVITE, validity_in_minutes=validity_in_minutes ) def check_user_able_to_register(self, email: str, invite_link: str) -> None: @@ -2861,8 +2871,7 @@ class MultiuseInviteTest(ZulipTestCase): email2 = self.nonreg_email("test1") email3 = self.nonreg_email("alice") - validity_in_days = 2 - date_sent = timezone_now() - datetime.timedelta(days=validity_in_days - 1) + date_sent = timezone_now() - datetime.timedelta(days=1) invite_link = self.generate_multiuse_invite_link(date_sent=date_sent) self.check_user_able_to_register(email1, invite_link) @@ -2948,7 +2957,9 @@ class MultiuseInviteTest(ZulipTestCase): def test_create_multiuse_link_api_call(self) -> None: self.login("iago") - result = self.client_post("/json/invites/multiuse", {"invite_expires_in_days": 2}) + result = self.client_post( + "/json/invites/multiuse", {"invite_expires_in_minutes": 2 * 24 * 60} + ) self.assert_json_success(result) invite_link = result.json()["invite_link"] @@ -2962,7 +2973,10 @@ class MultiuseInviteTest(ZulipTestCase): result = self.client_post( "/json/invites/multiuse", - {"stream_ids": orjson.dumps(stream_ids).decode(), "invite_expires_in_days": 2}, + { + "stream_ids": orjson.dumps(stream_ids).decode(), + "invite_expires_in_minutes": 2 * 24 * 60, + }, ) self.assert_json_success(result) @@ -2977,7 +2991,9 @@ class MultiuseInviteTest(ZulipTestCase): self.realm.invite_to_realm_policy = Realm.POLICY_MEMBERS_ONLY self.realm.save() - result = self.client_post("/json/invites/multiuse", {"invite_expires_in_days": 2}) + result = self.client_post( + "/json/invites/multiuse", {"invite_expires_in_minutes": 2 * 24 * 60} + ) self.assert_json_success(result) invite_link = result.json()["invite_link"] @@ -2993,7 +3009,7 @@ class MultiuseInviteTest(ZulipTestCase): "/json/invites/multiuse", { "invite_as": orjson.dumps(PreregistrationUser.INVITE_AS["REALM_OWNER"]).decode(), - "invite_expires_in_days": 2, + "invite_expires_in_minutes": 2 * 24 * 60, }, ) self.assert_json_error(result, "Must be an organization owner") @@ -3003,7 +3019,7 @@ class MultiuseInviteTest(ZulipTestCase): "/json/invites/multiuse", { "invite_as": orjson.dumps(PreregistrationUser.INVITE_AS["REALM_OWNER"]).decode(), - "invite_expires_in_days": 2, + "invite_expires_in_minutes": 2 * 24 * 60, }, ) self.assert_json_success(result) @@ -3015,7 +3031,10 @@ class MultiuseInviteTest(ZulipTestCase): self.login("iago") result = self.client_post( "/json/invites/multiuse", - {"stream_ids": orjson.dumps([54321]).decode(), "invite_expires_in_days": 2}, + { + "stream_ids": orjson.dumps([54321]).decode(), + "invite_expires_in_minutes": 2 * 24 * 60, + }, ) self.assert_json_error(result, "Invalid stream id 54321. No invites were sent.") diff --git a/zerver/tests/test_users.py b/zerver/tests/test_users.py index 79a838d737..ea6fa7abd2 100644 --- a/zerver/tests/test_users.py +++ b/zerver/tests/test_users.py @@ -795,12 +795,12 @@ class QueryCountTest(ZulipTestCase): ] streams = [get_stream(stream_name, realm) for stream_name in stream_names] - invite_expires_in_days = 4 + invite_expires_in_minutes = 4 * 24 * 60 do_invite_users( user_profile=self.example_user("hamlet"), invitee_emails=["fred@zulip.com"], streams=streams, - invite_expires_in_days=invite_expires_in_days, + invite_expires_in_minutes=invite_expires_in_minutes, ) prereg_user = PreregistrationUser.objects.get(email="fred@zulip.com") @@ -1461,19 +1461,19 @@ class ActivateTest(ZulipTestCase): iago = self.example_user("iago") desdemona = self.example_user("desdemona") - invite_expires_in_days = 2 + invite_expires_in_minutes = 2 * 24 * 60 do_invite_users( iago, ["new1@zulip.com", "new2@zulip.com"], [], - invite_expires_in_days=invite_expires_in_days, + invite_expires_in_minutes=invite_expires_in_minutes, invite_as=PreregistrationUser.INVITE_AS["REALM_ADMIN"], ) do_invite_users( desdemona, ["new3@zulip.com", "new4@zulip.com"], [], - invite_expires_in_days=invite_expires_in_days, + invite_expires_in_minutes=invite_expires_in_minutes, invite_as=PreregistrationUser.INVITE_AS["REALM_ADMIN"], ) @@ -1481,22 +1481,22 @@ class ActivateTest(ZulipTestCase): iago, ["new5@zulip.com"], [], - invite_expires_in_days=None, + invite_expires_in_minutes=None, invite_as=PreregistrationUser.INVITE_AS["REALM_ADMIN"], ) do_invite_users( desdemona, ["new6@zulip.com"], [], - invite_expires_in_days=None, + invite_expires_in_minutes=None, invite_as=PreregistrationUser.INVITE_AS["REALM_ADMIN"], ) iago_multiuse_key = do_create_multiuse_invite_link( - iago, PreregistrationUser.INVITE_AS["MEMBER"], invite_expires_in_days + iago, PreregistrationUser.INVITE_AS["MEMBER"], invite_expires_in_minutes ).split("/")[-2] desdemona_multiuse_key = do_create_multiuse_invite_link( - desdemona, PreregistrationUser.INVITE_AS["MEMBER"], invite_expires_in_days + desdemona, PreregistrationUser.INVITE_AS["MEMBER"], invite_expires_in_minutes ).split("/")[-2] iago_never_expire_multiuse_key = do_create_multiuse_invite_link( diff --git a/zerver/views/development/email_log.py b/zerver/views/development/email_log.py index 013ba74dde..1435266ff4 100755 --- a/zerver/views/development/email_log.py +++ b/zerver/views/development/email_log.py @@ -65,7 +65,7 @@ def generate_all_emails(request: HttpRequest) -> HttpResponse: registered_email = "hamlet@zulip.com" unregistered_email_1 = "new-person@zulip.com" unregistered_email_2 = "new-person-2@zulip.com" - invite_expires_in_days = settings.INVITATION_LINK_VALIDITY_DAYS + invite_expires_in_minutes = settings.INVITATION_LINK_VALIDITY_MINUTES realm = get_realm("zulip") other_realm = Realm.objects.exclude(string_id="zulip").first() user = get_user_by_delivery_email(registered_email, realm) @@ -110,7 +110,7 @@ def generate_all_emails(request: HttpRequest) -> HttpResponse: "/json/invites", { "invitee_emails": unregistered_email_2, - "invite_expires_in_days": invite_expires_in_days, + "invite_expires_in_minutes": invite_expires_in_minutes, "stream_ids": orjson.dumps([stream.id]).decode(), }, **host_kwargs, diff --git a/zerver/views/invite.py b/zerver/views/invite.py index 9b6621d734..3232b751c7 100644 --- a/zerver/views/invite.py +++ b/zerver/views/invite.py @@ -36,8 +36,8 @@ def invite_users_backend( request: HttpRequest, user_profile: UserProfile, invitee_emails_raw: str = REQ("invitee_emails"), - invite_expires_in_days: Optional[int] = REQ( - json_validator=check_none_or(check_int), default=settings.INVITATION_LINK_VALIDITY_DAYS + invite_expires_in_minutes: Optional[int] = REQ( + json_validator=check_none_or(check_int), default=settings.INVITATION_LINK_VALIDITY_MINUTES ), invite_as: int = REQ(json_validator=check_int, default=PreregistrationUser.INVITE_AS["MEMBER"]), stream_ids: List[int] = REQ(json_validator=check_list(check_int)), @@ -80,7 +80,7 @@ def invite_users_backend( user_profile, invitee_emails, streams, - invite_expires_in_days=invite_expires_in_days, + invite_expires_in_minutes=invite_expires_in_minutes, invite_as=invite_as, ) return json_success(request) @@ -174,8 +174,8 @@ def resend_user_invite_email( def generate_multiuse_invite_backend( request: HttpRequest, user_profile: UserProfile, - invite_expires_in_days: Optional[int] = REQ( - json_validator=check_none_or(check_int), default=settings.INVITATION_LINK_VALIDITY_DAYS + invite_expires_in_minutes: Optional[int] = REQ( + json_validator=check_none_or(check_int), default=settings.INVITATION_LINK_VALIDITY_MINUTES ), invite_as: int = REQ(json_validator=check_int, default=PreregistrationUser.INVITE_AS["MEMBER"]), stream_ids: Sequence[int] = REQ(json_validator=check_list(check_int), default=[]), @@ -191,6 +191,6 @@ def generate_multiuse_invite_backend( streams.append(stream) invite_link = do_create_multiuse_invite_link( - user_profile, invite_as, invite_expires_in_days, streams + user_profile, invite_as, invite_expires_in_minutes, streams ) return json_success(request, data={"invite_link": invite_link}) diff --git a/zerver/worker/queue_processors.py b/zerver/worker/queue_processors.py index 836851b267..d1c2c846ed 100644 --- a/zerver/worker/queue_processors.py +++ b/zerver/worker/queue_processors.py @@ -438,9 +438,12 @@ class LoopQueueProcessingWorker(QueueProcessingWorker): @assign_queue("invites") class ConfirmationEmailWorker(QueueProcessingWorker): def consume(self, data: Mapping[str, Any]) -> None: - invite_expires_in_days = data["invite_expires_in_days"] + if "invite_expires_in_days" in data: + invite_expires_in_minutes = data["invite_expires_in_days"] * 24 * 60 + elif "invite_expires_in_minutes" in data: + invite_expires_in_minutes = data["invite_expires_in_minutes"] invitee = filter_to_valid_prereg_users( - PreregistrationUser.objects.filter(id=data["prereg_id"]), invite_expires_in_days + PreregistrationUser.objects.filter(id=data["prereg_id"]), invite_expires_in_minutes ).first() if invitee is None: # The invitation could have been revoked @@ -456,9 +459,9 @@ class ConfirmationEmailWorker(QueueProcessingWorker): email_language = referrer.realm.default_language activate_url = do_send_confirmation_email( - invitee, referrer, email_language, invite_expires_in_days + invitee, referrer, email_language, invite_expires_in_minutes ) - if invite_expires_in_days is None: + if invite_expires_in_minutes is None: # We do not queue reminder email for never expiring # invitations. This is probably a low importance bug; it # would likely be more natural to send a reminder after 7 @@ -466,7 +469,7 @@ class ConfirmationEmailWorker(QueueProcessingWorker): return # queue invitation reminder - if invite_expires_in_days >= 4: + if invite_expires_in_minutes >= 4 * 24 * 60: context = common_context(referrer) context.update( activate_url=activate_url, @@ -481,7 +484,7 @@ class ConfirmationEmailWorker(QueueProcessingWorker): from_address=FromAddress.tokenized_no_reply_placeholder, language=email_language, context=context, - delay=datetime.timedelta(days=invite_expires_in_days - 2), + delay=datetime.timedelta(minutes=invite_expires_in_minutes - (2 * 24 * 60)), ) diff --git a/zproject/computed_settings.py b/zproject/computed_settings.py index a7f8cea755..f8e2f780f6 100644 --- a/zproject/computed_settings.py +++ b/zproject/computed_settings.py @@ -38,6 +38,7 @@ from .configured_settings import ( EXTERNAL_URI_SCHEME, EXTRA_INSTALLED_APPS, GOOGLE_OAUTH2_CLIENT_ID, + INVITATION_LINK_VALIDITY_DAYS, IS_DEV_DROPLET, LOCAL_UPLOADS_DIR, MEMCACHED_LOCATION, @@ -1198,6 +1199,9 @@ AUTH_LDAP_BIND_PASSWORD = get_secret("auth_ldap_bind_password", "") # MISC SETTINGS ######################################################################## +# Convert INVITATION_LINK_VALIDITY_DAYS into minutes. +INVITATION_LINK_VALIDITY_MINUTES = 24 * 60 * INVITATION_LINK_VALIDITY_DAYS + if PRODUCTION: # Filter out user data DEFAULT_EXCEPTION_REPORTER_FILTER = "zerver.filters.ZulipExceptionReporterFilter"