mirror of https://github.com/zulip/zulip.git
2350 lines
95 KiB
Python
2350 lines
95 KiB
Python
import datetime
|
|
import re
|
|
import urllib
|
|
from typing import TYPE_CHECKING, List, Optional, Sequence, Union
|
|
from unittest.mock import patch
|
|
from urllib.parse import urlencode
|
|
|
|
import orjson
|
|
import time_machine
|
|
from django.conf import settings
|
|
from django.core import mail
|
|
from django.core.mail.message import EmailMultiAlternatives
|
|
from django.http import HttpRequest
|
|
from django.test import override_settings
|
|
from django.urls import reverse
|
|
from django.utils.timezone import now as timezone_now
|
|
|
|
from confirmation import settings as confirmation_settings
|
|
from confirmation.models import (
|
|
Confirmation,
|
|
ConfirmationKeyError,
|
|
create_confirmation_link,
|
|
get_object_from_key,
|
|
)
|
|
from corporate.lib.stripe import get_latest_seat_count
|
|
from zerver.actions.create_realm import do_change_realm_subdomain, do_create_realm
|
|
from zerver.actions.create_user import do_create_user, process_new_human_user
|
|
from zerver.actions.invites import (
|
|
do_create_multiuse_invite_link,
|
|
do_get_invites_controlled_by_user,
|
|
do_invite_users,
|
|
do_revoke_multi_use_invite,
|
|
too_many_recent_realm_invites,
|
|
)
|
|
from zerver.actions.realm_settings import do_change_realm_plan_type, do_set_realm_property
|
|
from zerver.actions.user_settings import do_change_full_name
|
|
from zerver.actions.users import change_user_is_active
|
|
from zerver.context_processors import common_context
|
|
from zerver.lib.send_email import (
|
|
FromAddress,
|
|
deliver_scheduled_emails,
|
|
send_future_email,
|
|
)
|
|
from zerver.lib.test_classes import ZulipTestCase
|
|
from zerver.lib.test_helpers import find_key_by_email
|
|
from zerver.models import (
|
|
Message,
|
|
MultiuseInvite,
|
|
PreregistrationUser,
|
|
Realm,
|
|
ScheduledEmail,
|
|
Stream,
|
|
UserMessage,
|
|
UserProfile,
|
|
get_realm,
|
|
get_stream,
|
|
get_user_by_delivery_email,
|
|
)
|
|
from zerver.views.invite import INVITATION_LINK_VALIDITY_MINUTES, get_invitee_emails_set
|
|
from zerver.views.registration import accounts_home
|
|
|
|
if TYPE_CHECKING:
|
|
from django.test.client import _MonkeyPatchedWSGIResponse as TestHttpResponse
|
|
|
|
|
|
class InviteUserBase(ZulipTestCase):
|
|
def check_sent_emails(self, correct_recipients: List[str], clear: bool = False) -> None:
|
|
self.assert_length(mail.outbox, len(correct_recipients))
|
|
email_recipients = [email.recipients()[0] for email in mail.outbox]
|
|
self.assertEqual(sorted(email_recipients), sorted(correct_recipients))
|
|
if len(mail.outbox) == 0:
|
|
return
|
|
|
|
self.assertIn("Zulip", self.email_display_from(mail.outbox[0]))
|
|
|
|
self.assertEqual(self.email_envelope_from(mail.outbox[0]), settings.NOREPLY_EMAIL_ADDRESS)
|
|
self.assertRegex(
|
|
self.email_display_from(mail.outbox[0]), rf" <{self.TOKENIZED_NOREPLY_REGEX}>\Z"
|
|
)
|
|
|
|
if clear:
|
|
mail.outbox = []
|
|
|
|
def invite(
|
|
self,
|
|
invitee_emails: str,
|
|
stream_names: Sequence[str],
|
|
invite_expires_in_minutes: Optional[int] = INVITATION_LINK_VALIDITY_MINUTES,
|
|
body: str = "",
|
|
invite_as: int = PreregistrationUser.INVITE_AS["MEMBER"],
|
|
realm: Optional[Realm] = None,
|
|
) -> "TestHttpResponse":
|
|
"""
|
|
Invites the specified users to Zulip with the specified streams.
|
|
|
|
users should be a string containing the users to invite, comma or
|
|
newline separated.
|
|
|
|
streams should be a list of strings.
|
|
"""
|
|
stream_ids = []
|
|
for stream_name in stream_names:
|
|
stream_ids.append(self.get_stream_id(stream_name, realm=realm))
|
|
|
|
invite_expires_in: Union[str, Optional[int]] = invite_expires_in_minutes
|
|
if invite_expires_in is None:
|
|
invite_expires_in = orjson.dumps(None).decode()
|
|
|
|
return self.client_post(
|
|
"/json/invites",
|
|
{
|
|
"invitee_emails": invitee_emails,
|
|
"invite_expires_in_minutes": invite_expires_in,
|
|
"stream_ids": orjson.dumps(stream_ids).decode(),
|
|
"invite_as": invite_as,
|
|
},
|
|
subdomain=realm.string_id if realm else "zulip",
|
|
)
|
|
|
|
|
|
class InviteUserTest(InviteUserBase):
|
|
def test_successful_invite_user(self) -> None:
|
|
"""
|
|
A call to /json/invites with valid parameters causes an invitation
|
|
email to be sent.
|
|
"""
|
|
self.login("hamlet")
|
|
invitee = "alice-test@zulip.com"
|
|
self.assert_json_success(self.invite(invitee, ["Denmark"]))
|
|
self.assertTrue(find_key_by_email(invitee))
|
|
self.check_sent_emails([invitee])
|
|
|
|
def test_newbie_restrictions(self) -> None:
|
|
user_profile = self.example_user("hamlet")
|
|
invitee = "alice-test@zulip.com"
|
|
stream_name = "Denmark"
|
|
|
|
self.login_user(user_profile)
|
|
|
|
result = self.invite(invitee, [stream_name])
|
|
self.assert_json_success(result)
|
|
|
|
user_profile.date_joined = timezone_now() - datetime.timedelta(days=10)
|
|
user_profile.save()
|
|
|
|
with self.settings(INVITES_MIN_USER_AGE_DAYS=5):
|
|
result = self.invite(invitee, [stream_name])
|
|
self.assert_json_success(result)
|
|
|
|
with self.settings(INVITES_MIN_USER_AGE_DAYS=15):
|
|
result = self.invite(invitee, [stream_name])
|
|
self.assert_json_error_contains(result, "Your account is too new")
|
|
|
|
def test_invite_limits(self) -> None:
|
|
user_profile = self.example_user("hamlet")
|
|
realm = user_profile.realm
|
|
self.login_user(user_profile)
|
|
|
|
def try_invite(
|
|
num_invitees: int,
|
|
*,
|
|
default_realm_max: int,
|
|
new_realm_max: int,
|
|
realm_max: int,
|
|
open_realm_creation: bool = True,
|
|
realm: Optional[Realm] = None,
|
|
stream_name: str = "Denmark",
|
|
) -> "TestHttpResponse":
|
|
if realm is None:
|
|
realm = get_realm("zulip")
|
|
invitees = ",".join(
|
|
[f"{realm.string_id}-{i:02}@zulip.com" for i in range(num_invitees)]
|
|
)
|
|
with self.settings(
|
|
OPEN_REALM_CREATION=open_realm_creation,
|
|
INVITES_DEFAULT_REALM_DAILY_MAX=default_realm_max,
|
|
INVITES_NEW_REALM_LIMIT_DAYS=[(1, new_realm_max)],
|
|
):
|
|
realm.max_invites = realm_max
|
|
realm.save()
|
|
return self.invite(invitees, [stream_name], realm=realm)
|
|
|
|
# Trip the "new realm" limits
|
|
realm.date_created = timezone_now()
|
|
realm.save()
|
|
result = try_invite(30, default_realm_max=50, new_realm_max=20, realm_max=40)
|
|
self.assert_json_error_contains(result, "reached the limit")
|
|
self.check_sent_emails([])
|
|
|
|
# If some other realm consumes some invites, it affects our realm. Invite 20 users in lear:
|
|
lear_realm = get_realm("lear")
|
|
lear_realm.date_created = timezone_now()
|
|
lear_realm.save()
|
|
self.login_user(self.lear_user("king"))
|
|
result = try_invite(
|
|
20,
|
|
default_realm_max=50,
|
|
new_realm_max=20,
|
|
realm_max=40,
|
|
realm=lear_realm,
|
|
stream_name="general",
|
|
)
|
|
self.assert_json_success(result)
|
|
self.check_sent_emails([f"lear-{i:02}@zulip.com" for i in range(20)], clear=True)
|
|
|
|
# Which prevents inviting 1 in our realm:
|
|
self.login_user(user_profile)
|
|
result = try_invite(1, default_realm_max=50, new_realm_max=20, realm_max=40)
|
|
self.assert_json_error_contains(result, "reached the limit")
|
|
self.check_sent_emails([])
|
|
|
|
# If our realm max is over the default realm's, we're exempt from INVITES_NEW_REALM_LIMIT_DAYS
|
|
result = try_invite(10, default_realm_max=15, new_realm_max=5, realm_max=20)
|
|
self.assert_json_success(result)
|
|
self.check_sent_emails([f"zulip-{i:02}@zulip.com" for i in range(10)], clear=True)
|
|
|
|
# We've sent 10 invites. Trying to invite 15 people, even if
|
|
# 10 of them are the same, still trips the limit (10 previous
|
|
# + 15 in this submission > 20 realm max)
|
|
result = try_invite(15, default_realm_max=15, new_realm_max=5, realm_max=20)
|
|
self.assert_json_error_contains(result, "reached the limit")
|
|
self.check_sent_emails([])
|
|
|
|
# Inviting 10 more people (to the realm max of 20) works, and
|
|
# sends emails to the same 10 users again.
|
|
result = try_invite(10, default_realm_max=15, new_realm_max=5, realm_max=20)
|
|
self.assert_json_success(result)
|
|
self.check_sent_emails([f"zulip-{i:02}@zulip.com" for i in range(10)], clear=True)
|
|
|
|
# We've sent 20 invites. The 10 we just sent do count against
|
|
# us if we send to them again, since we did send mail
|
|
result = try_invite(10, default_realm_max=15, new_realm_max=5, realm_max=20)
|
|
self.assert_json_error_contains(result, "reached the limit")
|
|
self.check_sent_emails([])
|
|
|
|
# We've sent 20 invites. The realm is exempt from the new realm max
|
|
# (INVITES_NEW_REALM_LIMIT_DAYS) if it is old enough
|
|
realm.date_created = timezone_now() - datetime.timedelta(days=8)
|
|
realm.save()
|
|
result = try_invite(10, default_realm_max=50, new_realm_max=20, realm_max=40)
|
|
self.assert_json_success(result)
|
|
self.check_sent_emails([f"zulip-{i:02}@zulip.com" for i in range(10)], clear=True)
|
|
|
|
# We've sent 30 invites. None of the limits matter if open
|
|
# realm creation is disabled.
|
|
result = try_invite(
|
|
10, default_realm_max=30, new_realm_max=20, realm_max=10, open_realm_creation=False
|
|
)
|
|
self.assert_json_success(result)
|
|
self.check_sent_emails([f"zulip-{i:02}@zulip.com" for i in range(10)], clear=True)
|
|
|
|
# We've sent 40 invites "today". Fast-forward 48 hours
|
|
# and ensure that we can invite more people
|
|
with time_machine.travel(timezone_now() + datetime.timedelta(hours=48)):
|
|
result = try_invite(5, default_realm_max=30, new_realm_max=20, realm_max=10)
|
|
self.assert_json_success(result)
|
|
self.check_sent_emails([f"zulip-{i:02}@zulip.com" for i in range(5)], clear=True)
|
|
|
|
# We've sent 5 invites. Ensure we can trip the fresh "today" limit for the realm
|
|
result = try_invite(10, default_realm_max=30, new_realm_max=20, realm_max=10)
|
|
self.assert_json_error_contains(result, "reached the limit")
|
|
self.check_sent_emails([])
|
|
|
|
# We've sent 5 invites. Reset the realm to be "recently"
|
|
# created, and ensure that we can trip the whole-server
|
|
# limit
|
|
realm.date_created = timezone_now() - datetime.timedelta(days=3)
|
|
realm.save()
|
|
result = try_invite(10, default_realm_max=50, new_realm_max=10, realm_max=40)
|
|
self.assert_json_error_contains(result, "reached the limit")
|
|
self.check_sent_emails([])
|
|
|
|
@override_settings(OPEN_REALM_CREATION=True)
|
|
def test_limited_plan_heuristics(self) -> None:
|
|
# There additional limits only apply if OPEN_REALM_CREATION is
|
|
# True and the plan is "limited," which is primarily only
|
|
# relevant on Zulip Cloud.
|
|
|
|
realm = do_create_realm("sdfoijt23489fuskdfjhksdf", "Totally Normal")
|
|
realm.plan_type = Realm.PLAN_TYPE_LIMITED
|
|
realm.invite_required = False
|
|
realm.save()
|
|
|
|
# Create a first user
|
|
admin_user = do_create_user(
|
|
"someone@example.com",
|
|
"password",
|
|
realm,
|
|
"full name",
|
|
role=UserProfile.ROLE_REALM_OWNER,
|
|
realm_creation=True,
|
|
acting_user=None,
|
|
)
|
|
|
|
# Inviting would work at all
|
|
with self.assertLogs(level="INFO") as m:
|
|
self.assertFalse(too_many_recent_realm_invites(realm, 1))
|
|
self.assertEqual(
|
|
m.output,
|
|
[
|
|
(
|
|
"INFO:root:sdfoijt23489fuskdfjhksdf "
|
|
"(!: random-realm-name,no-realm-description,no-realm-icon,realm-created-in-last-hour,only-one-user,no-messages-sent) "
|
|
"inviting 1 more, have 0 recent, but only 1 current users. "
|
|
"Ratio 1.0, 2 allowed"
|
|
)
|
|
],
|
|
)
|
|
|
|
# This realm is currently very suspicious, so can only invite
|
|
# 2 users at once (2x current 1 user)
|
|
with self.assertLogs(level="INFO") as m:
|
|
self.assertFalse(too_many_recent_realm_invites(realm, 2))
|
|
self.assertTrue(too_many_recent_realm_invites(realm, 3))
|
|
self.assertEqual(
|
|
m.output,
|
|
[
|
|
(
|
|
"INFO:root:sdfoijt23489fuskdfjhksdf "
|
|
"(!: random-realm-name,no-realm-description,no-realm-icon,realm-created-in-last-hour,only-one-user,no-messages-sent) "
|
|
"inviting 2 more, have 0 recent, but only 1 current users. "
|
|
"Ratio 2.0, 2 allowed"
|
|
),
|
|
(
|
|
"WARNING:root:sdfoijt23489fuskdfjhksdf "
|
|
"(!: random-realm-name,no-realm-description,no-realm-icon,realm-created-in-last-hour,only-one-user,no-messages-sent) "
|
|
"inviting 3 more, have 0 recent, but only 1 current users. "
|
|
"Ratio 3.0, 2 allowed"
|
|
),
|
|
],
|
|
)
|
|
|
|
# Having another user makes it slightly less suspicious, and
|
|
# also able to invite more in ratio with the current count of
|
|
# users (3x current 2 users)
|
|
self.register("other@example.com", "test", subdomain=realm.string_id)
|
|
with self.assertLogs(level="INFO") as m:
|
|
self.assertFalse(too_many_recent_realm_invites(realm, 6))
|
|
self.assertTrue(too_many_recent_realm_invites(realm, 7))
|
|
self.assertEqual(
|
|
m.output,
|
|
[
|
|
(
|
|
"INFO:root:sdfoijt23489fuskdfjhksdf "
|
|
"(!: random-realm-name,no-realm-description,no-realm-icon,realm-created-in-last-hour,no-messages-sent) "
|
|
"inviting 6 more, have 0 recent, but only 2 current users. "
|
|
"Ratio 3.0, 3 allowed"
|
|
),
|
|
(
|
|
"WARNING:root:sdfoijt23489fuskdfjhksdf "
|
|
"(!: random-realm-name,no-realm-description,no-realm-icon,realm-created-in-last-hour,no-messages-sent) "
|
|
"inviting 7 more, have 0 recent, but only 2 current users. "
|
|
"Ratio 3.5, 3 allowed"
|
|
),
|
|
],
|
|
)
|
|
|
|
# Remove some more warning flags
|
|
do_change_realm_subdomain(realm, "reasonable", acting_user=None)
|
|
realm.description = "A real place"
|
|
realm.date_created = timezone_now() - datetime.timedelta(hours=2)
|
|
realm.save()
|
|
|
|
# This is now more allowable (5x current 2 users)
|
|
with self.assertLogs(level="INFO") as m:
|
|
self.assertFalse(too_many_recent_realm_invites(realm, 10))
|
|
self.assertTrue(too_many_recent_realm_invites(realm, 11))
|
|
self.assertEqual(
|
|
m.output,
|
|
[
|
|
(
|
|
"INFO:root:reasonable "
|
|
"(!: no-realm-icon,no-messages-sent) "
|
|
"inviting 10 more, have 0 recent, but only 2 current users. "
|
|
"Ratio 5.0, 5 allowed"
|
|
),
|
|
(
|
|
"WARNING:root:reasonable "
|
|
"(!: no-realm-icon,no-messages-sent) "
|
|
"inviting 11 more, have 0 recent, but only 2 current users. "
|
|
"Ratio 5.5, 5 allowed"
|
|
),
|
|
],
|
|
)
|
|
|
|
# If we have a different max_invites on the realm that kicks in, though
|
|
realm.max_invites = 8
|
|
realm.save()
|
|
self.assertFalse(too_many_recent_realm_invites(realm, 8))
|
|
self.assertTrue(too_many_recent_realm_invites(realm, 9))
|
|
|
|
# And if we have a non-default max invite then that applies
|
|
# but not the heuristics (which would limit us to 10, here)
|
|
realm.max_invites = 12
|
|
realm.save()
|
|
self.assertFalse(too_many_recent_realm_invites(realm, 12))
|
|
self.assertTrue(too_many_recent_realm_invites(realm, 13))
|
|
|
|
# Not being a limited plan also opens us up from the
|
|
# heuristics. First, set us back to the default invite limit
|
|
realm.max_invites = settings.INVITES_DEFAULT_REALM_DAILY_MAX
|
|
realm.save()
|
|
with self.assertLogs(level="INFO") as m:
|
|
self.assertFalse(too_many_recent_realm_invites(realm, 10))
|
|
self.assertTrue(too_many_recent_realm_invites(realm, 11))
|
|
self.assertEqual(
|
|
m.output,
|
|
[
|
|
(
|
|
"INFO:root:reasonable "
|
|
"(!: no-realm-icon,no-messages-sent) "
|
|
"inviting 10 more, have 0 recent, but only 2 current users. "
|
|
"Ratio 5.0, 5 allowed"
|
|
),
|
|
(
|
|
"WARNING:root:reasonable "
|
|
"(!: no-realm-icon,no-messages-sent) "
|
|
"inviting 11 more, have 0 recent, but only 2 current users. "
|
|
"Ratio 5.5, 5 allowed"
|
|
),
|
|
],
|
|
)
|
|
# Become a Standard plan
|
|
do_change_realm_plan_type(realm, Realm.PLAN_TYPE_STANDARD, acting_user=admin_user)
|
|
self.assertFalse(too_many_recent_realm_invites(realm, 3000))
|
|
self.assertTrue(too_many_recent_realm_invites(realm, 3001))
|
|
do_change_realm_plan_type(realm, Realm.PLAN_TYPE_STANDARD_FREE, acting_user=admin_user)
|
|
self.assertFalse(too_many_recent_realm_invites(realm, 3000))
|
|
self.assertTrue(too_many_recent_realm_invites(realm, 3001))
|
|
|
|
def test_invite_user_to_realm_on_manual_license_plan(self) -> None:
|
|
user = self.example_user("hamlet")
|
|
self.login_user(user)
|
|
_, ledger = self.subscribe_realm_to_monthly_plan_on_manual_license_management(
|
|
user.realm, 50, 50
|
|
)
|
|
|
|
with self.settings(BILLING_ENABLED=True):
|
|
result = self.invite(self.nonreg_email("alice"), ["Denmark"])
|
|
self.assert_json_success(result)
|
|
|
|
ledger.licenses_at_next_renewal = 5
|
|
ledger.save(update_fields=["licenses_at_next_renewal"])
|
|
with self.settings(BILLING_ENABLED=True):
|
|
result = self.invite(self.nonreg_email("bob"), ["Denmark"])
|
|
self.assert_json_success(result)
|
|
|
|
ledger.licenses = get_latest_seat_count(user.realm) + 1
|
|
ledger.save(update_fields=["licenses"])
|
|
with self.settings(BILLING_ENABLED=True):
|
|
invitee_emails = self.nonreg_email("bob") + "," + self.nonreg_email("alice")
|
|
result = self.invite(invitee_emails, ["Denmark"])
|
|
self.assert_json_error_contains(
|
|
result, "Your organization does not have enough unused Zulip licenses to invite 2 users"
|
|
)
|
|
|
|
ledger.licenses = get_latest_seat_count(user.realm)
|
|
ledger.save(update_fields=["licenses"])
|
|
with self.settings(BILLING_ENABLED=True):
|
|
result = self.invite(self.nonreg_email("bob"), ["Denmark"])
|
|
self.assert_json_error_contains(
|
|
result, "All Zulip licenses for this organization are currently in use"
|
|
)
|
|
|
|
with self.settings(BILLING_ENABLED=True):
|
|
result = self.invite(
|
|
self.nonreg_email("bob"),
|
|
["Denmark"],
|
|
invite_as=PreregistrationUser.INVITE_AS["GUEST_USER"],
|
|
)
|
|
self.assert_json_success(result)
|
|
|
|
def test_cross_realm_bot(self) -> None:
|
|
inviter = self.example_user("hamlet")
|
|
self.login_user(inviter)
|
|
|
|
cross_realm_bot_email = "emailgateway@zulip.com"
|
|
legit_new_email = "fred@zulip.com"
|
|
invitee_emails = ",".join([cross_realm_bot_email, legit_new_email])
|
|
|
|
result = self.invite(invitee_emails, ["Denmark"])
|
|
self.assert_json_error(
|
|
result,
|
|
(
|
|
"Some of those addresses are already using Zulip, so we didn't send them an"
|
|
" invitation. We did send invitations to everyone else!"
|
|
),
|
|
)
|
|
|
|
def test_invite_mirror_dummy_user(self) -> None:
|
|
"""
|
|
A mirror dummy account is a temporary account
|
|
that we keep in our system if we are mirroring
|
|
data from something like Zephyr or IRC.
|
|
|
|
We want users to eventually just sign up or
|
|
register for Zulip, in which case we will just
|
|
fully "activate" the account.
|
|
|
|
Here we test that you can invite a person who
|
|
has a mirror dummy account.
|
|
"""
|
|
inviter = self.example_user("hamlet")
|
|
self.login_user(inviter)
|
|
|
|
mirror_user = self.example_user("cordelia")
|
|
mirror_user.is_mirror_dummy = True
|
|
mirror_user.save()
|
|
change_user_is_active(mirror_user, False)
|
|
|
|
self.assertEqual(
|
|
PreregistrationUser.objects.filter(email=mirror_user.email).count(),
|
|
0,
|
|
)
|
|
|
|
result = self.invite(mirror_user.email, ["Denmark"])
|
|
self.assert_json_success(result)
|
|
|
|
prereg_user = PreregistrationUser.objects.get(email=mirror_user.email)
|
|
assert prereg_user.referred_by is not None and inviter is not None
|
|
self.assertEqual(
|
|
prereg_user.referred_by.email,
|
|
inviter.email,
|
|
)
|
|
|
|
def test_invite_from_now_deactivated_user(self) -> None:
|
|
"""
|
|
While accepting an invitation from a user,
|
|
processing for a new user account will only
|
|
be completed if the inviter is not deactivated
|
|
after sending the invite.
|
|
"""
|
|
inviter = self.example_user("hamlet")
|
|
self.login_user(inviter)
|
|
invitee = self.nonreg_email("alice")
|
|
|
|
result = self.invite(invitee, ["Denmark"])
|
|
self.assert_json_success(result)
|
|
|
|
prereg_user = PreregistrationUser.objects.get(email=invitee)
|
|
change_user_is_active(inviter, False)
|
|
do_create_user(
|
|
invitee,
|
|
"password",
|
|
inviter.realm,
|
|
"full name",
|
|
prereg_user=prereg_user,
|
|
acting_user=None,
|
|
)
|
|
|
|
def test_successful_invite_user_as_owner_from_owner_account(self) -> None:
|
|
self.login("desdemona")
|
|
invitee = self.nonreg_email("alice")
|
|
result = self.invite(
|
|
invitee, ["Denmark"], invite_as=PreregistrationUser.INVITE_AS["REALM_OWNER"]
|
|
)
|
|
self.assert_json_success(result)
|
|
self.assertTrue(find_key_by_email(invitee))
|
|
|
|
self.submit_reg_form_for_user(invitee, "password")
|
|
invitee_profile = self.nonreg_user("alice")
|
|
self.assertTrue(invitee_profile.is_realm_owner)
|
|
self.assertFalse(invitee_profile.is_guest)
|
|
self.check_user_added_in_system_group(invitee_profile)
|
|
|
|
def test_invite_user_as_owner_from_admin_account(self) -> None:
|
|
self.login("iago")
|
|
invitee = self.nonreg_email("alice")
|
|
response = self.invite(
|
|
invitee, ["Denmark"], invite_as=PreregistrationUser.INVITE_AS["REALM_OWNER"]
|
|
)
|
|
self.assert_json_error(response, "Must be an organization owner")
|
|
|
|
def test_successful_invite_user_as_admin_from_admin_account(self) -> None:
|
|
self.login("iago")
|
|
invitee = self.nonreg_email("alice")
|
|
result = self.invite(
|
|
invitee, ["Denmark"], invite_as=PreregistrationUser.INVITE_AS["REALM_ADMIN"]
|
|
)
|
|
self.assert_json_success(result)
|
|
self.assertTrue(find_key_by_email(invitee))
|
|
|
|
self.submit_reg_form_for_user(invitee, "password")
|
|
invitee_profile = self.nonreg_user("alice")
|
|
self.assertTrue(invitee_profile.is_realm_admin)
|
|
self.assertFalse(invitee_profile.is_realm_owner)
|
|
self.assertFalse(invitee_profile.is_guest)
|
|
self.check_user_added_in_system_group(invitee_profile)
|
|
|
|
def test_invite_user_as_admin_from_normal_account(self) -> None:
|
|
self.login("hamlet")
|
|
invitee = self.nonreg_email("alice")
|
|
response = self.invite(
|
|
invitee, ["Denmark"], invite_as=PreregistrationUser.INVITE_AS["REALM_ADMIN"]
|
|
)
|
|
self.assert_json_error(response, "Must be an organization administrator")
|
|
|
|
def test_successful_invite_user_as_moderator_from_admin_account(self) -> None:
|
|
self.login("iago")
|
|
invitee = self.nonreg_email("alice")
|
|
result = self.invite(
|
|
invitee, ["Denmark"], invite_as=PreregistrationUser.INVITE_AS["MODERATOR"]
|
|
)
|
|
self.assert_json_success(result)
|
|
self.assertTrue(find_key_by_email(invitee))
|
|
|
|
self.submit_reg_form_for_user(invitee, "password")
|
|
invitee_profile = self.nonreg_user("alice")
|
|
self.assertFalse(invitee_profile.is_realm_admin)
|
|
self.assertTrue(invitee_profile.is_moderator)
|
|
self.assertFalse(invitee_profile.is_guest)
|
|
self.check_user_added_in_system_group(invitee_profile)
|
|
|
|
def test_invite_user_as_moderator_from_normal_account(self) -> None:
|
|
self.login("hamlet")
|
|
invitee = self.nonreg_email("alice")
|
|
response = self.invite(
|
|
invitee, ["Denmark"], invite_as=PreregistrationUser.INVITE_AS["MODERATOR"]
|
|
)
|
|
self.assert_json_error(response, "Must be an organization administrator")
|
|
|
|
def test_invite_user_as_moderator_from_moderator_account(self) -> None:
|
|
self.login("shiva")
|
|
invitee = self.nonreg_email("alice")
|
|
response = self.invite(
|
|
invitee, ["Denmark"], invite_as=PreregistrationUser.INVITE_AS["MODERATOR"]
|
|
)
|
|
self.assert_json_error(response, "Must be an organization administrator")
|
|
|
|
def test_invite_user_as_invalid_type(self) -> None:
|
|
"""
|
|
Test inviting a user as invalid type of user i.e. type of invite_as
|
|
is not in PreregistrationUser.INVITE_AS
|
|
"""
|
|
self.login("iago")
|
|
invitee = self.nonreg_email("alice")
|
|
response = self.invite(invitee, ["Denmark"], invite_as=10)
|
|
self.assert_json_error(response, "Invalid invite_as")
|
|
|
|
def test_successful_invite_user_as_guest_from_normal_account(self) -> None:
|
|
self.login("hamlet")
|
|
invitee = self.nonreg_email("alice")
|
|
self.assert_json_success(
|
|
self.invite(invitee, ["Denmark"], invite_as=PreregistrationUser.INVITE_AS["GUEST_USER"])
|
|
)
|
|
self.assertTrue(find_key_by_email(invitee))
|
|
|
|
self.submit_reg_form_for_user(invitee, "password")
|
|
invitee_profile = self.nonreg_user("alice")
|
|
self.assertFalse(invitee_profile.is_realm_admin)
|
|
self.assertTrue(invitee_profile.is_guest)
|
|
self.check_user_added_in_system_group(invitee_profile)
|
|
|
|
def test_successful_invite_user_as_guest_from_admin_account(self) -> None:
|
|
self.login("iago")
|
|
invitee = self.nonreg_email("alice")
|
|
self.assert_json_success(
|
|
self.invite(invitee, ["Denmark"], invite_as=PreregistrationUser.INVITE_AS["GUEST_USER"])
|
|
)
|
|
self.assertTrue(find_key_by_email(invitee))
|
|
|
|
self.submit_reg_form_for_user(invitee, "password")
|
|
invitee_profile = self.nonreg_user("alice")
|
|
self.assertFalse(invitee_profile.is_realm_admin)
|
|
self.assertTrue(invitee_profile.is_guest)
|
|
self.check_user_added_in_system_group(invitee_profile)
|
|
|
|
def test_successful_invite_user_with_name(self) -> None:
|
|
"""
|
|
A call to /json/invites with valid parameters causes an invitation
|
|
email to be sent.
|
|
"""
|
|
self.login("hamlet")
|
|
email = "alice-test@zulip.com"
|
|
invitee = f"Alice Test <{email}>"
|
|
self.assert_json_success(self.invite(invitee, ["Denmark"]))
|
|
self.assertTrue(find_key_by_email(email))
|
|
self.check_sent_emails([email])
|
|
|
|
def test_successful_invite_user_with_name_and_normal_one(self) -> None:
|
|
"""
|
|
A call to /json/invites with valid parameters causes an invitation
|
|
email to be sent.
|
|
"""
|
|
self.login("hamlet")
|
|
email = "alice-test@zulip.com"
|
|
email2 = "bob-test@zulip.com"
|
|
invitee = f"Alice Test <{email}>, {email2}"
|
|
self.assert_json_success(self.invite(invitee, ["Denmark"]))
|
|
self.assertTrue(find_key_by_email(email))
|
|
self.assertTrue(find_key_by_email(email2))
|
|
self.check_sent_emails([email, email2])
|
|
|
|
def test_can_invite_others_to_realm(self) -> None:
|
|
def validation_func(user_profile: UserProfile) -> bool:
|
|
user_profile.refresh_from_db()
|
|
return user_profile.can_invite_others_to_realm()
|
|
|
|
realm = get_realm("zulip")
|
|
do_set_realm_property(
|
|
realm, "invite_to_realm_policy", Realm.POLICY_NOBODY, acting_user=None
|
|
)
|
|
desdemona = self.example_user("desdemona")
|
|
self.assertFalse(validation_func(desdemona))
|
|
|
|
self.check_has_permission_policies("invite_to_realm_policy", validation_func)
|
|
|
|
def test_invite_others_to_realm_setting(self) -> None:
|
|
"""
|
|
The invite_to_realm_policy realm setting works properly.
|
|
"""
|
|
realm = get_realm("zulip")
|
|
do_set_realm_property(
|
|
realm, "invite_to_realm_policy", Realm.POLICY_NOBODY, acting_user=None
|
|
)
|
|
self.login("desdemona")
|
|
email = "alice-test@zulip.com"
|
|
email2 = "bob-test@zulip.com"
|
|
invitee = f"Alice Test <{email}>, {email2}"
|
|
self.assert_json_error(
|
|
self.invite(invitee, ["Denmark"]),
|
|
"Insufficient permission",
|
|
)
|
|
|
|
do_set_realm_property(
|
|
realm, "invite_to_realm_policy", Realm.POLICY_ADMINS_ONLY, acting_user=None
|
|
)
|
|
|
|
self.login("shiva")
|
|
self.assert_json_error(
|
|
self.invite(invitee, ["Denmark"]),
|
|
"Insufficient permission",
|
|
)
|
|
|
|
# Now verify an administrator can do it
|
|
self.login("iago")
|
|
self.assert_json_success(self.invite(invitee, ["Denmark"]))
|
|
self.assertTrue(find_key_by_email(email))
|
|
self.assertTrue(find_key_by_email(email2))
|
|
|
|
self.check_sent_emails([email, email2])
|
|
|
|
mail.outbox = []
|
|
|
|
do_set_realm_property(
|
|
realm, "invite_to_realm_policy", Realm.POLICY_MODERATORS_ONLY, acting_user=None
|
|
)
|
|
self.login("hamlet")
|
|
email = "carol-test@zulip.com"
|
|
email2 = "earl-test@zulip.com"
|
|
invitee = f"Carol Test <{email}>, {email2}"
|
|
self.assert_json_error(
|
|
self.invite(invitee, ["Denmark"]),
|
|
"Insufficient permission",
|
|
)
|
|
|
|
self.login("shiva")
|
|
self.assert_json_success(self.invite(invitee, ["Denmark"]))
|
|
self.assertTrue(find_key_by_email(email))
|
|
self.assertTrue(find_key_by_email(email2))
|
|
self.check_sent_emails([email, email2])
|
|
|
|
mail.outbox = []
|
|
|
|
do_set_realm_property(
|
|
realm, "invite_to_realm_policy", Realm.POLICY_MEMBERS_ONLY, acting_user=None
|
|
)
|
|
|
|
self.login("polonius")
|
|
email = "dave-test@zulip.com"
|
|
email2 = "mark-test@zulip.com"
|
|
invitee = f"Dave Test <{email}>, {email2}"
|
|
self.assert_json_error(self.invite(invitee, ["Denmark"]), "Not allowed for guest users")
|
|
|
|
self.login("hamlet")
|
|
self.assert_json_success(self.invite(invitee, ["Denmark"]))
|
|
self.assertTrue(find_key_by_email(email))
|
|
self.assertTrue(find_key_by_email(email2))
|
|
self.check_sent_emails([email, email2])
|
|
|
|
mail.outbox = []
|
|
|
|
do_set_realm_property(
|
|
realm, "invite_to_realm_policy", Realm.POLICY_FULL_MEMBERS_ONLY, acting_user=None
|
|
)
|
|
do_set_realm_property(realm, "waiting_period_threshold", 1000, acting_user=None)
|
|
|
|
hamlet = self.example_user("hamlet")
|
|
hamlet.date_joined = timezone_now() - datetime.timedelta(
|
|
days=(realm.waiting_period_threshold - 1)
|
|
)
|
|
|
|
email = "issac-test@zulip.com"
|
|
email2 = "steven-test@zulip.com"
|
|
invitee = f"Issac Test <{email}>, {email2}"
|
|
self.assert_json_error(
|
|
self.invite(invitee, ["Denmark"]),
|
|
"Insufficient permission",
|
|
)
|
|
|
|
do_set_realm_property(realm, "waiting_period_threshold", 0, acting_user=None)
|
|
|
|
self.assert_json_success(self.invite(invitee, ["Denmark"]))
|
|
self.assertTrue(find_key_by_email(email))
|
|
self.assertTrue(find_key_by_email(email2))
|
|
self.check_sent_emails([email, email2])
|
|
|
|
def test_invite_user_signup_initial_history(self) -> None:
|
|
"""
|
|
Test that a new user invited to a stream receives some initial
|
|
history but only from public streams.
|
|
"""
|
|
self.login("hamlet")
|
|
user_profile = self.example_user("hamlet")
|
|
private_stream_name = "Secret"
|
|
self.make_stream(private_stream_name, invite_only=True)
|
|
self.subscribe(user_profile, private_stream_name)
|
|
public_msg_id = self.send_stream_message(
|
|
self.example_user("hamlet"),
|
|
"Denmark",
|
|
topic_name="Public topic",
|
|
content="Public message",
|
|
)
|
|
secret_msg_id = self.send_stream_message(
|
|
self.example_user("hamlet"),
|
|
private_stream_name,
|
|
topic_name="Secret topic",
|
|
content="Secret message",
|
|
)
|
|
invitee = self.nonreg_email("alice")
|
|
self.assert_json_success(self.invite(invitee, [private_stream_name, "Denmark"]))
|
|
self.assertTrue(find_key_by_email(invitee))
|
|
|
|
self.submit_reg_form_for_user(invitee, "password")
|
|
invitee_profile = self.nonreg_user("alice")
|
|
invitee_msg_ids = [
|
|
um.message_id for um in UserMessage.objects.filter(user_profile=invitee_profile)
|
|
]
|
|
self.assertTrue(public_msg_id in invitee_msg_ids)
|
|
self.assertFalse(secret_msg_id in invitee_msg_ids)
|
|
self.assertFalse(invitee_profile.is_realm_admin)
|
|
|
|
invitee_msg, signups_stream_msg, inviter_msg, secret_msg = Message.objects.all().order_by(
|
|
"-id"
|
|
)[0:4]
|
|
|
|
self.assertEqual(secret_msg.id, secret_msg_id)
|
|
|
|
self.assertEqual(inviter_msg.sender.email, "notification-bot@zulip.com")
|
|
self.assertTrue(
|
|
inviter_msg.content.startswith(
|
|
f"alice_zulip.com <`{invitee_profile.email}`> accepted your",
|
|
)
|
|
)
|
|
|
|
self.assertEqual(signups_stream_msg.sender.email, "notification-bot@zulip.com")
|
|
self.assertTrue(
|
|
signups_stream_msg.content.startswith(
|
|
f"@_**alice_zulip.com|{invitee_profile.id}** just signed up",
|
|
)
|
|
)
|
|
|
|
self.assertEqual(invitee_msg.sender.email, "welcome-bot@zulip.com")
|
|
self.assertTrue(invitee_msg.content.startswith("Hello, and welcome to Zulip!"))
|
|
self.assertNotIn("demo organization", invitee_msg.content)
|
|
|
|
def test_multi_user_invite(self) -> None:
|
|
"""
|
|
Invites multiple users with a variety of delimiters.
|
|
"""
|
|
self.login("hamlet")
|
|
# Intentionally use a weird string.
|
|
self.assert_json_success(
|
|
self.invite(
|
|
"""bob-test@zulip.com, carol-test@zulip.com,
|
|
dave-test@zulip.com
|
|
|
|
|
|
earl-test@zulip.com""",
|
|
["Denmark"],
|
|
)
|
|
)
|
|
for user in ("bob", "carol", "dave", "earl"):
|
|
self.assertTrue(find_key_by_email(f"{user}-test@zulip.com"))
|
|
self.check_sent_emails(
|
|
[
|
|
"bob-test@zulip.com",
|
|
"carol-test@zulip.com",
|
|
"dave-test@zulip.com",
|
|
"earl-test@zulip.com",
|
|
]
|
|
)
|
|
|
|
def test_max_invites_model(self) -> None:
|
|
realm = get_realm("zulip")
|
|
self.assertEqual(realm.max_invites, settings.INVITES_DEFAULT_REALM_DAILY_MAX)
|
|
realm.max_invites = 3
|
|
realm.save()
|
|
self.assertEqual(get_realm("zulip").max_invites, 3)
|
|
realm.max_invites = settings.INVITES_DEFAULT_REALM_DAILY_MAX
|
|
realm.save()
|
|
|
|
def test_missing_or_invalid_params(self) -> None:
|
|
"""
|
|
Tests inviting with various missing or invalid parameters.
|
|
"""
|
|
realm = get_realm("zulip")
|
|
do_set_realm_property(realm, "emails_restricted_to_domains", True, acting_user=None)
|
|
|
|
self.login("hamlet")
|
|
invitee_emails = "foo@zulip.com"
|
|
self.assert_json_error(
|
|
self.invite(invitee_emails, []),
|
|
"You must specify at least one stream for invitees to join.",
|
|
)
|
|
|
|
for address in ("noatsign.com", "outsideyourdomain@example.net"):
|
|
self.assert_json_error(
|
|
self.invite(address, ["Denmark"]),
|
|
"Some emails did not validate, so we didn't send any invitations.",
|
|
)
|
|
self.check_sent_emails([])
|
|
|
|
self.assert_json_error(
|
|
self.invite("", ["Denmark"]), "You must specify at least one email address."
|
|
)
|
|
self.check_sent_emails([])
|
|
|
|
def test_guest_user_invitation(self) -> None:
|
|
"""
|
|
Guest user can't invite new users
|
|
"""
|
|
self.login("polonius")
|
|
invitee = "alice-test@zulip.com"
|
|
self.assert_json_error(self.invite(invitee, ["Denmark"]), "Not allowed for guest users")
|
|
self.assertEqual(find_key_by_email(invitee), None)
|
|
self.check_sent_emails([])
|
|
|
|
def test_invalid_stream(self) -> None:
|
|
"""
|
|
Tests inviting to a non-existent stream.
|
|
"""
|
|
self.login("hamlet")
|
|
self.assert_json_error(
|
|
self.invite("iago-test@zulip.com", ["NotARealStream"]),
|
|
f"Stream does not exist with id: {self.INVALID_STREAM_ID}. No invites were sent.",
|
|
)
|
|
self.check_sent_emails([])
|
|
|
|
def test_invite_existing_user(self) -> None:
|
|
"""
|
|
If you invite an address already using Zulip, no invitation is sent.
|
|
"""
|
|
self.login("hamlet")
|
|
|
|
hamlet_email = "hAmLeT@zUlIp.com"
|
|
result = self.invite(hamlet_email, ["Denmark"])
|
|
self.assert_json_error(result, "We weren't able to invite anyone.")
|
|
|
|
self.assertFalse(
|
|
PreregistrationUser.objects.filter(email__iexact=hamlet_email).exists(),
|
|
)
|
|
self.check_sent_emails([])
|
|
|
|
def normalize_string(self, s: str) -> str:
|
|
s = s.strip()
|
|
return re.sub(r"\s+", " ", s)
|
|
|
|
def test_invite_links_in_name(self) -> None:
|
|
"""
|
|
Names are escaped in the emails which are sent.
|
|
"""
|
|
hamlet = self.example_user("hamlet")
|
|
self.login_user(hamlet)
|
|
# Test we properly handle links in user full names
|
|
do_change_full_name(hamlet, "</a> https://www.google.com", hamlet)
|
|
|
|
result = self.invite("newuser@zulip.com", ["Denmark"])
|
|
self.assert_json_success(result)
|
|
self.check_sent_emails(["newuser@zulip.com"])
|
|
assert isinstance(mail.outbox[0], EmailMultiAlternatives)
|
|
assert isinstance(mail.outbox[0].alternatives[0][0], str)
|
|
body = self.normalize_string(mail.outbox[0].alternatives[0][0])
|
|
|
|
# Verify that one can't get Zulip to send invitation emails
|
|
# that third-party products will linkify using the full_name
|
|
# field, because we've included that field inside the mailto:
|
|
# link for the sender.
|
|
self.assertIn(
|
|
'<a href="mailto:hamlet@zulip.com" style="color:#5f5ec7; text-decoration:underline"></a> https://www.google.com (hamlet@zulip.com)</a> wants',
|
|
body,
|
|
)
|
|
|
|
# TODO: Ideally, this test would also test the Invitation
|
|
# Reminder email generated, but the test setup for that is
|
|
# annoying.
|
|
|
|
def test_invite_some_existing_some_new(self) -> None:
|
|
"""
|
|
If you invite a mix of already existing and new users, invitations are
|
|
only sent to the new users.
|
|
"""
|
|
self.login("hamlet")
|
|
existing = [self.example_email("hamlet"), "othello@zulip.com"]
|
|
new = ["foo-test@zulip.com", "bar-test@zulip.com"]
|
|
invitee_emails = "\n".join(existing + new)
|
|
self.assert_json_error(
|
|
self.invite(invitee_emails, ["Denmark"]),
|
|
"Some of those addresses are already using Zulip, \
|
|
so we didn't send them an invitation. We did send invitations to everyone else!",
|
|
)
|
|
|
|
# We only created accounts for the new users.
|
|
for email in existing:
|
|
self.assertRaises(
|
|
PreregistrationUser.DoesNotExist,
|
|
lambda: PreregistrationUser.objects.get(email=email),
|
|
)
|
|
for email in new:
|
|
self.assertTrue(PreregistrationUser.objects.get(email=email))
|
|
|
|
# We only sent emails to the new users.
|
|
self.check_sent_emails(new)
|
|
|
|
def test_invite_outside_domain_in_closed_realm(self) -> None:
|
|
"""
|
|
In a realm with `emails_restricted_to_domains = True`, you can't invite people
|
|
with a different domain from that of the realm or your e-mail address.
|
|
"""
|
|
zulip_realm = get_realm("zulip")
|
|
zulip_realm.emails_restricted_to_domains = True
|
|
zulip_realm.save()
|
|
|
|
self.login("hamlet")
|
|
external_address = "foo@example.com"
|
|
|
|
self.assert_json_error(
|
|
self.invite(external_address, ["Denmark"]),
|
|
"Some emails did not validate, so we didn't send any invitations.",
|
|
)
|
|
|
|
def test_invite_using_disposable_email(self) -> None:
|
|
"""
|
|
In a realm with `disallow_disposable_email_addresses = True`, you can't invite
|
|
people with a disposable domain.
|
|
"""
|
|
zulip_realm = get_realm("zulip")
|
|
zulip_realm.emails_restricted_to_domains = False
|
|
zulip_realm.disallow_disposable_email_addresses = True
|
|
zulip_realm.save()
|
|
|
|
self.login("hamlet")
|
|
external_address = "foo@mailnator.com"
|
|
|
|
self.assert_json_error(
|
|
self.invite(external_address, ["Denmark"]),
|
|
"Some emails did not validate, so we didn't send any invitations.",
|
|
)
|
|
|
|
def test_invite_outside_domain_in_open_realm(self) -> None:
|
|
"""
|
|
In a realm with `emails_restricted_to_domains = False`, you can invite people
|
|
with a different domain from that of the realm or your e-mail address.
|
|
"""
|
|
zulip_realm = get_realm("zulip")
|
|
zulip_realm.emails_restricted_to_domains = False
|
|
zulip_realm.save()
|
|
|
|
self.login("hamlet")
|
|
external_address = "foo@example.com"
|
|
|
|
self.assert_json_success(self.invite(external_address, ["Denmark"]))
|
|
self.check_sent_emails([external_address])
|
|
|
|
def test_invite_outside_domain_before_closing(self) -> None:
|
|
"""
|
|
If you invite someone with a different domain from that of the realm
|
|
when `emails_restricted_to_domains = False`, but `emails_restricted_to_domains` later
|
|
changes to true, the invitation should succeed but the invitee's signup
|
|
attempt should fail.
|
|
"""
|
|
zulip_realm = get_realm("zulip")
|
|
zulip_realm.emails_restricted_to_domains = False
|
|
zulip_realm.save()
|
|
|
|
self.login("hamlet")
|
|
external_address = "foo@example.com"
|
|
|
|
self.assert_json_success(self.invite(external_address, ["Denmark"]))
|
|
self.check_sent_emails([external_address])
|
|
|
|
zulip_realm.emails_restricted_to_domains = True
|
|
zulip_realm.save()
|
|
|
|
result = self.submit_reg_form_for_user("foo@example.com", "password")
|
|
self.assertEqual(result.status_code, 200)
|
|
self.assert_in_response("only allows users with email addresses", result)
|
|
|
|
def test_disposable_emails_before_closing(self) -> None:
|
|
"""
|
|
If you invite someone with a disposable email when
|
|
`disallow_disposable_email_addresses = False`, but
|
|
later changes to true, the invitation should succeed
|
|
but the invitee's signup attempt should fail.
|
|
"""
|
|
zulip_realm = get_realm("zulip")
|
|
zulip_realm.emails_restricted_to_domains = False
|
|
zulip_realm.disallow_disposable_email_addresses = False
|
|
zulip_realm.save()
|
|
|
|
self.login("hamlet")
|
|
external_address = "foo@mailnator.com"
|
|
|
|
self.assert_json_success(self.invite(external_address, ["Denmark"]))
|
|
self.check_sent_emails([external_address])
|
|
|
|
zulip_realm.disallow_disposable_email_addresses = True
|
|
zulip_realm.save()
|
|
|
|
result = self.submit_reg_form_for_user("foo@mailnator.com", "password")
|
|
self.assertEqual(result.status_code, 200)
|
|
self.assert_in_response("Please sign up using a real email address.", result)
|
|
|
|
def test_invite_with_email_containing_plus_before_closing(self) -> None:
|
|
"""
|
|
If you invite someone with an email containing plus when
|
|
`emails_restricted_to_domains = False`, but later change
|
|
`emails_restricted_to_domains = True`, the invitation should
|
|
succeed but the invitee's signup attempt should fail as
|
|
users are not allowed to sign up using email containing +
|
|
when the realm is restricted to domain.
|
|
"""
|
|
zulip_realm = get_realm("zulip")
|
|
zulip_realm.emails_restricted_to_domains = False
|
|
zulip_realm.save()
|
|
|
|
self.login("hamlet")
|
|
external_address = "foo+label@zulip.com"
|
|
|
|
self.assert_json_success(self.invite(external_address, ["Denmark"]))
|
|
self.check_sent_emails([external_address])
|
|
|
|
zulip_realm.emails_restricted_to_domains = True
|
|
zulip_realm.save()
|
|
|
|
result = self.submit_reg_form_for_user(external_address, "password")
|
|
self.assertEqual(result.status_code, 200)
|
|
self.assert_in_response(
|
|
"Zulip Dev, does not allow signups using emails\n that contains +", result
|
|
)
|
|
|
|
def test_invalid_email_check_after_confirming_email(self) -> None:
|
|
self.login("hamlet")
|
|
email = "test@zulip.com"
|
|
|
|
self.assert_json_success(self.invite(email, ["Denmark"]))
|
|
|
|
obj = Confirmation.objects.get(confirmation_key=find_key_by_email(email))
|
|
prereg_user = obj.content_object
|
|
assert prereg_user is not None
|
|
prereg_user.email = "invalid.email"
|
|
prereg_user.save()
|
|
|
|
result = self.submit_reg_form_for_user(email, "password")
|
|
self.assertEqual(result.status_code, 200)
|
|
self.assert_in_response(
|
|
"The email address you are trying to sign up with is not valid", result
|
|
)
|
|
|
|
def test_invite_with_non_ascii_streams(self) -> None:
|
|
"""
|
|
Inviting someone to streams with non-ASCII characters succeeds.
|
|
"""
|
|
self.login("hamlet")
|
|
invitee = "alice-test@zulip.com"
|
|
|
|
stream_name = "hümbüǵ"
|
|
|
|
# Make sure we're subscribed before inviting someone.
|
|
self.subscribe(self.example_user("hamlet"), stream_name)
|
|
|
|
self.assert_json_success(self.invite(invitee, [stream_name]))
|
|
|
|
def test_invitation_reminder_email(self) -> None:
|
|
# All users belong to zulip realm
|
|
referrer_name = "hamlet"
|
|
current_user = self.example_user(referrer_name)
|
|
self.login_user(current_user)
|
|
invitee_email = self.nonreg_email("alice")
|
|
self.assert_json_success(self.invite(invitee_email, ["Denmark"]))
|
|
self.assertTrue(find_key_by_email(invitee_email))
|
|
self.check_sent_emails([invitee_email])
|
|
|
|
data = {"email": invitee_email, "referrer_email": current_user.email}
|
|
invitee = PreregistrationUser.objects.get(email=data["email"])
|
|
referrer = self.example_user(referrer_name)
|
|
validity_in_minutes = 2 * 24 * 60
|
|
link = create_confirmation_link(
|
|
invitee, Confirmation.INVITATION, validity_in_minutes=validity_in_minutes
|
|
)
|
|
context = common_context(referrer)
|
|
context.update(
|
|
activate_url=link,
|
|
referrer_name=referrer.full_name,
|
|
referrer_email=referrer.email,
|
|
referrer_realm_name=referrer.realm.name,
|
|
)
|
|
with self.settings(EMAIL_BACKEND="django.core.mail.backends.console.EmailBackend"):
|
|
email = data["email"]
|
|
send_future_email(
|
|
"zerver/emails/invitation_reminder",
|
|
referrer.realm,
|
|
to_emails=[email],
|
|
from_address=FromAddress.no_reply_placeholder,
|
|
context=context,
|
|
)
|
|
email_jobs_to_deliver = ScheduledEmail.objects.filter(
|
|
scheduled_timestamp__lte=timezone_now()
|
|
)
|
|
self.assert_length(email_jobs_to_deliver, 1)
|
|
email_count = len(mail.outbox)
|
|
for job in email_jobs_to_deliver:
|
|
deliver_scheduled_emails(job)
|
|
self.assert_length(mail.outbox, email_count + 1)
|
|
self.assertEqual(self.email_envelope_from(mail.outbox[-1]), settings.NOREPLY_EMAIL_ADDRESS)
|
|
self.assertIn(FromAddress.NOREPLY, self.email_display_from(mail.outbox[-1]))
|
|
|
|
# Now verify that signing up clears invite_reminder emails
|
|
with self.settings(EMAIL_BACKEND="django.core.mail.backends.console.EmailBackend"):
|
|
email = data["email"]
|
|
send_future_email(
|
|
"zerver/emails/invitation_reminder",
|
|
referrer.realm,
|
|
to_emails=[email],
|
|
from_address=FromAddress.no_reply_placeholder,
|
|
context=context,
|
|
)
|
|
|
|
email_jobs_to_deliver = ScheduledEmail.objects.filter(
|
|
scheduled_timestamp__lte=timezone_now(), type=ScheduledEmail.INVITATION_REMINDER
|
|
)
|
|
self.assert_length(email_jobs_to_deliver, 1)
|
|
|
|
self.register(invitee_email, "test")
|
|
email_jobs_to_deliver = ScheduledEmail.objects.filter(
|
|
scheduled_timestamp__lte=timezone_now(), type=ScheduledEmail.INVITATION_REMINDER
|
|
)
|
|
self.assert_length(email_jobs_to_deliver, 0)
|
|
|
|
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_minutes=4 * 24 * 60)
|
|
self.assertEqual(
|
|
ScheduledEmail.objects.filter(
|
|
address="alice@zulip.com", 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_minutes=3 * 24 * 60)
|
|
self.assertEqual(
|
|
ScheduledEmail.objects.filter(
|
|
address="bob@zulip.com", type=ScheduledEmail.INVITATION_REMINDER
|
|
).count(),
|
|
0,
|
|
)
|
|
|
|
# make sure users can't take a valid confirmation key from another
|
|
# pathway and use it with the invitation URL route
|
|
def test_confirmation_key_of_wrong_type(self) -> None:
|
|
email = self.nonreg_email("alice")
|
|
realm = get_realm("zulip")
|
|
inviter = self.example_user("iago")
|
|
prereg_user = PreregistrationUser.objects.create(
|
|
email=email, referred_by=inviter, realm=realm
|
|
)
|
|
url = create_confirmation_link(prereg_user, Confirmation.USER_REGISTRATION)
|
|
registration_key = url.split("/")[-1]
|
|
|
|
# Mainly a test of get_object_from_key, rather than of the invitation pathway
|
|
with self.assertRaises(ConfirmationKeyError) as cm:
|
|
get_object_from_key(registration_key, [Confirmation.INVITATION], mark_object_used=True)
|
|
self.assertEqual(cm.exception.error_type, ConfirmationKeyError.DOES_NOT_EXIST)
|
|
|
|
# Verify that using the wrong type doesn't work in the main confirm code path
|
|
email_change_url = create_confirmation_link(prereg_user, Confirmation.EMAIL_CHANGE)
|
|
email_change_key = email_change_url.split("/")[-1]
|
|
result = self.client_post("/accounts/register/", {"key": email_change_key})
|
|
self.assertEqual(result.status_code, 404)
|
|
self.assert_in_response(
|
|
"Whoops. We couldn't find your confirmation link in the system.", result
|
|
)
|
|
|
|
def test_confirmation_expired(self) -> None:
|
|
email = self.nonreg_email("alice")
|
|
realm = get_realm("zulip")
|
|
inviter = self.example_user("iago")
|
|
prereg_user = PreregistrationUser.objects.create(
|
|
email=email, referred_by=inviter, realm=realm
|
|
)
|
|
date_sent = timezone_now() - datetime.timedelta(weeks=3)
|
|
with patch("confirmation.models.timezone_now", return_value=date_sent):
|
|
url = create_confirmation_link(prereg_user, Confirmation.USER_REGISTRATION)
|
|
|
|
key = url.split("/")[-1]
|
|
confirmation_link_path = "/" + url.split("/", 3)[3]
|
|
# Both the confirmation link and submitting the key to the registration endpoint
|
|
# directly will return the appropriate error.
|
|
result = self.client_get(confirmation_link_path)
|
|
self.assertEqual(result.status_code, 404)
|
|
self.assert_in_response(
|
|
"Whoops. The confirmation link has expired or been deactivated.", result
|
|
)
|
|
|
|
result = self.client_post("/accounts/register/", {"key": key})
|
|
self.assertEqual(result.status_code, 404)
|
|
self.assert_in_response(
|
|
"Whoops. The confirmation link has expired or been deactivated.", result
|
|
)
|
|
|
|
def test_never_expire_confirmation_object(self) -> None:
|
|
email = self.nonreg_email("alice")
|
|
realm = get_realm("zulip")
|
|
inviter = self.example_user("iago")
|
|
prereg_user = PreregistrationUser.objects.create(
|
|
email=email, referred_by=inviter, realm=realm
|
|
)
|
|
activation_url = create_confirmation_link(
|
|
prereg_user, Confirmation.INVITATION, validity_in_minutes=None
|
|
)
|
|
confirmation = Confirmation.objects.last()
|
|
assert confirmation is not None
|
|
self.assertEqual(confirmation.expiry_date, None)
|
|
activation_key = activation_url.split("/")[-1]
|
|
response = self.client_post(
|
|
"/accounts/register/",
|
|
{"key": activation_key, "from_confirmation": 1, "full_nme": "alice"},
|
|
)
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
def test_send_more_than_one_invite_to_same_user(self) -> None:
|
|
self.user_profile = self.example_user("iago")
|
|
streams = []
|
|
for stream_name in ["Denmark", "Scotland"]:
|
|
streams.append(get_stream(stream_name, self.user_profile.realm))
|
|
|
|
invite_expires_in_minutes = 2 * 24 * 60
|
|
do_invite_users(
|
|
self.user_profile,
|
|
["foo@zulip.com"],
|
|
streams,
|
|
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_minutes=invite_expires_in_minutes,
|
|
)
|
|
do_invite_users(
|
|
self.user_profile,
|
|
["foo@zulip.com"],
|
|
streams,
|
|
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_minutes=invite_expires_in_minutes
|
|
)
|
|
|
|
invites = PreregistrationUser.objects.filter(email__iexact="foo@zulip.com")
|
|
self.assert_length(invites, 4)
|
|
|
|
created_user = do_create_user(
|
|
"foo@zulip.com",
|
|
"password",
|
|
self.user_profile.realm,
|
|
"full name",
|
|
prereg_user=prereg_user,
|
|
acting_user=None,
|
|
)
|
|
|
|
accepted_invite = PreregistrationUser.objects.filter(
|
|
email__iexact="foo@zulip.com", status=confirmation_settings.STATUS_USED
|
|
)
|
|
revoked_invites = PreregistrationUser.objects.filter(
|
|
email__iexact="foo@zulip.com", status=confirmation_settings.STATUS_REVOKED
|
|
)
|
|
# If a user was invited more than once, when it accepts one invite and register
|
|
# the others must be canceled.
|
|
self.assert_length(accepted_invite, 1)
|
|
self.assertEqual(accepted_invite[0].id, prereg_user.id)
|
|
self.assertEqual(accepted_invite[0].created_user, created_user)
|
|
|
|
expected_revoked_invites = set(invites.exclude(id=prereg_user.id).exclude(realm=lear))
|
|
self.assertEqual(set(revoked_invites), expected_revoked_invites)
|
|
|
|
self.assertEqual(
|
|
PreregistrationUser.objects.get(email__iexact="foo@zulip.com", realm=lear).status, 0
|
|
)
|
|
|
|
with self.assertRaises(AssertionError):
|
|
process_new_human_user(created_user, prereg_user)
|
|
|
|
def test_confirmation_obj_not_exist_error(self) -> None:
|
|
"""Since the key is a param input by the user to the registration endpoint,
|
|
if it inserts an invalid value, the confirmation object won't be found. This
|
|
tests if, in that scenario, we handle the exception by redirecting the user to
|
|
the link_expired page.
|
|
"""
|
|
email = self.nonreg_email("alice")
|
|
password = "password"
|
|
realm = get_realm("zulip")
|
|
inviter = self.example_user("iago")
|
|
prereg_user = PreregistrationUser.objects.create(
|
|
email=email, referred_by=inviter, realm=realm
|
|
)
|
|
confirmation_link = create_confirmation_link(prereg_user, Confirmation.USER_REGISTRATION)
|
|
|
|
registration_key = "invalid_confirmation_key"
|
|
url = "/accounts/register/"
|
|
response = self.client_post(
|
|
url, {"key": registration_key, "from_confirmation": 1, "full_name": "alice"}
|
|
)
|
|
self.assertEqual(response.status_code, 404)
|
|
self.assert_in_response(
|
|
"Whoops. We couldn't find your confirmation link in the system.", response
|
|
)
|
|
|
|
registration_key = confirmation_link.split("/")[-1]
|
|
response = self.client_post(
|
|
url, {"key": registration_key, "from_confirmation": 1, "full_name": "alice"}
|
|
)
|
|
self.assert_in_success_response(
|
|
["Enter your account details to complete registration."], response
|
|
)
|
|
response = self.submit_reg_form_for_user(email, password, key=registration_key)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
def test_validate_email_not_already_in_realm(self) -> None:
|
|
email = self.nonreg_email("alice")
|
|
password = "password"
|
|
realm = get_realm("zulip")
|
|
inviter = self.example_user("iago")
|
|
prereg_user = PreregistrationUser.objects.create(
|
|
email=email, referred_by=inviter, realm=realm
|
|
)
|
|
|
|
confirmation_link = create_confirmation_link(prereg_user, Confirmation.USER_REGISTRATION)
|
|
registration_key = confirmation_link.split("/")[-1]
|
|
|
|
url = "/accounts/register/"
|
|
self.client_post(
|
|
url, {"key": registration_key, "from_confirmation": 1, "full_name": "alice"}
|
|
)
|
|
self.submit_reg_form_for_user(email, password, key=registration_key)
|
|
|
|
new_prereg_user = PreregistrationUser.objects.create(
|
|
email=email, referred_by=inviter, realm=realm
|
|
)
|
|
new_confirmation_link = create_confirmation_link(
|
|
new_prereg_user, Confirmation.USER_REGISTRATION
|
|
)
|
|
new_registration_key = new_confirmation_link.split("/")[-1]
|
|
url = "/accounts/register/"
|
|
response = self.client_post(
|
|
url, {"key": new_registration_key, "from_confirmation": 1, "full_name": "alice"}
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(
|
|
response["Location"],
|
|
reverse("login") + "?" + urlencode({"email": email, "already_registered": 1}),
|
|
)
|
|
|
|
def test_confirmation_key_cant_be_reused(self) -> None:
|
|
email = self.nonreg_email("alice")
|
|
password = "password"
|
|
realm = get_realm("zulip")
|
|
inviter = self.example_user("iago")
|
|
prereg_user = PreregistrationUser.objects.create(
|
|
email=email, referred_by=inviter, realm=realm
|
|
)
|
|
|
|
confirmation_link = create_confirmation_link(prereg_user, Confirmation.USER_REGISTRATION)
|
|
registration_key = confirmation_link.split("/")[-1]
|
|
|
|
url = "/accounts/register/"
|
|
self.client_post(
|
|
url, {"key": registration_key, "from_confirmation": 1, "full_name": "alice"}
|
|
)
|
|
self.submit_reg_form_for_user(email, password, key=registration_key)
|
|
|
|
prereg_user.refresh_from_db()
|
|
self.assertIsNotNone(prereg_user.created_user)
|
|
|
|
# Now attempt to re-use the same key.
|
|
result = self.client_post("/accounts/register/", {"key": registration_key})
|
|
self.assertEqual(result.status_code, 404)
|
|
self.assert_in_response(
|
|
"Whoops. The confirmation link has expired or been deactivated.", result
|
|
)
|
|
|
|
def test_confirmation_link_in_manual_license_plan(self) -> None:
|
|
inviter = self.example_user("iago")
|
|
realm = get_realm("zulip")
|
|
|
|
email = self.nonreg_email("alice")
|
|
realm = get_realm("zulip")
|
|
prereg_user = PreregistrationUser.objects.create(
|
|
email=email, referred_by=inviter, realm=realm
|
|
)
|
|
confirmation_link = create_confirmation_link(prereg_user, Confirmation.USER_REGISTRATION)
|
|
registration_key = confirmation_link.split("/")[-1]
|
|
url = "/accounts/register/"
|
|
self.client_post(
|
|
url, {"key": registration_key, "from_confirmation": 1, "full_name": "alice"}
|
|
)
|
|
response = self.submit_reg_form_for_user(email, "password", key=registration_key)
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(response["Location"], "http://zulip.testserver/")
|
|
|
|
# We want to simulate the organization having exactly all their licenses
|
|
# used, to verify that joining as a regular user is not allowed,
|
|
# but as a guest still works (guests are free up to a certain number).
|
|
current_seat_count = get_latest_seat_count(realm)
|
|
self.subscribe_realm_to_monthly_plan_on_manual_license_management(
|
|
realm, current_seat_count, current_seat_count
|
|
)
|
|
|
|
email = self.nonreg_email("bob")
|
|
prereg_user = PreregistrationUser.objects.create(
|
|
email=email, referred_by=inviter, realm=realm
|
|
)
|
|
confirmation_link = create_confirmation_link(prereg_user, Confirmation.USER_REGISTRATION)
|
|
registration_key = confirmation_link.split("/")[-1]
|
|
url = "/accounts/register/"
|
|
self.client_post(url, {"key": registration_key, "from_confirmation": 1, "full_name": "bob"})
|
|
response = self.submit_reg_form_for_user(email, "password", key=registration_key)
|
|
self.assert_in_success_response(
|
|
["New members cannot join this organization because all Zulip licenses are"], response
|
|
)
|
|
|
|
guest_prereg_user = PreregistrationUser.objects.create(
|
|
email=email,
|
|
referred_by=inviter,
|
|
realm=realm,
|
|
invited_as=PreregistrationUser.INVITE_AS["GUEST_USER"],
|
|
)
|
|
confirmation_link = create_confirmation_link(
|
|
guest_prereg_user, Confirmation.USER_REGISTRATION
|
|
)
|
|
registration_key = confirmation_link.split("/")[-1]
|
|
url = "/accounts/register/"
|
|
|
|
self.client_post(
|
|
url, {"key": registration_key, "from_confirmation": 1, "full_name": "alice"}
|
|
)
|
|
response = self.submit_reg_form_for_user(email, "password", key=registration_key)
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(response["Location"], "http://zulip.testserver/")
|
|
|
|
|
|
class InvitationsTestCase(InviteUserBase):
|
|
def test_do_get_invites_controlled_by_user(self) -> None:
|
|
user_profile = self.example_user("iago")
|
|
hamlet = self.example_user("hamlet")
|
|
othello = self.example_user("othello")
|
|
|
|
streams = []
|
|
for stream_name in ["Denmark", "Scotland"]:
|
|
streams.append(get_stream(stream_name, user_profile.realm))
|
|
|
|
invite_expires_in_minutes = 2 * 24 * 60
|
|
do_invite_users(
|
|
user_profile,
|
|
["TestOne@zulip.com"],
|
|
streams,
|
|
invite_expires_in_minutes=invite_expires_in_minutes,
|
|
)
|
|
do_invite_users(
|
|
user_profile,
|
|
["TestTwo@zulip.com"],
|
|
streams,
|
|
invite_expires_in_minutes=invite_expires_in_minutes,
|
|
)
|
|
do_invite_users(
|
|
hamlet,
|
|
["TestThree@zulip.com"],
|
|
streams,
|
|
invite_expires_in_minutes=invite_expires_in_minutes,
|
|
)
|
|
do_invite_users(
|
|
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_minutes=invite_expires_in_minutes,
|
|
)
|
|
do_create_multiuse_invite_link(
|
|
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)
|
|
self.assert_length(do_get_invites_controlled_by_user(othello), 1)
|
|
|
|
def test_successful_get_open_invitations(self) -> None:
|
|
"""
|
|
A GET call to /json/invites returns all unexpired invitations.
|
|
"""
|
|
active_value = getattr(confirmation_settings, "STATUS_USED", "Wrong")
|
|
self.assertNotEqual(active_value, "Wrong")
|
|
|
|
self.login("iago")
|
|
user_profile = self.example_user("iago")
|
|
self.login_user(user_profile)
|
|
|
|
hamlet = self.example_user("hamlet")
|
|
othello = self.example_user("othello")
|
|
|
|
streams = []
|
|
for stream_name in ["Denmark", "Scotland"]:
|
|
streams.append(get_stream(stream_name, user_profile.realm))
|
|
|
|
invite_expires_in_minutes = 2 * 24 * 60
|
|
do_invite_users(
|
|
user_profile,
|
|
["TestOne@zulip.com"],
|
|
streams,
|
|
invite_expires_in_minutes=invite_expires_in_minutes,
|
|
)
|
|
|
|
with patch(
|
|
"confirmation.models.timezone_now",
|
|
return_value=timezone_now() - datetime.timedelta(days=3),
|
|
):
|
|
do_invite_users(
|
|
user_profile,
|
|
["TestTwo@zulip.com"],
|
|
streams,
|
|
invite_expires_in_minutes=invite_expires_in_minutes,
|
|
)
|
|
do_create_multiuse_invite_link(
|
|
othello, PreregistrationUser.INVITE_AS["MEMBER"], invite_expires_in_minutes
|
|
)
|
|
|
|
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,
|
|
validity_in_minutes=invite_expires_in_minutes,
|
|
)
|
|
|
|
do_create_multiuse_invite_link(
|
|
hamlet, PreregistrationUser.INVITE_AS["MEMBER"], invite_expires_in_minutes
|
|
)
|
|
|
|
result = self.client_get("/json/invites")
|
|
self.assertEqual(result.status_code, 200)
|
|
invites = orjson.loads(result.content)["invites"]
|
|
self.assert_length(invites, 2)
|
|
|
|
self.assertFalse(invites[0]["is_multiuse"])
|
|
self.assertEqual(invites[0]["email"], "TestOne@zulip.com")
|
|
self.assertTrue(invites[1]["is_multiuse"])
|
|
self.assertEqual(invites[1]["invited_by_user_id"], hamlet.id)
|
|
|
|
def test_get_never_expiring_invitations(self) -> None:
|
|
self.login("iago")
|
|
user_profile = self.example_user("iago")
|
|
|
|
streams = []
|
|
for stream_name in ["Denmark", "Scotland"]:
|
|
streams.append(get_stream(stream_name, user_profile.realm))
|
|
|
|
with patch(
|
|
"confirmation.models.timezone_now",
|
|
return_value=timezone_now() - datetime.timedelta(days=1000),
|
|
):
|
|
# Testing the invitation with expiry date set to "None" exists
|
|
# after a large amount of days.
|
|
do_invite_users(
|
|
user_profile,
|
|
["TestOne@zulip.com"],
|
|
streams,
|
|
invite_expires_in_minutes=None,
|
|
)
|
|
do_invite_users(
|
|
user_profile,
|
|
["TestTwo@zulip.com"],
|
|
streams,
|
|
invite_expires_in_minutes=100 * 24 * 60,
|
|
)
|
|
do_create_multiuse_invite_link(
|
|
user_profile, PreregistrationUser.INVITE_AS["MEMBER"], None
|
|
)
|
|
do_create_multiuse_invite_link(
|
|
user_profile, PreregistrationUser.INVITE_AS["MEMBER"], 100
|
|
)
|
|
|
|
result = self.client_get("/json/invites")
|
|
self.assertEqual(result.status_code, 200)
|
|
invites = orjson.loads(result.content)["invites"]
|
|
# We only get invitations that will never expire because we have mocked time such
|
|
# that the other invitations are created in the deep past.
|
|
self.assert_length(invites, 2)
|
|
|
|
self.assertFalse(invites[0]["is_multiuse"])
|
|
self.assertEqual(invites[0]["email"], "TestOne@zulip.com")
|
|
self.assertEqual(invites[0]["expiry_date"], None)
|
|
self.assertTrue(invites[1]["is_multiuse"])
|
|
self.assertEqual(invites[1]["invited_by_user_id"], user_profile.id)
|
|
self.assertEqual(invites[1]["expiry_date"], None)
|
|
|
|
def test_successful_delete_invitation(self) -> None:
|
|
"""
|
|
A DELETE call to /json/invites/<ID> should delete the invite and
|
|
any scheduled invitation reminder emails.
|
|
"""
|
|
self.login("iago")
|
|
|
|
invitee = "DeleteMe@zulip.com"
|
|
self.assert_json_success(self.invite(invitee, ["Denmark"]))
|
|
prereg_user = PreregistrationUser.objects.get(email=invitee)
|
|
|
|
# Verify that the scheduled email exists.
|
|
ScheduledEmail.objects.get(address__iexact=invitee, type=ScheduledEmail.INVITATION_REMINDER)
|
|
|
|
result = self.client_delete("/json/invites/" + str(prereg_user.id))
|
|
self.assertEqual(result.status_code, 200)
|
|
error_result = self.client_delete("/json/invites/" + str(prereg_user.id))
|
|
self.assert_json_error(error_result, "No such invitation")
|
|
|
|
self.assertRaises(
|
|
ScheduledEmail.DoesNotExist,
|
|
lambda: ScheduledEmail.objects.get(
|
|
address__iexact=invitee, type=ScheduledEmail.INVITATION_REMINDER
|
|
),
|
|
)
|
|
|
|
def test_successful_member_delete_invitation(self) -> None:
|
|
"""
|
|
A DELETE call from member account to /json/invites/<ID> should delete the invite and
|
|
any scheduled invitation reminder emails.
|
|
"""
|
|
user_profile = self.example_user("hamlet")
|
|
self.login_user(user_profile)
|
|
invitee = "DeleteMe@zulip.com"
|
|
self.assert_json_success(self.invite(invitee, ["Denmark"]))
|
|
|
|
# Verify that the scheduled email exists.
|
|
prereg_user = PreregistrationUser.objects.get(email=invitee, referred_by=user_profile)
|
|
ScheduledEmail.objects.get(address__iexact=invitee, type=ScheduledEmail.INVITATION_REMINDER)
|
|
|
|
# Verify another non-admin can't delete
|
|
result = self.api_delete(
|
|
self.example_user("othello"), "/api/v1/invites/" + str(prereg_user.id)
|
|
)
|
|
self.assert_json_error(result, "Must be an organization administrator")
|
|
|
|
# Verify that the scheduled email still exists.
|
|
prereg_user = PreregistrationUser.objects.get(email=invitee, referred_by=user_profile)
|
|
ScheduledEmail.objects.get(address__iexact=invitee, type=ScheduledEmail.INVITATION_REMINDER)
|
|
|
|
# Verify deletion works.
|
|
result = self.api_delete(user_profile, "/api/v1/invites/" + str(prereg_user.id))
|
|
self.assertEqual(result.status_code, 200)
|
|
|
|
result = self.api_delete(user_profile, "/api/v1/invites/" + str(prereg_user.id))
|
|
self.assert_json_error(result, "No such invitation")
|
|
|
|
self.assertRaises(
|
|
ScheduledEmail.DoesNotExist,
|
|
lambda: ScheduledEmail.objects.get(
|
|
address__iexact=invitee, type=ScheduledEmail.INVITATION_REMINDER
|
|
),
|
|
)
|
|
|
|
def test_delete_owner_invitation(self) -> None:
|
|
self.login("desdemona")
|
|
owner = self.example_user("desdemona")
|
|
|
|
invitee = "DeleteMe@zulip.com"
|
|
self.assert_json_success(
|
|
self.invite(
|
|
invitee, ["Denmark"], invite_as=PreregistrationUser.INVITE_AS["REALM_OWNER"]
|
|
)
|
|
)
|
|
prereg_user = PreregistrationUser.objects.get(email=invitee)
|
|
result = self.api_delete(
|
|
self.example_user("iago"), "/api/v1/invites/" + str(prereg_user.id)
|
|
)
|
|
self.assert_json_error(result, "Must be an organization owner")
|
|
|
|
result = self.api_delete(owner, "/api/v1/invites/" + str(prereg_user.id))
|
|
self.assert_json_success(result)
|
|
result = self.api_delete(owner, "/api/v1/invites/" + str(prereg_user.id))
|
|
self.assert_json_error(result, "No such invitation")
|
|
self.assertRaises(
|
|
ScheduledEmail.DoesNotExist,
|
|
lambda: ScheduledEmail.objects.get(
|
|
address__iexact=invitee, type=ScheduledEmail.INVITATION_REMINDER
|
|
),
|
|
)
|
|
|
|
def test_delete_multiuse_invite(self) -> None:
|
|
"""
|
|
A DELETE call to /json/invites/multiuse<ID> should delete the
|
|
multiuse_invite.
|
|
"""
|
|
self.login("iago")
|
|
|
|
zulip_realm = get_realm("zulip")
|
|
multiuse_invite = MultiuseInvite.objects.create(
|
|
referred_by=self.example_user("hamlet"), realm=zulip_realm
|
|
)
|
|
validity_in_minutes = 2 * 24 * 60
|
|
create_confirmation_link(
|
|
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)
|
|
self.assertEqual(
|
|
MultiuseInvite.objects.get(id=multiuse_invite.id).status,
|
|
confirmation_settings.STATUS_REVOKED,
|
|
)
|
|
# Test that trying to double-delete fails
|
|
error_result = self.client_delete("/json/invites/multiuse/" + str(multiuse_invite.id))
|
|
self.assert_json_error(error_result, "Invitation has already been revoked")
|
|
|
|
# Test deleting owner mutiuse_invite.
|
|
multiuse_invite = MultiuseInvite.objects.create(
|
|
referred_by=self.example_user("desdemona"),
|
|
realm=zulip_realm,
|
|
invited_as=PreregistrationUser.INVITE_AS["REALM_OWNER"],
|
|
)
|
|
validity_in_minutes = 2
|
|
create_confirmation_link(
|
|
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")
|
|
|
|
self.login("desdemona")
|
|
result = self.client_delete("/json/invites/multiuse/" + str(multiuse_invite.id))
|
|
self.assert_json_success(result)
|
|
self.assertEqual(
|
|
MultiuseInvite.objects.get(id=multiuse_invite.id).status,
|
|
confirmation_settings.STATUS_REVOKED,
|
|
)
|
|
|
|
# Test deleting multiuse invite from another realm
|
|
mit_realm = get_realm("zephyr")
|
|
multiuse_invite_in_mit = MultiuseInvite.objects.create(
|
|
referred_by=self.mit_user("sipbtest"), realm=mit_realm
|
|
)
|
|
validity_in_minutes = 2 * 24 * 60
|
|
create_confirmation_link(
|
|
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)
|
|
)
|
|
self.assert_json_error(error_result, "No such invitation")
|
|
|
|
non_existent_id = MultiuseInvite.objects.count() + 9999
|
|
error_result = self.client_delete(f"/json/invites/multiuse/{non_existent_id}")
|
|
self.assert_json_error(error_result, "No such invitation")
|
|
|
|
def test_successful_resend_invitation(self) -> None:
|
|
"""
|
|
A POST call to /json/invites/<ID>/resend should send an invitation reminder email
|
|
and delete any scheduled invitation reminder email.
|
|
"""
|
|
self.login("iago")
|
|
invitee = "resend_me@zulip.com"
|
|
|
|
self.assert_json_success(self.invite(invitee, ["Denmark"]))
|
|
prereg_user = PreregistrationUser.objects.get(email=invitee)
|
|
|
|
# Verify and then clear from the outbox the original invite email
|
|
self.check_sent_emails([invitee])
|
|
mail.outbox.pop()
|
|
|
|
# Verify that the scheduled email exists.
|
|
scheduledemail_filter = ScheduledEmail.objects.filter(
|
|
address__iexact=invitee, type=ScheduledEmail.INVITATION_REMINDER
|
|
)
|
|
self.assertEqual(scheduledemail_filter.count(), 1)
|
|
original_timestamp = scheduledemail_filter.values_list("scheduled_timestamp", flat=True)
|
|
|
|
# Resend invite
|
|
result = self.client_post("/json/invites/" + str(prereg_user.id) + "/resend")
|
|
self.assertEqual(
|
|
ScheduledEmail.objects.filter(
|
|
address__iexact=invitee, type=ScheduledEmail.INVITATION_REMINDER
|
|
).count(),
|
|
1,
|
|
)
|
|
|
|
# Check that we have exactly one scheduled email, and that it is different
|
|
self.assertEqual(scheduledemail_filter.count(), 1)
|
|
self.assertNotEqual(
|
|
original_timestamp, scheduledemail_filter.values_list("scheduled_timestamp", flat=True)
|
|
)
|
|
|
|
self.assertEqual(result.status_code, 200)
|
|
error_result = self.client_post("/json/invites/" + str(9999) + "/resend")
|
|
self.assert_json_error(error_result, "No such invitation")
|
|
|
|
self.check_sent_emails([invitee])
|
|
|
|
def test_successful_member_resend_invitation(self) -> None:
|
|
"""A POST call from member a account to /json/invites/<ID>/resend
|
|
should send an invitation reminder email and delete any
|
|
scheduled invitation reminder email if they send the invite.
|
|
"""
|
|
self.login("hamlet")
|
|
user_profile = self.example_user("hamlet")
|
|
invitee = "resend_me@zulip.com"
|
|
self.assert_json_success(self.invite(invitee, ["Denmark"]))
|
|
# Verify hamlet has only one invitation (Member can resend invitations only sent by him).
|
|
invitation = PreregistrationUser.objects.filter(referred_by=user_profile)
|
|
self.assert_length(invitation, 1)
|
|
prereg_user = PreregistrationUser.objects.get(email=invitee)
|
|
|
|
# Verify and then clear from the outbox the original invite email
|
|
self.check_sent_emails([invitee])
|
|
mail.outbox.pop()
|
|
|
|
# Verify that the scheduled email exists.
|
|
scheduledemail_filter = ScheduledEmail.objects.filter(
|
|
address__iexact=invitee, type=ScheduledEmail.INVITATION_REMINDER
|
|
)
|
|
self.assertEqual(scheduledemail_filter.count(), 1)
|
|
original_timestamp = scheduledemail_filter.values_list("scheduled_timestamp", flat=True)
|
|
|
|
# Resend invite
|
|
result = self.client_post("/json/invites/" + str(prereg_user.id) + "/resend")
|
|
self.assertEqual(
|
|
ScheduledEmail.objects.filter(
|
|
address__iexact=invitee, type=ScheduledEmail.INVITATION_REMINDER
|
|
).count(),
|
|
1,
|
|
)
|
|
|
|
# Check that we have exactly one scheduled email, and that it is different
|
|
self.assertEqual(scheduledemail_filter.count(), 1)
|
|
self.assertNotEqual(
|
|
original_timestamp, scheduledemail_filter.values_list("scheduled_timestamp", flat=True)
|
|
)
|
|
|
|
self.assertEqual(result.status_code, 200)
|
|
error_result = self.client_post("/json/invites/" + str(9999) + "/resend")
|
|
self.assert_json_error(error_result, "No such invitation")
|
|
|
|
self.check_sent_emails([invitee])
|
|
|
|
self.logout()
|
|
self.login("othello")
|
|
invitee = "TestOne@zulip.com"
|
|
prereg_user_one = PreregistrationUser(email=invitee, referred_by=user_profile)
|
|
prereg_user_one.save()
|
|
prereg_user = PreregistrationUser.objects.get(email=invitee)
|
|
error_result = self.client_post("/json/invites/" + str(prereg_user.id) + "/resend")
|
|
self.assert_json_error(error_result, "Must be an organization administrator")
|
|
|
|
def test_resend_owner_invitation(self) -> None:
|
|
self.login("desdemona")
|
|
|
|
invitee = "resend_owner@zulip.com"
|
|
self.assert_json_success(
|
|
self.invite(
|
|
invitee, ["Denmark"], invite_as=PreregistrationUser.INVITE_AS["REALM_OWNER"]
|
|
)
|
|
)
|
|
self.check_sent_emails([invitee])
|
|
scheduledemail_filter = ScheduledEmail.objects.filter(
|
|
address__iexact=invitee, type=ScheduledEmail.INVITATION_REMINDER
|
|
)
|
|
self.assertEqual(scheduledemail_filter.count(), 1)
|
|
original_timestamp = scheduledemail_filter.values_list("scheduled_timestamp", flat=True)
|
|
|
|
# Test only organization owners can resend owner invitation.
|
|
self.login("iago")
|
|
prereg_user = PreregistrationUser.objects.get(email=invitee)
|
|
error_result = self.client_post("/json/invites/" + str(prereg_user.id) + "/resend")
|
|
self.assert_json_error(error_result, "Must be an organization owner")
|
|
|
|
self.login("desdemona")
|
|
result = self.client_post("/json/invites/" + str(prereg_user.id) + "/resend")
|
|
self.assert_json_success(result)
|
|
|
|
self.assertEqual(
|
|
ScheduledEmail.objects.filter(
|
|
address__iexact=invitee, type=ScheduledEmail.INVITATION_REMINDER
|
|
).count(),
|
|
1,
|
|
)
|
|
|
|
# Check that we have exactly one scheduled email, and that it is different
|
|
self.assertEqual(scheduledemail_filter.count(), 1)
|
|
self.assertNotEqual(
|
|
original_timestamp, scheduledemail_filter.values_list("scheduled_timestamp", flat=True)
|
|
)
|
|
|
|
def test_resend_never_expiring_invitation(self) -> None:
|
|
self.login("iago")
|
|
invitee = "resend@zulip.com"
|
|
|
|
self.assert_json_success(self.invite(invitee, ["Denmark"], None))
|
|
prereg_user = PreregistrationUser.objects.get(email=invitee)
|
|
|
|
# Verify and then clear from the outbox the original invite email
|
|
self.check_sent_emails([invitee])
|
|
mail.outbox.pop()
|
|
|
|
result = self.client_post("/json/invites/" + str(prereg_user.id) + "/resend")
|
|
self.assert_json_success(result)
|
|
self.check_sent_emails([invitee])
|
|
|
|
def test_accessing_invites_in_another_realm(self) -> None:
|
|
inviter = UserProfile.objects.exclude(realm=get_realm("zulip")).first()
|
|
assert inviter is not None
|
|
prereg_user = PreregistrationUser.objects.create(
|
|
email="email", referred_by=inviter, realm=inviter.realm
|
|
)
|
|
self.login("iago")
|
|
error_result = self.client_post("/json/invites/" + str(prereg_user.id) + "/resend")
|
|
self.assert_json_error(error_result, "No such invitation")
|
|
error_result = self.client_delete("/json/invites/" + str(prereg_user.id))
|
|
self.assert_json_error(error_result, "No such invitation")
|
|
|
|
def test_prereg_user_status(self) -> None:
|
|
email = self.nonreg_email("alice")
|
|
password = "password"
|
|
realm = get_realm("zulip")
|
|
|
|
inviter = UserProfile.objects.filter(realm=realm).first()
|
|
prereg_user = PreregistrationUser.objects.create(
|
|
email=email, referred_by=inviter, realm=realm
|
|
)
|
|
|
|
confirmation_link = create_confirmation_link(prereg_user, Confirmation.USER_REGISTRATION)
|
|
registration_key = confirmation_link.split("/")[-1]
|
|
|
|
result = self.client_post(
|
|
"/accounts/register/",
|
|
{"key": registration_key, "from_confirmation": "1", "full_name": "alice"},
|
|
)
|
|
self.assertEqual(result.status_code, 200)
|
|
confirmation = Confirmation.objects.get(confirmation_key=registration_key)
|
|
assert confirmation.content_object is not None
|
|
prereg_user = confirmation.content_object
|
|
self.assertEqual(prereg_user.status, 0)
|
|
|
|
result = self.submit_reg_form_for_user(email, password, key=registration_key)
|
|
self.assertEqual(result.status_code, 302)
|
|
prereg_user = PreregistrationUser.objects.get(email=email, referred_by=inviter, realm=realm)
|
|
self.assertEqual(prereg_user.status, confirmation_settings.STATUS_USED)
|
|
user = get_user_by_delivery_email(email, realm)
|
|
self.assertIsNotNone(user)
|
|
self.assertEqual(user.delivery_email, email)
|
|
|
|
|
|
class InviteeEmailsParserTests(ZulipTestCase):
|
|
def setUp(self) -> None:
|
|
super().setUp()
|
|
self.email1 = "email1@zulip.com"
|
|
self.email2 = "email2@zulip.com"
|
|
self.email3 = "email3@zulip.com"
|
|
|
|
def test_if_emails_separated_by_commas_are_parsed_and_striped_correctly(self) -> None:
|
|
emails_raw = f"{self.email1} ,{self.email2}, {self.email3}"
|
|
expected_set = {self.email1, self.email2, self.email3}
|
|
self.assertEqual(get_invitee_emails_set(emails_raw), expected_set)
|
|
|
|
def test_if_emails_separated_by_newlines_are_parsed_and_striped_correctly(self) -> None:
|
|
emails_raw = f"{self.email1}\n {self.email2}\n {self.email3} "
|
|
expected_set = {self.email1, self.email2, self.email3}
|
|
self.assertEqual(get_invitee_emails_set(emails_raw), expected_set)
|
|
|
|
def test_if_emails_from_email_client_separated_by_newlines_are_parsed_correctly(self) -> None:
|
|
emails_raw = (
|
|
f"Email One <{self.email1}>\nEmailTwo<{self.email2}>\nEmail Three<{self.email3}>"
|
|
)
|
|
expected_set = {self.email1, self.email2, self.email3}
|
|
self.assertEqual(get_invitee_emails_set(emails_raw), expected_set)
|
|
|
|
def test_if_emails_in_mixed_style_are_parsed_correctly(self) -> None:
|
|
emails_raw = f"Email One <{self.email1}>,EmailTwo<{self.email2}>\n{self.email3}"
|
|
expected_set = {self.email1, self.email2, self.email3}
|
|
self.assertEqual(get_invitee_emails_set(emails_raw), expected_set)
|
|
|
|
|
|
class MultiuseInviteTest(ZulipTestCase):
|
|
def setUp(self) -> None:
|
|
super().setUp()
|
|
self.realm = get_realm("zulip")
|
|
self.realm.invite_required = True
|
|
self.realm.save()
|
|
|
|
def generate_multiuse_invite_link(
|
|
self, streams: Optional[List[Stream]] = None, date_sent: Optional[datetime.datetime] = None
|
|
) -> str:
|
|
invite = MultiuseInvite(realm=self.realm, referred_by=self.example_user("iago"))
|
|
invite.save()
|
|
|
|
if streams is not None:
|
|
invite.streams.set(streams)
|
|
|
|
if date_sent is None:
|
|
date_sent = timezone_now()
|
|
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_minutes=validity_in_minutes
|
|
)
|
|
|
|
def check_user_able_to_register(self, email: str, invite_link: str) -> None:
|
|
password = "password"
|
|
|
|
result = self.client_post(invite_link, {"email": email})
|
|
self.assertEqual(result.status_code, 302)
|
|
self.assertTrue(
|
|
result["Location"].endswith(
|
|
f"/accounts/send_confirm/?email={urllib.parse.quote(email)}"
|
|
)
|
|
)
|
|
result = self.client_get(result["Location"])
|
|
self.assert_in_response("Check your email", result)
|
|
|
|
confirmation_url = self.get_confirmation_url_from_outbox(email)
|
|
result = self.client_get(confirmation_url)
|
|
self.assertEqual(result.status_code, 200)
|
|
|
|
result = self.submit_reg_form_for_user(email, password)
|
|
self.assertEqual(result.status_code, 302)
|
|
|
|
# Verify the PreregistrationUser object was set up as expected.
|
|
prereg_user = PreregistrationUser.objects.last()
|
|
multiuse_invite = MultiuseInvite.objects.last()
|
|
|
|
assert prereg_user is not None
|
|
self.assertEqual(prereg_user.email, email)
|
|
self.assertEqual(prereg_user.multiuse_invite, multiuse_invite)
|
|
|
|
mail.outbox.pop()
|
|
|
|
def test_valid_multiuse_link(self) -> None:
|
|
email1 = self.nonreg_email("test")
|
|
email2 = self.nonreg_email("test1")
|
|
email3 = self.nonreg_email("alice")
|
|
|
|
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)
|
|
self.check_user_able_to_register(email2, invite_link)
|
|
self.check_user_able_to_register(email3, invite_link)
|
|
|
|
def test_expired_multiuse_link(self) -> None:
|
|
email = self.nonreg_email("newuser")
|
|
date_sent = timezone_now() - datetime.timedelta(
|
|
days=settings.INVITATION_LINK_VALIDITY_DAYS + 1
|
|
)
|
|
invite_link = self.generate_multiuse_invite_link(date_sent=date_sent)
|
|
result = self.client_post(invite_link, {"email": email})
|
|
|
|
self.assertEqual(result.status_code, 404)
|
|
self.assert_in_response("The confirmation link has expired or been deactivated.", result)
|
|
|
|
def test_revoked_multiuse_link(self) -> None:
|
|
email = self.nonreg_email("newuser")
|
|
invite_link = self.generate_multiuse_invite_link()
|
|
multiuse_invite = MultiuseInvite.objects.last()
|
|
assert multiuse_invite is not None
|
|
do_revoke_multi_use_invite(multiuse_invite)
|
|
|
|
result = self.client_post(invite_link, {"email": email})
|
|
|
|
self.assertEqual(result.status_code, 404)
|
|
self.assert_in_response("We couldn't find your confirmation link in the system.", result)
|
|
|
|
def test_invalid_multiuse_link(self) -> None:
|
|
email = self.nonreg_email("newuser")
|
|
invite_link = "/join/invalid_key/"
|
|
result = self.client_post(invite_link, {"email": email})
|
|
|
|
self.assertEqual(result.status_code, 404)
|
|
self.assert_in_response("Whoops. The confirmation link is malformed.", result)
|
|
|
|
def test_invalid_multiuse_link_in_open_realm(self) -> None:
|
|
self.realm.invite_required = False
|
|
self.realm.save()
|
|
|
|
email = self.nonreg_email("newuser")
|
|
invite_link = "/join/invalid_key/"
|
|
|
|
with patch("zerver.views.registration.get_realm_from_request", return_value=self.realm):
|
|
with patch("zerver.views.registration.get_realm", return_value=self.realm):
|
|
self.check_user_able_to_register(email, invite_link)
|
|
|
|
def test_multiuse_link_with_specified_streams(self) -> None:
|
|
name1 = "newuser"
|
|
name2 = "bob"
|
|
email1 = self.nonreg_email(name1)
|
|
email2 = self.nonreg_email(name2)
|
|
|
|
stream_names = ["Rome", "Scotland", "Venice"]
|
|
streams = [get_stream(stream_name, self.realm) for stream_name in stream_names]
|
|
invite_link = self.generate_multiuse_invite_link(streams=streams)
|
|
self.check_user_able_to_register(email1, invite_link)
|
|
self.check_user_subscribed_only_to_streams(name1, streams)
|
|
|
|
stream_names = ["Rome", "Verona"]
|
|
streams = [get_stream(stream_name, self.realm) for stream_name in stream_names]
|
|
invite_link = self.generate_multiuse_invite_link(streams=streams)
|
|
self.check_user_able_to_register(email2, invite_link)
|
|
self.check_user_subscribed_only_to_streams(name2, streams)
|
|
|
|
def test_multiuse_link_different_realms(self) -> None:
|
|
"""
|
|
Verify that an invitation generated for one realm can't be used
|
|
to join another.
|
|
"""
|
|
lear_realm = get_realm("lear")
|
|
self.realm = lear_realm
|
|
invite_link = self.generate_multiuse_invite_link(streams=[])
|
|
key = invite_link.split("/")[-2]
|
|
|
|
result = self.client_get(f"/join/{key}/", subdomain="zulip")
|
|
self.assertEqual(result.status_code, 404)
|
|
self.assert_in_response(
|
|
"Whoops. We couldn't find your confirmation link in the system.", result
|
|
)
|
|
|
|
# Now we want to test the accounts_home function, which can't be used
|
|
# for the multiuse invite case via an HTTP request, but is still supposed
|
|
# to do its own verification that the realms match as a hardening measure
|
|
# against a caller that fails to do that.
|
|
request = HttpRequest()
|
|
confirmation = Confirmation.objects.get(confirmation_key=key)
|
|
multiuse_object = confirmation.content_object
|
|
with patch(
|
|
"zerver.views.registration.get_subdomain", return_value="zulip"
|
|
), self.assertRaises(AssertionError):
|
|
accounts_home(request, multiuse_object=multiuse_object)
|
|
|
|
def test_create_multiuse_link_api_call(self) -> None:
|
|
self.login("iago")
|
|
|
|
result = self.client_post(
|
|
"/json/invites/multiuse", {"invite_expires_in_minutes": 2 * 24 * 60}
|
|
)
|
|
invite_link = self.assert_json_success(result)["invite_link"]
|
|
self.check_user_able_to_register(self.nonreg_email("test"), invite_link)
|
|
|
|
def test_create_multiuse_link_with_specified_streams_api_call(self) -> None:
|
|
self.login("iago")
|
|
stream_names = ["Rome", "Scotland", "Venice"]
|
|
streams = [get_stream(stream_name, self.realm) for stream_name in stream_names]
|
|
stream_ids = [stream.id for stream in streams]
|
|
|
|
result = self.client_post(
|
|
"/json/invites/multiuse",
|
|
{
|
|
"stream_ids": orjson.dumps(stream_ids).decode(),
|
|
"invite_expires_in_minutes": 2 * 24 * 60,
|
|
},
|
|
)
|
|
invite_link = self.assert_json_success(result)["invite_link"]
|
|
self.check_user_able_to_register(self.nonreg_email("test"), invite_link)
|
|
self.check_user_subscribed_only_to_streams("test", streams)
|
|
|
|
def test_only_admin_can_create_multiuse_link_api_call(self) -> None:
|
|
self.login("iago")
|
|
# Only admins should be able to create multiuse invites even if
|
|
# invite_to_realm_policy is set to Realm.POLICY_MEMBERS_ONLY.
|
|
self.realm.invite_to_realm_policy = Realm.POLICY_MEMBERS_ONLY
|
|
self.realm.save()
|
|
|
|
result = self.client_post(
|
|
"/json/invites/multiuse", {"invite_expires_in_minutes": 2 * 24 * 60}
|
|
)
|
|
invite_link = self.assert_json_success(result)["invite_link"]
|
|
self.check_user_able_to_register(self.nonreg_email("test"), invite_link)
|
|
|
|
self.login("hamlet")
|
|
result = self.client_post("/json/invites/multiuse")
|
|
self.assert_json_error(result, "Must be an organization administrator")
|
|
|
|
def test_multiuse_link_for_inviting_as_owner(self) -> None:
|
|
self.login("iago")
|
|
result = self.client_post(
|
|
"/json/invites/multiuse",
|
|
{
|
|
"invite_as": orjson.dumps(PreregistrationUser.INVITE_AS["REALM_OWNER"]).decode(),
|
|
"invite_expires_in_minutes": 2 * 24 * 60,
|
|
},
|
|
)
|
|
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_expires_in_minutes": 2 * 24 * 60,
|
|
},
|
|
)
|
|
invite_link = self.assert_json_success(result)["invite_link"]
|
|
self.check_user_able_to_register(self.nonreg_email("test"), invite_link)
|
|
|
|
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(),
|
|
"invite_expires_in_minutes": 2 * 24 * 60,
|
|
},
|
|
)
|
|
self.assert_json_error(result, "Invalid stream ID 54321. No invites were sent.")
|
|
|
|
def test_create_multiuse_link_invalid_invite_as_api_call(self) -> None:
|
|
self.login("iago")
|
|
result = self.client_post(
|
|
"/json/invites/multiuse",
|
|
{
|
|
"invite_as": orjson.dumps(PreregistrationUser.INVITE_AS["GUEST_USER"] + 1).decode(),
|
|
"invite_expires_in_minutes": 2 * 24 * 60,
|
|
},
|
|
)
|
|
self.assert_json_error(result, "Invalid invite_as")
|