invites: Use expiration time in minutes instead of days.

This commit changes the invite API to accept invitation
expiration time in minutes since we are going to add a
custom option in further commits which would allow a user
to set expiration time in minutes, hours and weeks as well.
This commit is contained in:
Sahil Batra 2022-02-10 16:22:34 +05:30 committed by Tim Abbott
parent 6bb7f57ec2
commit 61365fbe21
19 changed files with 164 additions and 133 deletions

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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()});
}

View File

@ -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,
},

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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(

View File

@ -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

View File

@ -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")

View File

@ -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,
},
)

View File

@ -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:

View File

@ -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.")

View File

@ -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(

View File

@ -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,

View File

@ -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})

View File

@ -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)),
)

View File

@ -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"