2020-06-11 00:54:34 +02:00
|
|
|
import datetime
|
2020-06-05 23:26:35 +02:00
|
|
|
from email.headerregistry import Address
|
2020-06-11 00:54:34 +02:00
|
|
|
from typing import Any, Dict, Iterable, List, Mapping, Optional, TypeVar, Union
|
|
|
|
from unittest import mock
|
2019-06-29 04:41:13 +02:00
|
|
|
|
2020-08-07 01:09:47 +02:00
|
|
|
import orjson
|
2020-06-11 00:54:34 +02:00
|
|
|
from django.conf import settings
|
2020-06-21 02:36:20 +02:00
|
|
|
from django.core.exceptions import ValidationError
|
2020-06-11 00:54:34 +02:00
|
|
|
from django.test import override_settings
|
2014-02-07 22:47:30 +01:00
|
|
|
|
2017-03-21 18:08:40 +01:00
|
|
|
from zerver.lib.actions import (
|
2020-03-06 17:58:06 +01:00
|
|
|
create_users,
|
2020-05-21 00:13:06 +02:00
|
|
|
do_change_user_role,
|
2017-10-24 19:25:50 +02:00
|
|
|
do_create_user,
|
2020-06-11 00:54:34 +02:00
|
|
|
do_deactivate_user,
|
|
|
|
do_reactivate_user,
|
2019-02-05 07:12:37 +01:00
|
|
|
do_set_realm_property,
|
2020-06-11 00:54:34 +02:00
|
|
|
get_emails_from_user_ids,
|
|
|
|
get_recipient_info,
|
2017-03-21 18:08:40 +01:00
|
|
|
)
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.lib.avatar import avatar_url, get_gravatar_url
|
2018-05-21 19:30:26 +02:00
|
|
|
from zerver.lib.create_user import copy_user_settings
|
2019-02-05 07:12:37 +01:00
|
|
|
from zerver.lib.events import do_events_register
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.lib.exceptions import JsonableError
|
|
|
|
from zerver.lib.send_email import clear_scheduled_emails, deliver_email, send_future_email
|
2017-10-24 00:07:03 +02:00
|
|
|
from zerver.lib.stream_topic import StreamTopicTarget
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.lib.test_classes import ZulipTestCase
|
|
|
|
from zerver.lib.test_helpers import (
|
|
|
|
get_subscription,
|
2020-06-26 19:35:42 +02:00
|
|
|
get_test_image_file,
|
2020-06-11 00:54:34 +02:00
|
|
|
queries_captured,
|
|
|
|
reset_emails_in_zulip_realm,
|
|
|
|
simulated_empty_cache,
|
|
|
|
tornado_redirected_to_list,
|
|
|
|
)
|
|
|
|
from zerver.lib.topic_mutes import add_topic_mute
|
2020-06-26 19:35:42 +02:00
|
|
|
from zerver.lib.upload import upload_avatar_image
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.lib.users import access_user_by_id, get_accounts_for_email, user_ids_to_users
|
|
|
|
from zerver.models import (
|
|
|
|
CustomProfileField,
|
|
|
|
InvalidFakeEmailDomain,
|
|
|
|
Realm,
|
|
|
|
RealmDomain,
|
|
|
|
Recipient,
|
|
|
|
ScheduledEmail,
|
|
|
|
UserHotspot,
|
|
|
|
UserProfile,
|
|
|
|
check_valid_user_ids,
|
|
|
|
get_client,
|
|
|
|
get_fake_email_domain,
|
|
|
|
get_realm,
|
|
|
|
get_source_profile,
|
|
|
|
get_stream,
|
|
|
|
get_system_bot,
|
|
|
|
get_user,
|
|
|
|
get_user_by_delivery_email,
|
|
|
|
get_user_by_id_in_realm_including_cross_realm,
|
|
|
|
)
|
2013-03-14 22:12:25 +01:00
|
|
|
|
2016-06-15 23:47:22 +02:00
|
|
|
K = TypeVar('K')
|
|
|
|
V = TypeVar('V')
|
2017-11-05 10:51:25 +01:00
|
|
|
def find_dict(lst: Iterable[Dict[K, V]], k: K, v: V) -> Dict[K, V]:
|
2014-01-14 20:38:45 +01:00
|
|
|
for dct in lst:
|
|
|
|
if dct[k] == v:
|
|
|
|
return dct
|
2020-06-10 06:41:04 +02:00
|
|
|
raise AssertionError(f'Cannot find element in list where key {k} == {v}')
|
2014-01-14 20:38:45 +01:00
|
|
|
|
2016-08-23 02:08:42 +02:00
|
|
|
class PermissionTest(ZulipTestCase):
|
2019-12-08 20:08:25 +01:00
|
|
|
def test_role_setters(self) -> None:
|
|
|
|
user_profile = self.example_user('hamlet')
|
|
|
|
|
|
|
|
user_profile.is_realm_admin = True
|
|
|
|
self.assertEqual(user_profile.is_realm_admin, True)
|
|
|
|
self.assertEqual(user_profile.role, UserProfile.ROLE_REALM_ADMINISTRATOR)
|
|
|
|
|
|
|
|
user_profile.is_guest = False
|
|
|
|
self.assertEqual(user_profile.is_guest, False)
|
|
|
|
self.assertEqual(user_profile.role, UserProfile.ROLE_REALM_ADMINISTRATOR)
|
|
|
|
|
|
|
|
user_profile.is_realm_admin = False
|
|
|
|
self.assertEqual(user_profile.is_realm_admin, False)
|
|
|
|
self.assertEqual(user_profile.role, UserProfile.ROLE_MEMBER)
|
|
|
|
|
|
|
|
user_profile.is_guest = True
|
|
|
|
self.assertEqual(user_profile.is_guest, True)
|
|
|
|
self.assertEqual(user_profile.role, UserProfile.ROLE_GUEST)
|
|
|
|
|
|
|
|
user_profile.is_realm_admin = False
|
|
|
|
self.assertEqual(user_profile.is_guest, True)
|
|
|
|
self.assertEqual(user_profile.role, UserProfile.ROLE_GUEST)
|
|
|
|
|
|
|
|
user_profile.is_guest = False
|
|
|
|
self.assertEqual(user_profile.is_guest, False)
|
|
|
|
self.assertEqual(user_profile.role, UserProfile.ROLE_MEMBER)
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_get_admin_users(self) -> None:
|
2017-05-07 17:21:26 +02:00
|
|
|
user_profile = self.example_user('hamlet')
|
2020-05-21 00:13:06 +02:00
|
|
|
do_change_user_role(user_profile, UserProfile.ROLE_MEMBER)
|
2020-05-16 20:10:42 +02:00
|
|
|
self.assertFalse(user_profile.is_realm_owner)
|
2019-06-20 23:26:54 +02:00
|
|
|
admin_users = user_profile.realm.get_human_admin_users()
|
|
|
|
self.assertFalse(user_profile in admin_users)
|
2019-06-20 23:36:15 +02:00
|
|
|
admin_users = user_profile.realm.get_admin_users_and_bots()
|
2013-11-02 15:36:17 +01:00
|
|
|
self.assertFalse(user_profile in admin_users)
|
2019-06-20 23:26:54 +02:00
|
|
|
|
2020-05-21 00:13:06 +02:00
|
|
|
do_change_user_role(user_profile, UserProfile.ROLE_REALM_ADMINISTRATOR)
|
2020-05-16 20:10:42 +02:00
|
|
|
self.assertFalse(user_profile.is_realm_owner)
|
|
|
|
admin_users = user_profile.realm.get_human_admin_users()
|
|
|
|
self.assertTrue(user_profile in admin_users)
|
|
|
|
admin_users = user_profile.realm.get_admin_users_and_bots()
|
|
|
|
self.assertTrue(user_profile in admin_users)
|
|
|
|
|
|
|
|
do_change_user_role(user_profile, UserProfile.ROLE_REALM_OWNER)
|
|
|
|
self.assertTrue(user_profile.is_realm_owner)
|
2019-06-20 23:26:54 +02:00
|
|
|
admin_users = user_profile.realm.get_human_admin_users()
|
|
|
|
self.assertTrue(user_profile in admin_users)
|
2019-06-20 23:36:15 +02:00
|
|
|
admin_users = user_profile.realm.get_admin_users_and_bots()
|
2013-11-02 15:36:17 +01:00
|
|
|
self.assertTrue(user_profile in admin_users)
|
2013-09-30 22:52:04 +02:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_updating_non_existent_user(self) -> None:
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login('hamlet')
|
2017-05-07 17:21:26 +02:00
|
|
|
admin = self.example_user('hamlet')
|
2020-05-21 00:13:06 +02:00
|
|
|
do_change_user_role(admin, UserProfile.ROLE_REALM_ADMINISTRATOR)
|
2016-07-13 05:05:55 +02:00
|
|
|
|
2018-05-17 19:36:33 +02:00
|
|
|
invalid_user_id = 1000
|
2020-06-09 00:25:09 +02:00
|
|
|
result = self.client_patch(f'/json/users/{invalid_user_id}', {})
|
2016-07-13 05:05:55 +02:00
|
|
|
self.assert_json_error(result, 'No such user')
|
|
|
|
|
2020-05-16 21:06:43 +02:00
|
|
|
def test_owner_api(self) -> None:
|
|
|
|
self.login('iago')
|
|
|
|
|
|
|
|
desdemona = self.example_user('desdemona')
|
|
|
|
othello = self.example_user('othello')
|
|
|
|
iago = self.example_user('iago')
|
|
|
|
realm = iago.realm
|
|
|
|
|
|
|
|
do_change_user_role(iago, UserProfile.ROLE_REALM_OWNER)
|
|
|
|
|
|
|
|
result = self.client_get('/json/users')
|
|
|
|
self.assert_json_success(result)
|
|
|
|
members = result.json()['members']
|
|
|
|
iago_dict = find_dict(members, 'email', iago.email)
|
|
|
|
self.assertTrue(iago_dict['is_owner'])
|
|
|
|
othello_dict = find_dict(members, 'email', othello.email)
|
|
|
|
self.assertFalse(othello_dict['is_owner'])
|
|
|
|
|
|
|
|
req = dict(role=UserProfile.ROLE_REALM_OWNER)
|
|
|
|
events: List[Mapping[str, Any]] = []
|
|
|
|
with tornado_redirected_to_list(events):
|
|
|
|
result = self.client_patch(f'/json/users/{othello.id}', req)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
owner_users = realm.get_human_owner_users()
|
|
|
|
self.assertTrue(othello in owner_users)
|
|
|
|
person = events[0]['event']['person']
|
|
|
|
self.assertEqual(person['user_id'], othello.id)
|
|
|
|
self.assertEqual(person['role'], UserProfile.ROLE_REALM_OWNER)
|
|
|
|
|
|
|
|
req = dict(role=UserProfile.ROLE_MEMBER)
|
|
|
|
events = []
|
|
|
|
with tornado_redirected_to_list(events):
|
|
|
|
result = self.client_patch(f'/json/users/{othello.id}', req)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
owner_users = realm.get_human_owner_users()
|
|
|
|
self.assertFalse(othello in owner_users)
|
|
|
|
person = events[0]['event']['person']
|
|
|
|
self.assertEqual(person['user_id'], othello.id)
|
|
|
|
self.assertEqual(person['role'], UserProfile.ROLE_MEMBER)
|
|
|
|
|
|
|
|
# Cannot take away from last owner
|
|
|
|
self.login('desdemona')
|
|
|
|
req = dict(role=UserProfile.ROLE_MEMBER)
|
|
|
|
events = []
|
|
|
|
with tornado_redirected_to_list(events):
|
|
|
|
result = self.client_patch(f'/json/users/{iago.id}', req)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
owner_users = realm.get_human_owner_users()
|
|
|
|
self.assertFalse(iago in owner_users)
|
|
|
|
person = events[0]['event']['person']
|
|
|
|
self.assertEqual(person['user_id'], iago.id)
|
|
|
|
self.assertEqual(person['role'], UserProfile.ROLE_MEMBER)
|
|
|
|
with tornado_redirected_to_list([]):
|
|
|
|
result = self.client_patch(f'/json/users/{desdemona.id}', req)
|
|
|
|
self.assert_json_error(result, 'The owner permission cannot be removed from the only organization owner.')
|
|
|
|
|
|
|
|
do_change_user_role(iago, UserProfile.ROLE_REALM_ADMINISTRATOR)
|
|
|
|
self.login('iago')
|
|
|
|
with tornado_redirected_to_list([]):
|
|
|
|
result = self.client_patch(f'/json/users/{desdemona.id}', req)
|
2020-06-15 06:32:10 +02:00
|
|
|
self.assert_json_error(result, 'Must be an organization owner')
|
2020-05-16 21:06:43 +02:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_admin_api(self) -> None:
|
2020-05-17 18:46:14 +02:00
|
|
|
self.login('desdemona')
|
2020-03-12 14:17:25 +01:00
|
|
|
|
|
|
|
hamlet = self.example_user('hamlet')
|
|
|
|
othello = self.example_user('othello')
|
2020-05-17 18:46:14 +02:00
|
|
|
desdemona = self.example_user('desdemona')
|
2020-03-12 14:17:25 +01:00
|
|
|
realm = hamlet.realm
|
|
|
|
|
2014-01-14 20:38:45 +01:00
|
|
|
# Make sure we see is_admin flag in /json/users
|
2016-07-28 00:38:45 +02:00
|
|
|
result = self.client_get('/json/users')
|
2014-01-14 20:38:45 +01:00
|
|
|
self.assert_json_success(result)
|
2017-08-17 08:46:17 +02:00
|
|
|
members = result.json()['members']
|
2020-05-17 18:46:14 +02:00
|
|
|
desdemona_dict = find_dict(members, 'email', desdemona.email)
|
|
|
|
self.assertTrue(desdemona_dict['is_admin'])
|
2020-03-12 14:17:25 +01:00
|
|
|
othello_dict = find_dict(members, 'email', othello.email)
|
|
|
|
self.assertFalse(othello_dict['is_admin'])
|
2014-01-14 16:19:26 +01:00
|
|
|
|
|
|
|
# Giveth
|
2020-08-07 01:09:47 +02:00
|
|
|
req = dict(role=orjson.dumps(UserProfile.ROLE_REALM_ADMINISTRATOR).decode())
|
2014-01-21 19:27:22 +01:00
|
|
|
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
events: List[Mapping[str, Any]] = []
|
2014-01-21 19:27:22 +01:00
|
|
|
with tornado_redirected_to_list(events):
|
2020-06-09 00:25:09 +02:00
|
|
|
result = self.client_patch(f'/json/users/{othello.id}', req)
|
2014-01-14 16:19:26 +01:00
|
|
|
self.assert_json_success(result)
|
2019-06-20 23:26:54 +02:00
|
|
|
admin_users = realm.get_human_admin_users()
|
2020-03-12 14:17:25 +01:00
|
|
|
self.assertTrue(othello in admin_users)
|
2014-01-21 19:27:22 +01:00
|
|
|
person = events[0]['event']['person']
|
2020-05-12 20:28:53 +02:00
|
|
|
self.assertEqual(person['user_id'], othello.id)
|
2020-05-30 21:43:19 +02:00
|
|
|
self.assertEqual(person['role'], UserProfile.ROLE_REALM_ADMINISTRATOR)
|
2014-01-14 16:19:26 +01:00
|
|
|
|
|
|
|
# Taketh away
|
2020-08-07 01:09:47 +02:00
|
|
|
req = dict(role=orjson.dumps(UserProfile.ROLE_MEMBER).decode())
|
2014-01-21 19:27:22 +01:00
|
|
|
events = []
|
|
|
|
with tornado_redirected_to_list(events):
|
2020-06-09 00:25:09 +02:00
|
|
|
result = self.client_patch(f'/json/users/{othello.id}', req)
|
2014-01-14 16:19:26 +01:00
|
|
|
self.assert_json_success(result)
|
2019-06-20 23:26:54 +02:00
|
|
|
admin_users = realm.get_human_admin_users()
|
2020-03-12 14:17:25 +01:00
|
|
|
self.assertFalse(othello in admin_users)
|
2014-01-21 19:27:22 +01:00
|
|
|
person = events[0]['event']['person']
|
2020-05-12 20:28:53 +02:00
|
|
|
self.assertEqual(person['user_id'], othello.id)
|
2020-05-30 21:43:19 +02:00
|
|
|
self.assertEqual(person['role'], UserProfile.ROLE_MEMBER)
|
2014-01-14 16:19:26 +01:00
|
|
|
|
|
|
|
# Make sure only admins can patch other user's info.
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login('othello')
|
2020-06-09 00:25:09 +02:00
|
|
|
result = self.client_patch(f'/json/users/{hamlet.id}', req)
|
2014-01-14 16:19:26 +01:00
|
|
|
self.assert_json_error(result, 'Insufficient permission')
|
|
|
|
|
2019-02-05 07:12:37 +01:00
|
|
|
def test_admin_api_hide_emails(self) -> None:
|
2020-03-12 14:17:25 +01:00
|
|
|
reset_emails_in_zulip_realm()
|
|
|
|
|
2019-02-05 07:12:37 +01:00
|
|
|
user = self.example_user('hamlet')
|
|
|
|
admin = self.example_user('iago')
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login_user(user)
|
2019-02-05 07:12:37 +01:00
|
|
|
|
|
|
|
# First, verify client_gravatar works normally
|
|
|
|
result = self.client_get('/json/users?client_gravatar=true')
|
|
|
|
self.assert_json_success(result)
|
|
|
|
members = result.json()['members']
|
|
|
|
hamlet = find_dict(members, 'user_id', user.id)
|
|
|
|
self.assertEqual(hamlet['email'], user.email)
|
|
|
|
self.assertIsNone(hamlet['avatar_url'])
|
2018-08-08 23:21:14 +02:00
|
|
|
self.assertNotIn('delivery_email', hamlet)
|
2019-02-05 07:12:37 +01:00
|
|
|
|
|
|
|
# Also verify the /events code path. This is a bit hacky, but
|
|
|
|
# we need to verify client_gravatar is not being overridden.
|
|
|
|
with mock.patch('zerver.lib.events.request_event_queue',
|
|
|
|
return_value=None) as mock_request_event_queue:
|
|
|
|
with self.assertRaises(JsonableError):
|
|
|
|
result = do_events_register(user, get_client("website"),
|
|
|
|
client_gravatar=True)
|
|
|
|
self.assertEqual(mock_request_event_queue.call_args_list[0][0][3],
|
|
|
|
True)
|
|
|
|
|
|
|
|
#############################################################
|
|
|
|
# Now, switch email address visibility, check client_gravatar
|
|
|
|
# is automatically disabled for the user.
|
|
|
|
do_set_realm_property(user.realm, "email_address_visibility",
|
|
|
|
Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS)
|
|
|
|
result = self.client_get('/json/users?client_gravatar=true')
|
|
|
|
self.assert_json_success(result)
|
|
|
|
members = result.json()['members']
|
|
|
|
hamlet = find_dict(members, 'user_id', user.id)
|
2020-06-10 06:41:04 +02:00
|
|
|
self.assertEqual(hamlet['email'], f"user{user.id}@zulip.testserver")
|
2019-11-05 20:23:58 +01:00
|
|
|
# Note that the Gravatar URL should still be computed from the
|
|
|
|
# `delivery_email`; otherwise, we won't be able to serve the
|
|
|
|
# user's Gravatar.
|
|
|
|
self.assertEqual(hamlet['avatar_url'], get_gravatar_url(user.delivery_email, 1))
|
2018-08-08 23:21:14 +02:00
|
|
|
self.assertNotIn('delivery_email', hamlet)
|
2019-02-05 07:12:37 +01:00
|
|
|
|
|
|
|
# Also verify the /events code path. This is a bit hacky, but
|
|
|
|
# basically we want to verify client_gravatar is being
|
|
|
|
# overridden.
|
|
|
|
with mock.patch('zerver.lib.events.request_event_queue',
|
|
|
|
return_value=None) as mock_request_event_queue:
|
|
|
|
with self.assertRaises(JsonableError):
|
|
|
|
result = do_events_register(user, get_client("website"),
|
|
|
|
client_gravatar=True)
|
|
|
|
self.assertEqual(mock_request_event_queue.call_args_list[0][0][3],
|
|
|
|
False)
|
|
|
|
|
2018-08-08 23:21:14 +02:00
|
|
|
# client_gravatar is still turned off for admins. In theory,
|
|
|
|
# it doesn't need to be, but client-side changes would be
|
|
|
|
# required in apps like the mobile apps.
|
|
|
|
# delivery_email is sent for admins.
|
2019-02-05 07:12:37 +01:00
|
|
|
admin.refresh_from_db()
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login_user(admin)
|
2019-02-05 07:12:37 +01:00
|
|
|
result = self.client_get('/json/users?client_gravatar=true')
|
|
|
|
self.assert_json_success(result)
|
|
|
|
members = result.json()['members']
|
|
|
|
hamlet = find_dict(members, 'user_id', user.id)
|
2020-06-10 06:41:04 +02:00
|
|
|
self.assertEqual(hamlet['email'], f"user{user.id}@zulip.testserver")
|
2019-11-05 20:23:58 +01:00
|
|
|
self.assertEqual(hamlet['avatar_url'], get_gravatar_url(user.email, 1))
|
2018-08-08 23:21:14 +02:00
|
|
|
self.assertEqual(hamlet['delivery_email'], self.example_email("hamlet"))
|
2019-02-05 07:12:37 +01:00
|
|
|
|
2018-04-01 01:06:56 +02:00
|
|
|
def test_user_cannot_promote_to_admin(self) -> None:
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login('hamlet')
|
2020-08-07 01:09:47 +02:00
|
|
|
req = dict(role=orjson.dumps(UserProfile.ROLE_REALM_ADMINISTRATOR).decode())
|
2018-05-17 19:36:33 +02:00
|
|
|
result = self.client_patch('/json/users/{}'.format(self.example_user('hamlet').id), req)
|
2018-04-01 01:06:56 +02:00
|
|
|
self.assert_json_error(result, 'Insufficient permission')
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_admin_user_can_change_full_name(self) -> None:
|
2016-09-27 14:25:52 +02:00
|
|
|
new_name = 'new name'
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login('iago')
|
2018-05-17 19:36:33 +02:00
|
|
|
hamlet = self.example_user('hamlet')
|
2020-08-07 01:09:47 +02:00
|
|
|
req = dict(full_name=orjson.dumps(new_name).decode())
|
2020-06-09 00:25:09 +02:00
|
|
|
result = self.client_patch(f'/json/users/{hamlet.id}', req)
|
2018-06-21 14:56:18 +02:00
|
|
|
self.assert_json_success(result)
|
2017-05-07 17:21:26 +02:00
|
|
|
hamlet = self.example_user('hamlet')
|
2016-09-27 14:25:52 +02:00
|
|
|
self.assertEqual(hamlet.full_name, new_name)
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_non_admin_cannot_change_full_name(self) -> None:
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login('hamlet')
|
2020-08-07 01:09:47 +02:00
|
|
|
req = dict(full_name=orjson.dumps('new name').decode())
|
2018-05-17 19:36:33 +02:00
|
|
|
result = self.client_patch('/json/users/{}'.format(self.example_user('othello').id), req)
|
2016-09-27 14:25:52 +02:00
|
|
|
self.assert_json_error(result, 'Insufficient permission')
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_admin_cannot_set_long_full_name(self) -> None:
|
2016-09-27 14:25:52 +02:00
|
|
|
new_name = 'a' * (UserProfile.MAX_NAME_LENGTH + 1)
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login('iago')
|
2020-08-07 01:09:47 +02:00
|
|
|
req = dict(full_name=orjson.dumps(new_name).decode())
|
2018-05-17 19:36:33 +02:00
|
|
|
result = self.client_patch('/json/users/{}'.format(self.example_user('hamlet').id), req)
|
2016-09-27 14:25:52 +02:00
|
|
|
self.assert_json_error(result, 'Name too long!')
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_admin_cannot_set_short_full_name(self) -> None:
|
2017-05-12 04:21:49 +02:00
|
|
|
new_name = 'a'
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login('iago')
|
2020-08-07 01:09:47 +02:00
|
|
|
req = dict(full_name=orjson.dumps(new_name).decode())
|
2018-05-17 19:36:33 +02:00
|
|
|
result = self.client_patch('/json/users/{}'.format(self.example_user('hamlet').id), req)
|
2017-05-12 04:21:49 +02:00
|
|
|
self.assert_json_error(result, 'Name too short!')
|
|
|
|
|
2020-04-25 19:18:13 +02:00
|
|
|
def test_not_allowed_format(self) -> None:
|
2020-08-11 01:47:49 +02:00
|
|
|
# Name of format "Alice|999" breaks in Markdown
|
2020-04-25 19:18:13 +02:00
|
|
|
new_name = 'iago|72'
|
|
|
|
self.login('iago')
|
2020-08-07 01:09:47 +02:00
|
|
|
req = dict(full_name=orjson.dumps(new_name).decode())
|
2020-04-25 19:18:13 +02:00
|
|
|
result = self.client_patch('/json/users/{}'.format(self.example_user('hamlet').id), req)
|
|
|
|
self.assert_json_error(result, 'Invalid format!')
|
|
|
|
|
|
|
|
def test_allowed_format_complex(self) -> None:
|
2020-08-11 01:47:49 +02:00
|
|
|
# Adding characters after r'|d+' doesn't break Markdown
|
2020-04-25 19:18:13 +02:00
|
|
|
new_name = 'Hello- 12iago|72k'
|
|
|
|
self.login('iago')
|
2020-08-07 01:09:47 +02:00
|
|
|
req = dict(full_name=orjson.dumps(new_name).decode())
|
2020-04-25 19:18:13 +02:00
|
|
|
result = self.client_patch('/json/users/{}'.format(self.example_user('hamlet').id), req)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
|
|
|
|
def test_not_allowed_format_complex(self) -> None:
|
|
|
|
new_name = 'Hello- 12iago|72'
|
|
|
|
self.login('iago')
|
2020-08-07 01:09:47 +02:00
|
|
|
req = dict(full_name=orjson.dumps(new_name).decode())
|
2020-04-25 19:18:13 +02:00
|
|
|
result = self.client_patch('/json/users/{}'.format(self.example_user('hamlet').id), req)
|
|
|
|
self.assert_json_error(result, 'Invalid format!')
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_admin_cannot_set_full_name_with_invalid_characters(self) -> None:
|
2017-01-16 04:18:42 +01:00
|
|
|
new_name = 'Opheli*'
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login('iago')
|
2020-08-07 01:09:47 +02:00
|
|
|
req = dict(full_name=orjson.dumps(new_name).decode())
|
2018-05-17 19:36:33 +02:00
|
|
|
result = self.client_patch('/json/users/{}'.format(self.example_user('hamlet').id), req)
|
2017-01-16 04:18:42 +01:00
|
|
|
self.assert_json_error(result, 'Invalid characters in name!')
|
|
|
|
|
2018-06-04 07:04:19 +02:00
|
|
|
def test_access_user_by_id(self) -> None:
|
|
|
|
iago = self.example_user("iago")
|
|
|
|
|
|
|
|
# Must be a valid user ID in the realm
|
|
|
|
with self.assertRaises(JsonableError):
|
|
|
|
access_user_by_id(iago, 1234)
|
|
|
|
with self.assertRaises(JsonableError):
|
|
|
|
access_user_by_id(iago, self.mit_user("sipbtest").id)
|
|
|
|
|
|
|
|
# Can only access bot users if allow_deactivated is passed
|
2019-07-15 20:58:41 +02:00
|
|
|
bot = self.example_user("default_bot")
|
2018-06-04 07:04:19 +02:00
|
|
|
access_user_by_id(iago, bot.id, allow_bots=True)
|
|
|
|
with self.assertRaises(JsonableError):
|
|
|
|
access_user_by_id(iago, bot.id)
|
|
|
|
|
|
|
|
# Can only access deactivated users if allow_deactivated is passed
|
|
|
|
hamlet = self.example_user("hamlet")
|
|
|
|
do_deactivate_user(hamlet)
|
|
|
|
with self.assertRaises(JsonableError):
|
|
|
|
access_user_by_id(iago, hamlet.id)
|
|
|
|
access_user_by_id(iago, hamlet.id, allow_deactivated=True)
|
|
|
|
|
|
|
|
# Non-admin user can't admin another user
|
|
|
|
with self.assertRaises(JsonableError):
|
|
|
|
access_user_by_id(self.example_user("cordelia"), self.example_user("aaron").id)
|
2020-02-07 02:46:59 +01:00
|
|
|
# But does have read-only access to it.
|
|
|
|
access_user_by_id(self.example_user("cordelia"), self.example_user("aaron").id,
|
|
|
|
read_only=True)
|
2018-06-04 07:04:19 +02:00
|
|
|
|
2018-10-19 12:29:46 +02:00
|
|
|
def test_change_regular_member_to_guest(self) -> None:
|
|
|
|
iago = self.example_user("iago")
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login_user(iago)
|
2018-10-19 12:29:46 +02:00
|
|
|
|
|
|
|
hamlet = self.example_user("hamlet")
|
|
|
|
self.assertFalse(hamlet.is_guest)
|
|
|
|
|
2020-08-07 01:09:47 +02:00
|
|
|
req = dict(role=orjson.dumps(UserProfile.ROLE_GUEST).decode())
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
events: List[Mapping[str, Any]] = []
|
2018-10-19 12:29:46 +02:00
|
|
|
with tornado_redirected_to_list(events):
|
2020-06-09 00:25:09 +02:00
|
|
|
result = self.client_patch(f'/json/users/{hamlet.id}', req)
|
2018-10-19 12:29:46 +02:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
|
|
|
hamlet = self.example_user("hamlet")
|
|
|
|
self.assertTrue(hamlet.is_guest)
|
2019-05-02 06:37:23 +02:00
|
|
|
self.assertFalse(hamlet.can_access_all_realm_members())
|
2018-10-19 12:29:46 +02:00
|
|
|
person = events[0]['event']['person']
|
2020-05-12 20:28:53 +02:00
|
|
|
self.assertEqual(person['user_id'], hamlet.id)
|
2020-05-30 21:43:19 +02:00
|
|
|
self.assertTrue(person['role'], UserProfile.ROLE_GUEST)
|
2018-10-19 12:29:46 +02:00
|
|
|
|
|
|
|
def test_change_guest_to_regular_member(self) -> None:
|
|
|
|
iago = self.example_user("iago")
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login_user(iago)
|
2018-10-19 12:29:46 +02:00
|
|
|
|
|
|
|
polonius = self.example_user("polonius")
|
|
|
|
self.assertTrue(polonius.is_guest)
|
2020-08-07 01:09:47 +02:00
|
|
|
req = dict(role=orjson.dumps(UserProfile.ROLE_MEMBER).decode())
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
events: List[Mapping[str, Any]] = []
|
2018-10-19 12:29:46 +02:00
|
|
|
with tornado_redirected_to_list(events):
|
2020-06-09 00:25:09 +02:00
|
|
|
result = self.client_patch(f'/json/users/{polonius.id}', req)
|
2018-10-19 12:29:46 +02:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
|
|
|
polonius = self.example_user("polonius")
|
|
|
|
self.assertFalse(polonius.is_guest)
|
|
|
|
person = events[0]['event']['person']
|
2020-05-12 20:28:53 +02:00
|
|
|
self.assertEqual(person['user_id'], polonius.id)
|
2020-05-30 21:43:19 +02:00
|
|
|
self.assertEqual(person['role'], UserProfile.ROLE_MEMBER)
|
2018-10-19 12:29:46 +02:00
|
|
|
|
|
|
|
def test_change_admin_to_guest(self) -> None:
|
|
|
|
iago = self.example_user("iago")
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login_user(iago)
|
2018-10-19 12:29:46 +02:00
|
|
|
hamlet = self.example_user("hamlet")
|
2020-05-21 00:13:06 +02:00
|
|
|
do_change_user_role(hamlet, UserProfile.ROLE_REALM_ADMINISTRATOR)
|
2018-10-19 12:29:46 +02:00
|
|
|
self.assertFalse(hamlet.is_guest)
|
|
|
|
self.assertTrue(hamlet.is_realm_admin)
|
|
|
|
|
|
|
|
# Test changing a user from admin to guest and revoking admin status
|
|
|
|
hamlet = self.example_user("hamlet")
|
|
|
|
self.assertFalse(hamlet.is_guest)
|
2020-08-07 01:09:47 +02:00
|
|
|
req = dict(role=orjson.dumps(UserProfile.ROLE_GUEST).decode())
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
events: List[Mapping[str, Any]] = []
|
2018-10-19 12:29:46 +02:00
|
|
|
with tornado_redirected_to_list(events):
|
2020-06-09 00:25:09 +02:00
|
|
|
result = self.client_patch(f'/json/users/{hamlet.id}', req)
|
2018-10-19 12:29:46 +02:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
|
|
|
hamlet = self.example_user("hamlet")
|
|
|
|
self.assertTrue(hamlet.is_guest)
|
|
|
|
self.assertFalse(hamlet.is_realm_admin)
|
|
|
|
|
|
|
|
person = events[0]['event']['person']
|
2020-05-12 20:28:53 +02:00
|
|
|
self.assertEqual(person['user_id'], hamlet.id)
|
2020-05-30 21:43:19 +02:00
|
|
|
self.assertEqual(person['role'], UserProfile.ROLE_GUEST)
|
2018-10-19 12:29:46 +02:00
|
|
|
|
|
|
|
def test_change_guest_to_admin(self) -> None:
|
|
|
|
iago = self.example_user("iago")
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login_user(iago)
|
2018-10-19 12:29:46 +02:00
|
|
|
polonius = self.example_user("polonius")
|
|
|
|
self.assertTrue(polonius.is_guest)
|
|
|
|
self.assertFalse(polonius.is_realm_admin)
|
|
|
|
|
|
|
|
# Test changing a user from guest to admin and revoking guest status
|
|
|
|
polonius = self.example_user("polonius")
|
|
|
|
self.assertFalse(polonius.is_realm_admin)
|
2020-08-07 01:09:47 +02:00
|
|
|
req = dict(role=orjson.dumps(UserProfile.ROLE_REALM_ADMINISTRATOR).decode())
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
events: List[Mapping[str, Any]] = []
|
2018-10-19 12:29:46 +02:00
|
|
|
with tornado_redirected_to_list(events):
|
2020-06-09 00:25:09 +02:00
|
|
|
result = self.client_patch(f'/json/users/{polonius.id}', req)
|
2018-10-19 12:29:46 +02:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
|
|
|
polonius = self.example_user("polonius")
|
|
|
|
self.assertFalse(polonius.is_guest)
|
|
|
|
self.assertTrue(polonius.is_realm_admin)
|
|
|
|
|
|
|
|
person = events[0]['event']['person']
|
2020-05-12 20:28:53 +02:00
|
|
|
self.assertEqual(person['user_id'], polonius.id)
|
2020-05-30 21:43:19 +02:00
|
|
|
self.assertEqual(person['role'], UserProfile.ROLE_REALM_ADMINISTRATOR)
|
2018-10-19 12:29:46 +02:00
|
|
|
|
2020-05-16 21:06:43 +02:00
|
|
|
def test_change_owner_to_guest(self) -> None:
|
|
|
|
self.login("desdemona")
|
|
|
|
iago = self.example_user("iago")
|
|
|
|
do_change_user_role(iago, UserProfile.ROLE_REALM_OWNER)
|
|
|
|
self.assertFalse(iago.is_guest)
|
|
|
|
self.assertTrue(iago.is_realm_owner)
|
|
|
|
|
|
|
|
# Test changing a user from owner to guest and revoking owner status
|
|
|
|
iago = self.example_user("iago")
|
|
|
|
self.assertFalse(iago.is_guest)
|
|
|
|
req = dict(role=UserProfile.ROLE_GUEST)
|
|
|
|
events: List[Mapping[str, Any]] = []
|
|
|
|
with tornado_redirected_to_list(events):
|
2020-06-13 08:57:35 +02:00
|
|
|
result = self.client_patch(f'/json/users/{iago.id}', req)
|
2020-05-16 21:06:43 +02:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
|
|
|
iago = self.example_user("iago")
|
|
|
|
self.assertTrue(iago.is_guest)
|
|
|
|
self.assertFalse(iago.is_realm_owner)
|
|
|
|
|
|
|
|
person = events[0]['event']['person']
|
|
|
|
self.assertEqual(person['user_id'], iago.id)
|
|
|
|
self.assertEqual(person['role'], UserProfile.ROLE_GUEST)
|
|
|
|
|
|
|
|
def test_change_guest_to_owner(self) -> None:
|
|
|
|
desdemona = self.example_user("desdemona")
|
|
|
|
self.login_user(desdemona)
|
|
|
|
polonius = self.example_user("polonius")
|
|
|
|
self.assertTrue(polonius.is_guest)
|
|
|
|
self.assertFalse(polonius.is_realm_owner)
|
|
|
|
|
|
|
|
# Test changing a user from guest to admin and revoking guest status
|
|
|
|
polonius = self.example_user("polonius")
|
|
|
|
self.assertFalse(polonius.is_realm_owner)
|
|
|
|
req = dict(role=UserProfile.ROLE_REALM_OWNER)
|
|
|
|
events: List[Mapping[str, Any]] = []
|
|
|
|
with tornado_redirected_to_list(events):
|
2020-06-13 08:57:35 +02:00
|
|
|
result = self.client_patch(f'/json/users/{polonius.id}', req)
|
2020-05-16 21:06:43 +02:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
|
|
|
polonius = self.example_user("polonius")
|
|
|
|
self.assertFalse(polonius.is_guest)
|
|
|
|
self.assertTrue(polonius.is_realm_owner)
|
|
|
|
|
|
|
|
person = events[0]['event']['person']
|
|
|
|
self.assertEqual(person['user_id'], polonius.id)
|
|
|
|
self.assertEqual(person['role'], UserProfile.ROLE_REALM_OWNER)
|
|
|
|
|
|
|
|
def test_change_admin_to_owner(self) -> None:
|
|
|
|
desdemona = self.example_user("desdemona")
|
|
|
|
self.login_user(desdemona)
|
|
|
|
iago = self.example_user("iago")
|
|
|
|
self.assertTrue(iago.is_realm_admin)
|
|
|
|
self.assertFalse(iago.is_realm_owner)
|
|
|
|
|
|
|
|
# Test changing a user from admin to owner and revoking admin status
|
|
|
|
iago = self.example_user("iago")
|
|
|
|
self.assertFalse(iago.is_realm_owner)
|
|
|
|
req = dict(role=UserProfile.ROLE_REALM_OWNER)
|
|
|
|
events: List[Mapping[str, Any]] = []
|
|
|
|
with tornado_redirected_to_list(events):
|
2020-06-13 08:57:35 +02:00
|
|
|
result = self.client_patch(f'/json/users/{iago.id}', req)
|
2020-05-16 21:06:43 +02:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
|
|
|
iago = self.example_user("iago")
|
|
|
|
self.assertTrue(iago.is_realm_owner)
|
|
|
|
|
|
|
|
person = events[0]['event']['person']
|
|
|
|
self.assertEqual(person['user_id'], iago.id)
|
|
|
|
self.assertEqual(person['role'], UserProfile.ROLE_REALM_OWNER)
|
|
|
|
|
|
|
|
def test_change_owner_to_admin(self) -> None:
|
|
|
|
desdemona = self.example_user("desdemona")
|
|
|
|
self.login_user(desdemona)
|
|
|
|
iago = self.example_user("iago")
|
|
|
|
do_change_user_role(iago, UserProfile.ROLE_REALM_OWNER)
|
|
|
|
self.assertTrue(iago.is_realm_owner)
|
|
|
|
|
|
|
|
# Test changing a user from admin to owner and revoking admin status
|
|
|
|
iago = self.example_user("iago")
|
|
|
|
self.assertTrue(iago.is_realm_owner)
|
|
|
|
req = dict(role=UserProfile.ROLE_REALM_ADMINISTRATOR)
|
|
|
|
events: List[Mapping[str, Any]] = []
|
|
|
|
with tornado_redirected_to_list(events):
|
2020-06-13 08:57:35 +02:00
|
|
|
result = self.client_patch(f'/json/users/{iago.id}', req)
|
2020-05-16 21:06:43 +02:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
|
|
|
iago = self.example_user("iago")
|
|
|
|
self.assertFalse(iago.is_realm_owner)
|
|
|
|
|
|
|
|
person = events[0]['event']['person']
|
|
|
|
self.assertEqual(person['user_id'], iago.id)
|
|
|
|
self.assertEqual(person['role'], UserProfile.ROLE_REALM_ADMINISTRATOR)
|
|
|
|
|
2018-09-04 20:46:11 +02:00
|
|
|
def test_admin_user_can_change_profile_data(self) -> None:
|
|
|
|
realm = get_realm('zulip')
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login('iago')
|
2018-09-04 20:46:11 +02:00
|
|
|
new_profile_data = []
|
|
|
|
cordelia = self.example_user("cordelia")
|
|
|
|
|
|
|
|
# Test for all type of data
|
|
|
|
fields = {
|
|
|
|
'Phone number': 'short text data',
|
|
|
|
'Biography': 'long text data',
|
|
|
|
'Favorite food': 'short text data',
|
|
|
|
'Favorite editor': 'vim',
|
|
|
|
'Birthday': '1909-3-5',
|
2020-06-08 23:04:39 +02:00
|
|
|
'Favorite website': 'https://zulip.com',
|
2018-09-04 20:46:11 +02:00
|
|
|
'Mentor': [cordelia.id],
|
2019-05-27 10:59:55 +02:00
|
|
|
'GitHub': 'timabbott',
|
2018-09-04 20:46:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
for field_name in fields:
|
|
|
|
field = CustomProfileField.objects.get(name=field_name, realm=realm)
|
|
|
|
new_profile_data.append({
|
|
|
|
'id': field.id,
|
|
|
|
'value': fields[field_name],
|
|
|
|
})
|
|
|
|
|
2020-06-09 00:25:09 +02:00
|
|
|
result = self.client_patch(f'/json/users/{cordelia.id}',
|
2020-08-07 01:09:47 +02:00
|
|
|
{'profile_data': orjson.dumps(new_profile_data).decode()})
|
2018-09-04 20:46:11 +02:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
|
|
|
cordelia = self.example_user("cordelia")
|
|
|
|
for field_dict in cordelia.profile_data:
|
2019-02-13 09:13:49 +01:00
|
|
|
with self.subTest(field_name=field_dict['name']):
|
2019-08-10 00:30:34 +02:00
|
|
|
self.assertEqual(field_dict['value'], fields[field_dict['name']])
|
2018-09-04 20:46:11 +02:00
|
|
|
|
|
|
|
# Test admin user cannot set invalid profile data
|
|
|
|
invalid_fields = [
|
|
|
|
('Favorite editor', 'invalid choice', "'invalid choice' is not a valid choice for 'Favorite editor'."),
|
|
|
|
('Birthday', '1909-34-55', "Birthday is not a date"),
|
2019-06-28 09:25:26 +02:00
|
|
|
('Favorite website', 'not url', "Favorite website is not a URL"),
|
2018-09-04 20:46:11 +02:00
|
|
|
('Mentor', "not list of user ids", "User IDs is not a list"),
|
|
|
|
]
|
|
|
|
|
|
|
|
for field_name, field_value, error_msg in invalid_fields:
|
|
|
|
new_profile_data = []
|
|
|
|
field = CustomProfileField.objects.get(name=field_name, realm=realm)
|
|
|
|
new_profile_data.append({
|
|
|
|
'id': field.id,
|
|
|
|
'value': field_value,
|
|
|
|
})
|
|
|
|
|
2020-06-09 00:25:09 +02:00
|
|
|
result = self.client_patch(f'/json/users/{cordelia.id}',
|
2020-08-07 01:09:47 +02:00
|
|
|
{'profile_data': orjson.dumps(new_profile_data).decode()})
|
2018-09-04 20:46:11 +02:00
|
|
|
self.assert_json_error(result, error_msg)
|
|
|
|
|
2020-03-28 01:25:56 +01:00
|
|
|
# non-existent field and no data
|
2019-01-15 12:21:14 +01:00
|
|
|
invalid_profile_data = [{
|
|
|
|
'id': 9001,
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
'value': '',
|
2019-01-15 12:21:14 +01:00
|
|
|
}]
|
2020-06-09 00:25:09 +02:00
|
|
|
result = self.client_patch(f'/json/users/{cordelia.id}',
|
2020-08-07 01:09:47 +02:00
|
|
|
{'profile_data': orjson.dumps(invalid_profile_data).decode()})
|
2019-01-15 12:21:14 +01:00
|
|
|
self.assert_json_error(result, 'Field id 9001 not found.')
|
|
|
|
|
2020-03-28 01:25:56 +01:00
|
|
|
# non-existent field and data
|
2019-01-15 12:21:14 +01:00
|
|
|
invalid_profile_data = [{
|
|
|
|
'id': 9001,
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
'value': 'some data',
|
2019-01-15 12:21:14 +01:00
|
|
|
}]
|
2020-06-09 00:25:09 +02:00
|
|
|
result = self.client_patch(f'/json/users/{cordelia.id}',
|
2020-08-07 01:09:47 +02:00
|
|
|
{'profile_data': orjson.dumps(invalid_profile_data).decode()})
|
2019-01-15 12:21:14 +01:00
|
|
|
self.assert_json_error(result, 'Field id 9001 not found.')
|
|
|
|
|
|
|
|
# Test for clearing/resetting field values.
|
|
|
|
empty_profile_data = []
|
|
|
|
for field_name in fields:
|
|
|
|
field = CustomProfileField.objects.get(name=field_name, realm=realm)
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
value: Union[str, None, List[Any]] = ''
|
2019-01-15 12:21:14 +01:00
|
|
|
if field.field_type == CustomProfileField.USER:
|
|
|
|
value = []
|
|
|
|
empty_profile_data.append({
|
|
|
|
'id': field.id,
|
|
|
|
'value': value,
|
|
|
|
})
|
2020-06-09 00:25:09 +02:00
|
|
|
result = self.client_patch(f'/json/users/{cordelia.id}',
|
2020-08-07 01:09:47 +02:00
|
|
|
{'profile_data': orjson.dumps(empty_profile_data).decode()})
|
2019-01-15 12:21:14 +01:00
|
|
|
self.assert_json_success(result)
|
|
|
|
for field_dict in cordelia.profile_data:
|
2019-02-13 09:13:49 +01:00
|
|
|
with self.subTest(field_name=field_dict['name']):
|
|
|
|
self.assertEqual(field_dict['value'], None)
|
2019-01-15 12:21:14 +01:00
|
|
|
|
|
|
|
# Test adding some of the field values after removing all.
|
|
|
|
hamlet = self.example_user("hamlet")
|
|
|
|
new_fields = {
|
|
|
|
'Phone number': None,
|
|
|
|
'Biography': 'A test user',
|
|
|
|
'Favorite food': None,
|
|
|
|
'Favorite editor': None,
|
|
|
|
'Birthday': None,
|
2019-06-28 09:25:26 +02:00
|
|
|
'Favorite website': 'https://zulip.github.io',
|
2019-05-27 10:59:55 +02:00
|
|
|
'Mentor': [hamlet.id],
|
|
|
|
'GitHub': 'timabbott',
|
2019-01-15 12:21:14 +01:00
|
|
|
}
|
|
|
|
new_profile_data = []
|
|
|
|
for field_name in fields:
|
|
|
|
field = CustomProfileField.objects.get(name=field_name, realm=realm)
|
|
|
|
value = None
|
|
|
|
if new_fields[field_name]:
|
|
|
|
value = new_fields[field_name]
|
|
|
|
new_profile_data.append({
|
|
|
|
'id': field.id,
|
|
|
|
'value': value,
|
|
|
|
})
|
2020-06-09 00:25:09 +02:00
|
|
|
result = self.client_patch(f'/json/users/{cordelia.id}',
|
2020-08-07 01:09:47 +02:00
|
|
|
{'profile_data': orjson.dumps(new_profile_data).decode()})
|
2019-01-15 12:21:14 +01:00
|
|
|
self.assert_json_success(result)
|
|
|
|
for field_dict in cordelia.profile_data:
|
2019-02-13 09:13:49 +01:00
|
|
|
with self.subTest(field_name=field_dict['name']):
|
|
|
|
self.assertEqual(field_dict['value'], new_fields[str(field_dict['name'])])
|
2019-01-15 12:21:14 +01:00
|
|
|
|
2018-09-04 20:46:11 +02:00
|
|
|
def test_non_admin_user_cannot_change_profile_data(self) -> None:
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login('cordelia')
|
2018-09-04 20:46:11 +02:00
|
|
|
hamlet = self.example_user("hamlet")
|
|
|
|
realm = get_realm("zulip")
|
|
|
|
|
|
|
|
new_profile_data = []
|
|
|
|
field = CustomProfileField.objects.get(name="Biography", realm=realm)
|
|
|
|
new_profile_data.append({
|
|
|
|
'id': field.id,
|
|
|
|
'value': "New hamlet Biography",
|
|
|
|
})
|
2020-06-09 00:25:09 +02:00
|
|
|
result = self.client_patch(f'/json/users/{hamlet.id}',
|
2020-08-07 01:09:47 +02:00
|
|
|
{'profile_data': orjson.dumps(new_profile_data).decode()})
|
2018-09-04 20:46:11 +02:00
|
|
|
self.assert_json_error(result, 'Insufficient permission')
|
|
|
|
|
|
|
|
result = self.client_patch('/json/users/{}'.format(self.example_user("cordelia").id),
|
2020-08-07 01:09:47 +02:00
|
|
|
{'profile_data': orjson.dumps(new_profile_data).decode()})
|
2018-09-04 20:46:11 +02:00
|
|
|
self.assert_json_error(result, 'Insufficient permission')
|
|
|
|
|
2020-03-06 17:58:06 +01:00
|
|
|
class BulkCreateUserTest(ZulipTestCase):
|
|
|
|
def test_create_users(self) -> None:
|
|
|
|
realm = get_realm('zulip')
|
|
|
|
realm.email_address_visibility = Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS
|
|
|
|
realm.save()
|
|
|
|
|
|
|
|
name_list = [
|
|
|
|
('Fred Flinstone', 'fred@zulip.com'),
|
|
|
|
('Lisa Simpson', 'lisa@zulip.com'),
|
|
|
|
]
|
|
|
|
|
|
|
|
create_users(realm, name_list)
|
|
|
|
|
|
|
|
fred = get_user_by_delivery_email('fred@zulip.com', realm)
|
|
|
|
self.assertEqual(
|
|
|
|
fred.email,
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
f'user{fred.id}@zulip.testserver',
|
2020-03-06 17:58:06 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
lisa = get_user_by_delivery_email('lisa@zulip.com', realm)
|
|
|
|
self.assertEqual(lisa.full_name, 'Lisa Simpson')
|
|
|
|
self.assertEqual(lisa.is_bot, False)
|
|
|
|
self.assertEqual(lisa.bot_type, None)
|
|
|
|
|
|
|
|
realm.email_address_visibility = Realm.EMAIL_ADDRESS_VISIBILITY_EVERYONE
|
|
|
|
realm.save()
|
|
|
|
|
|
|
|
name_list = [
|
|
|
|
('Bono', 'bono@zulip.com'),
|
|
|
|
('Cher', 'cher@zulip.com'),
|
|
|
|
]
|
|
|
|
|
|
|
|
create_users(realm, name_list)
|
|
|
|
bono = get_user_by_delivery_email('bono@zulip.com', realm)
|
|
|
|
self.assertEqual(bono.email, 'bono@zulip.com')
|
|
|
|
self.assertEqual(bono.delivery_email, 'bono@zulip.com')
|
|
|
|
|
|
|
|
cher = get_user_by_delivery_email('cher@zulip.com', realm)
|
|
|
|
self.assertEqual(cher.full_name, 'Cher')
|
|
|
|
|
2016-08-23 02:08:42 +02:00
|
|
|
class AdminCreateUserTest(ZulipTestCase):
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_create_user_backend(self) -> None:
|
2016-07-12 20:46:55 +02:00
|
|
|
|
|
|
|
# This test should give us complete coverage on
|
|
|
|
# create_user_backend. It mostly exercises error
|
|
|
|
# conditions, and it also does a basic test of the success
|
|
|
|
# path.
|
|
|
|
|
2017-05-07 21:25:59 +02:00
|
|
|
admin = self.example_user('hamlet')
|
2018-03-05 20:19:07 +01:00
|
|
|
realm = admin.realm
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login_user(admin)
|
2020-05-21 00:13:06 +02:00
|
|
|
do_change_user_role(admin, UserProfile.ROLE_REALM_ADMINISTRATOR)
|
2016-07-12 20:46:55 +02:00
|
|
|
|
2020-09-02 08:14:51 +02:00
|
|
|
result = self.client_post("/json/users", {})
|
2016-07-12 20:46:55 +02:00
|
|
|
self.assert_json_error(result, "Missing 'email' argument")
|
|
|
|
|
2016-12-31 08:07:22 +01:00
|
|
|
result = self.client_post("/json/users", dict(
|
2016-07-12 20:46:55 +02:00
|
|
|
email='romeo@not-zulip.com',
|
2017-01-24 06:34:26 +01:00
|
|
|
))
|
2016-07-12 20:46:55 +02:00
|
|
|
self.assert_json_error(result, "Missing 'password' argument")
|
|
|
|
|
2016-12-31 08:07:22 +01:00
|
|
|
result = self.client_post("/json/users", dict(
|
2016-07-12 20:46:55 +02:00
|
|
|
email='romeo@not-zulip.com',
|
|
|
|
password='xxxx',
|
2017-01-24 06:34:26 +01:00
|
|
|
))
|
2016-07-12 20:46:55 +02:00
|
|
|
self.assert_json_error(result, "Missing 'full_name' argument")
|
|
|
|
|
2020-07-16 23:56:34 +02:00
|
|
|
# Test short_name gets properly ignored
|
2016-12-31 08:07:22 +01:00
|
|
|
result = self.client_post("/json/users", dict(
|
2020-07-16 23:56:34 +02:00
|
|
|
email='romeo@zulip.com',
|
2016-07-12 20:46:55 +02:00
|
|
|
password='xxxx',
|
|
|
|
full_name='Romeo Montague',
|
2020-07-16 23:56:34 +02:00
|
|
|
short_name='DEPRECATED'
|
2017-01-24 06:34:26 +01:00
|
|
|
))
|
2020-07-16 23:56:34 +02:00
|
|
|
self.assert_json_success(result)
|
2016-07-12 20:46:55 +02:00
|
|
|
|
2016-12-31 08:07:22 +01:00
|
|
|
result = self.client_post("/json/users", dict(
|
2016-07-12 20:46:55 +02:00
|
|
|
email='broken',
|
|
|
|
password='xxxx',
|
|
|
|
full_name='Romeo Montague',
|
2017-01-24 06:34:26 +01:00
|
|
|
))
|
2016-07-12 20:46:55 +02:00
|
|
|
self.assert_json_error(result, "Bad name or username")
|
|
|
|
|
2020-03-06 16:22:23 +01:00
|
|
|
do_set_realm_property(realm, 'emails_restricted_to_domains', True)
|
2016-12-31 08:07:22 +01:00
|
|
|
result = self.client_post("/json/users", dict(
|
2016-07-12 20:46:55 +02:00
|
|
|
email='romeo@not-zulip.com',
|
|
|
|
password='xxxx',
|
|
|
|
full_name='Romeo Montague',
|
2017-01-24 06:34:26 +01:00
|
|
|
))
|
2016-07-12 20:46:55 +02:00
|
|
|
self.assert_json_error(result,
|
2018-03-08 02:05:50 +01:00
|
|
|
"Email 'romeo@not-zulip.com' not allowed in this organization")
|
2016-07-12 20:46:55 +02:00
|
|
|
|
2017-03-31 16:20:07 +02:00
|
|
|
RealmDomain.objects.create(realm=get_realm('zulip'), domain='zulip.net')
|
2016-07-12 20:46:55 +02:00
|
|
|
valid_params = dict(
|
2016-09-19 23:13:13 +02:00
|
|
|
email='romeo@zulip.net',
|
2016-07-12 20:46:55 +02:00
|
|
|
password='xxxx',
|
|
|
|
full_name='Romeo Montague',
|
|
|
|
)
|
auth: Use zxcvbn to ensure password strength on server side.
For a long time, we've been only doing the zxcvbn password strength
checks on the browser, which is helpful, but means users could through
hackery (or a bug in the frontend validation code) manage to set a
too-weak password. We fix this by running our password strength
validation on the backend as well, using python-zxcvbn.
In theory, a bug in python-zxcvbn could result in it producing a
different opinion than the frontend version; if so, it'd be a pretty
bad bug in the library, and hopefully we'd hear about it from users,
report upstream, and get it fixed that way. Alternatively, we can
switch to shelling out to node like we do for KaTeX.
Fixes #6880.
2019-11-18 08:11:03 +01:00
|
|
|
# Check can't use a bad password with zxcvbn enabled
|
|
|
|
with self.settings(PASSWORD_MIN_LENGTH=6, PASSWORD_MIN_GUESSES=1000):
|
|
|
|
result = self.client_post("/json/users", valid_params)
|
|
|
|
self.assert_json_error(result, "The password is too weak.")
|
|
|
|
|
2016-12-31 08:07:22 +01:00
|
|
|
result = self.client_post("/json/users", valid_params)
|
2016-07-12 20:46:55 +02:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
2017-05-24 02:42:31 +02:00
|
|
|
# Romeo is a newly registered user
|
2020-03-12 14:17:25 +01:00
|
|
|
new_user = get_user_by_delivery_email('romeo@zulip.net', get_realm('zulip'))
|
2020-08-11 23:01:01 +02:00
|
|
|
result = orjson.loads(result.content)
|
2016-07-12 20:46:55 +02:00
|
|
|
self.assertEqual(new_user.full_name, 'Romeo Montague')
|
2020-08-11 23:01:01 +02:00
|
|
|
self.assertEqual(new_user.id, result['user_id'])
|
2016-07-12 20:46:55 +02:00
|
|
|
|
2019-11-28 16:56:04 +01:00
|
|
|
# Make sure the recipient field is set correctly.
|
2020-02-18 17:13:47 +01:00
|
|
|
self.assertEqual(new_user.recipient, Recipient.objects.get(type=Recipient.PERSONAL,
|
|
|
|
type_id=new_user.id))
|
2019-11-28 16:56:04 +01:00
|
|
|
|
2018-03-05 20:19:07 +01:00
|
|
|
# we can't create the same user twice.
|
2016-12-31 08:07:22 +01:00
|
|
|
result = self.client_post("/json/users", valid_params)
|
2016-07-12 20:46:55 +02:00
|
|
|
self.assert_json_error(result,
|
2016-12-03 00:04:17 +01:00
|
|
|
"Email 'romeo@zulip.net' already in use")
|
2016-07-12 20:46:55 +02:00
|
|
|
|
2018-03-05 20:19:07 +01:00
|
|
|
# Don't allow user to sign up with disposable email.
|
2018-07-27 23:26:29 +02:00
|
|
|
realm.emails_restricted_to_domains = False
|
2018-03-05 20:19:07 +01:00
|
|
|
realm.disallow_disposable_email_addresses = True
|
|
|
|
realm.save()
|
|
|
|
|
|
|
|
valid_params["email"] = "abc@mailnator.com"
|
|
|
|
result = self.client_post("/json/users", valid_params)
|
2018-03-17 00:49:29 +01:00
|
|
|
self.assert_json_error(result, "Disposable email addresses are not allowed in this organization")
|
2018-03-05 20:19:07 +01:00
|
|
|
|
2018-06-20 13:08:07 +02:00
|
|
|
# Don't allow creating a user with + in their email address when realm
|
|
|
|
# is restricted to a domain.
|
2018-07-27 23:26:29 +02:00
|
|
|
realm.emails_restricted_to_domains = True
|
2018-06-20 13:08:07 +02:00
|
|
|
realm.save()
|
|
|
|
|
|
|
|
valid_params["email"] = "iago+label@zulip.com"
|
|
|
|
result = self.client_post("/json/users", valid_params)
|
|
|
|
self.assert_json_error(result, "Email addresses containing + are not allowed.")
|
|
|
|
|
|
|
|
# Users can be created with + in their email address when realm
|
|
|
|
# is not restricted to a domain.
|
2018-07-27 23:26:29 +02:00
|
|
|
realm.emails_restricted_to_domains = False
|
2018-06-20 13:08:07 +02:00
|
|
|
realm.save()
|
|
|
|
|
|
|
|
valid_params["email"] = "iago+label@zulip.com"
|
|
|
|
result = self.client_post("/json/users", valid_params)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
|
2017-05-07 17:21:26 +02:00
|
|
|
class UserProfileTest(ZulipTestCase):
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_get_emails_from_user_ids(self) -> None:
|
2017-05-07 17:21:26 +02:00
|
|
|
hamlet = self.example_user('hamlet')
|
|
|
|
othello = self.example_user('othello')
|
2013-10-20 21:10:03 +02:00
|
|
|
dct = get_emails_from_user_ids([hamlet.id, othello.id])
|
2020-03-12 14:17:25 +01:00
|
|
|
self.assertEqual(dct[hamlet.id], hamlet.email)
|
|
|
|
self.assertEqual(dct[othello.id], othello.email)
|
2013-10-20 21:10:03 +02:00
|
|
|
|
2018-05-08 13:54:40 +02:00
|
|
|
def test_valid_user_id(self) -> None:
|
|
|
|
realm = get_realm("zulip")
|
|
|
|
hamlet = self.example_user('hamlet')
|
|
|
|
othello = self.example_user('othello')
|
2019-07-15 20:58:41 +02:00
|
|
|
bot = self.example_user("default_bot")
|
2018-05-08 13:54:40 +02:00
|
|
|
|
|
|
|
# Invalid user ID
|
2020-06-23 01:12:03 +02:00
|
|
|
invalid_uid: object = 1000
|
2020-06-21 02:36:20 +02:00
|
|
|
with self.assertRaisesRegex(ValidationError, r"User IDs is not a list"):
|
|
|
|
check_valid_user_ids(realm.id, invalid_uid)
|
|
|
|
with self.assertRaisesRegex(ValidationError, rf"Invalid user ID: {invalid_uid}"):
|
|
|
|
check_valid_user_ids(realm.id, [invalid_uid])
|
2018-06-07 20:01:31 +02:00
|
|
|
|
|
|
|
invalid_uid = "abc"
|
2020-06-21 02:36:20 +02:00
|
|
|
with self.assertRaisesRegex(ValidationError, r"User IDs\[0\] is not an integer"):
|
|
|
|
check_valid_user_ids(realm.id, [invalid_uid])
|
|
|
|
|
2018-06-07 20:01:31 +02:00
|
|
|
invalid_uid = str(othello.id)
|
2020-06-21 02:36:20 +02:00
|
|
|
with self.assertRaisesRegex(ValidationError, r"User IDs\[0\] is not an integer"):
|
|
|
|
check_valid_user_ids(realm.id, [invalid_uid])
|
2018-05-08 13:54:40 +02:00
|
|
|
|
|
|
|
# User is in different realm
|
2020-06-21 02:36:20 +02:00
|
|
|
with self.assertRaisesRegex(ValidationError, rf"Invalid user ID: {hamlet.id}"):
|
|
|
|
check_valid_user_ids(get_realm("zephyr").id, [hamlet.id])
|
2018-05-08 13:54:40 +02:00
|
|
|
|
|
|
|
# User is not active
|
|
|
|
hamlet.is_active = False
|
|
|
|
hamlet.save()
|
2020-06-21 02:36:20 +02:00
|
|
|
with self.assertRaisesRegex(ValidationError, rf"User with ID {hamlet.id} is deactivated"):
|
|
|
|
check_valid_user_ids(realm.id, [hamlet.id])
|
|
|
|
check_valid_user_ids(realm.id, [hamlet.id], allow_deactivated=True)
|
2018-05-08 13:54:40 +02:00
|
|
|
|
2018-06-07 20:01:31 +02:00
|
|
|
# User is a bot
|
2020-06-21 02:36:20 +02:00
|
|
|
with self.assertRaisesRegex(ValidationError, rf"User with ID {bot.id} is a bot"):
|
|
|
|
check_valid_user_ids(realm.id, [bot.id])
|
2018-05-08 13:54:40 +02:00
|
|
|
|
2020-03-28 01:25:56 +01:00
|
|
|
# Successfully get non-bot, active user belong to your realm
|
2020-06-21 02:36:20 +02:00
|
|
|
check_valid_user_ids(realm.id, [othello.id])
|
2018-05-08 13:54:40 +02:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_cache_invalidation(self) -> None:
|
2017-10-22 03:14:44 +02:00
|
|
|
hamlet = self.example_user('hamlet')
|
|
|
|
with mock.patch('zerver.lib.cache.delete_display_recipient_cache') as m:
|
|
|
|
hamlet.full_name = 'Hamlet Junior'
|
|
|
|
hamlet.save(update_fields=["full_name"])
|
|
|
|
|
|
|
|
self.assertTrue(m.called)
|
|
|
|
|
|
|
|
with mock.patch('zerver.lib.cache.delete_display_recipient_cache') as m:
|
|
|
|
hamlet.long_term_idle = True
|
|
|
|
hamlet.save(update_fields=["long_term_idle"])
|
|
|
|
|
|
|
|
self.assertFalse(m.called)
|
|
|
|
|
2017-11-01 10:04:16 +01:00
|
|
|
def test_user_ids_to_users(self) -> None:
|
|
|
|
real_user_ids = [
|
|
|
|
self.example_user('hamlet').id,
|
|
|
|
self.example_user('cordelia').id,
|
|
|
|
]
|
|
|
|
|
2018-04-05 01:31:30 +02:00
|
|
|
self.assertEqual(user_ids_to_users([], get_realm("zulip")), [])
|
2020-04-09 21:51:58 +02:00
|
|
|
self.assertEqual({user_profile.id for user_profile in user_ids_to_users(real_user_ids, get_realm("zulip"))},
|
2018-04-05 01:31:30 +02:00
|
|
|
set(real_user_ids))
|
2017-11-01 10:04:16 +01:00
|
|
|
with self.assertRaises(JsonableError):
|
|
|
|
user_ids_to_users([1234], get_realm("zephyr"))
|
|
|
|
with self.assertRaises(JsonableError):
|
|
|
|
user_ids_to_users(real_user_ids, get_realm("zephyr"))
|
|
|
|
|
2017-11-16 02:28:50 +01:00
|
|
|
def test_bulk_get_users(self) -> None:
|
|
|
|
from zerver.lib.users import bulk_get_users
|
2020-03-12 14:17:25 +01:00
|
|
|
hamlet = self.example_user("hamlet")
|
|
|
|
cordelia = self.example_user("cordelia")
|
|
|
|
webhook_bot = self.example_user("webhook_bot")
|
|
|
|
result = bulk_get_users(
|
|
|
|
[hamlet.email, cordelia.email],
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
get_realm("zulip"),
|
2020-03-12 14:17:25 +01:00
|
|
|
)
|
|
|
|
self.assertEqual(result[hamlet.email].email, hamlet.email)
|
|
|
|
self.assertEqual(result[cordelia.email].email, cordelia.email)
|
|
|
|
|
|
|
|
result = bulk_get_users(
|
|
|
|
[hamlet.email, cordelia.email, webhook_bot.email],
|
|
|
|
None,
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
base_query=UserProfile.objects.all(),
|
2020-03-12 14:17:25 +01:00
|
|
|
)
|
|
|
|
self.assertEqual(result[hamlet.email].email, hamlet.email)
|
|
|
|
self.assertEqual(result[cordelia.email].email, cordelia.email)
|
|
|
|
self.assertEqual(result[webhook_bot.email].email, webhook_bot.email)
|
2017-11-16 02:28:50 +01:00
|
|
|
|
2018-06-19 10:55:56 +02:00
|
|
|
def test_get_accounts_for_email(self) -> None:
|
2020-03-12 14:17:25 +01:00
|
|
|
reset_emails_in_zulip_realm()
|
|
|
|
|
2018-06-19 10:55:56 +02:00
|
|
|
def check_account_present_in_accounts(user: UserProfile, accounts: List[Dict[str, Optional[str]]]) -> None:
|
|
|
|
for account in accounts:
|
|
|
|
realm = user.realm
|
|
|
|
if account["avatar"] == avatar_url(user) and account["full_name"] == user.full_name \
|
|
|
|
and account["realm_name"] == realm.name and account["string_id"] == realm.string_id:
|
|
|
|
return
|
|
|
|
raise AssertionError("Account not found")
|
|
|
|
|
2018-05-18 16:17:03 +02:00
|
|
|
lear_realm = get_realm("lear")
|
2018-06-19 10:55:56 +02:00
|
|
|
cordelia_in_zulip = self.example_user("cordelia")
|
2020-03-12 14:17:25 +01:00
|
|
|
cordelia_in_lear = get_user_by_delivery_email("cordelia@zulip.com", lear_realm)
|
2018-05-18 16:17:03 +02:00
|
|
|
|
|
|
|
email = "cordelia@zulip.com"
|
2018-06-19 10:55:56 +02:00
|
|
|
accounts = get_accounts_for_email(email)
|
|
|
|
self.assert_length(accounts, 2)
|
|
|
|
check_account_present_in_accounts(cordelia_in_zulip, accounts)
|
|
|
|
check_account_present_in_accounts(cordelia_in_lear, accounts)
|
2018-05-18 16:17:03 +02:00
|
|
|
|
|
|
|
email = "CORDelia@zulip.com"
|
2018-06-19 10:55:56 +02:00
|
|
|
accounts = get_accounts_for_email(email)
|
|
|
|
self.assert_length(accounts, 2)
|
|
|
|
check_account_present_in_accounts(cordelia_in_zulip, accounts)
|
|
|
|
check_account_present_in_accounts(cordelia_in_lear, accounts)
|
2018-05-18 16:17:03 +02:00
|
|
|
|
|
|
|
email = "IAGO@ZULIP.COM"
|
2018-06-19 10:55:56 +02:00
|
|
|
accounts = get_accounts_for_email(email)
|
|
|
|
self.assert_length(accounts, 1)
|
|
|
|
check_account_present_in_accounts(self.example_user("iago"), accounts)
|
2018-05-18 16:17:03 +02:00
|
|
|
|
2020-06-19 03:13:52 +02:00
|
|
|
# We verify that get_accounts_for_email don't return deactivated users accounts
|
|
|
|
user = self.example_user("hamlet")
|
|
|
|
do_deactivate_user(user)
|
|
|
|
email = self.example_email("hamlet")
|
|
|
|
accounts = get_accounts_for_email(email)
|
|
|
|
with self.assertRaises(AssertionError):
|
|
|
|
check_account_present_in_accounts(user, accounts)
|
|
|
|
|
2018-05-18 19:54:50 +02:00
|
|
|
def test_get_source_profile(self) -> None:
|
2020-03-12 14:17:25 +01:00
|
|
|
reset_emails_in_zulip_realm()
|
2018-05-18 19:54:50 +02:00
|
|
|
iago = get_source_profile("iago@zulip.com", "zulip")
|
|
|
|
assert iago is not None
|
|
|
|
self.assertEqual(iago.email, "iago@zulip.com")
|
|
|
|
self.assertEqual(iago.realm, get_realm("zulip"))
|
|
|
|
|
|
|
|
iago = get_source_profile("IAGO@ZULIP.com", "zulip")
|
|
|
|
assert iago is not None
|
|
|
|
self.assertEqual(iago.email, "iago@zulip.com")
|
|
|
|
|
|
|
|
cordelia = get_source_profile("cordelia@zulip.com", "lear")
|
|
|
|
assert cordelia is not None
|
|
|
|
self.assertEqual(cordelia.email, "cordelia@zulip.com")
|
|
|
|
|
|
|
|
self.assertIsNone(get_source_profile("iagod@zulip.com", "zulip"))
|
|
|
|
self.assertIsNone(get_source_profile("iago@zulip.com", "ZULIP"))
|
|
|
|
self.assertIsNone(get_source_profile("iago@zulip.com", "lear"))
|
|
|
|
|
2018-05-21 19:30:26 +02:00
|
|
|
def test_copy_user_settings(self) -> None:
|
|
|
|
iago = self.example_user("iago")
|
|
|
|
cordelia = self.example_user("cordelia")
|
|
|
|
hamlet = self.example_user("hamlet")
|
2020-05-16 13:13:59 +02:00
|
|
|
hamlet.color_scheme = UserProfile.COLOR_SCHEME_LIGHT
|
2018-05-21 19:30:26 +02:00
|
|
|
|
|
|
|
cordelia.default_language = "de"
|
2019-09-18 22:33:00 +02:00
|
|
|
cordelia.emojiset = "twitter"
|
2018-05-21 19:30:26 +02:00
|
|
|
cordelia.timezone = "America/Phoenix"
|
2020-05-16 13:13:59 +02:00
|
|
|
cordelia.color_scheme = UserProfile.COLOR_SCHEME_NIGHT
|
2018-05-21 19:30:26 +02:00
|
|
|
cordelia.enable_offline_email_notifications = False
|
|
|
|
cordelia.enable_stream_push_notifications = True
|
2018-06-13 20:16:51 +02:00
|
|
|
cordelia.enter_sends = False
|
2020-06-26 19:35:42 +02:00
|
|
|
cordelia.avatar_source = UserProfile.AVATAR_FROM_USER
|
2018-05-21 19:30:26 +02:00
|
|
|
cordelia.save()
|
|
|
|
|
2020-06-26 19:35:42 +02:00
|
|
|
# Upload cordelia's avatar
|
|
|
|
with get_test_image_file('img.png') as image_file:
|
|
|
|
upload_avatar_image(image_file, cordelia, cordelia)
|
|
|
|
|
2018-06-13 14:10:53 +02:00
|
|
|
UserHotspot.objects.filter(user=cordelia).delete()
|
|
|
|
UserHotspot.objects.filter(user=iago).delete()
|
|
|
|
hotspots_completed = ['intro_reply', 'intro_streams', 'intro_topics']
|
|
|
|
for hotspot in hotspots_completed:
|
|
|
|
UserHotspot.objects.create(user=cordelia, hotspot=hotspot)
|
|
|
|
|
2020-06-26 19:35:42 +02:00
|
|
|
events: List[Mapping[str, Any]] = []
|
|
|
|
with tornado_redirected_to_list(events):
|
|
|
|
copy_user_settings(cordelia, iago)
|
|
|
|
|
|
|
|
# Check that we didn't send an realm_user update events to
|
|
|
|
# users; this work is happening before the user account is
|
|
|
|
# created, so any changes will be reflected in the "add" event
|
|
|
|
# introducing the user to clients.
|
|
|
|
self.assertEqual(len(events), 0)
|
2018-05-21 19:30:26 +02:00
|
|
|
|
|
|
|
# We verify that cordelia and iago match, but hamlet has the defaults.
|
2018-05-25 19:24:30 +02:00
|
|
|
self.assertEqual(iago.full_name, "Cordelia Lear")
|
|
|
|
self.assertEqual(cordelia.full_name, "Cordelia Lear")
|
|
|
|
self.assertEqual(hamlet.full_name, "King Hamlet")
|
|
|
|
|
2018-05-21 19:30:26 +02:00
|
|
|
self.assertEqual(iago.default_language, "de")
|
|
|
|
self.assertEqual(cordelia.default_language, "de")
|
|
|
|
self.assertEqual(hamlet.default_language, "en")
|
|
|
|
|
2019-09-18 22:33:00 +02:00
|
|
|
self.assertEqual(iago.emojiset, "twitter")
|
|
|
|
self.assertEqual(cordelia.emojiset, "twitter")
|
2018-08-25 09:18:49 +02:00
|
|
|
self.assertEqual(hamlet.emojiset, "google-blob")
|
2018-05-21 19:30:26 +02:00
|
|
|
|
|
|
|
self.assertEqual(iago.timezone, "America/Phoenix")
|
|
|
|
self.assertEqual(cordelia.timezone, "America/Phoenix")
|
|
|
|
self.assertEqual(hamlet.timezone, "")
|
|
|
|
|
2020-05-16 13:13:59 +02:00
|
|
|
self.assertEqual(iago.color_scheme, UserProfile.COLOR_SCHEME_NIGHT)
|
|
|
|
self.assertEqual(cordelia.color_scheme, UserProfile.COLOR_SCHEME_NIGHT)
|
|
|
|
self.assertEqual(hamlet.color_scheme, UserProfile.COLOR_SCHEME_LIGHT)
|
2018-05-21 19:30:26 +02:00
|
|
|
|
|
|
|
self.assertEqual(iago.enable_offline_email_notifications, False)
|
|
|
|
self.assertEqual(cordelia.enable_offline_email_notifications, False)
|
|
|
|
self.assertEqual(hamlet.enable_offline_email_notifications, True)
|
|
|
|
|
|
|
|
self.assertEqual(iago.enable_stream_push_notifications, True)
|
|
|
|
self.assertEqual(cordelia.enable_stream_push_notifications, True)
|
|
|
|
self.assertEqual(hamlet.enable_stream_push_notifications, False)
|
|
|
|
|
2018-06-13 20:16:51 +02:00
|
|
|
self.assertEqual(iago.enter_sends, False)
|
|
|
|
self.assertEqual(cordelia.enter_sends, False)
|
|
|
|
self.assertEqual(hamlet.enter_sends, True)
|
|
|
|
|
2018-06-13 14:10:53 +02:00
|
|
|
hotspots = list(UserHotspot.objects.filter(user=iago).values_list('hotspot', flat=True))
|
|
|
|
self.assertEqual(hotspots, hotspots_completed)
|
|
|
|
|
2018-09-01 22:39:29 +02:00
|
|
|
def test_get_user_by_id_in_realm_including_cross_realm(self) -> None:
|
|
|
|
realm = get_realm('zulip')
|
|
|
|
hamlet = self.example_user('hamlet')
|
|
|
|
othello = self.example_user('othello')
|
2019-07-15 20:58:41 +02:00
|
|
|
bot = get_system_bot(settings.WELCOME_BOT)
|
2018-09-01 22:39:29 +02:00
|
|
|
|
|
|
|
# Pass in the ID of a cross-realm bot and a valid realm
|
|
|
|
cross_realm_bot = get_user_by_id_in_realm_including_cross_realm(
|
|
|
|
bot.id, realm)
|
|
|
|
self.assertEqual(cross_realm_bot.email, bot.email)
|
|
|
|
self.assertEqual(cross_realm_bot.id, bot.id)
|
|
|
|
|
|
|
|
# Pass in the ID of a cross-realm bot but with a invalid realm,
|
|
|
|
# note that the realm should be irrelevant here
|
|
|
|
cross_realm_bot = get_user_by_id_in_realm_including_cross_realm(
|
2019-05-04 04:47:44 +02:00
|
|
|
bot.id, None)
|
2018-09-01 22:39:29 +02:00
|
|
|
self.assertEqual(cross_realm_bot.email, bot.email)
|
|
|
|
self.assertEqual(cross_realm_bot.id, bot.id)
|
|
|
|
|
|
|
|
# Pass in the ID of a non-cross-realm user with a realm
|
|
|
|
user_profile = get_user_by_id_in_realm_including_cross_realm(
|
|
|
|
othello.id, realm)
|
|
|
|
self.assertEqual(user_profile.email, othello.email)
|
|
|
|
self.assertEqual(user_profile.id, othello.id)
|
|
|
|
|
|
|
|
# If the realm doesn't match, or if the ID is not that of a
|
|
|
|
# cross-realm bot, UserProfile.DoesNotExist is raised
|
|
|
|
with self.assertRaises(UserProfile.DoesNotExist):
|
|
|
|
get_user_by_id_in_realm_including_cross_realm(
|
2019-05-04 04:47:44 +02:00
|
|
|
hamlet.id, None)
|
2018-09-01 22:39:29 +02:00
|
|
|
|
2020-05-31 19:10:41 +02:00
|
|
|
def test_get_user_subscription_status(self) -> None:
|
|
|
|
self.login('hamlet')
|
|
|
|
iago = self.example_user('iago')
|
|
|
|
stream = get_stream('Rome', iago.realm)
|
|
|
|
|
|
|
|
# Invalid User ID.
|
2020-06-13 08:57:35 +02:00
|
|
|
result = self.client_get(f"/json/users/25/subscriptions/{stream.id}")
|
2020-05-31 19:10:41 +02:00
|
|
|
self.assert_json_error(result, "No such user")
|
|
|
|
|
|
|
|
# Invalid Stream ID.
|
2020-06-13 08:57:35 +02:00
|
|
|
result = self.client_get(f"/json/users/{iago.id}/subscriptions/25")
|
2020-05-31 19:10:41 +02:00
|
|
|
self.assert_json_error(result, "Invalid stream id")
|
|
|
|
|
2020-08-07 01:09:47 +02:00
|
|
|
result = orjson.loads(self.client_get(f"/json/users/{iago.id}/subscriptions/{stream.id}").content)
|
2020-05-31 19:10:41 +02:00
|
|
|
self.assertFalse(result['is_subscribed'])
|
|
|
|
|
|
|
|
# Subscribe to the stream.
|
|
|
|
self.subscribe(iago, stream.name)
|
|
|
|
with queries_captured() as queries:
|
2020-08-07 01:09:47 +02:00
|
|
|
result = orjson.loads(self.client_get(f"/json/users/{iago.id}/subscriptions/{stream.id}").content)
|
2020-05-31 19:10:41 +02:00
|
|
|
|
|
|
|
self.assert_length(queries, 7)
|
|
|
|
self.assertTrue(result['is_subscribed'])
|
|
|
|
|
|
|
|
# Logging in with a Guest user.
|
|
|
|
polonius = self.example_user("polonius")
|
|
|
|
self.login('polonius')
|
|
|
|
self.assertTrue(polonius.is_guest)
|
2020-07-23 23:18:32 +02:00
|
|
|
self.assertTrue(stream.is_web_public)
|
2020-05-31 19:10:41 +02:00
|
|
|
|
2020-08-07 01:09:47 +02:00
|
|
|
result = orjson.loads(self.client_get(f"/json/users/{iago.id}/subscriptions/{stream.id}").content)
|
2020-07-23 23:18:32 +02:00
|
|
|
self.assertTrue(result['is_subscribed'])
|
2020-05-31 19:10:41 +02:00
|
|
|
|
2016-08-23 02:08:42 +02:00
|
|
|
class ActivateTest(ZulipTestCase):
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_basics(self) -> None:
|
2017-05-07 17:21:26 +02:00
|
|
|
user = self.example_user('hamlet')
|
2013-11-16 17:11:15 +01:00
|
|
|
do_deactivate_user(user)
|
2013-11-15 18:57:44 +01:00
|
|
|
self.assertFalse(user.is_active)
|
2013-11-16 17:11:15 +01:00
|
|
|
do_reactivate_user(user)
|
2013-11-15 18:57:44 +01:00
|
|
|
self.assertTrue(user.is_active)
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_api(self) -> None:
|
2017-05-07 17:21:26 +02:00
|
|
|
admin = self.example_user('othello')
|
2020-05-21 00:13:06 +02:00
|
|
|
do_change_user_role(admin, UserProfile.ROLE_REALM_ADMINISTRATOR)
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login('othello')
|
2013-11-15 19:39:03 +01:00
|
|
|
|
2017-05-07 17:21:26 +02:00
|
|
|
user = self.example_user('hamlet')
|
2013-11-15 19:39:03 +01:00
|
|
|
self.assertTrue(user.is_active)
|
|
|
|
|
2020-06-09 00:25:09 +02:00
|
|
|
result = self.client_delete(f'/json/users/{user.id}')
|
2013-11-15 19:39:03 +01:00
|
|
|
self.assert_json_success(result)
|
2017-05-07 17:21:26 +02:00
|
|
|
user = self.example_user('hamlet')
|
2013-11-15 19:39:03 +01:00
|
|
|
self.assertFalse(user.is_active)
|
|
|
|
|
2020-06-09 00:25:09 +02:00
|
|
|
result = self.client_post(f'/json/users/{user.id}/reactivate')
|
2013-11-15 19:39:03 +01:00
|
|
|
self.assert_json_success(result)
|
2017-05-07 17:21:26 +02:00
|
|
|
user = self.example_user('hamlet')
|
2013-11-15 19:39:03 +01:00
|
|
|
self.assertTrue(user.is_active)
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_api_with_nonexistent_user(self) -> None:
|
2020-06-10 22:33:48 +02:00
|
|
|
self.login('iago')
|
|
|
|
|
|
|
|
# Organization Administrator cannot deactivate organization owner.
|
|
|
|
result = self.client_delete(f'/json/users/{self.example_user("desdemona").id}')
|
2020-06-15 06:32:10 +02:00
|
|
|
self.assert_json_error(result, 'Must be an organization owner')
|
2020-05-16 21:06:43 +02:00
|
|
|
|
|
|
|
iago = self.example_user('iago')
|
2020-06-10 22:33:48 +02:00
|
|
|
desdemona = self.example_user('desdemona')
|
2020-05-16 21:06:43 +02:00
|
|
|
do_change_user_role(iago, UserProfile.ROLE_REALM_OWNER)
|
2016-07-13 05:24:11 +02:00
|
|
|
|
2018-04-03 00:36:31 +02:00
|
|
|
# Cannot deactivate a user with the bot api
|
2018-05-15 15:26:04 +02:00
|
|
|
result = self.client_delete('/json/bots/{}'.format(self.example_user("hamlet").id))
|
2014-02-11 17:14:33 +01:00
|
|
|
self.assert_json_error(result, 'No such bot')
|
|
|
|
|
2018-04-03 00:36:31 +02:00
|
|
|
# Cannot deactivate a nonexistent user.
|
2018-05-17 19:36:33 +02:00
|
|
|
invalid_user_id = 1000
|
2020-06-09 00:25:09 +02:00
|
|
|
result = self.client_delete(f'/json/users/{invalid_user_id}')
|
2016-07-13 05:24:11 +02:00
|
|
|
self.assert_json_error(result, 'No such user')
|
|
|
|
|
2018-05-17 19:36:33 +02:00
|
|
|
result = self.client_delete('/json/users/{}'.format(self.example_user("webhook_bot").id))
|
2018-05-15 15:26:04 +02:00
|
|
|
self.assert_json_error(result, 'No such user')
|
|
|
|
|
2020-06-13 08:57:35 +02:00
|
|
|
result = self.client_delete(f'/json/users/{desdemona.id}')
|
2016-12-05 06:40:00 +01:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
2020-06-10 22:33:48 +02:00
|
|
|
result = self.client_delete(f'/json/users/{iago.id}')
|
2020-05-16 21:06:43 +02:00
|
|
|
self.assert_json_error(result, 'Cannot deactivate the only organization owner')
|
2016-12-05 06:40:00 +01:00
|
|
|
|
2018-04-03 00:36:31 +02:00
|
|
|
# Cannot reactivate a nonexistent user.
|
2018-05-17 19:45:13 +02:00
|
|
|
invalid_user_id = 1000
|
2020-06-09 00:25:09 +02:00
|
|
|
result = self.client_post(f'/json/users/{invalid_user_id}/reactivate')
|
2016-07-13 05:24:11 +02:00
|
|
|
self.assert_json_error(result, 'No such user')
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_api_with_insufficient_permissions(self) -> None:
|
2017-05-07 17:21:26 +02:00
|
|
|
non_admin = self.example_user('othello')
|
2020-05-21 00:13:06 +02:00
|
|
|
do_change_user_role(non_admin, UserProfile.ROLE_MEMBER)
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login('othello')
|
2016-07-13 05:42:29 +02:00
|
|
|
|
2018-04-03 00:36:31 +02:00
|
|
|
# Cannot deactivate a user with the users api
|
2018-05-17 19:36:33 +02:00
|
|
|
result = self.client_delete('/json/users/{}'.format(self.example_user("hamlet").id))
|
2016-07-13 05:42:29 +02:00
|
|
|
self.assert_json_error(result, 'Insufficient permission')
|
|
|
|
|
2018-04-03 00:36:31 +02:00
|
|
|
# Cannot reactivate a user
|
2018-05-17 19:45:13 +02:00
|
|
|
result = self.client_post('/json/users/{}/reactivate'.format(self.example_user("hamlet").id))
|
2016-07-13 05:42:29 +02:00
|
|
|
self.assert_json_error(result, 'Insufficient permission')
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_clear_scheduled_jobs(self) -> None:
|
2017-07-01 03:56:40 +02:00
|
|
|
user = self.example_user('hamlet')
|
2017-12-05 03:19:48 +01:00
|
|
|
send_future_email('zerver/emails/followup_day1', user.realm,
|
2018-12-03 23:26:51 +01:00
|
|
|
to_user_ids=[user.id], delay=datetime.timedelta(hours=1))
|
2017-07-02 21:10:41 +02:00
|
|
|
self.assertEqual(ScheduledEmail.objects.count(), 1)
|
2017-07-01 03:56:40 +02:00
|
|
|
do_deactivate_user(user)
|
2017-07-02 21:10:41 +02:00
|
|
|
self.assertEqual(ScheduledEmail.objects.count(), 0)
|
2017-07-01 03:56:40 +02:00
|
|
|
|
2019-01-04 01:50:21 +01:00
|
|
|
def test_send_future_email_with_multiple_recipients(self) -> None:
|
|
|
|
hamlet = self.example_user('hamlet')
|
|
|
|
iago = self.example_user('iago')
|
|
|
|
send_future_email('zerver/emails/followup_day1', iago.realm,
|
|
|
|
to_user_ids=[hamlet.id, iago.id], delay=datetime.timedelta(hours=1))
|
|
|
|
self.assertEqual(ScheduledEmail.objects.filter(users__in=[hamlet, iago]).distinct().count(), 1)
|
|
|
|
email = ScheduledEmail.objects.all().first()
|
|
|
|
self.assertEqual(email.users.count(), 2)
|
|
|
|
|
|
|
|
def test_clear_scheduled_emails_with_multiple_user_ids(self) -> None:
|
|
|
|
hamlet = self.example_user('hamlet')
|
|
|
|
iago = self.example_user('iago')
|
|
|
|
send_future_email('zerver/emails/followup_day1', iago.realm,
|
|
|
|
to_user_ids=[hamlet.id, iago.id], delay=datetime.timedelta(hours=1))
|
|
|
|
self.assertEqual(ScheduledEmail.objects.count(), 1)
|
|
|
|
clear_scheduled_emails([hamlet.id, iago.id])
|
|
|
|
self.assertEqual(ScheduledEmail.objects.count(), 0)
|
|
|
|
|
|
|
|
def test_clear_schedule_emails_with_one_user_id(self) -> None:
|
|
|
|
hamlet = self.example_user('hamlet')
|
|
|
|
iago = self.example_user('iago')
|
|
|
|
send_future_email('zerver/emails/followup_day1', iago.realm,
|
|
|
|
to_user_ids=[hamlet.id, iago.id], delay=datetime.timedelta(hours=1))
|
|
|
|
self.assertEqual(ScheduledEmail.objects.count(), 1)
|
|
|
|
clear_scheduled_emails([hamlet.id])
|
|
|
|
self.assertEqual(ScheduledEmail.objects.count(), 1)
|
|
|
|
self.assertEqual(ScheduledEmail.objects.filter(users=hamlet).count(), 0)
|
|
|
|
self.assertEqual(ScheduledEmail.objects.filter(users=iago).count(), 1)
|
|
|
|
|
2019-03-16 02:32:43 +01:00
|
|
|
def test_deliver_email(self) -> None:
|
|
|
|
iago = self.example_user('iago')
|
|
|
|
hamlet = self.example_user('hamlet')
|
|
|
|
send_future_email('zerver/emails/followup_day1', iago.realm,
|
|
|
|
to_user_ids=[hamlet.id, iago.id], delay=datetime.timedelta(hours=1))
|
|
|
|
self.assertEqual(ScheduledEmail.objects.count(), 1)
|
|
|
|
email = ScheduledEmail.objects.all().first()
|
|
|
|
deliver_email(email)
|
|
|
|
from django.core.mail import outbox
|
|
|
|
self.assertEqual(len(outbox), 1)
|
|
|
|
for message in outbox:
|
2020-06-05 23:26:35 +02:00
|
|
|
self.assertEqual(
|
|
|
|
set(message.to),
|
|
|
|
{
|
|
|
|
str(Address(display_name=hamlet.full_name, addr_spec=hamlet.delivery_email)),
|
|
|
|
str(Address(display_name=iago.full_name, addr_spec=iago.delivery_email)),
|
|
|
|
},
|
|
|
|
)
|
2019-03-16 02:32:43 +01:00
|
|
|
self.assertEqual(ScheduledEmail.objects.count(), 0)
|
2019-01-04 01:50:21 +01:00
|
|
|
|
2017-10-23 22:03:28 +02:00
|
|
|
class RecipientInfoTest(ZulipTestCase):
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_stream_recipient_info(self) -> None:
|
2017-10-23 22:03:28 +02:00
|
|
|
hamlet = self.example_user('hamlet')
|
|
|
|
cordelia = self.example_user('cordelia')
|
|
|
|
othello = self.example_user('othello')
|
|
|
|
|
2019-12-11 01:41:20 +01:00
|
|
|
# These tests were written with the old default for
|
|
|
|
# enable_online_push_notifications; that default is better for
|
|
|
|
# testing the full code path anyway.
|
|
|
|
hamlet.enable_online_push_notifications = False
|
|
|
|
cordelia.enable_online_push_notifications = False
|
|
|
|
othello.enable_online_push_notifications = False
|
|
|
|
hamlet.save()
|
|
|
|
cordelia.save()
|
|
|
|
othello.save()
|
|
|
|
|
2017-10-23 22:03:28 +02:00
|
|
|
realm = hamlet.realm
|
|
|
|
|
|
|
|
stream_name = 'Test Stream'
|
2017-10-24 00:07:03 +02:00
|
|
|
topic_name = 'test topic'
|
2017-10-23 22:03:28 +02:00
|
|
|
|
|
|
|
for user in [hamlet, cordelia, othello]:
|
|
|
|
self.subscribe(user, stream_name)
|
|
|
|
|
|
|
|
stream = get_stream(stream_name, realm)
|
2020-02-18 17:25:43 +01:00
|
|
|
recipient = stream.recipient
|
2017-10-23 22:03:28 +02:00
|
|
|
|
2017-10-24 00:07:03 +02:00
|
|
|
stream_topic = StreamTopicTarget(
|
|
|
|
stream_id=stream.id,
|
|
|
|
topic_name=topic_name,
|
|
|
|
)
|
|
|
|
|
2017-10-23 22:03:28 +02:00
|
|
|
info = get_recipient_info(
|
|
|
|
recipient=recipient,
|
|
|
|
sender_id=hamlet.id,
|
2017-10-24 00:07:03 +02:00
|
|
|
stream_topic=stream_topic,
|
2019-09-03 23:27:45 +02:00
|
|
|
possible_wildcard_mention=False,
|
2017-10-23 22:03:28 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
all_user_ids = {hamlet.id, cordelia.id, othello.id}
|
|
|
|
|
|
|
|
expected_info = dict(
|
|
|
|
active_user_ids=all_user_ids,
|
|
|
|
push_notify_user_ids=set(),
|
2019-02-13 10:22:16 +01:00
|
|
|
stream_push_user_ids=set(),
|
2018-07-12 11:33:51 +02:00
|
|
|
stream_email_user_ids=set(),
|
2019-09-03 23:27:45 +02:00
|
|
|
wildcard_mention_user_ids=set(),
|
2017-10-23 22:03:28 +02:00
|
|
|
um_eligible_user_ids=all_user_ids,
|
|
|
|
long_term_idle_user_ids=set(),
|
2017-10-24 20:08:19 +02:00
|
|
|
default_bot_user_ids=set(),
|
2017-10-23 22:03:28 +02:00
|
|
|
service_bot_tuples=[],
|
|
|
|
)
|
|
|
|
|
|
|
|
self.assertEqual(info, expected_info)
|
|
|
|
|
2019-09-03 23:27:45 +02:00
|
|
|
cordelia.wildcard_mentions_notify = False
|
|
|
|
cordelia.save()
|
2019-02-13 10:22:16 +01:00
|
|
|
hamlet.enable_stream_push_notifications = True
|
|
|
|
hamlet.save()
|
|
|
|
info = get_recipient_info(
|
|
|
|
recipient=recipient,
|
|
|
|
sender_id=hamlet.id,
|
|
|
|
stream_topic=stream_topic,
|
2019-09-03 23:27:45 +02:00
|
|
|
possible_wildcard_mention=False,
|
2019-02-13 10:22:16 +01:00
|
|
|
)
|
|
|
|
self.assertEqual(info['stream_push_user_ids'], {hamlet.id})
|
2019-09-03 23:27:45 +02:00
|
|
|
self.assertEqual(info['wildcard_mention_user_ids'], set())
|
|
|
|
|
|
|
|
info = get_recipient_info(
|
|
|
|
recipient=recipient,
|
|
|
|
sender_id=hamlet.id,
|
|
|
|
stream_topic=stream_topic,
|
|
|
|
possible_wildcard_mention=True,
|
|
|
|
)
|
|
|
|
self.assertEqual(info['wildcard_mention_user_ids'], {hamlet.id, othello.id})
|
2019-02-13 10:22:16 +01:00
|
|
|
|
|
|
|
sub = get_subscription(stream_name, hamlet)
|
|
|
|
sub.push_notifications = False
|
|
|
|
sub.save()
|
|
|
|
info = get_recipient_info(
|
|
|
|
recipient=recipient,
|
|
|
|
sender_id=hamlet.id,
|
|
|
|
stream_topic=stream_topic,
|
|
|
|
)
|
|
|
|
self.assertEqual(info['stream_push_user_ids'], set())
|
|
|
|
|
|
|
|
hamlet.enable_stream_push_notifications = False
|
|
|
|
hamlet.save()
|
|
|
|
sub = get_subscription(stream_name, hamlet)
|
|
|
|
sub.push_notifications = True
|
|
|
|
sub.save()
|
|
|
|
info = get_recipient_info(
|
|
|
|
recipient=recipient,
|
|
|
|
sender_id=hamlet.id,
|
|
|
|
stream_topic=stream_topic,
|
|
|
|
)
|
|
|
|
self.assertEqual(info['stream_push_user_ids'], {hamlet.id})
|
|
|
|
|
2017-10-24 00:07:03 +02:00
|
|
|
# Now mute Hamlet to omit him from stream_push_user_ids.
|
|
|
|
add_topic_mute(
|
|
|
|
user_profile=hamlet,
|
|
|
|
stream_id=stream.id,
|
|
|
|
recipient_id=recipient.id,
|
|
|
|
topic_name=topic_name,
|
|
|
|
)
|
|
|
|
|
|
|
|
info = get_recipient_info(
|
|
|
|
recipient=recipient,
|
|
|
|
sender_id=hamlet.id,
|
|
|
|
stream_topic=stream_topic,
|
2019-09-03 23:27:45 +02:00
|
|
|
possible_wildcard_mention=False,
|
|
|
|
)
|
|
|
|
self.assertEqual(info['stream_push_user_ids'], set())
|
|
|
|
self.assertEqual(info['wildcard_mention_user_ids'], set())
|
|
|
|
|
|
|
|
info = get_recipient_info(
|
|
|
|
recipient=recipient,
|
|
|
|
sender_id=hamlet.id,
|
|
|
|
stream_topic=stream_topic,
|
|
|
|
possible_wildcard_mention=True,
|
2017-10-24 00:07:03 +02:00
|
|
|
)
|
2019-09-03 23:27:45 +02:00
|
|
|
self.assertEqual(info['stream_push_user_ids'], set())
|
|
|
|
# Since Hamlet has muted the stream and Cordelia has disabled
|
|
|
|
# wildcard notifications, it should just be Othello here.
|
|
|
|
self.assertEqual(info['wildcard_mention_user_ids'], {othello.id})
|
2017-10-24 00:07:03 +02:00
|
|
|
|
2019-09-03 23:27:45 +02:00
|
|
|
sub = get_subscription(stream_name, othello)
|
|
|
|
sub.wildcard_mentions_notify = False
|
|
|
|
sub.save()
|
|
|
|
|
|
|
|
info = get_recipient_info(
|
|
|
|
recipient=recipient,
|
|
|
|
sender_id=hamlet.id,
|
|
|
|
stream_topic=stream_topic,
|
|
|
|
possible_wildcard_mention=True,
|
|
|
|
)
|
|
|
|
self.assertEqual(info['stream_push_user_ids'], set())
|
|
|
|
# Verify that stream-level wildcard_mentions_notify=False works correctly.
|
|
|
|
self.assertEqual(info['wildcard_mention_user_ids'], set())
|
|
|
|
|
|
|
|
# Verify that True works as expected as well
|
|
|
|
sub = get_subscription(stream_name, othello)
|
|
|
|
sub.wildcard_mentions_notify = True
|
|
|
|
sub.save()
|
|
|
|
|
|
|
|
info = get_recipient_info(
|
|
|
|
recipient=recipient,
|
|
|
|
sender_id=hamlet.id,
|
|
|
|
stream_topic=stream_topic,
|
|
|
|
possible_wildcard_mention=True,
|
|
|
|
)
|
2017-10-24 00:07:03 +02:00
|
|
|
self.assertEqual(info['stream_push_user_ids'], set())
|
2019-09-03 23:27:45 +02:00
|
|
|
self.assertEqual(info['wildcard_mention_user_ids'], {othello.id})
|
2017-10-24 00:07:03 +02:00
|
|
|
|
2017-10-24 19:25:50 +02:00
|
|
|
# Add a service bot.
|
|
|
|
service_bot = do_create_user(
|
|
|
|
email='service-bot@zulip.com',
|
|
|
|
password='',
|
|
|
|
realm=realm,
|
|
|
|
full_name='',
|
|
|
|
bot_type=UserProfile.EMBEDDED_BOT,
|
|
|
|
)
|
|
|
|
|
|
|
|
info = get_recipient_info(
|
|
|
|
recipient=recipient,
|
|
|
|
sender_id=hamlet.id,
|
|
|
|
stream_topic=stream_topic,
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
possibly_mentioned_user_ids={service_bot.id},
|
2017-10-24 19:25:50 +02:00
|
|
|
)
|
|
|
|
self.assertEqual(info['service_bot_tuples'], [
|
|
|
|
(service_bot.id, UserProfile.EMBEDDED_BOT),
|
|
|
|
])
|
|
|
|
|
2017-10-24 20:08:19 +02:00
|
|
|
# Add a normal bot.
|
|
|
|
normal_bot = do_create_user(
|
|
|
|
email='normal-bot@zulip.com',
|
|
|
|
password='',
|
|
|
|
realm=realm,
|
|
|
|
full_name='',
|
|
|
|
bot_type=UserProfile.DEFAULT_BOT,
|
|
|
|
)
|
|
|
|
|
|
|
|
info = get_recipient_info(
|
|
|
|
recipient=recipient,
|
|
|
|
sender_id=hamlet.id,
|
|
|
|
stream_topic=stream_topic,
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
possibly_mentioned_user_ids={service_bot.id, normal_bot.id},
|
2017-10-24 20:08:19 +02:00
|
|
|
)
|
|
|
|
self.assertEqual(info['default_bot_user_ids'], {normal_bot.id})
|
|
|
|
|
2018-05-16 03:07:36 +02:00
|
|
|
def test_get_recipient_info_invalid_recipient_type(self) -> None:
|
|
|
|
hamlet = self.example_user('hamlet')
|
|
|
|
realm = hamlet.realm
|
|
|
|
|
|
|
|
stream = get_stream('Rome', realm)
|
|
|
|
stream_topic = StreamTopicTarget(
|
|
|
|
stream_id=stream.id,
|
|
|
|
topic_name='test topic',
|
|
|
|
)
|
|
|
|
|
|
|
|
# Make sure get_recipient_info asserts on invalid recipient types
|
|
|
|
with self.assertRaisesRegex(ValueError, 'Bad recipient type'):
|
2018-05-16 05:07:33 +02:00
|
|
|
invalid_recipient = Recipient(type=999) # 999 is not a valid type
|
|
|
|
get_recipient_info(
|
2018-05-16 03:07:36 +02:00
|
|
|
recipient=invalid_recipient,
|
|
|
|
sender_id=hamlet.id,
|
|
|
|
stream_topic=stream_topic,
|
|
|
|
)
|
|
|
|
|
2017-10-10 04:51:04 +02:00
|
|
|
class BulkUsersTest(ZulipTestCase):
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_client_gravatar_option(self) -> None:
|
2020-03-12 14:17:25 +01:00
|
|
|
reset_emails_in_zulip_realm()
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login('cordelia')
|
2017-10-10 04:51:04 +02:00
|
|
|
|
|
|
|
hamlet = self.example_user('hamlet')
|
|
|
|
|
2018-05-11 01:39:38 +02:00
|
|
|
def get_hamlet_avatar(client_gravatar: bool) -> Optional[str]:
|
2020-08-07 01:09:47 +02:00
|
|
|
data = dict(client_gravatar=orjson.dumps(client_gravatar).decode())
|
2017-10-10 04:51:04 +02:00
|
|
|
result = self.client_get('/json/users', data)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
rows = result.json()['members']
|
|
|
|
hamlet_data = [
|
|
|
|
row for row in rows
|
|
|
|
if row['user_id'] == hamlet.id
|
|
|
|
][0]
|
|
|
|
return hamlet_data['avatar_url']
|
|
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
get_hamlet_avatar(client_gravatar=True),
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
None,
|
2017-10-10 04:51:04 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
'''
|
|
|
|
The main purpose of this test is to make sure we
|
|
|
|
return None for avatar_url when client_gravatar is
|
|
|
|
set to True. And we do a sanity check for when it's
|
|
|
|
False, but we leave it to other tests to validate
|
|
|
|
the specific URL.
|
|
|
|
'''
|
|
|
|
self.assertIn(
|
|
|
|
'gravatar.com',
|
|
|
|
get_hamlet_avatar(client_gravatar=False),
|
|
|
|
)
|
|
|
|
|
2016-08-23 02:08:42 +02:00
|
|
|
class GetProfileTest(ZulipTestCase):
|
2013-01-22 20:07:51 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_cache_behavior(self) -> None:
|
2017-07-12 21:59:19 +02:00
|
|
|
"""Tests whether fetching a user object the normal way, with
|
|
|
|
`get_user`, makes 1 cache query and 1 database query.
|
|
|
|
"""
|
|
|
|
realm = get_realm("zulip")
|
2020-03-12 14:17:25 +01:00
|
|
|
email = self.example_user("hamlet").email
|
2013-09-28 01:05:08 +02:00
|
|
|
with queries_captured() as queries:
|
|
|
|
with simulated_empty_cache() as cache_queries:
|
2017-07-12 21:59:19 +02:00
|
|
|
user_profile = get_user(email, realm)
|
2013-09-28 01:05:08 +02:00
|
|
|
|
2017-03-31 11:08:45 +02:00
|
|
|
self.assert_length(queries, 1)
|
2016-09-25 21:30:10 +02:00
|
|
|
self.assert_length(cache_queries, 1)
|
2017-07-12 21:59:19 +02:00
|
|
|
self.assertEqual(user_profile.email, email)
|
2013-09-28 01:05:08 +02:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_get_user_profile(self) -> None:
|
2020-03-12 14:17:25 +01:00
|
|
|
hamlet = self.example_user('hamlet')
|
|
|
|
iago = self.example_user('iago')
|
2020-06-01 21:47:18 +02:00
|
|
|
desdemona = self.example_user('desdemona')
|
2020-03-12 14:17:25 +01:00
|
|
|
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login('hamlet')
|
2020-08-07 01:09:47 +02:00
|
|
|
result = orjson.loads(self.client_get('/json/users/me').content)
|
2020-03-12 14:17:25 +01:00
|
|
|
self.assertEqual(result['email'], hamlet.email)
|
2016-12-13 19:17:49 +01:00
|
|
|
self.assertEqual(result['full_name'], 'King Hamlet')
|
|
|
|
self.assertIn("user_id", result)
|
|
|
|
self.assertFalse(result['is_bot'])
|
|
|
|
self.assertFalse(result['is_admin'])
|
2020-06-01 21:47:18 +02:00
|
|
|
self.assertFalse(result['is_owner'])
|
2020-06-08 00:29:47 +02:00
|
|
|
self.assertFalse(result['is_guest'])
|
2020-03-12 14:17:25 +01:00
|
|
|
self.assertFalse('delivery_email' in result)
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login('iago')
|
2020-08-07 01:09:47 +02:00
|
|
|
result = orjson.loads(self.client_get('/json/users/me').content)
|
2020-03-12 14:17:25 +01:00
|
|
|
self.assertEqual(result['email'], iago.email)
|
2016-12-13 19:17:49 +01:00
|
|
|
self.assertEqual(result['full_name'], 'Iago')
|
|
|
|
self.assertFalse(result['is_bot'])
|
|
|
|
self.assertTrue(result['is_admin'])
|
2020-06-01 21:47:18 +02:00
|
|
|
self.assertFalse(result['is_owner'])
|
2020-06-08 00:29:47 +02:00
|
|
|
self.assertFalse(result['is_guest'])
|
2020-06-01 21:47:18 +02:00
|
|
|
self.login('desdemona')
|
2020-08-07 01:09:47 +02:00
|
|
|
result = orjson.loads(self.client_get('/json/users/me').content)
|
2020-06-01 21:47:18 +02:00
|
|
|
self.assertEqual(result['email'], desdemona.email)
|
|
|
|
self.assertFalse(result['is_bot'])
|
|
|
|
self.assertTrue(result['is_admin'])
|
|
|
|
self.assertTrue(result['is_owner'])
|
2020-06-08 00:29:47 +02:00
|
|
|
self.assertFalse(result['is_guest'])
|
2016-12-13 19:17:49 +01:00
|
|
|
|
2020-01-02 00:39:54 +01:00
|
|
|
# Tests the GET ../users/{id} api endpoint.
|
|
|
|
user = self.example_user('hamlet')
|
2020-08-07 01:09:47 +02:00
|
|
|
result = orjson.loads(self.client_get(f'/json/users/{user.id}').content)
|
2020-03-08 21:13:11 +01:00
|
|
|
self.assertEqual(result['user']['email'], user.email)
|
|
|
|
self.assertEqual(result['user']['full_name'], user.full_name)
|
|
|
|
self.assertIn("user_id", result['user'])
|
|
|
|
self.assertNotIn("profile_data", result['user'])
|
|
|
|
self.assertFalse(result['user']['is_bot'])
|
|
|
|
self.assertFalse(result['user']['is_admin'])
|
2020-06-01 21:47:18 +02:00
|
|
|
self.assertFalse(result['user']['is_owner'])
|
2020-01-02 00:39:54 +01:00
|
|
|
|
2020-08-07 01:09:47 +02:00
|
|
|
result = orjson.loads(self.client_get(f'/json/users/{user.id}?include_custom_profile_fields=true').content)
|
2020-01-02 00:39:54 +01:00
|
|
|
|
2020-03-08 21:13:11 +01:00
|
|
|
self.assertIn('profile_data', result['user'])
|
2020-06-09 00:25:09 +02:00
|
|
|
result = self.client_get(f'/json/users/{30}?')
|
2020-01-02 00:39:54 +01:00
|
|
|
self.assert_json_error(result, "No such user")
|
|
|
|
|
2020-04-21 22:45:58 +02:00
|
|
|
bot = self.example_user("default_bot")
|
2020-08-07 01:09:47 +02:00
|
|
|
result = orjson.loads(self.client_get(f'/json/users/{bot.id}').content)
|
2020-04-21 22:45:58 +02:00
|
|
|
self.assertEqual(result['user']['email'], bot.email)
|
|
|
|
self.assertTrue(result['user']['is_bot'])
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_get_all_profiles_avatar_urls(self) -> None:
|
2020-03-19 15:14:38 +01:00
|
|
|
hamlet = self.example_user('hamlet')
|
|
|
|
result = self.api_get(hamlet, "/api/v1/users")
|
2014-07-18 06:16:14 +02:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
2020-03-19 23:34:50 +01:00
|
|
|
(my_user,) = [
|
|
|
|
user for user in result.json()['members']
|
|
|
|
if user['email'] == hamlet.email
|
|
|
|
]
|
2020-03-19 15:14:38 +01:00
|
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
my_user['avatar_url'],
|
|
|
|
avatar_url(hamlet),
|
|
|
|
)
|
2019-08-30 00:21:36 +02:00
|
|
|
|
|
|
|
class FakeEmailDomainTest(ZulipTestCase):
|
|
|
|
@override_settings(FAKE_EMAIL_DOMAIN="invaliddomain")
|
|
|
|
def test_invalid_fake_email_domain(self) -> None:
|
|
|
|
with self.assertRaises(InvalidFakeEmailDomain):
|
|
|
|
get_fake_email_domain()
|
|
|
|
|
|
|
|
@override_settings(FAKE_EMAIL_DOMAIN="127.0.0.1")
|
|
|
|
def test_invalid_fake_email_domain_ip(self) -> None:
|
|
|
|
with self.assertRaises(InvalidFakeEmailDomain):
|
|
|
|
get_fake_email_domain()
|