mirror of https://github.com/zulip/zulip.git
invite: Extend invite api for handling expiration duration.
This extends the invite api endpoints to handle an extra argument, expiration duration, which states the number of days before the invitation link expires. For prereg users, expiration info is attached to event object to pass it to invite queue processor in order to create and send confirmation link. In case of multiuse invites, confirmation links are created directly inside do_create_multiuse_invite_link(), For filtering valid user invites, expiration info stored in Confirmation object is used, which is accessed by a prereg user using reverse generic relations. Fixes #16359.
This commit is contained in:
parent
9caa71c7fd
commit
8c1ea78d7d
|
@ -1366,24 +1366,46 @@ class TestLoggingCountStats(AnalyticsTestCase):
|
|||
|
||||
user = self.create_user(email="first@domain.tld")
|
||||
stream, _ = self.create_stream_with_recipient()
|
||||
do_invite_users(user, ["user1@domain.tld", "user2@domain.tld"], [stream])
|
||||
|
||||
invite_expires_in_days = 2
|
||||
do_invite_users(
|
||||
user,
|
||||
["user1@domain.tld", "user2@domain.tld"],
|
||||
[stream],
|
||||
invite_expires_in_days,
|
||||
)
|
||||
assertInviteCountEquals(2)
|
||||
|
||||
# We currently send emails when re-inviting users that haven't
|
||||
# turned into accounts, so count them towards the total
|
||||
do_invite_users(user, ["user1@domain.tld", "user2@domain.tld"], [stream])
|
||||
do_invite_users(
|
||||
user,
|
||||
["user1@domain.tld", "user2@domain.tld"],
|
||||
[stream],
|
||||
invite_expires_in_days,
|
||||
)
|
||||
assertInviteCountEquals(4)
|
||||
|
||||
# Test mix of good and malformed invite emails
|
||||
try:
|
||||
do_invite_users(user, ["user3@domain.tld", "malformed"], [stream])
|
||||
do_invite_users(
|
||||
user,
|
||||
["user3@domain.tld", "malformed"],
|
||||
[stream],
|
||||
invite_expires_in_days,
|
||||
)
|
||||
except InvitationError:
|
||||
pass
|
||||
assertInviteCountEquals(4)
|
||||
|
||||
# Test inviting existing users
|
||||
try:
|
||||
do_invite_users(user, ["first@domain.tld", "user4@domain.tld"], [stream])
|
||||
do_invite_users(
|
||||
user,
|
||||
["first@domain.tld", "user4@domain.tld"],
|
||||
[stream],
|
||||
invite_expires_in_days,
|
||||
)
|
||||
except InvitationError:
|
||||
pass
|
||||
assertInviteCountEquals(5)
|
||||
|
|
|
@ -262,6 +262,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
|
||||
stream_ids = [self.get_stream_id("Denmark")]
|
||||
invitee_emails = [self.nonreg_email("test1")]
|
||||
self.client_post(
|
||||
|
@ -269,6 +270,7 @@ class TestSupportEndpoint(ZulipTestCase):
|
|||
{
|
||||
"invitee_emails": invitee_emails,
|
||||
"stream_ids": orjson.dumps(stream_ids).decode(),
|
||||
"invite_expires_in_days": invite_expires_in_days,
|
||||
"invite_as": PreregistrationUser.INVITE_AS["MEMBER"],
|
||||
},
|
||||
)
|
||||
|
@ -281,7 +283,11 @@ class TestSupportEndpoint(ZulipTestCase):
|
|||
result = self.client_get("/activity/support", {"q": email})
|
||||
check_realm_creation_query_result(result, email)
|
||||
|
||||
do_create_multiuse_invite_link(self.example_user("hamlet"), invited_as=1)
|
||||
do_create_multiuse_invite_link(
|
||||
self.example_user("hamlet"),
|
||||
invited_as=1,
|
||||
invite_expires_in_days=invite_expires_in_days,
|
||||
)
|
||||
result = self.client_get("/activity/support", {"q": "zulip"})
|
||||
check_multiuse_invite_link_query_result(result)
|
||||
check_zulip_realm_query_result(result)
|
||||
|
|
|
@ -14,7 +14,7 @@ from django.utils.timesince import timesince
|
|||
from django.utils.timezone import now as timezone_now
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from confirmation.models import Confirmation, _properties, confirmation_url
|
||||
from confirmation.models import Confirmation, confirmation_url
|
||||
from confirmation.settings import STATUS_ACTIVE
|
||||
from zerver.decorator import require_server_admin
|
||||
from zerver.forms import check_subdomain_available
|
||||
|
@ -73,8 +73,7 @@ def get_confirmations(
|
|||
content_object = confirmation.content_object
|
||||
|
||||
type = confirmation.type
|
||||
days_to_activate = _properties[type].validity_in_days
|
||||
expiry_date = confirmation.date_sent + timedelta(days=days_to_activate)
|
||||
expiry_date = confirmation.expiry_date
|
||||
|
||||
assert content_object is not None
|
||||
if hasattr(content_object, "status"):
|
||||
|
|
|
@ -6865,12 +6865,17 @@ def filter_presence_idle_user_ids(user_ids: Set[int]) -> List[int]:
|
|||
|
||||
|
||||
def do_send_confirmation_email(
|
||||
invitee: PreregistrationUser, referrer: UserProfile, email_language: str
|
||||
invitee: PreregistrationUser,
|
||||
referrer: UserProfile,
|
||||
email_language: str,
|
||||
invite_expires_in_days: int,
|
||||
) -> str:
|
||||
"""
|
||||
Send the confirmation/welcome e-mail to an invited user.
|
||||
"""
|
||||
activation_url = create_confirmation_link(invitee, Confirmation.INVITATION)
|
||||
activation_url = create_confirmation_link(
|
||||
invitee, Confirmation.INVITATION, validity_in_days=invite_expires_in_days
|
||||
)
|
||||
context = {
|
||||
"referrer_full_name": referrer.full_name,
|
||||
"referrer_email": referrer.delivery_email,
|
||||
|
@ -6953,6 +6958,7 @@ def do_invite_users(
|
|||
user_profile: UserProfile,
|
||||
invitee_emails: Collection[str],
|
||||
streams: Collection[Stream],
|
||||
invite_expires_in_days: int,
|
||||
invite_as: int = PreregistrationUser.INVITE_AS["MEMBER"],
|
||||
) -> None:
|
||||
num_invites = len(invitee_emails)
|
||||
|
@ -7047,6 +7053,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,
|
||||
}
|
||||
queue_json_publish("invites", event)
|
||||
|
||||
|
@ -7076,11 +7083,13 @@ def do_get_user_invites(user_profile: UserProfile) -> List[Dict[str, Any]]:
|
|||
invites = []
|
||||
|
||||
for invitee in prereg_users:
|
||||
expiry_date = invitee.confirmation.get().expiry_date
|
||||
invites.append(
|
||||
dict(
|
||||
email=invitee.email,
|
||||
invited_by_user_id=invitee.referred_by.id,
|
||||
invited=datetime_to_timestamp(invitee.invited_at),
|
||||
expiry_date=datetime_to_timestamp(expiry_date),
|
||||
id=invitee.id,
|
||||
invited_as=invitee.invited_as,
|
||||
is_multiuse=False,
|
||||
|
@ -7091,11 +7100,8 @@ def do_get_user_invites(user_profile: UserProfile) -> List[Dict[str, Any]]:
|
|||
# We do not return multiuse invites to non-admin users.
|
||||
return invites
|
||||
|
||||
lowest_datetime = timezone_now() - datetime.timedelta(
|
||||
days=settings.INVITATION_LINK_VALIDITY_DAYS
|
||||
)
|
||||
multiuse_confirmation_objs = Confirmation.objects.filter(
|
||||
realm=user_profile.realm, type=Confirmation.MULTIUSE_INVITE, date_sent__gte=lowest_datetime
|
||||
realm=user_profile.realm, type=Confirmation.MULTIUSE_INVITE, expiry_date__gte=timezone_now()
|
||||
)
|
||||
for confirmation_obj in multiuse_confirmation_objs:
|
||||
invite = confirmation_obj.content_object
|
||||
|
@ -7104,6 +7110,7 @@ def do_get_user_invites(user_profile: UserProfile) -> List[Dict[str, Any]]:
|
|||
dict(
|
||||
invited_by_user_id=invite.referred_by.id,
|
||||
invited=datetime_to_timestamp(confirmation_obj.date_sent),
|
||||
expiry_date=datetime_to_timestamp(confirmation_obj.expiry_date),
|
||||
id=invite.id,
|
||||
link_url=confirmation_url(
|
||||
confirmation_obj.confirmation_key,
|
||||
|
@ -7118,7 +7125,10 @@ def do_get_user_invites(user_profile: UserProfile) -> List[Dict[str, Any]]:
|
|||
|
||||
|
||||
def do_create_multiuse_invite_link(
|
||||
referred_by: UserProfile, invited_as: int, streams: Sequence[Stream] = []
|
||||
referred_by: UserProfile,
|
||||
invited_as: int,
|
||||
invite_expires_in_days: int,
|
||||
streams: Sequence[Stream] = [],
|
||||
) -> str:
|
||||
realm = referred_by.realm
|
||||
invite = MultiuseInvite.objects.create(realm=realm, referred_by=referred_by)
|
||||
|
@ -7127,7 +7137,9 @@ def do_create_multiuse_invite_link(
|
|||
invite.invited_as = invited_as
|
||||
invite.save()
|
||||
notify_invites_changed(referred_by)
|
||||
return create_confirmation_link(invite, Confirmation.MULTIUSE_INVITE)
|
||||
return create_confirmation_link(
|
||||
invite, Confirmation.MULTIUSE_INVITE, validity_in_days=invite_expires_in_days
|
||||
)
|
||||
|
||||
|
||||
def do_revoke_user_invite(prereg_user: PreregistrationUser) -> None:
|
||||
|
@ -7160,6 +7172,10 @@ def do_resend_user_invite_email(prereg_user: PreregistrationUser) -> int:
|
|||
|
||||
prereg_user.invited_at = timezone_now()
|
||||
prereg_user.save()
|
||||
invite_expires_in_days = (
|
||||
prereg_user.confirmation.get().expiry_date - prereg_user.invited_at
|
||||
).days
|
||||
prereg_user.confirmation.clear()
|
||||
|
||||
do_increment_logging_stat(
|
||||
prereg_user.realm, COUNT_STATS["invites_sent::day"], None, prereg_user.invited_at
|
||||
|
@ -7171,6 +7187,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,
|
||||
}
|
||||
queue_json_publish("invites", event)
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ from bitfield import BitField
|
|||
from bitfield.types import BitHandler
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, UserManager
|
||||
from django.contrib.contenttypes.fields import GenericRelation
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import MinLengthValidator, RegexValidator, URLValidator, validate_email
|
||||
from django.db import models, transaction
|
||||
|
@ -1918,6 +1919,7 @@ class PreregistrationUser(models.Model):
|
|||
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name="ID")
|
||||
email: str = models.EmailField()
|
||||
|
||||
confirmation = GenericRelation("confirmation.Confirmation", related_query_name="prereg_user")
|
||||
# If the pre-registration process provides a suggested full name for this user,
|
||||
# store it here to use it to prepopulate the full name field in the registration form:
|
||||
full_name: Optional[str] = models.CharField(max_length=UserProfile.MAX_NAME_LENGTH, null=True)
|
||||
|
@ -1952,14 +1954,19 @@ class PreregistrationUser(models.Model):
|
|||
invited_as: int = models.PositiveSmallIntegerField(default=INVITE_AS["MEMBER"])
|
||||
|
||||
|
||||
def filter_to_valid_prereg_users(query: QuerySet) -> QuerySet:
|
||||
days_to_activate = settings.INVITATION_LINK_VALIDITY_DAYS
|
||||
def filter_to_valid_prereg_users(
|
||||
query: QuerySet,
|
||||
invite_expires_in_days: Optional[int] = None,
|
||||
) -> QuerySet:
|
||||
active_value = confirmation_settings.STATUS_ACTIVE
|
||||
revoked_value = confirmation_settings.STATUS_REVOKED
|
||||
lowest_datetime = timezone_now() - datetime.timedelta(days=days_to_activate)
|
||||
return query.exclude(status__in=[active_value, revoked_value]).filter(
|
||||
invited_at__gte=lowest_datetime
|
||||
)
|
||||
|
||||
query = query.exclude(status__in=[active_value, revoked_value])
|
||||
if invite_expires_in_days:
|
||||
lowest_datetime = timezone_now() - datetime.timedelta(days=invite_expires_in_days)
|
||||
return query.filter(invited_at__gte=lowest_datetime)
|
||||
else:
|
||||
return query.filter(confirmation__expiry_date__gte=timezone_now())
|
||||
|
||||
|
||||
class MultiuseInvite(models.Model):
|
||||
|
|
|
@ -1470,7 +1470,7 @@ class SocialAuthBase(DesktopFlowTestingLib, ZulipTestCase):
|
|||
realm = get_realm("zulip")
|
||||
|
||||
iago = self.example_user("iago")
|
||||
do_invite_users(iago, [email], [])
|
||||
do_invite_users(iago, [email], [], invite_expires_in_days=2)
|
||||
|
||||
account_data_dict = self.get_account_data_dict(email=email, name=name)
|
||||
result = self.social_auth_test(
|
||||
|
@ -1761,11 +1761,15 @@ class SocialAuthBase(DesktopFlowTestingLib, ZulipTestCase):
|
|||
email = self.nonreg_email("alice")
|
||||
name = "Alice Jones"
|
||||
|
||||
do_invite_users(iago, [email], [], invite_as=PreregistrationUser.INVITE_AS["REALM_ADMIN"])
|
||||
expired_date = timezone_now() - datetime.timedelta(
|
||||
days=settings.INVITATION_LINK_VALIDITY_DAYS + 1
|
||||
invite_expires_in_days = 2
|
||||
do_invite_users(
|
||||
iago,
|
||||
[email],
|
||||
[],
|
||||
invite_expires_in_days,
|
||||
invite_as=PreregistrationUser.INVITE_AS["REALM_ADMIN"],
|
||||
)
|
||||
PreregistrationUser.objects.filter(email=email).update(invited_at=expired_date)
|
||||
now = timezone_now() + datetime.timedelta(days=invite_expires_in_days + 1)
|
||||
|
||||
subdomain = "zulip"
|
||||
realm = get_realm("zulip")
|
||||
|
@ -1773,9 +1777,10 @@ class SocialAuthBase(DesktopFlowTestingLib, ZulipTestCase):
|
|||
result = self.social_auth_test(
|
||||
account_data_dict, expect_choose_email_screen=True, subdomain=subdomain, is_signup=True
|
||||
)
|
||||
self.stage_two_of_registration(
|
||||
result, realm, subdomain, email, name, name, self.BACKEND_CLASS.full_name_validated
|
||||
)
|
||||
with mock.patch("zerver.models.timezone_now", return_value=now):
|
||||
self.stage_two_of_registration(
|
||||
result, realm, subdomain, email, name, name, self.BACKEND_CLASS.full_name_validated
|
||||
)
|
||||
|
||||
# The invitation is expired, so the user should be created as normal member only.
|
||||
created_user = get_user_by_delivery_email(email, realm)
|
||||
|
@ -6304,12 +6309,13 @@ class TestMaybeSendToRegistration(ZulipTestCase):
|
|||
email = self.example_email("hamlet")
|
||||
user = PreregistrationUser(email=email)
|
||||
user.save()
|
||||
create_confirmation_link(user, Confirmation.USER_REGISTRATION)
|
||||
|
||||
with mock.patch("zerver.views.auth.HomepageForm", return_value=Form()):
|
||||
self.assertEqual(PreregistrationUser.objects.all().count(), 1)
|
||||
result = maybe_send_to_registration(request, email, is_signup=True)
|
||||
self.assertEqual(result.status_code, 302)
|
||||
confirmation = Confirmation.objects.all().first()
|
||||
confirmation = Confirmation.objects.all().last()
|
||||
assert confirmation is not None
|
||||
confirmation_key = confirmation.confirmation_key
|
||||
self.assertIn("do_confirm/" + confirmation_key, result.url)
|
||||
|
|
|
@ -648,8 +648,12 @@ class NormalActionsTest(BaseAction):
|
|||
streams = []
|
||||
for stream_name in ["Denmark", "Scotland"]:
|
||||
streams.append(get_stream(stream_name, self.user_profile.realm))
|
||||
|
||||
invite_expires_in_days = 2
|
||||
events = self.verify_action(
|
||||
lambda: do_invite_users(self.user_profile, ["foo@zulip.com"], streams, False),
|
||||
lambda: do_invite_users(
|
||||
self.user_profile, ["foo@zulip.com"], streams, invite_expires_in_days
|
||||
),
|
||||
state_change_expected=False,
|
||||
)
|
||||
check_invites_changed("events[0]", events[0])
|
||||
|
@ -660,9 +664,13 @@ class NormalActionsTest(BaseAction):
|
|||
for stream_name in ["Denmark", "Verona"]:
|
||||
streams.append(get_stream(stream_name, self.user_profile.realm))
|
||||
|
||||
invite_expires_in_days = 2
|
||||
events = self.verify_action(
|
||||
lambda: do_create_multiuse_invite_link(
|
||||
self.user_profile, PreregistrationUser.INVITE_AS["MEMBER"], streams
|
||||
self.user_profile,
|
||||
PreregistrationUser.INVITE_AS["MEMBER"],
|
||||
invite_expires_in_days,
|
||||
streams,
|
||||
),
|
||||
state_change_expected=False,
|
||||
)
|
||||
|
@ -673,7 +681,9 @@ class NormalActionsTest(BaseAction):
|
|||
streams = []
|
||||
for stream_name in ["Denmark", "Verona"]:
|
||||
streams.append(get_stream(stream_name, self.user_profile.realm))
|
||||
do_invite_users(self.user_profile, ["foo@zulip.com"], streams)
|
||||
|
||||
invite_expires_in_days = 2
|
||||
do_invite_users(self.user_profile, ["foo@zulip.com"], streams, invite_expires_in_days)
|
||||
prereg_users = PreregistrationUser.objects.filter(
|
||||
referred_by__realm=self.user_profile.realm
|
||||
)
|
||||
|
@ -688,8 +698,13 @@ class NormalActionsTest(BaseAction):
|
|||
streams = []
|
||||
for stream_name in ["Denmark", "Verona"]:
|
||||
streams.append(get_stream(stream_name, self.user_profile.realm))
|
||||
|
||||
invite_expires_in_days = 2
|
||||
do_create_multiuse_invite_link(
|
||||
self.user_profile, PreregistrationUser.INVITE_AS["MEMBER"], streams
|
||||
self.user_profile,
|
||||
PreregistrationUser.INVITE_AS["MEMBER"],
|
||||
invite_expires_in_days,
|
||||
streams,
|
||||
)
|
||||
|
||||
multiuse_object = MultiuseInvite.objects.get()
|
||||
|
@ -707,7 +722,8 @@ class NormalActionsTest(BaseAction):
|
|||
for stream_name in ["Denmark", "Scotland"]:
|
||||
streams.append(get_stream(stream_name, self.user_profile.realm))
|
||||
|
||||
do_invite_users(self.user_profile, ["foo@zulip.com"], streams)
|
||||
invite_expires_in_days = 2
|
||||
do_invite_users(self.user_profile, ["foo@zulip.com"], streams, invite_expires_in_days)
|
||||
prereg_user = PreregistrationUser.objects.get(email="foo@zulip.com")
|
||||
|
||||
events = self.verify_action(
|
||||
|
|
|
@ -35,6 +35,7 @@ class EmailTranslationTestCase(ZulipTestCase):
|
|||
realm.default_language = "de"
|
||||
realm.save()
|
||||
stream = get_realm_stream("Denmark", realm.id)
|
||||
invite_expires_in_days = 2
|
||||
self.login_user(hamlet)
|
||||
|
||||
# TODO: Uncomment and replace with translation once we have German translations for the strings
|
||||
|
@ -58,6 +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,
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
@ -605,15 +605,25 @@ class WorkerTest(ZulipTestCase):
|
|||
PreregistrationUser.objects.create(
|
||||
email=self.nonreg_email("bob"), referred_by=inviter, realm=inviter.realm
|
||||
)
|
||||
invite_expires_in_days = 4
|
||||
data: List[Dict[str, Any]] = [
|
||||
dict(prereg_id=prereg_alice.id, referrer_id=inviter.id),
|
||||
dict(
|
||||
prereg_id=prereg_alice.id,
|
||||
referrer_id=inviter.id,
|
||||
invite_expires_in_days=invite_expires_in_days,
|
||||
),
|
||||
dict(
|
||||
prereg_id=prereg_alice.id,
|
||||
referrer_id=inviter.id,
|
||||
email_language="en",
|
||||
invite_expires_in_days=invite_expires_in_days,
|
||||
),
|
||||
# Nonexistent prereg_id, as if the invitation was deleted
|
||||
dict(prereg_id=-1, referrer_id=inviter.id),
|
||||
dict(
|
||||
prereg_id=-1,
|
||||
referrer_id=inviter.id,
|
||||
invite_expires_in_days=invite_expires_in_days,
|
||||
),
|
||||
]
|
||||
for element in data:
|
||||
fake_client.enqueue("invites", element)
|
||||
|
|
|
@ -36,6 +36,7 @@ from zerver.lib.actions import (
|
|||
do_change_realm_subdomain,
|
||||
do_change_user_role,
|
||||
do_create_default_stream_group,
|
||||
do_create_multiuse_invite_link,
|
||||
do_create_realm,
|
||||
do_create_user,
|
||||
do_deactivate_realm,
|
||||
|
@ -1027,6 +1028,7 @@ class InviteUserBase(ZulipTestCase):
|
|||
self,
|
||||
invitee_emails: str,
|
||||
stream_names: Sequence[str],
|
||||
invite_expires_in_days: int = settings.INVITATION_LINK_VALIDITY_DAYS,
|
||||
body: str = "",
|
||||
invite_as: int = PreregistrationUser.INVITE_AS["MEMBER"],
|
||||
) -> HttpResponse:
|
||||
|
@ -1045,6 +1047,7 @@ class InviteUserBase(ZulipTestCase):
|
|||
"/json/invites",
|
||||
{
|
||||
"invitee_emails": invitee_emails,
|
||||
"invite_expires_in_days": invite_expires_in_days,
|
||||
"stream_ids": orjson.dumps(stream_ids).decode(),
|
||||
"invite_as": invite_as,
|
||||
},
|
||||
|
@ -1977,14 +1980,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
|
||||
with self.settings(INVITATION_LINK_VALIDITY_DAYS=4):
|
||||
self.invite("alice@zulip.com", ["Denmark"])
|
||||
self.invite("alice@zulip.com", ["Denmark"], invite_expires_in_days=4)
|
||||
self.assertEqual(
|
||||
ScheduledEmail.objects.filter(type=ScheduledEmail.INVITATION_REMINDER).count(), 1
|
||||
)
|
||||
# Check invitation reminder email is not scheduled with 3 day link expiry
|
||||
with self.settings(INVITATION_LINK_VALIDITY_DAYS=3):
|
||||
self.invite("bob@zulip.com", ["Denmark"])
|
||||
self.invite("bob@zulip.com", ["Denmark"], invite_expires_in_days=3)
|
||||
self.assertEqual(
|
||||
ScheduledEmail.objects.filter(type=ScheduledEmail.INVITATION_REMINDER).count(), 1
|
||||
)
|
||||
|
@ -2038,15 +2039,16 @@ 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))
|
||||
|
||||
do_invite_users(self.user_profile, ["foo@zulip.com"], streams)
|
||||
invite_expires_in_days = 2
|
||||
do_invite_users(self.user_profile, ["foo@zulip.com"], streams, invite_expires_in_days)
|
||||
prereg_user = PreregistrationUser.objects.get(email="foo@zulip.com")
|
||||
do_invite_users(self.user_profile, ["foo@zulip.com"], streams)
|
||||
do_invite_users(self.user_profile, ["foo@zulip.com"], streams)
|
||||
do_invite_users(self.user_profile, ["foo@zulip.com"], streams, invite_expires_in_days)
|
||||
do_invite_users(self.user_profile, ["foo@zulip.com"], streams, invite_expires_in_days)
|
||||
|
||||
# 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"], [])
|
||||
do_invite_users(lear_user, ["foo@zulip.com"], [], invite_expires_in_days)
|
||||
|
||||
invites = PreregistrationUser.objects.filter(email__iexact="foo@zulip.com")
|
||||
self.assert_length(invites, 4)
|
||||
|
@ -2174,26 +2176,23 @@ so we didn't send them an invitation. We did send invitations to everyone else!"
|
|||
|
||||
class InvitationsTestCase(InviteUserBase):
|
||||
def test_do_get_user_invites(self) -> None:
|
||||
self.login("iago")
|
||||
user_profile = self.example_user("iago")
|
||||
hamlet = self.example_user("hamlet")
|
||||
othello = self.example_user("othello")
|
||||
prereg_user_one = PreregistrationUser(email="TestOne@zulip.com", referred_by=user_profile)
|
||||
prereg_user_one.save()
|
||||
prereg_user_two = PreregistrationUser(email="TestTwo@zulip.com", referred_by=user_profile)
|
||||
prereg_user_two.save()
|
||||
prereg_user_three = PreregistrationUser(email="TestThree@zulip.com", referred_by=hamlet)
|
||||
prereg_user_three.save()
|
||||
prereg_user_four = PreregistrationUser(email="TestFour@zulip.com", referred_by=othello)
|
||||
prereg_user_four.save()
|
||||
prereg_user_other_realm = PreregistrationUser(
|
||||
email="TestOne@zulip.com", referred_by=self.mit_user("sipbtest")
|
||||
|
||||
streams = []
|
||||
for stream_name in ["Denmark", "Scotland"]:
|
||||
streams.append(get_stream(stream_name, user_profile.realm))
|
||||
|
||||
invite_expires_in_days = 2
|
||||
do_invite_users(user_profile, ["TestOne@zulip.com"], streams, invite_expires_in_days)
|
||||
do_invite_users(user_profile, ["TestTwo@zulip.com"], streams, invite_expires_in_days)
|
||||
do_invite_users(hamlet, ["TestThree@zulip.com"], streams, invite_expires_in_days)
|
||||
do_invite_users(othello, ["TestFour@zulip.com"], streams, invite_expires_in_days)
|
||||
do_invite_users(self.mit_user("sipbtest"), ["TestOne@mit.edu"], [], invite_expires_in_days)
|
||||
do_create_multiuse_invite_link(
|
||||
user_profile, PreregistrationUser.INVITE_AS["MEMBER"], invite_expires_in_days
|
||||
)
|
||||
prereg_user_other_realm.save()
|
||||
multiuse_invite = MultiuseInvite.objects.create(
|
||||
referred_by=user_profile, realm=user_profile.realm
|
||||
)
|
||||
create_confirmation_link(multiuse_invite, Confirmation.MULTIUSE_INVITE)
|
||||
self.assert_length(do_get_user_invites(user_profile), 5)
|
||||
self.assert_length(do_get_user_invites(hamlet), 1)
|
||||
self.assert_length(do_get_user_invites(othello), 1)
|
||||
|
@ -2202,40 +2201,41 @@ class InvitationsTestCase(InviteUserBase):
|
|||
"""
|
||||
A GET call to /json/invites returns all unexpired invitations.
|
||||
"""
|
||||
realm = get_realm("zulip")
|
||||
days_to_activate = getattr(settings, "INVITATION_LINK_VALIDITY_DAYS", "Wrong")
|
||||
active_value = getattr(confirmation_settings, "STATUS_ACTIVE", "Wrong")
|
||||
self.assertNotEqual(days_to_activate, "Wrong")
|
||||
self.assertNotEqual(active_value, "Wrong")
|
||||
|
||||
self.login("iago")
|
||||
user_profile = self.example_user("iago")
|
||||
|
||||
prereg_user_one = PreregistrationUser(email="TestOne@zulip.com", referred_by=user_profile)
|
||||
prereg_user_one.save()
|
||||
expired_datetime = timezone_now() - datetime.timedelta(days=(days_to_activate + 1))
|
||||
prereg_user_two = PreregistrationUser(email="TestTwo@zulip.com", referred_by=user_profile)
|
||||
prereg_user_two.save()
|
||||
PreregistrationUser.objects.filter(id=prereg_user_two.id).update(
|
||||
invited_at=expired_datetime
|
||||
)
|
||||
prereg_user_three = PreregistrationUser(
|
||||
email="TestThree@zulip.com", referred_by=user_profile, status=active_value
|
||||
)
|
||||
prereg_user_three.save()
|
||||
self.login_user(user_profile)
|
||||
|
||||
hamlet = self.example_user("hamlet")
|
||||
othello = self.example_user("othello")
|
||||
|
||||
multiuse_invite_one = MultiuseInvite.objects.create(referred_by=hamlet, realm=realm)
|
||||
create_confirmation_link(multiuse_invite_one, Confirmation.MULTIUSE_INVITE)
|
||||
streams = []
|
||||
for stream_name in ["Denmark", "Scotland"]:
|
||||
streams.append(get_stream(stream_name, user_profile.realm))
|
||||
|
||||
multiuse_invite_two = MultiuseInvite.objects.create(referred_by=othello, realm=realm)
|
||||
create_confirmation_link(multiuse_invite_two, Confirmation.MULTIUSE_INVITE)
|
||||
confirmation = Confirmation.objects.last()
|
||||
assert confirmation is not None
|
||||
confirmation.date_sent = expired_datetime
|
||||
confirmation.save()
|
||||
invite_expires_in_days = 2
|
||||
do_invite_users(user_profile, ["TestOne@zulip.com"], streams, invite_expires_in_days)
|
||||
|
||||
with patch(
|
||||
"confirmation.models.timezone_now",
|
||||
return_value=timezone_now() - datetime.timedelta(days=invite_expires_in_days + 1),
|
||||
):
|
||||
do_invite_users(user_profile, ["TestTwo@zulip.com"], streams, invite_expires_in_days)
|
||||
do_create_multiuse_invite_link(
|
||||
othello, PreregistrationUser.INVITE_AS["MEMBER"], invite_expires_in_days
|
||||
)
|
||||
|
||||
prereg_user_three = PreregistrationUser(
|
||||
email="TestThree@zulip.com", referred_by=user_profile, status=active_value
|
||||
)
|
||||
prereg_user_three.save()
|
||||
create_confirmation_link(prereg_user_three, Confirmation.INVITATION, invite_expires_in_days)
|
||||
|
||||
do_create_multiuse_invite_link(
|
||||
hamlet, PreregistrationUser.INVITE_AS["MEMBER"], invite_expires_in_days
|
||||
)
|
||||
|
||||
result = self.client_get("/json/invites")
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
@ -2709,7 +2709,7 @@ class MultiuseInviteTest(ZulipTestCase):
|
|||
def test_create_multiuse_link_api_call(self) -> None:
|
||||
self.login("iago")
|
||||
|
||||
result = self.client_post("/json/invites/multiuse")
|
||||
result = self.client_post("/json/invites/multiuse", {"invite_expires_in_days": 2})
|
||||
self.assert_json_success(result)
|
||||
|
||||
invite_link = result.json()["invite_link"]
|
||||
|
@ -2722,7 +2722,8 @@ class MultiuseInviteTest(ZulipTestCase):
|
|||
stream_ids = [stream.id for stream in streams]
|
||||
|
||||
result = self.client_post(
|
||||
"/json/invites/multiuse", {"stream_ids": orjson.dumps(stream_ids).decode()}
|
||||
"/json/invites/multiuse",
|
||||
{"stream_ids": orjson.dumps(stream_ids).decode(), "invite_expires_in_days": 2},
|
||||
)
|
||||
self.assert_json_success(result)
|
||||
|
||||
|
@ -2737,7 +2738,7 @@ class MultiuseInviteTest(ZulipTestCase):
|
|||
self.realm.invite_to_realm_policy = Realm.POLICY_MEMBERS_ONLY
|
||||
self.realm.save()
|
||||
|
||||
result = self.client_post("/json/invites/multiuse")
|
||||
result = self.client_post("/json/invites/multiuse", {"invite_expires_in_days": 2})
|
||||
self.assert_json_success(result)
|
||||
|
||||
invite_link = result.json()["invite_link"]
|
||||
|
@ -2751,14 +2752,20 @@ class MultiuseInviteTest(ZulipTestCase):
|
|||
self.login("iago")
|
||||
result = self.client_post(
|
||||
"/json/invites/multiuse",
|
||||
{"invite_as": orjson.dumps(PreregistrationUser.INVITE_AS["REALM_OWNER"]).decode()},
|
||||
{
|
||||
"invite_as": orjson.dumps(PreregistrationUser.INVITE_AS["REALM_OWNER"]).decode(),
|
||||
"invite_expires_in_days": 2,
|
||||
},
|
||||
)
|
||||
self.assert_json_error(result, "Must be an organization owner")
|
||||
|
||||
self.login("desdemona")
|
||||
result = self.client_post(
|
||||
"/json/invites/multiuse",
|
||||
{"invite_as": orjson.dumps(PreregistrationUser.INVITE_AS["REALM_OWNER"]).decode()},
|
||||
{
|
||||
"invite_as": orjson.dumps(PreregistrationUser.INVITE_AS["REALM_OWNER"]).decode(),
|
||||
"invite_expires_in_days": 2,
|
||||
},
|
||||
)
|
||||
self.assert_json_success(result)
|
||||
|
||||
|
@ -2768,7 +2775,8 @@ class MultiuseInviteTest(ZulipTestCase):
|
|||
def test_create_multiuse_link_invalid_stream_api_call(self) -> None:
|
||||
self.login("iago")
|
||||
result = self.client_post(
|
||||
"/json/invites/multiuse", {"stream_ids": orjson.dumps([54321]).decode()}
|
||||
"/json/invites/multiuse",
|
||||
{"stream_ids": orjson.dumps([54321]).decode(), "invite_expires_in_days": 2},
|
||||
)
|
||||
self.assert_json_error(result, "Invalid stream id 54321. No invites were sent.")
|
||||
|
||||
|
|
|
@ -774,10 +774,12 @@ class QueryCountTest(ZulipTestCase):
|
|||
]
|
||||
streams = [get_stream(stream_name, realm) for stream_name in stream_names]
|
||||
|
||||
invite_expires_in_days = 4
|
||||
do_invite_users(
|
||||
user_profile=self.example_user("hamlet"),
|
||||
invitee_emails=["fred@zulip.com"],
|
||||
streams=streams,
|
||||
invite_expires_in_days=invite_expires_in_days,
|
||||
)
|
||||
|
||||
prereg_user = PreregistrationUser.objects.get(email="fred@zulip.com")
|
||||
|
|
|
@ -67,6 +67,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
|
||||
realm = get_realm("zulip")
|
||||
other_realm = Realm.objects.exclude(string_id="zulip").first()
|
||||
user = get_user_by_delivery_email(registered_email, realm)
|
||||
|
@ -109,7 +110,11 @@ def generate_all_emails(request: HttpRequest) -> HttpResponse:
|
|||
stream = get_realm_stream("Denmark", user.realm.id)
|
||||
result = client.post(
|
||||
"/json/invites",
|
||||
{"invitee_emails": unregistered_email_2, "stream_ids": orjson.dumps([stream.id]).decode()},
|
||||
{
|
||||
"invitee_emails": unregistered_email_2,
|
||||
"invite_expires_in_days": invite_expires_in_days,
|
||||
"stream_ids": orjson.dumps([stream.id]).decode(),
|
||||
},
|
||||
**host_kwargs,
|
||||
)
|
||||
assert result.status_code == 200
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import re
|
||||
from typing import List, Sequence, Set
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
|
@ -35,6 +36,9 @@ def invite_users_backend(
|
|||
request: HttpRequest,
|
||||
user_profile: UserProfile,
|
||||
invitee_emails_raw: str = REQ("invitee_emails"),
|
||||
invite_expires_in_days: int = REQ(
|
||||
json_validator=check_int, default=settings.INVITATION_LINK_VALIDITY_DAYS
|
||||
),
|
||||
invite_as: int = REQ(json_validator=check_int, default=PreregistrationUser.INVITE_AS["MEMBER"]),
|
||||
stream_ids: List[int] = REQ(json_validator=check_list(check_int)),
|
||||
) -> HttpResponse:
|
||||
|
@ -72,7 +76,7 @@ def invite_users_backend(
|
|||
)
|
||||
streams.append(stream)
|
||||
|
||||
do_invite_users(user_profile, invitee_emails, streams, invite_as)
|
||||
do_invite_users(user_profile, invitee_emails, streams, invite_expires_in_days, invite_as)
|
||||
return json_success()
|
||||
|
||||
|
||||
|
@ -164,6 +168,9 @@ def resend_user_invite_email(
|
|||
def generate_multiuse_invite_backend(
|
||||
request: HttpRequest,
|
||||
user_profile: UserProfile,
|
||||
invite_expires_in_days: int = REQ(
|
||||
json_validator=check_int, default=settings.INVITATION_LINK_VALIDITY_DAYS
|
||||
),
|
||||
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=[]),
|
||||
) -> HttpResponse:
|
||||
|
@ -177,5 +184,7 @@ def generate_multiuse_invite_backend(
|
|||
raise JsonableError(_("Invalid stream id {}. No invites were sent.").format(stream_id))
|
||||
streams.append(stream)
|
||||
|
||||
invite_link = do_create_multiuse_invite_link(user_profile, invite_as, streams)
|
||||
invite_link = do_create_multiuse_invite_link(
|
||||
user_profile, invite_as, invite_expires_in_days, streams
|
||||
)
|
||||
return json_success({"invite_link": invite_link})
|
||||
|
|
|
@ -430,8 +430,9 @@ 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"]
|
||||
invitee = filter_to_valid_prereg_users(
|
||||
PreregistrationUser.objects.filter(id=data["prereg_id"])
|
||||
PreregistrationUser.objects.filter(id=data["prereg_id"]), invite_expires_in_days
|
||||
).first()
|
||||
if invitee is None:
|
||||
# The invitation could have been revoked
|
||||
|
@ -445,10 +446,13 @@ class ConfirmationEmailWorker(QueueProcessingWorker):
|
|||
email_language = data["email_language"]
|
||||
else:
|
||||
email_language = referrer.realm.default_language
|
||||
activate_url = do_send_confirmation_email(invitee, referrer, email_language)
|
||||
|
||||
activate_url = do_send_confirmation_email(
|
||||
invitee, referrer, email_language, invite_expires_in_days
|
||||
)
|
||||
|
||||
# queue invitation reminder
|
||||
if settings.INVITATION_LINK_VALIDITY_DAYS >= 4:
|
||||
if invite_expires_in_days >= 4:
|
||||
context = common_context(referrer)
|
||||
context.update(
|
||||
activate_url=activate_url,
|
||||
|
@ -463,7 +467,7 @@ class ConfirmationEmailWorker(QueueProcessingWorker):
|
|||
from_address=FromAddress.tokenized_no_reply_placeholder,
|
||||
language=email_language,
|
||||
context=context,
|
||||
delay=datetime.timedelta(days=settings.INVITATION_LINK_VALIDITY_DAYS - 2),
|
||||
delay=datetime.timedelta(days=invite_expires_in_days - 2),
|
||||
)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue