def test_change_delivery_email_end_to_end_with_admins_visibility(self) -> None: user_profile = self.example_user('hamlet') do_set_realm_property(user_profile.realm, 'email_address_visibility', Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS) old_email = user_profile.email new_email = '*****@*****.**' self.login(self.example_email('hamlet')) obj = EmailChangeStatus.objects.create(new_email=new_email, old_email=old_email, user_profile=user_profile, realm=user_profile.realm) key = generate_key() Confirmation.objects.create(content_object=obj, date_sent=now(), confirmation_key=key, type=Confirmation.EMAIL_CHANGE) url = confirmation_url(key, user_profile.realm.host, Confirmation.EMAIL_CHANGE) response = self.client_get(url) self.assertEqual(response.status_code, 200) self.assert_in_success_response(["This confirms that the email address for your Zulip"], response) user_profile = get_user_profile_by_id(user_profile.id) self.assertEqual(user_profile.delivery_email, new_email) self.assertEqual(user_profile.email, "*****@*****.**") obj.refresh_from_db() self.assertEqual(obj.status, 1) with self.assertRaises(UserProfile.DoesNotExist): get_user(old_email, user_profile.realm) with self.assertRaises(UserProfile.DoesNotExist): get_user_by_delivery_email(old_email, user_profile.realm) self.assertEqual(get_user_by_delivery_email(new_email, user_profile.realm), user_profile)
def create_user_backend(request: HttpRequest, user_profile: UserProfile, email: Text=REQ(), password: Text=REQ(), full_name_raw: Text=REQ("full_name"), short_name: Text=REQ()) -> HttpResponse: full_name = check_full_name(full_name_raw) form = CreateUserForm({'full_name': full_name, 'email': email}) if not form.is_valid(): return json_error(_('Bad name or username')) # Check that the new user's email address belongs to the admin's realm # (Since this is an admin API, we don't require the user to have been # invited first.) realm = user_profile.realm try: email_allowed_for_realm(email, user_profile.realm) except DomainNotAllowedForRealmError: return json_error(_("Email '%(email)s' not allowed in this organization") % {'email': email}) except DisposableEmailError: return json_error(_("Disposable email addresses are not allowed in this organization")) try: get_user(email, user_profile.realm) return json_error(_("Email '%s' already in use") % (email,)) except UserProfile.DoesNotExist: pass do_create_user(email, password, realm, full_name, short_name) return json_success()
def add_bot_backend(request, user_profile, full_name_raw=REQ("full_name"), short_name=REQ(), default_sending_stream_name=REQ('default_sending_stream', default=None), default_events_register_stream_name=REQ('default_events_register_stream', default=None), default_all_public_streams=REQ(validator=check_bool, default=None)): # type: (HttpRequest, UserProfile, Text, Text, Optional[Text], Optional[Text], Optional[bool]) -> HttpResponse short_name += "-bot" full_name = check_full_name(full_name_raw) email = '%s@%s' % (short_name, user_profile.realm.get_bot_domain()) form = CreateUserForm({'full_name': full_name, 'email': email}) if not form.is_valid(): # We validate client-side as well return json_error(_('Bad name or username')) try: get_user(email, user_profile.realm) return json_error(_("Username already in use")) except UserProfile.DoesNotExist: pass if len(request.FILES) == 0: avatar_source = UserProfile.AVATAR_FROM_GRAVATAR elif len(request.FILES) != 1: return json_error(_("You may only upload one file at a time")) else: avatar_source = UserProfile.AVATAR_FROM_USER default_sending_stream = None if default_sending_stream_name is not None: (default_sending_stream, ignored_rec, ignored_sub) = access_stream_by_name( user_profile, default_sending_stream_name) default_events_register_stream = None if default_events_register_stream_name is not None: (default_events_register_stream, ignored_rec, ignored_sub) = access_stream_by_name( user_profile, default_events_register_stream_name) bot_profile = do_create_user(email=email, password='', realm=user_profile.realm, full_name=full_name, short_name=short_name, active=True, bot_type=UserProfile.DEFAULT_BOT, bot_owner=user_profile, avatar_source=avatar_source, default_sending_stream=default_sending_stream, default_events_register_stream=default_events_register_stream, default_all_public_streams=default_all_public_streams) if len(request.FILES) == 1: user_file = list(request.FILES.values())[0] upload_avatar_image(user_file, user_profile, bot_profile) json_result = dict( api_key=bot_profile.api_key, avatar_url=avatar_url(bot_profile), default_sending_stream=get_stream_name(bot_profile.default_sending_stream), default_events_register_stream=get_stream_name(bot_profile.default_events_register_stream), default_all_public_streams=bot_profile.default_all_public_streams, ) return json_success(json_result)
def test_confirm_email_change(self) -> None: user_profile = self.example_user('hamlet') old_email = user_profile.email new_email = '*****@*****.**' new_realm = get_realm('zulip') self.login(self.example_email('hamlet')) obj = EmailChangeStatus.objects.create(new_email=new_email, old_email=old_email, user_profile=user_profile, realm=user_profile.realm) key = generate_key() Confirmation.objects.create(content_object=obj, date_sent=now(), confirmation_key=key, type=Confirmation.EMAIL_CHANGE) url = confirmation_url(key, user_profile.realm.host, Confirmation.EMAIL_CHANGE) response = self.client_get(url) self.assertEqual(response.status_code, 200) self.assert_in_success_response(["This confirms that the email address for your Zulip"], response) user_profile = get_user(new_email, new_realm) self.assertTrue(bool(user_profile)) obj.refresh_from_db() self.assertEqual(obj.status, 1)
def _login(self, user_email, user_realm, password=None): # type: (Text, Realm, str) -> None if password: user_profile = get_user(user_email, user_realm) user_profile.set_password(password) user_profile.save() self.login(user_email, password)
def test_create_outgoing_webhook_bot(self, **extras: Any) -> None: self.login(self.example_email('hamlet')) bot_info = { 'full_name': 'Outgoing Webhook test bot', 'short_name': 'outgoingservicebot', 'bot_type': UserProfile.OUTGOING_WEBHOOK_BOT, 'payload_url': ujson.dumps('http://127.0.0.1:5002/bots/followup'), } bot_info.update(extras) result = self.client_post("/json/bots", bot_info) self.assert_json_success(result) bot_email = "*****@*****.**" bot_realm = get_realm('zulip') bot = get_user(bot_email, bot_realm) services = get_bot_services(bot.id) service = services[0] self.assertEqual(len(services), 1) self.assertEqual(service.name, "outgoingservicebot") self.assertEqual(service.base_url, "http://127.0.0.1:5002/bots/followup") self.assertEqual(service.user_profile, bot) # invalid URL test case. bot_info['payload_url'] = ujson.dumps('http://127.0.0.:5002/bots/followup') result = self.client_post("/json/bots", bot_info) self.assert_json_error(result, "Enter a valid URL.")
def get_presence_backend(request: HttpRequest, user_profile: UserProfile, email: Text) -> HttpResponse: try: target = get_user(email, user_profile.realm) except UserProfile.DoesNotExist: return json_error(_('No such user')) if not target.is_active: return json_error(_('No such user')) if target.is_bot: return json_error(_('Presence is not supported for bot users.')) presence_dict = UserPresence.get_status_dict_by_user(target) if len(presence_dict) == 0: return json_error(_('No presence data for %s' % (target.email,))) # For initial version, we just include the status and timestamp keys result = dict(presence=presence_dict[target.email]) aggregated_info = result['presence']['aggregated'] aggr_status_duration = datetime_to_timestamp(timezone_now()) - aggregated_info['timestamp'] if aggr_status_duration > settings.OFFLINE_THRESHOLD_SECS: aggregated_info['status'] = 'offline' for val in result['presence'].values(): val.pop('client', None) val.pop('pushable', None) return json_success(result)
def test_notify_bot_owner_on_invalid_json(self) -> None: @api_key_only_webhook_view('ClientName', notify_bot_owner_on_invalid_json=False) def my_webhook_no_notify(request: HttpRequest, user_profile: UserProfile) -> None: raise InvalidJSONError("Malformed JSON") @api_key_only_webhook_view('ClientName', notify_bot_owner_on_invalid_json=True) def my_webhook_notify(request: HttpRequest, user_profile: UserProfile) -> None: raise InvalidJSONError("Malformed JSON") webhook_bot_email = '*****@*****.**' webhook_bot_realm = get_realm('zulip') webhook_bot = get_user(webhook_bot_email, webhook_bot_realm) webhook_bot_api_key = get_api_key(webhook_bot) request = HostRequestMock() request.POST['api_key'] = webhook_bot_api_key request.host = "zulip.testserver" expected_msg = INVALID_JSON_MESSAGE.format(webhook_name='ClientName') last_message_id = self.get_last_message().id with self.assertRaisesRegex(JsonableError, "Malformed JSON"): my_webhook_no_notify(request) # type: ignore # mypy doesn't seem to apply the decorator # First verify that without the setting, it doesn't send a PM to bot owner. msg = self.get_last_message() self.assertEqual(msg.id, last_message_id) self.assertNotEqual(msg.content, expected_msg.strip()) # Then verify that with the setting, it does send such a message. with self.assertRaisesRegex(JsonableError, "Malformed JSON"): my_webhook_notify(request) # type: ignore # mypy doesn't seem to apply the decorator msg = self.get_last_message() self.assertNotEqual(msg.id, last_message_id) self.assertEqual(msg.sender.email, self.notification_bot().email) self.assertEqual(msg.content, expected_msg.strip())
def common_get_active_user(email: str, realm: Realm, return_data: Dict[str, Any]=None) -> Optional[UserProfile]: try: user_profile = get_user(email, realm) except UserProfile.DoesNotExist: # If the user doesn't have an account in the target realm, we # check whether they might have an account in another realm, # and if so, provide a helpful error message via # `invalid_subdomain`. if not UserProfile.objects.filter(email__iexact=email).exists(): return None if return_data is not None: return_data['invalid_subdomain'] = True return None if not user_profile.is_active: if return_data is not None: if user_profile.is_mirror_dummy: # Record whether it's a mirror dummy account return_data['is_mirror_dummy'] = True return_data['inactive_user'] = True return None if user_profile.realm.deactivated: if return_data is not None: return_data['inactive_realm'] = True return None return user_profile
def test_file_download_authorization_public(self): # type: () -> None subscribed_users = [self.example_email("hamlet"), self.example_email("iago")] unsubscribed_users = [self.example_email("othello"), self.example_email("prospero")] realm = get_realm("zulip") for email in subscribed_users: self.subscribe(get_user(email, realm), "test-subscribe") self.login(self.example_email("hamlet")) fp = StringIO("zulip!") fp.name = "zulip.txt" result = self.client_post("/json/user_uploads", {'file': fp}) uri = result.json()['uri'] fp_path_id = re.sub('/user_uploads/', '', uri) body = "First message ...[zulip.txt](http://localhost:9991/user_uploads/" + fp_path_id + ")" self.send_stream_message(self.example_email("hamlet"), "test-subscribe", body, "test") self.logout() # Now all users should be able to access the files for user in subscribed_users + unsubscribed_users: self.login(user) response = self.client_get(uri) data = b"".join(response.streaming_content) self.assertEqual(b"zulip!", data) self.logout()
def test_get_accounts_for_email(self) -> None: 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") lear_realm = get_realm("lear") cordelia_in_zulip = self.example_user("cordelia") cordelia_in_lear = get_user("*****@*****.**", lear_realm) email = "*****@*****.**" 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) email = "*****@*****.**" 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) email = "*****@*****.**" accounts = get_accounts_for_email(email) self.assert_length(accounts, 1) check_account_present_in_accounts(self.example_user("iago"), accounts)
def get_assignee_mention(assignee_email: str, realm: Realm) -> str: if assignee_email != '': try: assignee_name = get_user(assignee_email, realm).full_name except UserProfile.DoesNotExist: assignee_name = assignee_email return u"**{}**".format(assignee_name) return ''
def api_yo_app_webhook(request: HttpRequest, user_profile: UserProfile, email: str = REQ(default=""), username: str = REQ(default='Yo Bot'), topic: Optional[str] = REQ(default=None), user_ip: Optional[str] = REQ(default=None)) -> HttpResponse: body = ('Yo from %s') % (username,) receiving_user = get_user(email, user_profile.realm) check_send_private_message(user_profile, request.client, receiving_user, body) return json_success()
def deactivate_bot_backend(request: HttpRequest, user_profile: UserProfile, email: Text) -> HttpResponse: try: target = get_user(email, user_profile.realm) except UserProfile.DoesNotExist: return json_error(_('No such bot')) if not target.is_bot: return json_error(_('No such bot')) return _deactivate_user_profile_backend(request, user_profile, target)
def principal_to_user_profile(agent: UserProfile, principal: Text) -> UserProfile: try: return get_user(principal, agent.realm) except UserProfile.DoesNotExist: # We have to make sure we don't leak information about which users # are registered for Zulip in a different realm. We could do # something a little more clever and check the domain part of the # principal to maybe give a better error message raise PrincipalError(principal)
def get_streams(self, email: str, realm: Realm) -> List[str]: """ Helper function to get the stream names for a user """ user_profile = get_user(email, realm) subs = get_stream_subscriptions_for_user(user_profile).filter( active=True, ) return [cast(str, get_display_recipient(sub.recipient)) for sub in subs]
def test_webhook_http_header_header_exists(self) -> None: webhook_bot = get_user('*****@*****.**', get_realm('zulip')) request = HostRequestMock() request.META['HTTP_X_CUSTOM_HEADER'] = 'custom_value' request.user = webhook_bot header_value = validate_extract_webhook_http_header(request, 'X_CUSTOM_HEADER', 'test_webhook') self.assertEqual(header_value, 'custom_value')
def test_add_bot_with_default_sending_stream_not_subscribed(self) -> None: email = '*****@*****.**' realm = get_realm('zulip') self.login(self.example_email('hamlet')) self.assert_num_bots_equal(0) result = self.create_bot(default_sending_stream='Rome') self.assert_num_bots_equal(1) self.assertEqual(result['default_sending_stream'], 'Rome') profile = get_user(email, realm) self.assertEqual(profile.default_sending_stream.name, 'Rome')
def send_personal_message(self, from_email: str, to_email: str, content: str="test content", sender_realm: str="zulip") -> int: sender = get_user(from_email, get_realm(sender_realm)) recipient_list = [to_email] (sending_client, _) = Client.objects.get_or_create(name="test suite") return check_send_message( sender, sending_client, 'private', recipient_list, None, content )
def test_add_bot_with_bot_type_incoming_webhook(self) -> None: bot_email = '*****@*****.**' bot_realm = get_realm('zulip') self.login(self.example_email('hamlet')) self.assert_num_bots_equal(0) self.create_bot(bot_type=UserProfile.INCOMING_WEBHOOK_BOT) self.assert_num_bots_equal(1) profile = get_user(bot_email, bot_realm) self.assertEqual(profile.bot_type, UserProfile.INCOMING_WEBHOOK_BOT)
def test_add_bot_with_default_all_public_streams(self) -> None: self.login(self.example_email('hamlet')) self.assert_num_bots_equal(0) result = self.create_bot(default_all_public_streams=ujson.dumps(True)) self.assert_num_bots_equal(1) self.assertTrue(result['default_all_public_streams']) bot_email = '*****@*****.**' bot_realm = get_realm('zulip') profile = get_user(bot_email, bot_realm) self.assertEqual(profile.default_all_public_streams, True)
def test_add_bot_with_bot_type_default(self) -> None: bot_email = '*****@*****.**' bot_realm = get_realm('zulip') self.login(self.example_email('hamlet')) self.assert_num_bots_equal(0) self.create_bot(bot_type=UserProfile.DEFAULT_BOT) self.assert_num_bots_equal(1) profile = get_user(bot_email, bot_realm) self.assertEqual(profile.bot_type, UserProfile.DEFAULT_BOT)
def test_api_update_pointer(self) -> None: """ Same as above, but for the API view """ user = self.example_user('hamlet') email = user.email self.assertEqual(user.pointer, -1) msg_id = self.send_stream_message(self.example_email("othello"), "Verona") result = self.api_post(email, "/api/v1/users/me/pointer", {"pointer": msg_id}) self.assert_json_success(result) self.assertEqual(get_user(email, user.realm).pointer, msg_id)
def test_authenticated_json_post_view_if_user_realm_is_deactivated(self): # type: () -> None user_email = self.example_email('hamlet') user_realm = get_realm('zulip') user_profile = get_user(user_email, user_realm) self._login(user_email, user_realm) # we deactivate user's realm manually because do_deactivate_user removes user session user_profile.realm.deactivated = True user_profile.realm.save() self.assert_json_error_contains(self._do_test(user_email), "Realm for account has been deactivated") do_reactivate_realm(user_profile.realm)
def test_create_embedded_bot(self, **extras: Any) -> None: self.login(self.example_email('hamlet')) # Test to create embedded bot with correct service_name bot_config_info = {'key': 'value'} bot_info = { 'full_name': 'Embedded test bot', 'short_name': 'embeddedservicebot', 'bot_type': UserProfile.EMBEDDED_BOT, 'service_name': 'followup', 'config_data': ujson.dumps(bot_config_info), } bot_info.update(extras) with self.settings(EMBEDDED_BOTS_ENABLED=False): result = self.client_post("/json/bots", bot_info) self.assert_json_error(result, 'Embedded bots are not enabled.') result = self.client_post("/json/bots", bot_info) self.assert_json_success(result) bot_email = "*****@*****.**" bot_realm = get_realm('zulip') bot = get_user(bot_email, bot_realm) services = get_bot_services(bot.id) service = services[0] bot_config = get_bot_config(bot) self.assertEqual(bot_config, bot_config_info) self.assertEqual(len(services), 1) self.assertEqual(service.name, "followup") self.assertEqual(service.user_profile, bot) # Test to create embedded bot with incorrect service_name bot_info = { 'full_name': 'Embedded test bot', 'short_name': 'embeddedservicebot', 'bot_type': UserProfile.EMBEDDED_BOT, 'service_name': 'not_existing_service', } bot_info.update(extras) result = self.client_post("/json/bots", bot_info) self.assert_json_error(result, 'Invalid embedded bot name.') # Test to create embedded bot with an invalid config value malformatted_bot_config_info = {'foo': ['bar', 'baz']} bot_info = { 'full_name': 'Embedded test bot', 'short_name': 'embeddedservicebot2', 'bot_type': UserProfile.EMBEDDED_BOT, 'service_name': 'followup', 'config_data': ujson.dumps(malformatted_bot_config_info) } bot_info.update(extras) result = self.client_post("/json/bots", bot_info) self.assert_json_error(result, 'config_data contains a value that is not a string')
def test_non_generic_payload_ignore_pm_notification(self) -> None: expected_message = MISCONFIGURED_PAYLOAD_TYPE_ERROR_MESSAGE.format( bot_name=get_user('*****@*****.**', get_realm('zulip')).full_name, support_email=FromAddress.SUPPORT ).strip() payload = self.get_body('slack_non_generic_payload') self.client_post(self.url, payload, content_type="application/json") msg = self.get_last_message() self.assertEqual(msg.content, expected_message) self.assertEqual(msg.recipient.type, Recipient.PERSONAL)
def deactivate_user_backend(request: HttpRequest, user_profile: UserProfile, email: Text) -> HttpResponse: try: target = get_user(email, user_profile.realm) except UserProfile.DoesNotExist: return json_error(_('No such user')) if target.is_bot: return json_error(_('No such user')) if check_last_admin(target): return json_error(_('Cannot deactivate the only organization administrator')) return _deactivate_user_profile_backend(request, user_profile, target)
def get_streams(self, email, realm): # type: (Text, Realm) -> List[Text] """ Helper function to get the stream names for a user """ user_profile = get_user(email, realm) subs = Subscription.objects.filter( user_profile=user_profile, active=True, recipient__type=Recipient.STREAM) return [cast(Text, get_display_recipient(sub.recipient)) for sub in subs]
def send_huddle_message(self, from_email: str, to_emails: List[str], content: str="test content", sender_realm: str="zulip") -> int: sender = get_user(from_email, get_realm(sender_realm)) assert(len(to_emails) >= 2) (sending_client, _) = Client.objects.get_or_create(name="test suite") return check_send_message( sender, sending_client, 'private', to_emails, None, content )
def reactivate_user_backend(request: HttpRequest, user_profile: UserProfile, email: Text) -> HttpResponse: try: target = get_user(email, user_profile.realm) except UserProfile.DoesNotExist: return json_error(_('No such user')) if not user_profile.can_admin_user(target): return json_error(_('Insufficient permission')) do_reactivate_user(target, acting_user=user_profile) return json_success()
def patch_bot_backend( request: HttpRequest, user_profile: UserProfile, email: Text, full_name: Optional[Text] = REQ(default=None), bot_owner: Optional[Text] = REQ(default=None), config_data: Optional[Dict[Text, Text]] = REQ( default=None, validator=check_dict(value_validator=check_string)), service_payload_url: Optional[Text] = REQ(validator=check_url, default=None), service_interface: Optional[int] = REQ(validator=check_int, default=1), default_sending_stream: Optional[Text] = REQ(default=None), default_events_register_stream: Optional[Text] = REQ(default=None), default_all_public_streams: Optional[bool] = REQ(default=None, validator=check_bool) ) -> HttpResponse: try: bot = get_user(email, user_profile.realm) except UserProfile.DoesNotExist: return json_error(_('No such user')) if not bot.is_bot: return json_error(_('No such bot')) if not user_profile.can_admin_user(bot): return json_error(_('Insufficient permission')) if full_name is not None: check_change_full_name(bot, full_name, user_profile) if bot_owner is not None: try: owner = get_user(bot_owner, user_profile.realm) except UserProfile.DoesNotExist: return json_error(_('Failed to change owner, no such user')) if not owner.is_active: return json_error(_('Failed to change owner, user is deactivated')) if owner.is_bot: return json_error( _("Failed to change owner, bots can't own other bots")) do_change_bot_owner(bot, owner, user_profile) if default_sending_stream is not None: if default_sending_stream == "": stream = None # type: Optional[Stream] else: (stream, recipient, sub) = access_stream_by_name(user_profile, default_sending_stream) do_change_default_sending_stream(bot, stream) if default_events_register_stream is not None: if default_events_register_stream == "": stream = None else: (stream, recipient, sub) = access_stream_by_name(user_profile, default_events_register_stream) do_change_default_events_register_stream(bot, stream) if default_all_public_streams is not None: do_change_default_all_public_streams(bot, default_all_public_streams) if service_payload_url is not None: check_valid_interface_type(service_interface) do_update_outgoing_webhook_service(bot, service_interface, service_payload_url) if config_data is not None: do_update_bot_config_data(bot, config_data) if len(request.FILES) == 0: pass elif len(request.FILES) == 1: user_file = list(request.FILES.values())[0] upload_avatar_image(user_file, user_profile, bot) avatar_source = UserProfile.AVATAR_FROM_USER do_change_avatar_fields(bot, avatar_source) else: return json_error(_("You may only upload one file at a time")) json_result = dict( full_name=bot.full_name, avatar_url=avatar_url(bot), service_interface=service_interface, service_payload_url=service_payload_url, config_data=config_data, default_sending_stream=get_stream_name(bot.default_sending_stream), default_events_register_stream=get_stream_name( bot.default_events_register_stream), default_all_public_streams=bot.default_all_public_streams, ) # Don't include the bot owner in case it is not set. # Default bots have no owner. if bot.bot_owner is not None: json_result['bot_owner'] = bot.bot_owner.email return json_success(json_result)
def create_user(email: Text, realm_id: Text) -> UserProfile: self.register(email, 'test', subdomain=realm_id) return get_user(email, get_realm(realm_id))
def create_user_groups() -> None: zulip = get_realm('zulip') members = [get_user('*****@*****.**', zulip), get_user('*****@*****.**', zulip)] create_user_group("hamletcharacters", members, zulip, description="Characters of Hamlet")
def test_send_all_webhook_fixture_messages_for_success_with_non_json_fixtures(self) -> None: bot = get_user("*****@*****.**", self.zulip_realm) url = f"/api/v1/external/wordpress?api_key={bot.api_key}&stream=Denmark&topic=WordPress bulk notifications" target_url = "/devtools/integrations/send_all_webhook_fixture_messages" data = { "url": url, "custom_headers": "{}", "integration_name": "wordpress", } response = self.client_post(target_url, data) expected_responses = [ { "message": { "msg": "Unknown WordPress webhook action: WordPress action", "result": "error", "code": "BAD_REQUEST", }, "fixture_name": "user_register.txt", "status_code": 400, }, { "message": { "msg": "Unknown WordPress webhook action: WordPress action", "result": "error", "code": "BAD_REQUEST", }, "fixture_name": "publish_post_no_data_provided.txt", "status_code": 400, }, { "message": { "msg": "Unknown WordPress webhook action: WordPress action", "result": "error", "code": "BAD_REQUEST", }, "fixture_name": "unknown_action_no_data.txt", "status_code": 400, }, { "message": { "msg": "Unknown WordPress webhook action: WordPress action", "result": "error", "code": "BAD_REQUEST", }, "fixture_name": "publish_page.txt", "status_code": 400, }, { "message": { "msg": "Unknown WordPress webhook action: WordPress action", "result": "error", "code": "BAD_REQUEST", }, "fixture_name": "unknown_action_no_hook_provided.txt", "status_code": 400, }, { "message": { "msg": "Unknown WordPress webhook action: WordPress action", "result": "error", "code": "BAD_REQUEST", }, "fixture_name": "publish_post_type_not_provided.txt", "status_code": 400, }, { "message": { "msg": "Unknown WordPress webhook action: WordPress action", "result": "error", "code": "BAD_REQUEST", }, "fixture_name": "wp_login.txt", "status_code": 400, }, { "message": { "msg": "Unknown WordPress webhook action: WordPress action", "result": "error", "code": "BAD_REQUEST", }, "fixture_name": "publish_post.txt", "status_code": 400, }, ] responses = orjson.loads(response.content)["responses"] for r in responses: r["message"] = orjson.loads(r["message"]) self.assertEqual(response.status_code, 200) for r in responses: # We have to use this roundabout manner since the order may vary each time. This is not # an issue. Basically, we're trying to compare 2 lists and since we're not resorting to # using sets or a sorted order, we're sticking with O(n*m) time complexity for this # comparison (where n and m are the lengths of the two lists respectively). But since # this is just a unit test and more importantly n = m = some-low-number we don't really # care about the time complexity being what it is. self.assertTrue(r in expected_responses) expected_responses.remove(r)
def handle(self, *args: Any, **options: Any) -> None: # Suppress spammy output from the push notifications logger push_notifications_logger.disabled = True if options["percent_huddles"] + options["percent_personals"] > 100: self.stderr.write("Error! More than 100% of messages allocated.\n") return # Get consistent data for backend tests. if options["test_suite"]: random.seed(0) with connection.cursor() as cursor: # Sometimes bugs relating to confusing recipient.id for recipient.type_id # or <object>.id for <object>.recipient_id remain undiscovered by the test suite # due to these numbers happening to coincide in such a way that it makes tests # accidentally pass. By bumping the Recipient.id sequence by a large enough number, # we can have those ids in a completely different range of values than object ids, # eliminatng the possibility of such coincidences. cursor.execute("SELECT setval('zerver_recipient_id_seq', 100)") # If max_topics is not set, we set it proportional to the # number of messages. if options["max_topics"] is None: options["max_topics"] = 1 + options["num_messages"] // 100 if options["delete"]: # Start by clearing all the data in our database clear_database() # Create our three default realms # Could in theory be done via zerver.lib.actions.do_create_realm, but # welcome-bot (needed for do_create_realm) hasn't been created yet create_internal_realm() zulip_realm = do_create_realm( string_id="zulip", name="Zulip Dev", emails_restricted_to_domains=False, email_address_visibility=Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS, description="The Zulip development environment default organization." " It's great for testing!", invite_required=False, plan_type=Realm.SELF_HOSTED, org_type=Realm.ORG_TYPES["business"]["id"], ) RealmDomain.objects.create(realm=zulip_realm, domain="zulip.com") assert zulip_realm.notifications_stream is not None zulip_realm.notifications_stream.name = "Verona" zulip_realm.notifications_stream.description = "A city in Italy" zulip_realm.notifications_stream.save(update_fields=["name", "description"]) if options["test_suite"]: mit_realm = do_create_realm( string_id="zephyr", name="MIT", emails_restricted_to_domains=True, invite_required=False, plan_type=Realm.SELF_HOSTED, org_type=Realm.ORG_TYPES["business"]["id"], ) RealmDomain.objects.create(realm=mit_realm, domain="mit.edu") lear_realm = do_create_realm( string_id="lear", name="Lear & Co.", emails_restricted_to_domains=False, invite_required=False, plan_type=Realm.SELF_HOSTED, org_type=Realm.ORG_TYPES["business"]["id"], ) # Default to allowing all members to send mentions in # large streams for the test suite to keep # mention-related tests simple. zulip_realm.wildcard_mention_policy = Realm.WILDCARD_MENTION_POLICY_MEMBERS zulip_realm.save(update_fields=["wildcard_mention_policy"]) # Create test Users (UserProfiles are automatically created, # as are subscriptions to the ability to receive personals). names = [ ("Zoe", "*****@*****.**"), ("Othello, the Moor of Venice", "*****@*****.**"), ("Iago", "*****@*****.**"), ("Prospero from The Tempest", "*****@*****.**"), ("Cordelia, Lear's daughter", "*****@*****.**"), ("King Hamlet", "*****@*****.**"), ("aaron", "*****@*****.**"), ("Polonius", "*****@*****.**"), ("Desdemona", "*****@*****.**"), ("शिव", "*****@*****.**"), ] # For testing really large batches: # Create extra users with semi realistic names to make search # functions somewhat realistic. We'll still create 1000 users # like Extra222 User for some predicability. num_names = options["extra_users"] num_boring_names = 300 for i in range(min(num_names, num_boring_names)): full_name = f"Extra{i:03} User" names.append((full_name, f"extrauser{i}@zulip.com")) if num_names > num_boring_names: fnames = [ "Amber", "Arpita", "Bob", "Cindy", "Daniela", "Dan", "Dinesh", "Faye", "François", "George", "Hank", "Irene", "James", "Janice", "Jenny", "Jill", "John", "Kate", "Katelyn", "Kobe", "Lexi", "Manish", "Mark", "Matt", "Mayna", "Michael", "Pete", "Peter", "Phil", "Phillipa", "Preston", "Sally", "Scott", "Sandra", "Steve", "Stephanie", "Vera", ] mnames = ["de", "van", "von", "Shaw", "T."] lnames = [ "Adams", "Agarwal", "Beal", "Benson", "Bonita", "Davis", "George", "Harden", "James", "Jones", "Johnson", "Jordan", "Lee", "Leonard", "Singh", "Smith", "Patel", "Towns", "Wall", ] non_ascii_names = [ "Günter", "أحمد", "Magnús", "आशी", "イツキ", "语嫣", "அருண்", "Александр", "José", ] # to imitate emoji insertions in usernames raw_emojis = ["😎", "😂", "🐱👤"] for i in range(num_boring_names, num_names): fname = random.choice(fnames) + str(i) full_name = fname if random.random() < 0.7: if random.random() < 0.3: full_name += " " + random.choice(non_ascii_names) else: full_name += " " + random.choice(mnames) if random.random() < 0.1: full_name += " {} ".format(random.choice(raw_emojis)) else: full_name += " " + random.choice(lnames) email = fname.lower() + "@zulip.com" names.append((full_name, email)) create_users(zulip_realm, names, tos_version=settings.TOS_VERSION) iago = get_user_by_delivery_email("*****@*****.**", zulip_realm) do_change_user_role(iago, UserProfile.ROLE_REALM_ADMINISTRATOR, acting_user=None) iago.is_staff = True iago.save(update_fields=["is_staff"]) # We need to create at least two test draft for Iago for the sake # of the cURL tests. Two since one will be deleted. Draft.objects.create( user_profile=iago, recipient=None, topic="Release Notes", content="Release 4.0 will contain ...", last_edit_time=datetime.now(), ) Draft.objects.create( user_profile=iago, recipient=None, topic="Release Notes", content="Release 4.0 will contain many new features such as ... ", last_edit_time=datetime.now(), ) desdemona = get_user_by_delivery_email("*****@*****.**", zulip_realm) do_change_user_role(desdemona, UserProfile.ROLE_REALM_OWNER, acting_user=None) shiva = get_user_by_delivery_email("*****@*****.**", zulip_realm) do_change_user_role(shiva, UserProfile.ROLE_MODERATOR, acting_user=None) guest_user = get_user_by_delivery_email("*****@*****.**", zulip_realm) guest_user.role = UserProfile.ROLE_GUEST guest_user.save(update_fields=["role"]) # These bots are directly referenced from code and thus # are needed for the test suite. zulip_realm_bots = [ ("Zulip Error Bot", "*****@*****.**"), ("Zulip Default Bot", "*****@*****.**"), ] for i in range(options["extra_bots"]): zulip_realm_bots.append((f"Extra Bot {i}", f"extrabot{i}@zulip.com")) create_users(zulip_realm, zulip_realm_bots, bot_type=UserProfile.DEFAULT_BOT) zoe = get_user_by_delivery_email("*****@*****.**", zulip_realm) zulip_webhook_bots = [ ("Zulip Webhook Bot", "*****@*****.**"), ] # If a stream is not supplied in the webhook URL, the webhook # will (in some cases) send the notification as a PM to the # owner of the webhook bot, so bot_owner can't be None create_users( zulip_realm, zulip_webhook_bots, bot_type=UserProfile.INCOMING_WEBHOOK_BOT, bot_owner=zoe, ) aaron = get_user_by_delivery_email("*****@*****.**", zulip_realm) zulip_outgoing_bots = [ ("Outgoing Webhook", "*****@*****.**"), ] create_users( zulip_realm, zulip_outgoing_bots, bot_type=UserProfile.OUTGOING_WEBHOOK_BOT, bot_owner=aaron, ) outgoing_webhook = get_user("*****@*****.**", zulip_realm) add_service( "outgoing-webhook", user_profile=outgoing_webhook, interface=Service.GENERIC, base_url="http://127.0.0.1:5002", token=generate_api_key(), ) # Add the realm internal bots to each realm. create_if_missing_realm_internal_bots() # Create public streams. signups_stream = Realm.INITIAL_PRIVATE_STREAM_NAME stream_list = [ "Verona", "Denmark", "Scotland", "Venice", "Rome", signups_stream, ] stream_dict: Dict[str, Dict[str, Any]] = { "Denmark": {"description": "A Scandinavian country"}, "Scotland": {"description": "Located in the United Kingdom"}, "Venice": {"description": "A northeastern Italian city"}, "Rome": {"description": "Yet another Italian city", "is_web_public": True}, } bulk_create_streams(zulip_realm, stream_dict) recipient_streams: List[int] = [ Stream.objects.get(name=name, realm=zulip_realm).id for name in stream_list ] # Create subscriptions to streams. The following # algorithm will give each of the users a different but # deterministic subset of the streams (given a fixed list # of users). For the test suite, we have a fixed list of # subscriptions to make sure test data is consistent # across platforms. subscriptions_list: List[Tuple[UserProfile, Recipient]] = [] profiles: Sequence[UserProfile] = list( UserProfile.objects.select_related().filter(is_bot=False).order_by("email") ) if options["test_suite"]: subscriptions_map = { "*****@*****.**": ["Verona"], "*****@*****.**": ["Verona"], "*****@*****.**": ["Verona", "Denmark", signups_stream], "*****@*****.**": [ "Verona", "Denmark", "Scotland", signups_stream, ], "*****@*****.**": ["Verona", "Denmark", "Scotland"], "*****@*****.**": ["Verona", "Denmark", "Scotland", "Venice"], "*****@*****.**": ["Verona", "Denmark", "Scotland", "Venice", "Rome"], "*****@*****.**": ["Verona"], "*****@*****.**": [ "Verona", "Denmark", "Venice", signups_stream, ], "*****@*****.**": ["Verona", "Denmark", "Scotland"], } for profile in profiles: email = profile.delivery_email if email not in subscriptions_map: raise Exception(f"Subscriptions not listed for user {email}") for stream_name in subscriptions_map[email]: stream = Stream.objects.get(name=stream_name, realm=zulip_realm) r = Recipient.objects.get(type=Recipient.STREAM, type_id=stream.id) subscriptions_list.append((profile, r)) else: num_streams = len(recipient_streams) num_users = len(profiles) for i, profile in enumerate(profiles): # Subscribe to some streams. fraction = float(i) / num_users num_recips = int(num_streams * fraction) + 1 for type_id in recipient_streams[:num_recips]: r = Recipient.objects.get(type=Recipient.STREAM, type_id=type_id) subscriptions_list.append((profile, r)) subscriptions_to_add: List[Subscription] = [] event_time = timezone_now() all_subscription_logs: (List[RealmAuditLog]) = [] i = 0 for profile, recipient in subscriptions_list: i += 1 color = STREAM_ASSIGNMENT_COLORS[i % len(STREAM_ASSIGNMENT_COLORS)] s = Subscription( recipient=recipient, user_profile=profile, is_user_active=profile.is_active, color=color, ) subscriptions_to_add.append(s) log = RealmAuditLog( realm=profile.realm, modified_user=profile, modified_stream_id=recipient.type_id, event_last_message_id=0, event_type=RealmAuditLog.SUBSCRIPTION_CREATED, event_time=event_time, ) all_subscription_logs.append(log) Subscription.objects.bulk_create(subscriptions_to_add) RealmAuditLog.objects.bulk_create(all_subscription_logs) # Create custom profile field data phone_number = try_add_realm_custom_profile_field( zulip_realm, "Phone number", CustomProfileField.SHORT_TEXT, hint="" ) biography = try_add_realm_custom_profile_field( zulip_realm, "Biography", CustomProfileField.LONG_TEXT, hint="What are you known for?", ) favorite_food = try_add_realm_custom_profile_field( zulip_realm, "Favorite food", CustomProfileField.SHORT_TEXT, hint="Or drink, if you'd prefer", ) field_data: ProfileFieldData = { "vim": {"text": "Vim", "order": "1"}, "emacs": {"text": "Emacs", "order": "2"}, } favorite_editor = try_add_realm_custom_profile_field( zulip_realm, "Favorite editor", CustomProfileField.SELECT, field_data=field_data ) birthday = try_add_realm_custom_profile_field( zulip_realm, "Birthday", CustomProfileField.DATE ) favorite_website = try_add_realm_custom_profile_field( zulip_realm, "Favorite website", CustomProfileField.URL, hint="Or your personal blog's URL", ) mentor = try_add_realm_custom_profile_field( zulip_realm, "Mentor", CustomProfileField.USER ) github_profile = try_add_realm_default_custom_profile_field(zulip_realm, "github") # Fill in values for Iago and Hamlet hamlet = get_user_by_delivery_email("*****@*****.**", zulip_realm) do_update_user_custom_profile_data_if_changed( iago, [ {"id": phone_number.id, "value": "+1-234-567-8901"}, {"id": biography.id, "value": "Betrayer of Othello."}, {"id": favorite_food.id, "value": "Apples"}, {"id": favorite_editor.id, "value": "emacs"}, {"id": birthday.id, "value": "2000-01-01"}, {"id": favorite_website.id, "value": "https://zulip.readthedocs.io/en/latest/"}, {"id": mentor.id, "value": [hamlet.id]}, {"id": github_profile.id, "value": "zulip"}, ], ) do_update_user_custom_profile_data_if_changed( hamlet, [ {"id": phone_number.id, "value": "+0-11-23-456-7890"}, { "id": biography.id, "value": "I am:\n* The prince of Denmark\n* Nephew to the usurping Claudius", }, {"id": favorite_food.id, "value": "Dark chocolate"}, {"id": favorite_editor.id, "value": "vim"}, {"id": birthday.id, "value": "1900-01-01"}, {"id": favorite_website.id, "value": "https://blog.zulig.org"}, {"id": mentor.id, "value": [iago.id]}, {"id": github_profile.id, "value": "zulipbot"}, ], ) else: zulip_realm = get_realm("zulip") recipient_streams = [ klass.type_id for klass in Recipient.objects.filter(type=Recipient.STREAM) ] # Extract a list of all users user_profiles: List[UserProfile] = list(UserProfile.objects.filter(is_bot=False)) # Create a test realm emoji. IMAGE_FILE_PATH = static_path("images/test-images/checkbox.png") with open(IMAGE_FILE_PATH, "rb") as fp: check_add_realm_emoji(zulip_realm, "green_tick", iago, File(fp)) if not options["test_suite"]: # Populate users with some bar data for user in user_profiles: status: int = UserPresence.ACTIVE date = timezone_now() client = get_client("website") if user.full_name[0] <= "H": client = get_client("ZulipAndroid") UserPresence.objects.get_or_create( user_profile=user, realm_id=user.realm_id, client=client, timestamp=date, status=status, ) user_profiles_ids = [user_profile.id for user_profile in user_profiles] # Create several initial huddles for i in range(options["num_huddles"]): get_huddle(random.sample(user_profiles_ids, random.randint(3, 4))) # Create several initial pairs for personals personals_pairs = [ random.sample(user_profiles_ids, 2) for i in range(options["num_personals"]) ] create_alert_words(zulip_realm.id) # Generate a new set of test data. create_test_data() # prepopulate the URL preview/embed data for the links present # in the config.generate_data.json data set. This makes it # possible for populate_db to run happily without Internet # access. with open("zerver/tests/fixtures/docs_url_preview_data.json", "rb") as f: urls_with_preview_data = orjson.loads(f.read()) for url in urls_with_preview_data: cache_set(url, urls_with_preview_data[url], PREVIEW_CACHE_NAME) if options["delete"]: if options["test_suite"]: # Create test users; the MIT ones are needed to test # the Zephyr mirroring codepaths. testsuite_mit_users = [ ("Fred Sipb (MIT)", "*****@*****.**"), ("Athena Consulting Exchange User (MIT)", "*****@*****.**"), ("Esp Classroom (MIT)", "*****@*****.**"), ] create_users(mit_realm, testsuite_mit_users, tos_version=settings.TOS_VERSION) testsuite_lear_users = [ ("King Lear", "*****@*****.**"), ("Cordelia, Lear's daughter", "*****@*****.**"), ] create_users(lear_realm, testsuite_lear_users, tos_version=settings.TOS_VERSION) if not options["test_suite"]: # To keep the messages.json fixtures file for the test # suite fast, don't add these users and subscriptions # when running populate_db for the test suite # to imitate emoji insertions in stream names raw_emojis = ["😎", "😂", "🐱👤"] zulip_stream_dict: Dict[str, Dict[str, Any]] = { "devel": {"description": "For developing"}, # ビデオゲーム - VideoGames (japanese) "ビデオゲーム": { "description": "Share your favorite video games! {}".format(raw_emojis[2]) }, "announce": { "description": "For announcements", "stream_post_policy": Stream.STREAM_POST_POLICY_ADMINS, }, "design": {"description": "For design"}, "support": {"description": "For support"}, "social": {"description": "For socializing"}, "test": {"description": "For testing `code`"}, "errors": {"description": "For errors"}, # 조리법 - Recipes (Korean) , Пельмени - Dumplings (Russian) "조리법 " + raw_emojis[0]: {"description": "Everything cooking, from pasta to Пельмени"}, } extra_stream_names = [ "802.11a", "Ad Hoc Network", "Augmented Reality", "Cycling", "DPI", "FAQ", "FiFo", "commits", "Control panel", "desktop", "компьютеры", "Data security", "desktop", "काम", "discussions", "Cloud storage", "GCI", "Vaporware", "Recent Trends", "issues", "live", "Health", "mobile", "空間", "provision", "hidrógeno", "HR", "アニメ", ] # Add stream names and stream descriptions for i in range(options["extra_streams"]): extra_stream_name = random.choice(extra_stream_names) + " " + str(i) # to imitate emoji insertions in stream names if random.random() <= 0.15: extra_stream_name += random.choice(raw_emojis) zulip_stream_dict[extra_stream_name] = { "description": "Auto-generated extra stream.", } bulk_create_streams(zulip_realm, zulip_stream_dict) # Now that we've created the notifications stream, configure it properly. zulip_realm.notifications_stream = get_stream("announce", zulip_realm) zulip_realm.save(update_fields=["notifications_stream"]) # Add a few default streams for default_stream_name in ["design", "devel", "social", "support"]: DefaultStream.objects.create( realm=zulip_realm, stream=get_stream(default_stream_name, zulip_realm) ) # Now subscribe everyone to these streams subscribe_users_to_streams(zulip_realm, zulip_stream_dict) create_user_groups() if not options["test_suite"]: # We populate the analytics database here for # development purpose only call_command("populate_analytics_db") threads = options["threads"] jobs: List[Tuple[int, List[List[int]], Dict[str, Any], int]] = [] for i in range(threads): count = options["num_messages"] // threads if i < options["num_messages"] % threads: count += 1 jobs.append((count, personals_pairs, options, random.randint(0, 10 ** 10))) for job in jobs: generate_and_send_messages(job) if options["delete"]: if not options["test_suite"]: # These bots are not needed by the test suite # Also, we don't want interacting with each other # in dev setup. internal_zulip_users_nosubs = [ ("Zulip Commit Bot", "*****@*****.**"), ("Zulip Trac Bot", "*****@*****.**"), ("Zulip Nagios Bot", "*****@*****.**"), ] create_users( zulip_realm, internal_zulip_users_nosubs, bot_type=UserProfile.DEFAULT_BOT ) mark_all_messages_as_read() self.stdout.write("Successfully populated test database.\n") push_notifications_logger.disabled = False
def test_do_convert_data(self) -> None: mattermost_data_dir = self.fixture_file_name("", "mattermost_fixtures") output_dir = self.make_import_output_dir("mattermost") with patch("builtins.print") as mock_print, self.assertLogs( level="WARNING") as warn_log: do_convert_data( mattermost_data_dir=mattermost_data_dir, output_dir=output_dir, masking_content=False, ) self.assertEqual( mock_print.mock_calls, [ call("Generating data for", "gryffindor"), call("Generating data for", "slytherin") ], ) self.assertEqual( warn_log.output, [ "WARNING:root:Skipping importing huddles and PMs since there are multiple teams in the export", "WARNING:root:Skipping importing huddles and PMs since there are multiple teams in the export", ], ) harry_team_output_dir = self.team_output_dir(output_dir, "gryffindor") self.assertEqual( os.path.exists(os.path.join(harry_team_output_dir, "avatars")), True) self.assertEqual( os.path.exists(os.path.join(harry_team_output_dir, "emoji")), True) self.assertEqual( os.path.exists( os.path.join(harry_team_output_dir, "attachment.json")), True) realm = self.read_file(harry_team_output_dir, "realm.json") self.assertEqual("Organization imported from Mattermost!", realm["zerver_realm"][0]["description"]) exported_user_ids = self.get_set(realm["zerver_userprofile"], "id") exported_user_full_names = self.get_set(realm["zerver_userprofile"], "full_name") self.assertEqual({"Harry Potter", "Ron Weasley", "Severus Snape"}, exported_user_full_names) exported_user_emails = self.get_set(realm["zerver_userprofile"], "email") self.assertEqual( {"*****@*****.**", "*****@*****.**", "*****@*****.**"}, exported_user_emails) self.assert_length(realm["zerver_stream"], 3) exported_stream_names = self.get_set(realm["zerver_stream"], "name") self.assertEqual( exported_stream_names, { "Gryffindor common room", "Gryffindor quidditch team", "Dumbledores army" }, ) self.assertEqual(self.get_set(realm["zerver_stream"], "realm"), {realm["zerver_realm"][0]["id"]}) self.assertEqual(self.get_set(realm["zerver_stream"], "deactivated"), {False}) self.assert_length(realm["zerver_defaultstream"], 0) exported_recipient_ids = self.get_set(realm["zerver_recipient"], "id") self.assert_length(exported_recipient_ids, 6) exported_recipient_types = self.get_set(realm["zerver_recipient"], "type") self.assertEqual(exported_recipient_types, {1, 2}) exported_recipient_type_ids = self.get_set(realm["zerver_recipient"], "type_id") self.assert_length(exported_recipient_type_ids, 3) exported_subscription_userprofile = self.get_set( realm["zerver_subscription"], "user_profile") self.assert_length(exported_subscription_userprofile, 3) exported_subscription_recipients = self.get_set( realm["zerver_subscription"], "recipient") self.assert_length(exported_subscription_recipients, 6) messages = self.read_file(harry_team_output_dir, "messages-000001.json") exported_messages_id = self.get_set(messages["zerver_message"], "id") self.assertIn(messages["zerver_message"][0]["sender"], exported_user_ids) self.assertIn(messages["zerver_message"][0]["recipient"], exported_recipient_ids) self.assertIn(messages["zerver_message"][0]["content"], "harry joined the channel.\n\n") exported_usermessage_userprofiles = self.get_set( messages["zerver_usermessage"], "user_profile") self.assert_length(exported_usermessage_userprofiles, 3) exported_usermessage_messages = self.get_set( messages["zerver_usermessage"], "message") self.assertEqual(exported_usermessage_messages, exported_messages_id) with self.assertLogs(level="INFO"): do_import_realm( import_dir=harry_team_output_dir, subdomain="gryffindor", ) realm = get_realm("gryffindor") self.assertFalse(get_user("*****@*****.**", realm).is_mirror_dummy) self.assertFalse(get_user("*****@*****.**", realm).is_mirror_dummy) self.assertTrue(get_user("*****@*****.**", realm).is_mirror_dummy) messages = Message.objects.filter(sender__realm=realm) for message in messages: self.assertIsNotNone(message.rendered_content)
def example_user(self, name: str) -> UserProfile: email = self.example_user_map[name] return get_user(email, get_realm('zulip'))
def nonreg_user(self, name: str) -> UserProfile: email = self.nonreg_user_map[name] return get_user(email, get_realm("zulip"))
def test_user(self) -> UserProfile: return get_user(self.TEST_USER_EMAIL, get_realm("zulip"))
def apply_event(state: Dict[str, Any], event: Dict[str, Any], user_profile: UserProfile, client_gravatar: bool, include_subscribers: bool) -> None: if event['type'] == "message": state['max_message_id'] = max(state['max_message_id'], event['message']['id']) if 'raw_unread_msgs' in state: apply_unread_message_event( user_profile, state['raw_unread_msgs'], event['message'], event['flags'], ) # Below, we handle maintaining first_message_id. if event['message']['type'] != "stream": return for sub_dict in state['subscriptions']: if event['message']['stream_id'] == sub_dict['stream_id']: if sub_dict['first_message_id'] is None: sub_dict['first_message_id'] = event['message']['id'] for stream_dict in state['streams']: if event['message']['stream_id'] == stream_dict['stream_id']: if stream_dict['first_message_id'] is None: stream_dict['first_message_id'] = event['message']['id'] elif event['type'] == "hotspots": state['hotspots'] = event['hotspots'] elif event['type'] == "custom_profile_fields": state['custom_profile_fields'] = event['fields'] elif event['type'] == "pointer": state['pointer'] = max(state['pointer'], event['pointer']) elif event['type'] == "realm_user": person = event['person'] person_user_id = person['user_id'] if event['op'] == "add": person = copy.deepcopy(person) if client_gravatar: if 'gravatar.com' in person['avatar_url']: person['avatar_url'] = None person['is_active'] = True if not person['is_bot']: person['profile_data'] = {} state['raw_users'][person_user_id] = person elif event['op'] == "remove": state['raw_users'][person_user_id]['is_active'] = False elif event['op'] == 'update': is_me = (person_user_id == user_profile.id) if is_me: if ('avatar_url' in person and 'avatar_url' in state): state['avatar_source'] = person['avatar_source'] state['avatar_url'] = person['avatar_url'] state['avatar_url_medium'] = person['avatar_url_medium'] for field in ['is_admin', 'delivery_email', 'email', 'full_name']: if field in person and field in state: state[field] = person[field] # In the unlikely event that the current user # just changed to/from being an admin, we need # to add/remove the data on all bots in the # realm. This is ugly and probably better # solved by removing the all-realm-bots data # given to admin users from this flow. if ('is_admin' in person and 'realm_bots' in state): prev_state = state['raw_users'][user_profile.id] was_admin = prev_state['is_admin'] now_admin = person['is_admin'] if was_admin and not now_admin: state['realm_bots'] = [] if not was_admin and now_admin: state['realm_bots'] = get_owned_bot_dicts(user_profile) if client_gravatar and 'avatar_url' in person: # Respect the client_gravatar setting in the `users` data. if 'gravatar.com' in person['avatar_url']: person['avatar_url'] = None person['avatar_url_medium'] = None if person_user_id in state['raw_users']: p = state['raw_users'][person_user_id] for field in p: if field in person: p[field] = person[field] if 'custom_profile_field' in person: custom_field_id = person['custom_profile_field']['id'] custom_field_new_value = person['custom_profile_field']['value'] if 'rendered_value' in person['custom_profile_field']: p['profile_data'][custom_field_id] = { 'value': custom_field_new_value, 'rendered_value': person['custom_profile_field']['rendered_value'] } else: p['profile_data'][custom_field_id] = { 'value': custom_field_new_value } elif event['type'] == 'realm_bot': if event['op'] == 'add': state['realm_bots'].append(event['bot']) if event['op'] == 'remove': email = event['bot']['email'] for bot in state['realm_bots']: if bot['email'] == email: bot['is_active'] = False if event['op'] == 'delete': state['realm_bots'] = [item for item in state['realm_bots'] if item['email'] != event['bot']['email']] if event['op'] == 'update': for bot in state['realm_bots']: if bot['email'] == event['bot']['email']: if 'owner_id' in event['bot']: bot['owner'] = get_user_profile_by_id(event['bot']['owner_id']).email else: bot.update(event['bot']) elif event['type'] == 'stream': if event['op'] == 'create': for stream in event['streams']: if not stream['invite_only']: stream_data = copy.deepcopy(stream) if include_subscribers: stream_data['subscribers'] = [] stream_data['stream_weekly_traffic'] = None stream_data['is_old_stream'] = False stream_data['is_announcement_only'] = False # Add stream to never_subscribed (if not invite_only) state['never_subscribed'].append(stream_data) state['streams'].append(stream) state['streams'].sort(key=lambda elt: elt["name"]) if event['op'] == 'delete': deleted_stream_ids = {stream['stream_id'] for stream in event['streams']} state['streams'] = [s for s in state['streams'] if s['stream_id'] not in deleted_stream_ids] state['never_subscribed'] = [stream for stream in state['never_subscribed'] if stream['stream_id'] not in deleted_stream_ids] if event['op'] == 'update': # For legacy reasons, we call stream data 'subscriptions' in # the state var here, for the benefit of the JS code. for obj in state['subscriptions']: if obj['name'].lower() == event['name'].lower(): obj[event['property']] = event['value'] if event['property'] == "description": obj['rendered_description'] = event['rendered_description'] # Also update the pure streams data for stream in state['streams']: if stream['name'].lower() == event['name'].lower(): prop = event['property'] if prop in stream: stream[prop] = event['value'] if prop == 'description': stream['rendered_description'] = event['rendered_description'] elif event['op'] == "occupy": state['streams'] += event['streams'] elif event['op'] == "vacate": stream_ids = [s["stream_id"] for s in event['streams']] state['streams'] = [s for s in state['streams'] if s["stream_id"] not in stream_ids] elif event['type'] == 'default_streams': state['realm_default_streams'] = event['default_streams'] elif event['type'] == 'default_stream_groups': state['realm_default_stream_groups'] = event['default_stream_groups'] elif event['type'] == 'realm': if event['op'] == "update": field = 'realm_' + event['property'] state[field] = event['value'] # Tricky interaction: Whether we can create streams can get changed here. if (field in ['realm_create_stream_by_admins_only', 'realm_waiting_period_threshold']) and 'can_create_streams' in state: state['can_create_streams'] = user_profile.can_create_streams() state['can_subscribe_other_users'] = user_profile.can_subscribe_other_users() elif event['op'] == "update_dict": for key, value in event['data'].items(): state['realm_' + key] = value # It's a bit messy, but this is where we need to # update the state for whether password authentication # is enabled on this server. if key == 'authentication_methods': state['realm_password_auth_enabled'] = (value['Email'] or value['LDAP']) state['realm_email_auth_enabled'] = value['Email'] elif event['type'] == "subscription": if not include_subscribers and event['op'] in ['peer_add', 'peer_remove']: return if event['op'] in ["add"]: if not include_subscribers: # Avoid letting 'subscribers' entries end up in the list for i, sub in enumerate(event['subscriptions']): event['subscriptions'][i] = copy.deepcopy(event['subscriptions'][i]) del event['subscriptions'][i]['subscribers'] def name(sub: Dict[str, Any]) -> str: return sub['name'].lower() if event['op'] == "add": added_names = set(map(name, event["subscriptions"])) was_added = lambda s: name(s) in added_names # add the new subscriptions state['subscriptions'] += event['subscriptions'] # remove them from unsubscribed if they had been there state['unsubscribed'] = [s for s in state['unsubscribed'] if not was_added(s)] # remove them from never_subscribed if they had been there state['never_subscribed'] = [s for s in state['never_subscribed'] if not was_added(s)] elif event['op'] == "remove": removed_names = set(map(name, event["subscriptions"])) was_removed = lambda s: name(s) in removed_names # Find the subs we are affecting. removed_subs = list(filter(was_removed, state['subscriptions'])) # Remove our user from the subscribers of the removed subscriptions. if include_subscribers: for sub in removed_subs: sub['subscribers'] = [id for id in sub['subscribers'] if id != user_profile.id] # We must effectively copy the removed subscriptions from subscriptions to # unsubscribe, since we only have the name in our data structure. state['unsubscribed'] += removed_subs # Now filter out the removed subscriptions from subscriptions. state['subscriptions'] = [s for s in state['subscriptions'] if not was_removed(s)] elif event['op'] == 'update': for sub in state['subscriptions']: if sub['name'].lower() == event['name'].lower(): sub[event['property']] = event['value'] elif event['op'] == 'peer_add': user_id = event['user_id'] for sub in state['subscriptions']: if (sub['name'] in event['subscriptions'] and user_id not in sub['subscribers']): sub['subscribers'].append(user_id) for sub in state['never_subscribed']: if (sub['name'] in event['subscriptions'] and user_id not in sub['subscribers']): sub['subscribers'].append(user_id) elif event['op'] == 'peer_remove': user_id = event['user_id'] for sub in state['subscriptions']: if (sub['name'] in event['subscriptions'] and user_id in sub['subscribers']): sub['subscribers'].remove(user_id) elif event['type'] == "presence": # TODO: Add user_id to presence update events / state format! presence_user_profile = get_user(event['email'], user_profile.realm) state['presences'][event['email']] = UserPresence.get_status_dict_by_user( presence_user_profile)[event['email']] elif event['type'] == "update_message": # We don't return messages in /register, so we don't need to # do anything for content updates, but we may need to update # the unread_msgs data if the topic of an unread message changed. if TOPIC_NAME in event: stream_dict = state['raw_unread_msgs']['stream_dict'] topic = event[TOPIC_NAME] for message_id in event['message_ids']: if message_id in stream_dict: stream_dict[message_id]['topic'] = topic elif event['type'] == "delete_message": max_message = Message.objects.filter( usermessage__user_profile=user_profile).order_by('-id').first() if max_message: state['max_message_id'] = max_message.id else: state['max_message_id'] = -1 remove_id = event['message_id'] remove_message_id_from_unread_mgs(state, remove_id) elif event['type'] == "reaction": # The client will get the message with the reactions directly pass elif event['type'] == "submessage": # The client will get submessages with their messages pass elif event['type'] == 'typing': # Typing notification events are transient and thus ignored pass elif event['type'] == "attachment": # Attachment events are just for updating the "uploads" UI; # they are not sent directly. pass elif event['type'] == "update_message_flags": # We don't return messages in `/register`, so most flags we # can ignore, but we do need to update the unread_msgs data if # unread state is changed. if event['flag'] == 'read' and event['operation'] == 'add': for remove_id in event['messages']: remove_message_id_from_unread_mgs(state, remove_id) if event['flag'] == 'starred' and event['operation'] == 'add': state['starred_messages'] += event['messages'] if event['flag'] == 'starred' and event['operation'] == 'remove': state['starred_messages'] = [message for message in state['starred_messages'] if not (message in event['messages'])] elif event['type'] == "realm_domains": if event['op'] == 'add': state['realm_domains'].append(event['realm_domain']) elif event['op'] == 'change': for realm_domain in state['realm_domains']: if realm_domain['domain'] == event['realm_domain']['domain']: realm_domain['allow_subdomains'] = event['realm_domain']['allow_subdomains'] elif event['op'] == 'remove': state['realm_domains'] = [realm_domain for realm_domain in state['realm_domains'] if realm_domain['domain'] != event['domain']] elif event['type'] == "realm_emoji": state['realm_emoji'] = event['realm_emoji'] elif event['type'] == "alert_words": state['alert_words'] = event['alert_words'] elif event['type'] == "muted_topics": state['muted_topics'] = event["muted_topics"] elif event['type'] == "realm_filters": state['realm_filters'] = event["realm_filters"] elif event['type'] == "update_display_settings": assert event['setting_name'] in UserProfile.property_types state[event['setting_name']] = event['setting'] elif event['type'] == "update_global_notifications": assert event['notification_name'] in UserProfile.notification_setting_types state[event['notification_name']] = event['setting'] elif event['type'] == "invites_changed": pass elif event['type'] == "user_group": if event['op'] == 'add': state['realm_user_groups'].append(event['group']) state['realm_user_groups'].sort(key=lambda group: group['id']) elif event['op'] == 'update': for user_group in state['realm_user_groups']: if user_group['id'] == event['group_id']: user_group.update(event['data']) elif event['op'] == 'add_members': for user_group in state['realm_user_groups']: if user_group['id'] == event['group_id']: user_group['members'].extend(event['user_ids']) user_group['members'].sort() elif event['op'] == 'remove_members': for user_group in state['realm_user_groups']: if user_group['id'] == event['group_id']: members = set(user_group['members']) user_group['members'] = list(members - set(event['user_ids'])) user_group['members'].sort() elif event['op'] == 'remove': state['realm_user_groups'] = [ug for ug in state['realm_user_groups'] if ug['id'] != event['group_id']] elif event['type'] == 'user_status': user_id = event['user_id'] user_status = state['user_status'] away = event.get('away') status_text = event.get('status_text') if user_id not in user_status: user_status[user_id] = dict() if away is not None: if away: user_status[user_id]['away'] = True else: user_status[user_id].pop('away', None) if status_text is not None: if status_text == '': user_status[user_id].pop('status_text', None) else: user_status[user_id]['status_text'] = status_text if not user_status[user_id]: user_status.pop(user_id, None) state['user_status'] = user_status else: raise AssertionError("Unexpected event type %s" % (event['type'],))
def apply_event(state, event, user_profile, include_subscribers): # type: (Dict[str, Any], Dict[str, Any], UserProfile, bool) -> None if event['type'] == "message": state['max_message_id'] = max(state['max_message_id'], event['message']['id']) elif event['type'] == "hotspots": state['hotspots'] = event['hotspots'] elif event['type'] == "custom_profile_fields": state['custom_profile_fields'] = event['fields'] elif event['type'] == "pointer": state['pointer'] = max(state['pointer'], event['pointer']) elif event['type'] == "realm_user": person = event['person'] def our_person(p): # type: (Dict[str, Any]) -> bool return p['user_id'] == person['user_id'] if event['op'] == "add": state['realm_users'].append(person) elif event['op'] == "remove": state['realm_users'] = [ user for user in state['realm_users'] if not our_person(user) ] elif event['op'] == 'update': if (person['user_id'] == user_profile.id and 'avatar_url' in person and 'avatar_url' in state): state['avatar_source'] = person['avatar_source'] state['avatar_url'] = person['avatar_url'] state['avatar_url_medium'] = person['avatar_url_medium'] if 'avatar_source' in person: # Drop these so that they don't modify the # `realm_user` structure in the `p.update()` line # later; they're only used in the above lines del person['avatar_source'] del person['avatar_url_medium'] for field in ['is_admin', 'email', 'full_name']: if person[ 'user_id'] == user_profile.id and field in person and field in state: state[field] = person[field] for p in state['realm_users']: if our_person(p): # In the unlikely event that the current user # just changed to/from being an admin, we need # to add/remove the data on all bots in the # realm. This is ugly and probably better # solved by removing the all-realm-bots data # given to admin users from this flow. if ('is_admin' in person and 'realm_bots' in state and user_profile.email == person['email']): if p['is_admin'] and not person['is_admin']: state['realm_bots'] = [] if not p['is_admin'] and person['is_admin']: state['realm_bots'] = get_owned_bot_dicts( user_profile) # Now update the person p.update(person) elif event['type'] == 'realm_bot': if event['op'] == 'add': state['realm_bots'].append(event['bot']) if event['op'] == 'remove': email = event['bot']['email'] for bot in state['realm_bots']: if bot['email'] == email: bot['is_active'] = False if event['op'] == 'update': for bot in state['realm_bots']: if bot['email'] == event['bot']['email']: if 'owner_id' in event['bot']: bot['owner'] = get_user_profile_by_id( event['bot']['owner_id']).email else: bot.update(event['bot']) elif event['type'] == 'stream': if event['op'] == 'create': for stream in event['streams']: if not stream['invite_only']: stream_data = copy.deepcopy(stream) if include_subscribers: stream_data['subscribers'] = [] # Add stream to never_subscribed (if not invite_only) state['never_subscribed'].append(stream_data) state['streams'].append(stream) state['streams'].sort(key=lambda elt: elt["name"]) if event['op'] == 'delete': deleted_stream_ids = { stream['stream_id'] for stream in event['streams'] } state['streams'] = [ s for s in state['streams'] if s['stream_id'] not in deleted_stream_ids ] state['never_subscribed'] = [ stream for stream in state['never_subscribed'] if stream['stream_id'] not in deleted_stream_ids ] if event['op'] == 'update': # For legacy reasons, we call stream data 'subscriptions' in # the state var here, for the benefit of the JS code. for obj in state['subscriptions']: if obj['name'].lower() == event['name'].lower(): obj[event['property']] = event['value'] # Also update the pure streams data for stream in state['streams']: if stream['name'].lower() == event['name'].lower(): prop = event['property'] if prop in stream: stream[prop] = event['value'] elif event['op'] == "occupy": state['streams'] += event['streams'] elif event['op'] == "vacate": stream_ids = [s["stream_id"] for s in event['streams']] state['streams'] = [ s for s in state['streams'] if s["stream_id"] not in stream_ids ] elif event['type'] == 'default_streams': state['realm_default_streams'] = event['default_streams'] elif event['type'] == 'realm': if event['op'] == "update": field = 'realm_' + event['property'] state[field] = event['value'] # Tricky interaction: Whether we can create streams can get changed here. if (field in [ 'realm_create_stream_by_admins_only', 'realm_waiting_period_threshold' ]) and 'can_create_streams' in state: state['can_create_streams'] = user_profile.can_create_streams() elif event['op'] == "update_dict": for key, value in event['data'].items(): state['realm_' + key] = value # It's a bit messy, but this is where we need to # update the state for whether password authentication # is enabled on this server. if key == 'authentication_methods': state['realm_password_auth_enabled'] = (value['Email'] or value['LDAP']) elif event['type'] == "subscription": if not include_subscribers and event['op'] in [ 'peer_add', 'peer_remove' ]: return if event['op'] in ["add"]: if include_subscribers: # Convert the emails to user_profile IDs since that's what register() returns # TODO: Clean up this situation by making the event also have IDs for item in event["subscriptions"]: item["subscribers"] = [ get_user(email, user_profile.realm).id for email in item["subscribers"] ] else: # Avoid letting 'subscribers' entries end up in the list for i, sub in enumerate(event['subscriptions']): event['subscriptions'][i] = copy.deepcopy( event['subscriptions'][i]) del event['subscriptions'][i]['subscribers'] def name(sub): # type: (Dict[str, Any]) -> Text return sub['name'].lower() if event['op'] == "add": added_names = set(map(name, event["subscriptions"])) was_added = lambda s: name(s) in added_names # add the new subscriptions state['subscriptions'] += event['subscriptions'] # remove them from unsubscribed if they had been there state['unsubscribed'] = [ s for s in state['unsubscribed'] if not was_added(s) ] # remove them from never_subscribed if they had been there state['never_subscribed'] = [ s for s in state['never_subscribed'] if not was_added(s) ] elif event['op'] == "remove": removed_names = set(map(name, event["subscriptions"])) was_removed = lambda s: name(s) in removed_names # Find the subs we are affecting. removed_subs = list(filter(was_removed, state['subscriptions'])) # Remove our user from the subscribers of the removed subscriptions. if include_subscribers: for sub in removed_subs: sub['subscribers'] = [ id for id in sub['subscribers'] if id != user_profile.id ] # We must effectively copy the removed subscriptions from subscriptions to # unsubscribe, since we only have the name in our data structure. state['unsubscribed'] += removed_subs # Now filter out the removed subscriptions from subscriptions. state['subscriptions'] = [ s for s in state['subscriptions'] if not was_removed(s) ] elif event['op'] == 'update': for sub in state['subscriptions']: if sub['name'].lower() == event['name'].lower(): sub[event['property']] = event['value'] elif event['op'] == 'peer_add': user_id = event['user_id'] for sub in state['subscriptions']: if (sub['name'] in event['subscriptions'] and user_id not in sub['subscribers']): sub['subscribers'].append(user_id) for sub in state['never_subscribed']: if (sub['name'] in event['subscriptions'] and user_id not in sub['subscribers']): sub['subscribers'].append(user_id) elif event['op'] == 'peer_remove': user_id = event['user_id'] for sub in state['subscriptions']: if (sub['name'] in event['subscriptions'] and user_id in sub['subscribers']): sub['subscribers'].remove(user_id) elif event['type'] == "presence": # TODO: Add user_id to presence update events / state format! presence_user_profile = get_user(event['email'], user_profile.realm) state['presences'][ event['email']] = UserPresence.get_status_dict_by_user( presence_user_profile)[event['email']] elif event['type'] == "update_message": # The client will get the updated message directly pass elif event['type'] == "reaction": # The client will get the message with the reactions directly pass elif event['type'] == "referral": state['referrals'] = event['referrals'] elif event['type'] == 'typing': # Typing notification events are transient and thus ignored pass elif event['type'] == "update_message_flags": # The client will get the message with the updated flags directly pass elif event['type'] == "realm_domains": if event['op'] == 'add': state['realm_domains'].append(event['realm_domain']) elif event['op'] == 'change': for realm_domain in state['realm_domains']: if realm_domain['domain'] == event['realm_domain']['domain']: realm_domain['allow_subdomains'] = event['realm_domain'][ 'allow_subdomains'] elif event['op'] == 'remove': state['realm_domains'] = [ realm_domain for realm_domain in state['realm_domains'] if realm_domain['domain'] != event['domain'] ] elif event['type'] == "realm_emoji": state['realm_emoji'] = event['realm_emoji'] elif event['type'] == "alert_words": state['alert_words'] = event['alert_words'] elif event['type'] == "muted_topics": state['muted_topics'] = event["muted_topics"] elif event['type'] == "realm_filters": state['realm_filters'] = event["realm_filters"] elif event['type'] == "update_display_settings": if event['setting_name'] == "twenty_four_hour_time": state['twenty_four_hour_time'] = event["setting"] if event['setting_name'] == 'left_side_userlist': state['left_side_userlist'] = event["setting"] if event['setting_name'] == 'emoji_alt_code': state['emoji_alt_code'] = event["setting"] if event['setting_name'] == 'emojiset': state['emojiset'] = event["setting"] if event['setting_name'] == 'default_language': state['default_language'] = event["setting"] if event['setting_name'] == 'timezone': state['timezone'] = event["setting"] elif event['type'] == "update_global_notifications": if event['notification_name'] == "enable_stream_desktop_notifications": state['enable_stream_desktop_notifications'] = event['setting'] elif event['notification_name'] == "enable_stream_sounds": state['enable_stream_sounds'] = event['setting'] elif event['notification_name'] == "enable_desktop_notifications": state['enable_desktop_notifications'] = event['setting'] elif event['notification_name'] == "enable_sounds": state['enable_sounds'] = event['setting'] elif event[ 'notification_name'] == "enable_offline_email_notifications": state['enable_offline_email_notifications'] = event['setting'] elif event['notification_name'] == "enable_offline_push_notifications": state['enable_offline_push_notifications'] = event['setting'] elif event['notification_name'] == "enable_online_push_notifications": state['enable_online_push_notifications'] = event['setting'] elif event['notification_name'] == "enable_digest_emails": state['enable_digest_emails'] = event['setting'] elif event[ 'notification_name'] == "pm_content_in_desktop_notifications": state['pm_content_in_desktop_notifications'] = event['setting'] else: raise AssertionError("Unexpected event type %s" % (event['type'], ))
def unsubscribe_from_stream(self, email, stream_name, realm): # type: (Text, Text, Realm) -> None user_profile = get_user(email, realm) stream = get_stream(stream_name, realm) bulk_remove_subscriptions([user_profile], [stream])
def test_create_user_backend(self) -> None: # 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. admin = self.example_user('hamlet') admin_email = admin.email realm = admin.realm self.login(admin_email) do_change_is_admin(admin, True) result = self.client_post("/json/users", dict()) self.assert_json_error(result, "Missing 'email' argument") result = self.client_post("/json/users", dict(email='*****@*****.**', )) self.assert_json_error(result, "Missing 'password' argument") result = self.client_post( "/json/users", dict( email='*****@*****.**', password='******', )) self.assert_json_error(result, "Missing 'full_name' argument") result = self.client_post( "/json/users", dict( email='*****@*****.**', password='******', full_name='Romeo Montague', )) self.assert_json_error(result, "Missing 'short_name' argument") result = self.client_post( "/json/users", dict( email='broken', password='******', full_name='Romeo Montague', short_name='Romeo', )) self.assert_json_error(result, "Bad name or username") result = self.client_post( "/json/users", dict( email='*****@*****.**', password='******', full_name='Romeo Montague', short_name='Romeo', )) self.assert_json_error( result, "Email '*****@*****.**' not allowed in this organization") RealmDomain.objects.create(realm=get_realm('zulip'), domain='zulip.net') valid_params = dict( email='*****@*****.**', password='******', full_name='Romeo Montague', short_name='Romeo', ) result = self.client_post("/json/users", valid_params) self.assert_json_success(result) # Romeo is a newly registered user new_user = get_user('*****@*****.**', get_realm('zulip')) self.assertEqual(new_user.full_name, 'Romeo Montague') self.assertEqual(new_user.short_name, 'Romeo') # we can't create the same user twice. result = self.client_post("/json/users", valid_params) self.assert_json_error(result, "Email '*****@*****.**' already in use") # Don't allow user to sign up with disposable email. realm.restricted_to_domain = False realm.disallow_disposable_email_addresses = True realm.save() valid_params["email"] = "*****@*****.**" result = self.client_post("/json/users", valid_params) self.assert_json_error( result, "Disposable email addresses are not allowed in this organization")
def nonreg_user(self, name): # type: (str) -> UserProfile email = self.nonreg_user_map[name] return get_user(email, get_realm_by_email_domain(email))
def patch_bot_backend( request: HttpRequest, user_profile: UserProfile, email: Text, full_name: Optional[Text] = REQ(default=None), bot_owner: Optional[Text] = REQ(default=None), default_sending_stream: Optional[Text] = REQ(default=None), default_events_register_stream: Optional[Text] = REQ(default=None), default_all_public_streams: Optional[bool] = REQ(default=None, validator=check_bool) ) -> HttpResponse: try: bot = get_user(email, user_profile.realm) except UserProfile.DoesNotExist: return json_error(_('No such user')) if not user_profile.can_admin_user(bot): return json_error(_('Insufficient permission')) if full_name is not None: check_change_full_name(bot, full_name, user_profile) if bot_owner is not None: owner = get_user(bot_owner, user_profile.realm) do_change_bot_owner(bot, owner, user_profile) if default_sending_stream is not None: if default_sending_stream == "": stream = None # type: Optional[Stream] else: (stream, recipient, sub) = access_stream_by_name(user_profile, default_sending_stream) do_change_default_sending_stream(bot, stream) if default_events_register_stream is not None: if default_events_register_stream == "": stream = None else: (stream, recipient, sub) = access_stream_by_name(user_profile, default_events_register_stream) do_change_default_events_register_stream(bot, stream) if default_all_public_streams is not None: do_change_default_all_public_streams(bot, default_all_public_streams) if len(request.FILES) == 0: pass elif len(request.FILES) == 1: user_file = list(request.FILES.values())[0] upload_avatar_image(user_file, user_profile, bot) avatar_source = UserProfile.AVATAR_FROM_USER do_change_avatar_fields(bot, avatar_source) else: return json_error(_("You may only upload one file at a time")) json_result = dict( full_name=bot.full_name, avatar_url=avatar_url(bot), default_sending_stream=get_stream_name(bot.default_sending_stream), default_events_register_stream=get_stream_name( bot.default_events_register_stream), default_all_public_streams=bot.default_all_public_streams, ) # Don't include the bot owner in case it is not set. # Default bots have no owner. if bot.bot_owner is not None: json_result['bot_owner'] = bot.bot_owner.email return json_success(json_result)
def handle(self, **options: Any) -> None: if options["percent_huddles"] + options["percent_personals"] > 100: self.stderr.write( "Error! More than 100% of messages allocated.\n") return if options["delete"]: # Start by clearing all the data in our database clear_database() # Create our two default realms # Could in theory be done via zerver.lib.actions.do_create_realm, but # welcome-bot (needed for do_create_realm) hasn't been created yet zulip_realm = Realm.objects.create( string_id="zulip", name="Zulip Dev", restricted_to_domain=True, description= "The Zulip development environment default organization." " It's great for testing!", invite_required=False, org_type=Realm.CORPORATE) RealmDomain.objects.create(realm=zulip_realm, domain="zulip.com") if options["test_suite"]: mit_realm = Realm.objects.create(string_id="zephyr", name="MIT", restricted_to_domain=True, invite_required=False, org_type=Realm.CORPORATE) RealmDomain.objects.create(realm=mit_realm, domain="mit.edu") lear_realm = Realm.objects.create(string_id="lear", name="Lear & Co.", restricted_to_domain=False, invite_required=False, org_type=Realm.CORPORATE) # Create test Users (UserProfiles are automatically created, # as are subscriptions to the ability to receive personals). names = [ ("Zoe", "*****@*****.**"), ("Othello, the Moor of Venice", "*****@*****.**"), ("Iago", "*****@*****.**"), ("Prospero from The Tempest", "*****@*****.**"), ("Cordelia Lear", "*****@*****.**"), ("King Hamlet", "*****@*****.**"), ("aaron", "*****@*****.**"), ("Polonius", "*****@*****.**"), ] for i in range(options["extra_users"]): names.append( ('Extra User %d' % (i, ), '*****@*****.**' % (i, ))) create_users(zulip_realm, names) iago = get_user("*****@*****.**", zulip_realm) do_change_is_admin(iago, True) iago.is_staff = True iago.save(update_fields=['is_staff']) guest_user = get_user("*****@*****.**", zulip_realm) guest_user.is_guest = True guest_user.save(update_fields=['is_guest']) # These bots are directly referenced from code and thus # are needed for the test suite. all_realm_bots = [ (bot['name'], bot['email_template'] % (settings.INTERNAL_BOT_DOMAIN, )) for bot in settings.INTERNAL_BOTS ] zulip_realm_bots = [ ("Zulip New User Bot", "*****@*****.**"), ("Zulip Error Bot", "*****@*****.**"), ("Zulip Default Bot", "*****@*****.**"), ("Welcome Bot", "*****@*****.**"), ] for i in range(options["extra_bots"]): zulip_realm_bots.append( ('Extra Bot %d' % (i, ), '*****@*****.**' % (i, ))) zulip_realm_bots.extend(all_realm_bots) create_users(zulip_realm, zulip_realm_bots, bot_type=UserProfile.DEFAULT_BOT) zoe = get_user("*****@*****.**", zulip_realm) zulip_webhook_bots = [ ("Zulip Webhook Bot", "*****@*****.**"), ] # If a stream is not supplied in the webhook URL, the webhook # will (in some cases) send the notification as a PM to the # owner of the webhook bot, so bot_owner can't be None create_users(zulip_realm, zulip_webhook_bots, bot_type=UserProfile.INCOMING_WEBHOOK_BOT, bot_owner=zoe) aaron = get_user("*****@*****.**", zulip_realm) zulip_outgoing_bots = [("Outgoing Webhook", "*****@*****.**")] create_users(zulip_realm, zulip_outgoing_bots, bot_type=UserProfile.OUTGOING_WEBHOOK_BOT, bot_owner=aaron) # TODO: Clean up this initial bot creation code Service.objects.create( name="test", user_profile=get_user("*****@*****.**", zulip_realm), base_url="http://127.0.0.1:5002/bots/followup", token="abcd1234", interface=1) # Create public streams. stream_list = ["Verona", "Denmark", "Scotland", "Venice", "Rome"] stream_dict = { "Verona": { "description": "A city in Italy", "invite_only": False }, "Denmark": { "description": "A Scandinavian country", "invite_only": False }, "Scotland": { "description": "Located in the United Kingdom", "invite_only": False }, "Venice": { "description": "A northeastern Italian city", "invite_only": False }, "Rome": { "description": "Yet another Italian city", "invite_only": False } } # type: Dict[Text, Dict[Text, Any]] bulk_create_streams(zulip_realm, stream_dict) recipient_streams = [ Stream.objects.get(name=name, realm=zulip_realm).id for name in stream_list ] # type: List[int] # Create subscriptions to streams. The following # algorithm will give each of the users a different but # deterministic subset of the streams (given a fixed list # of users). subscriptions_to_add = [] # type: List[Subscription] event_time = timezone_now() all_subscription_logs = [] # type: (List[RealmAuditLog]) profiles = UserProfile.objects.select_related().filter( is_bot=False, is_guest=False).order_by( "email") # type: Sequence[UserProfile] for i, profile in enumerate(profiles): # Subscribe to some streams. for type_id in recipient_streams[:int( len(recipient_streams) * float(i) / len(profiles)) + 1]: r = Recipient.objects.get(type=Recipient.STREAM, type_id=type_id) s = Subscription(recipient=r, user_profile=profile, color=STREAM_ASSIGNMENT_COLORS[ i % len(STREAM_ASSIGNMENT_COLORS)]) subscriptions_to_add.append(s) log = RealmAuditLog(realm=profile.realm, modified_user=profile, modified_stream_id=type_id, event_last_message_id=0, event_type='subscription_created', event_time=event_time) all_subscription_logs.append(log) Subscription.objects.bulk_create(subscriptions_to_add) RealmAuditLog.objects.bulk_create(all_subscription_logs) # Create custom profile field data phone_number = try_add_realm_custom_profile_field( zulip_realm, "Phone number", CustomProfileField.SHORT_TEXT, hint='') biography = try_add_realm_custom_profile_field( zulip_realm, "Biography", CustomProfileField.LONG_TEXT, hint='What are you known for?') favorite_food = try_add_realm_custom_profile_field( zulip_realm, "Favorite food", CustomProfileField.SHORT_TEXT, hint="Or drink, if you'd prefer") # Fill in values for Iago and Hamlet hamlet = get_user("*****@*****.**", zulip_realm) do_update_user_custom_profile_data(iago, [ { "id": phone_number.id, "value": "+1-234-567-8901" }, { "id": biography.id, "value": "Betrayer of Othello." }, { "id": favorite_food.id, "value": "Apples" }, ]) do_update_user_custom_profile_data(hamlet, [ { "id": phone_number.id, "value": "+0-11-23-456-7890" }, { "id": biography.id, "value": "Prince of Denmark, and other things!" }, { "id": favorite_food.id, "value": "Dark chocolate" }, ]) else: zulip_realm = get_realm("zulip") recipient_streams = [ klass.type_id for klass in Recipient.objects.filter(type=Recipient.STREAM) ] # Extract a list of all users user_profiles = list(UserProfile.objects.filter( is_bot=False)) # type: List[UserProfile] # Create a test realm emoji. IMAGE_FILE_PATH = os.path.join(settings.STATIC_ROOT, 'images', 'test-images', 'checkbox.png') with open(IMAGE_FILE_PATH, 'rb') as fp: check_add_realm_emoji(zulip_realm, 'green_tick', iago, fp) if not options["test_suite"]: # Populate users with some bar data for user in user_profiles: status = UserPresence.ACTIVE # type: int date = timezone_now() client = get_client("website") if user.full_name[0] <= 'H': client = get_client("ZulipAndroid") UserPresence.objects.get_or_create(user_profile=user, client=client, timestamp=date, status=status) user_profiles_ids = [user_profile.id for user_profile in user_profiles] # Create several initial huddles for i in range(options["num_huddles"]): get_huddle(random.sample(user_profiles_ids, random.randint(3, 4))) # Create several initial pairs for personals personals_pairs = [ random.sample(user_profiles_ids, 2) for i in range(options["num_personals"]) ] # Generate a new set of test data. create_test_data() # prepopulate the URL preview/embed data for the links present # in the config.generate_data.json data set. This makes it # possible for populate_db to run happily without Internet # access. with open("zerver/tests/fixtures/docs_url_preview_data.json", "r") as f: urls_with_preview_data = ujson.load(f) for url in urls_with_preview_data: cache_set(url, urls_with_preview_data[url], PREVIEW_CACHE_NAME) threads = options["threads"] jobs = [ ] # type: List[Tuple[int, List[List[int]], Dict[str, Any], Callable[[str], int], int]] for i in range(threads): count = options["num_messages"] // threads if i < options["num_messages"] % threads: count += 1 jobs.append((count, personals_pairs, options, self.stdout.write, random.randint(0, 10**10))) for job in jobs: send_messages(job) if options["delete"]: # Create the "website" and "API" clients; if we don't, the # default values in zerver/decorators.py will not work # with the Django test suite. get_client("website") get_client("API") if options["test_suite"]: # Create test users; the MIT ones are needed to test # the Zephyr mirroring codepaths. testsuite_mit_users = [ ("Fred Sipb (MIT)", "*****@*****.**"), ("Athena Consulting Exchange User (MIT)", "*****@*****.**"), ("Esp Classroom (MIT)", "*****@*****.**"), ] create_users(mit_realm, testsuite_mit_users) testsuite_lear_users = [ ("King Lear", "*****@*****.**"), ("Cordelia Lear", "*****@*****.**"), ] create_users(lear_realm, testsuite_lear_users) if not options["test_suite"]: # Initialize the email gateway bot as an API Super User email_gateway_bot = get_system_bot(settings.EMAIL_GATEWAY_BOT) email_gateway_bot.is_api_super_user = True email_gateway_bot.save() # To keep the messages.json fixtures file for the test # suite fast, don't add these users and subscriptions # when running populate_db for the test suite zulip_stream_dict = { "devel": { "description": "For developing", "invite_only": False }, "all": { "description": "For everything", "invite_only": False }, "announce": { "description": "For announcements", "invite_only": False }, "design": { "description": "For design", "invite_only": False }, "support": { "description": "For support", "invite_only": False }, "social": { "description": "For socializing", "invite_only": False }, "test": { "description": "For testing", "invite_only": False }, "errors": { "description": "For errors", "invite_only": False }, "sales": { "description": "For sales discussion", "invite_only": False } } # type: Dict[Text, Dict[Text, Any]] # Calculate the maximum number of digits in any extra stream's # number, since a stream with name "Extra Stream 3" could show # up after "Extra Stream 29". (Used later to pad numbers with # 0s). maximum_digits = len(str(options['extra_streams'] - 1)) for i in range(options['extra_streams']): # Pad the number with 0s based on `maximum_digits`. number_str = str(i).zfill(maximum_digits) extra_stream_name = 'Extra Stream ' + number_str zulip_stream_dict[extra_stream_name] = { "description": "Auto-generated extra stream.", "invite_only": False, } bulk_create_streams(zulip_realm, zulip_stream_dict) # Now that we've created the notifications stream, configure it properly. zulip_realm.notifications_stream = get_stream( "announce", zulip_realm) zulip_realm.save(update_fields=['notifications_stream']) # Add a few default streams for default_stream_name in [ "design", "devel", "social", "support" ]: DefaultStream.objects.create(realm=zulip_realm, stream=get_stream( default_stream_name, zulip_realm)) # Now subscribe everyone to these streams subscriptions_to_add = [] event_time = timezone_now() all_subscription_logs = [] profiles = UserProfile.objects.select_related().filter( realm=zulip_realm) for i, stream_name in enumerate(zulip_stream_dict): stream = Stream.objects.get(name=stream_name, realm=zulip_realm) recipient = Recipient.objects.get(type=Recipient.STREAM, type_id=stream.id) for profile in profiles: # Subscribe to some streams. s = Subscription( recipient=recipient, user_profile=profile, color=STREAM_ASSIGNMENT_COLORS[ i % len(STREAM_ASSIGNMENT_COLORS)]) subscriptions_to_add.append(s) log = RealmAuditLog(realm=profile.realm, modified_user=profile, modified_stream=stream, event_last_message_id=0, event_type='subscription_created', event_time=event_time) all_subscription_logs.append(log) Subscription.objects.bulk_create(subscriptions_to_add) RealmAuditLog.objects.bulk_create(all_subscription_logs) # These bots are not needed by the test suite internal_zulip_users_nosubs = [ ("Zulip Commit Bot", "*****@*****.**"), ("Zulip Trac Bot", "*****@*****.**"), ("Zulip Nagios Bot", "*****@*****.**"), ] create_users(zulip_realm, internal_zulip_users_nosubs, bot_type=UserProfile.DEFAULT_BOT) zulip_cross_realm_bots = [ ("Zulip Feedback Bot", "*****@*****.**"), ] create_users(zulip_realm, zulip_cross_realm_bots, bot_type=UserProfile.DEFAULT_BOT) # Mark all messages as read UserMessage.objects.all().update(flags=UserMessage.flags.read) if not options["test_suite"]: # Update pointer of each user to point to the last message in their # UserMessage rows with sender_id=user_profile_id. users = list( UserMessage.objects.filter(message__sender_id=F( 'user_profile_id')).values('user_profile_id').annotate( pointer=Max('message_id'))) for user in users: UserProfile.objects.filter( id=user['user_profile_id']).update( pointer=user['pointer']) create_user_groups() self.stdout.write("Successfully populated test database.\n")
def generate_all_emails(request: HttpRequest) -> HttpResponse: if not settings.TEST_SUITE: # nocoverage # It's really convenient to automatically inline the email CSS # here, since that saves a step when testing out changes to # the email CSS. But we don't run this inside the test suite, # because by role, the tests shouldn't be doing a provision-like thing. subprocess.check_call(["./tools/inline-email-css"]) # We import the Django test client inside the view function, # because it isn't needed in production elsewhere, and not # importing it saves ~50ms of unnecessary manage.py startup time. from django.test import Client client = Client() # write fake data for all variables registered_email = "*****@*****.**" unregistered_email_1 = "*****@*****.**" unregistered_email_2 = "*****@*****.**" realm = get_realm("zulip") host_kwargs = {'HTTP_HOST': realm.host} # Password reset email result = client.post('/accounts/password/reset/', {'email': registered_email}, **host_kwargs) assert result.status_code == 302 # Confirm account email result = client.post('/accounts/home/', {'email': unregistered_email_1}, **host_kwargs) assert result.status_code == 302 # Find account email result = client.post('/accounts/find/', {'emails': registered_email}, **host_kwargs) assert result.status_code == 302 # New login email logged_in = client.login(dev_auth_username=registered_email, realm=realm) assert logged_in # New user invite and reminder emails result = client.post("/json/invites", {"invitee_emails": unregistered_email_2, "stream": ["Denmark"]}, **host_kwargs) assert result.status_code == 200 # Verification for new email result = client.patch('/json/settings', urllib.parse.urlencode({'email': '*****@*****.**'}), **host_kwargs) assert result.status_code == 200 # Email change successful key = Confirmation.objects.filter(type=Confirmation.EMAIL_CHANGE).latest('id').confirmation_key url = confirmation_url(key, realm.host, Confirmation.EMAIL_CHANGE) user_profile = get_user(registered_email, realm) result = client.get(url) assert result.status_code == 200 # Reset the email value so we can run this again user_profile.email = registered_email user_profile.save(update_fields=['email']) # Follow up day1 day2 emails for normal user enqueue_welcome_emails(user_profile) # Follow up day1 day2 emails for admin user enqueue_welcome_emails(get_user("*****@*****.**", realm), realm_creation=True) return redirect(email_page)
def test_do_convert_data(self) -> None: mattermost_data_dir = self.fixture_file_name("", "mattermost_fixtures") output_dir = self.make_import_output_dir("mattermost") do_convert_data( mattermost_data_dir=mattermost_data_dir, output_dir=output_dir, masking_content=False, ) harry_team_output_dir = self.team_output_dir(output_dir, "gryffindor") self.assertEqual( os.path.exists(os.path.join(harry_team_output_dir, 'avatars')), True) self.assertEqual( os.path.exists(os.path.join(harry_team_output_dir, 'emoji')), True) self.assertEqual( os.path.exists( os.path.join(harry_team_output_dir, 'attachment.json')), True) realm = self.read_file(harry_team_output_dir, 'realm.json') self.assertEqual('Organization imported from Mattermost!', realm['zerver_realm'][0]['description']) exported_user_ids = self.get_set(realm['zerver_userprofile'], 'id') exported_user_full_names = self.get_set(realm['zerver_userprofile'], 'full_name') self.assertEqual({'Harry Potter', 'Ron Weasley', 'Severus Snape'}, exported_user_full_names) exported_user_emails = self.get_set(realm['zerver_userprofile'], 'email') self.assertEqual( {'*****@*****.**', '*****@*****.**', '*****@*****.**'}, exported_user_emails) self.assertEqual(len(realm['zerver_stream']), 3) exported_stream_names = self.get_set(realm['zerver_stream'], 'name') self.assertEqual( exported_stream_names, { 'Gryffindor common room', 'Gryffindor quidditch team', 'Dumbledores army' }) self.assertEqual(self.get_set(realm['zerver_stream'], 'realm'), {realm['zerver_realm'][0]['id']}) self.assertEqual(self.get_set(realm['zerver_stream'], 'deactivated'), {False}) self.assertEqual(len(realm['zerver_defaultstream']), 0) exported_recipient_ids = self.get_set(realm['zerver_recipient'], 'id') self.assertEqual(len(exported_recipient_ids), 6) exported_recipient_types = self.get_set(realm['zerver_recipient'], 'type') self.assertEqual(exported_recipient_types, {1, 2}) exported_recipient_type_ids = self.get_set(realm['zerver_recipient'], 'type_id') self.assertEqual(len(exported_recipient_type_ids), 3) exported_subscription_userprofile = self.get_set( realm['zerver_subscription'], 'user_profile') self.assertEqual(len(exported_subscription_userprofile), 3) exported_subscription_recipients = self.get_set( realm['zerver_subscription'], 'recipient') self.assertEqual(len(exported_subscription_recipients), 6) messages = self.read_file(harry_team_output_dir, 'messages-000001.json') exported_messages_id = self.get_set(messages['zerver_message'], 'id') self.assertIn(messages['zerver_message'][0]['sender'], exported_user_ids) self.assertIn(messages['zerver_message'][0]['recipient'], exported_recipient_ids) self.assertIn(messages['zerver_message'][0]['content'], 'harry joined the channel.\n\n') exported_usermessage_userprofiles = self.get_set( messages['zerver_usermessage'], 'user_profile') self.assertEqual(len(exported_usermessage_userprofiles), 3) exported_usermessage_messages = self.get_set( messages['zerver_usermessage'], 'message') self.assertEqual(exported_usermessage_messages, exported_messages_id) do_import_realm( import_dir=harry_team_output_dir, subdomain='gryffindor', ) realm = get_realm('gryffindor') self.assertFalse(get_user("*****@*****.**", realm).is_mirror_dummy) self.assertFalse(get_user("*****@*****.**", realm).is_mirror_dummy) self.assertTrue(get_user("*****@*****.**", realm).is_mirror_dummy) messages = Message.objects.filter(sender__realm=realm) for message in messages: self.assertIsNotNone(message.rendered_content)
def test_people(self) -> None: hamlet = self.example_user('hamlet') realm = get_realm('zulip') self.login_user(hamlet) bots = {} for i in range(3): bots[i] = self.create_bot( owner=hamlet, bot_email='*****@*****.**' % (i, ), bot_name='Bot %d' % (i, ), ) for i in range(3): defunct_user = self.create_non_active_user( realm=realm, email='*****@*****.**' % (i, ), name='Defunct User %d' % (i, ), ) result = self._get_home_page() page_params = self._get_page_params(result) ''' We send three lists of users. The first two below are disjoint lists of users, and the records we send for them have identical structure. The realm_bots bucket is somewhat redundant, since all bots will be in one of the first two buckets. They do include fields, however, that normal users don't care about, such as default_sending_stream. ''' buckets = [ 'realm_users', 'realm_non_active_users', 'realm_bots', ] for field in buckets: users = page_params[field] self.assertTrue(len(users) >= 3, field) for rec in users: self.assertEqual(rec['user_id'], get_user(rec['email'], realm).id) if field == 'realm_bots': self.assertNotIn('is_bot', rec) self.assertIn('is_active', rec) self.assertIn('owner_id', rec) else: self.assertIn('is_bot', rec) self.assertNotIn('is_active', rec) active_ids = {p['user_id'] for p in page_params['realm_users']} non_active_ids = { p['user_id'] for p in page_params['realm_non_active_users'] } bot_ids = {p['user_id'] for p in page_params['realm_bots']} self.assertIn(hamlet.id, active_ids) self.assertIn(defunct_user.id, non_active_ids) # Bots can show up in multiple buckets. self.assertIn(bots[2].id, bot_ids) self.assertIn(bots[2].id, active_ids) # Make sure nobody got mis-bucketed. self.assertNotIn(hamlet.id, non_active_ids) self.assertNotIn(defunct_user.id, active_ids) cross_bots = page_params['cross_realm_bots'] self.assertEqual(len(cross_bots), 3) cross_bots.sort(key=lambda d: d['email']) for cross_bot in cross_bots: # These are either nondeterministic or boring del cross_bot['timezone'] del cross_bot['avatar_url'] del cross_bot['date_joined'] notification_bot = self.notification_bot() email_gateway_bot = get_system_bot(settings.EMAIL_GATEWAY_BOT) welcome_bot = get_system_bot(settings.WELCOME_BOT) by_email = lambda d: d['email'] self.assertEqual( sorted(cross_bots, key=by_email), sorted([ dict(avatar_version=email_gateway_bot.avatar_version, bot_owner_id=None, bot_type=1, email=email_gateway_bot.email, user_id=email_gateway_bot.id, full_name=email_gateway_bot.full_name, is_active=True, is_bot=True, is_admin=False, is_cross_realm_bot=True, is_guest=False), dict(avatar_version=email_gateway_bot.avatar_version, bot_owner_id=None, bot_type=1, email=notification_bot.email, user_id=notification_bot.id, full_name=notification_bot.full_name, is_active=True, is_bot=True, is_admin=False, is_cross_realm_bot=True, is_guest=False), dict(avatar_version=email_gateway_bot.avatar_version, bot_owner_id=None, bot_type=1, email=welcome_bot.email, user_id=welcome_bot.id, full_name=welcome_bot.full_name, is_active=True, is_bot=True, is_admin=False, is_cross_realm_bot=True, is_guest=False), ], key=by_email))
def notification_bot(self) -> UserProfile: return get_user('*****@*****.**', get_realm('zulip'))
def accounts_register(request: HttpRequest) -> HttpResponse: key = request.POST['key'] confirmation = Confirmation.objects.get(confirmation_key=key) prereg_user = confirmation.content_object email = prereg_user.email realm_creation = prereg_user.realm_creation password_required = prereg_user.password_required is_realm_admin = prereg_user.invited_as_admin or realm_creation validators.validate_email(email) if realm_creation: # For creating a new realm, there is no existing realm or domain realm = None else: realm = get_realm(get_subdomain(request)) if realm is None or realm != prereg_user.realm: return render_confirmation_key_error( request, ConfirmationKeyException( ConfirmationKeyException.DOES_NOT_EXIST)) if not email_allowed_for_realm(email, realm): return render(request, "zerver/closed_realm.html", context={"closed_domain_name": realm.name}) if realm.deactivated: # The user is trying to register for a deactivated realm. Advise them to # contact support. return redirect_to_deactivation_notice() try: validate_email_for_realm(realm, email) except ValidationError: # nocoverage # We need to add a test for this. return HttpResponseRedirect( reverse('django.contrib.auth.views.login') + '?email=' + urllib.parse.quote_plus(email)) name_validated = False full_name = None if request.POST.get('from_confirmation'): try: del request.session['authenticated_full_name'] except KeyError: pass if realm is not None and realm.is_zephyr_mirror_realm: # For MIT users, we can get an authoritative name from Hesiod. # Technically we should check that this is actually an MIT # realm, but we can cross that bridge if we ever get a non-MIT # zephyr mirroring realm. hesiod_name = compute_mit_user_fullname(email) form = RegistrationForm(initial={ 'full_name': hesiod_name if "@" not in hesiod_name else "" }, realm_creation=realm_creation) name_validated = True elif settings.POPULATE_PROFILE_VIA_LDAP: for backend in get_backends(): if isinstance(backend, LDAPBackend): ldap_attrs = _LDAPUser( backend, backend.django_to_ldap_username(email)).attrs try: ldap_full_name = ldap_attrs[ settings.AUTH_LDAP_USER_ATTR_MAP['full_name']][0] request.session[ 'authenticated_full_name'] = ldap_full_name name_validated = True # We don't use initial= here, because if the form is # complete (that is, no additional fields need to be # filled out by the user) we want the form to validate, # so they can be directly registered without having to # go through this interstitial. form = RegistrationForm({'full_name': ldap_full_name}, realm_creation=realm_creation) # FIXME: This will result in the user getting # validation errors if they have to enter a password. # Not relevant for ONLY_SSO, though. break except TypeError: # Let the user fill out a name and/or try another backend form = RegistrationForm(realm_creation=realm_creation) elif 'full_name' in request.POST: form = RegistrationForm( initial={'full_name': request.POST.get('full_name')}, realm_creation=realm_creation) else: form = RegistrationForm(realm_creation=realm_creation) else: postdata = request.POST.copy() if name_changes_disabled(realm): # If we populate profile information via LDAP and we have a # verified name from you on file, use that. Otherwise, fall # back to the full name in the request. try: postdata.update( {'full_name': request.session['authenticated_full_name']}) name_validated = True except KeyError: pass form = RegistrationForm(postdata, realm_creation=realm_creation) if not (password_auth_enabled(realm) and password_required): form['password'].field.required = False if form.is_valid(): if password_auth_enabled(realm): password = form.cleaned_data['password'] else: # SSO users don't need no passwords password = None if realm_creation: string_id = form.cleaned_data['realm_subdomain'] realm_name = form.cleaned_data['realm_name'] realm = do_create_realm(string_id, realm_name) setup_initial_streams(realm) assert (realm is not None) full_name = form.cleaned_data['full_name'] short_name = email_to_username(email) default_stream_group_names = request.POST.getlist( 'default_stream_group') default_stream_groups = lookup_default_stream_groups( default_stream_group_names, realm) timezone = u"" if 'timezone' in request.POST and request.POST[ 'timezone'] in get_all_timezones(): timezone = request.POST['timezone'] if not realm_creation: try: existing_user_profile = get_user(email, realm) except UserProfile.DoesNotExist: existing_user_profile = None else: existing_user_profile = None return_data = {} # type: Dict[str, bool] if ldap_auth_enabled(realm): # If the user was authenticated using an external SSO # mechanism like Google or GitHub auth, then authentication # will have already been done before creating the # PreregistrationUser object with password_required=False, and # so we don't need to worry about passwords. # # If instead the realm is using EmailAuthBackend, we will # set their password above. # # But if the realm is using LDAPAuthBackend, we need to verify # their LDAP password (which will, as a side effect, create # the user account) here using authenticate. auth_result = authenticate(request, username=email, password=password, realm=realm, return_data=return_data) if auth_result is None: # TODO: This probably isn't going to give a # user-friendly error message, but it doesn't # particularly matter, because the registration form # is hidden for most users. return HttpResponseRedirect( reverse('django.contrib.auth.views.login') + '?email=' + urllib.parse.quote_plus(email)) # Since we'll have created a user, we now just log them in. return login_and_go_to_home(request, auth_result) elif existing_user_profile is not None and existing_user_profile.is_mirror_dummy: user_profile = existing_user_profile do_activate_user(user_profile) do_change_password(user_profile, password) do_change_full_name(user_profile, full_name, user_profile) do_set_user_display_setting(user_profile, 'timezone', timezone) # TODO: When we clean up the `do_activate_user` code path, # make it respect invited_as_admin / is_realm_admin. else: user_profile = do_create_user( email, password, realm, full_name, short_name, prereg_user=prereg_user, is_realm_admin=is_realm_admin, tos_version=settings.TOS_VERSION, timezone=timezone, newsletter_data={"IP": request.META['REMOTE_ADDR']}, default_stream_groups=default_stream_groups) if realm_creation: bulk_add_subscriptions([realm.signup_notifications_stream], [user_profile]) send_initial_realm_messages(realm) # Because for realm creation, registration happens on the # root domain, we need to log them into the subdomain for # their new realm. return redirect_and_log_into_subdomain(realm, full_name, email) # This dummy_backend check below confirms the user is # authenticating to the correct subdomain. auth_result = authenticate(username=user_profile.email, realm=realm, return_data=return_data, use_dummy_backend=True) if return_data.get('invalid_subdomain'): # By construction, this should never happen. logging.error("Subdomain mismatch in registration %s: %s" % ( realm.subdomain, user_profile.email, )) return redirect('/') return login_and_go_to_home(request, auth_result) return render( request, 'zerver/register.html', context={ 'form': form, 'email': email, 'key': key, 'full_name': request.session.get('authenticated_full_name', None), 'lock_name': name_validated and name_changes_disabled(realm), # password_auth_enabled is normally set via our context processor, # but for the registration form, there is no logged in user yet, so # we have to set it here. 'creating_new_team': realm_creation, 'password_required': password_auth_enabled(realm) and password_required, 'password_auth_enabled': password_auth_enabled(realm), 'root_domain_available': is_root_domain_available(), 'default_stream_groups': get_default_stream_groups(realm), 'MAX_REALM_NAME_LENGTH': str(Realm.MAX_REALM_NAME_LENGTH), 'MAX_NAME_LENGTH': str(UserProfile.MAX_NAME_LENGTH), 'MAX_PASSWORD_LENGTH': str(form.MAX_PASSWORD_LENGTH), 'MAX_REALM_SUBDOMAIN_LENGTH': str(Realm.MAX_REALM_SUBDOMAIN_LENGTH) })
def add_bot_backend( request: HttpRequest, user_profile: UserProfile, full_name_raw: Text = REQ("full_name"), short_name_raw: Text = REQ("short_name"), bot_type: int = REQ(validator=check_int, default=UserProfile.DEFAULT_BOT), payload_url: Optional[Text] = REQ(validator=check_url, default=""), service_name: Optional[Text] = REQ(default=None), config_data: Dict[Text, Text] = REQ( default={}, validator=check_dict(value_validator=check_string)), interface_type: int = REQ(validator=check_int, default=Service.GENERIC), default_sending_stream_name: Optional[Text] = REQ('default_sending_stream', default=None), default_events_register_stream_name: Optional[Text] = REQ( 'default_events_register_stream', default=None), default_all_public_streams: Optional[bool] = REQ(validator=check_bool, default=None) ) -> HttpResponse: short_name = check_short_name(short_name_raw) service_name = service_name or short_name short_name += "-bot" full_name = check_full_name(full_name_raw) email = '%s@%s' % (short_name, user_profile.realm.get_bot_domain()) form = CreateUserForm({'full_name': full_name, 'email': email}) if bot_type == UserProfile.EMBEDDED_BOT: if not settings.EMBEDDED_BOTS_ENABLED: return json_error(_("Embedded bots are not enabled.")) if service_name not in [bot.name for bot in EMBEDDED_BOTS]: return json_error(_("Invalid embedded bot name.")) if not form.is_valid(): # We validate client-side as well return json_error(_('Bad name or username')) try: get_user(email, user_profile.realm) return json_error(_("Username already in use")) except UserProfile.DoesNotExist: pass check_bot_creation_policy(user_profile, bot_type) check_valid_bot_type(user_profile, bot_type) check_valid_interface_type(interface_type) if len(request.FILES) == 0: avatar_source = UserProfile.AVATAR_FROM_GRAVATAR elif len(request.FILES) != 1: return json_error(_("You may only upload one file at a time")) else: avatar_source = UserProfile.AVATAR_FROM_USER default_sending_stream = None if default_sending_stream_name is not None: (default_sending_stream, ignored_rec, ignored_sub) = access_stream_by_name(user_profile, default_sending_stream_name) default_events_register_stream = None if default_events_register_stream_name is not None: (default_events_register_stream, ignored_rec, ignored_sub) = access_stream_by_name( user_profile, default_events_register_stream_name) if bot_type == UserProfile.EMBEDDED_BOT: check_valid_bot_config(service_name, config_data) bot_profile = do_create_user( email=email, password='', realm=user_profile.realm, full_name=full_name, short_name=short_name, bot_type=bot_type, bot_owner=user_profile, avatar_source=avatar_source, default_sending_stream=default_sending_stream, default_events_register_stream=default_events_register_stream, default_all_public_streams=default_all_public_streams) if len(request.FILES) == 1: user_file = list(request.FILES.values())[0] upload_avatar_image(user_file, user_profile, bot_profile) if bot_type in (UserProfile.OUTGOING_WEBHOOK_BOT, UserProfile.EMBEDDED_BOT): add_service(name=service_name, user_profile=bot_profile, base_url=payload_url, interface=interface_type, token=random_api_key()) if bot_type == UserProfile.EMBEDDED_BOT: for key, value in config_data.items(): set_bot_config(bot_profile, key, value) notify_created_bot(bot_profile) json_result = dict( api_key=bot_profile.api_key, avatar_url=avatar_url(bot_profile), default_sending_stream=get_stream_name( bot_profile.default_sending_stream), default_events_register_stream=get_stream_name( bot_profile.default_events_register_stream), default_all_public_streams=bot_profile.default_all_public_streams, ) return json_success(json_result)
def test_people(self) -> None: hamlet = self.example_user('hamlet') realm = get_realm('zulip') self.login(hamlet.email) for i in range(3): self.create_bot( owner=hamlet, bot_email='*****@*****.**' % (i, ), bot_name='Bot %d' % (i, ), ) for i in range(3): self.create_non_active_user( realm=realm, email='*****@*****.**' % (i, ), name='Defunct User %d' % (i, ), ) result = self._get_home_page() page_params = self._get_page_params(result) ''' We send three lists of users. The first two below are disjoint lists of users, and the records we send for them have identical structure. The realm_bots bucket is somewhat redundant, since all bots will be in one of the first two buckets. They do include fields, however, that normal users don't care about, such as default_sending_stream. ''' buckets = [ 'realm_users', 'realm_non_active_users', 'realm_bots', ] for field in buckets: users = page_params[field] self.assertTrue(len(users) >= 3, field) for rec in users: self.assertEqual(rec['user_id'], get_user(rec['email'], realm).id) if field == 'realm_bots': self.assertNotIn('is_bot', rec) self.assertIn('is_active', rec) self.assertIn('owner', rec) else: self.assertIn('is_bot', rec) self.assertNotIn('is_active', rec) active_emails = {p['email'] for p in page_params['realm_users']} non_active_emails = { p['email'] for p in page_params['realm_non_active_users'] } bot_emails = {p['email'] for p in page_params['realm_bots']} self.assertIn(hamlet.email, active_emails) self.assertIn('*****@*****.**', non_active_emails) # Bots can show up in multiple buckets. self.assertIn('*****@*****.**', bot_emails) self.assertIn('*****@*****.**', active_emails) # Make sure nobody got mis-bucketed. self.assertNotIn(hamlet.email, non_active_emails) self.assertNotIn('*****@*****.**', active_emails) cross_bots = page_params['cross_realm_bots'] self.assertEqual(len(cross_bots), 4) cross_bots.sort(key=lambda d: d['email']) for cross_bot in cross_bots: # These are either nondeterministic or boring del cross_bot['timezone'] del cross_bot['avatar_url'] del cross_bot['date_joined'] notification_bot = self.notification_bot() by_email = lambda d: d['email'] self.assertEqual( sorted(cross_bots, key=by_email), sorted([ dict(user_id=get_system_bot('*****@*****.**').id, is_admin=False, email='*****@*****.**', full_name='Email Gateway', is_bot=True), dict(user_id=get_system_bot('*****@*****.**').id, is_admin=False, email='*****@*****.**', full_name='Zulip Feedback Bot', is_bot=True), dict(user_id=notification_bot.id, is_admin=False, email=notification_bot.email, full_name='Notification Bot', is_bot=True), dict(user_id=get_system_bot('*****@*****.**').id, is_admin=False, email='*****@*****.**', full_name='Welcome Bot', is_bot=True), ], key=by_email))
def add_bot_backend(request, user_profile, full_name_raw=REQ("full_name"), short_name_raw=REQ("short_name"), bot_type=REQ(validator=check_int, default=UserProfile.DEFAULT_BOT), payload_url=REQ(validator=check_url, default=None), interface_type=REQ(validator=check_int, default=Service.GENERIC), default_sending_stream_name=REQ('default_sending_stream', default=None), default_events_register_stream_name=REQ( 'default_events_register_stream', default=None), default_all_public_streams=REQ(validator=check_bool, default=None)): # type: (HttpRequest, UserProfile, Text, Text, int, Optional[Text], int, Optional[Text], Optional[Text], Optional[bool]) -> HttpResponse short_name = check_short_name(short_name_raw) service_name = short_name short_name += "-bot" full_name = check_full_name(full_name_raw) email = '%s@%s' % (short_name, user_profile.realm.get_bot_domain()) form = CreateUserForm({'full_name': full_name, 'email': email}) if not form.is_valid(): # We validate client-side as well return json_error(_('Bad name or username')) try: get_user(email, user_profile.realm) return json_error(_("Username already in use")) except UserProfile.DoesNotExist: pass check_valid_bot_type(bot_type) check_valid_interface_type(interface_type) if len(request.FILES) == 0: avatar_source = UserProfile.AVATAR_FROM_GRAVATAR elif len(request.FILES) != 1: return json_error(_("You may only upload one file at a time")) else: avatar_source = UserProfile.AVATAR_FROM_USER default_sending_stream = None if default_sending_stream_name is not None: (default_sending_stream, ignored_rec, ignored_sub) = access_stream_by_name(user_profile, default_sending_stream_name) default_events_register_stream = None if default_events_register_stream_name is not None: (default_events_register_stream, ignored_rec, ignored_sub) = access_stream_by_name( user_profile, default_events_register_stream_name) bot_profile = do_create_user( email=email, password='', realm=user_profile.realm, full_name=full_name, short_name=short_name, active=True, bot_type=bot_type, bot_owner=user_profile, avatar_source=avatar_source, default_sending_stream=default_sending_stream, default_events_register_stream=default_events_register_stream, default_all_public_streams=default_all_public_streams) if len(request.FILES) == 1: user_file = list(request.FILES.values())[0] upload_avatar_image(user_file, user_profile, bot_profile) if bot_type == UserProfile.OUTGOING_WEBHOOK_BOT: add_outgoing_webhook_service(name=service_name, user_profile=bot_profile, base_url=payload_url, interface=interface_type, token=random_api_key()) json_result = dict( api_key=bot_profile.api_key, avatar_url=avatar_url(bot_profile), default_sending_stream=get_stream_name( bot_profile.default_sending_stream), default_events_register_stream=get_stream_name( bot_profile.default_events_register_stream), default_all_public_streams=bot_profile.default_all_public_streams, ) return json_success(json_result)
def test_create_user_backend(self) -> None: # 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. admin = self.example_user('hamlet') admin_email = admin.email self.login(admin_email) do_change_is_admin(admin, True) result = self.client_post("/json/users", dict()) self.assert_json_error(result, "Missing 'email' argument") result = self.client_post("/json/users", dict(email='*****@*****.**', )) self.assert_json_error(result, "Missing 'password' argument") result = self.client_post( "/json/users", dict( email='*****@*****.**', password='******', )) self.assert_json_error(result, "Missing 'full_name' argument") result = self.client_post( "/json/users", dict( email='*****@*****.**', password='******', full_name='Romeo Montague', )) self.assert_json_error(result, "Missing 'short_name' argument") result = self.client_post( "/json/users", dict( email='broken', password='******', full_name='Romeo Montague', short_name='Romeo', )) self.assert_json_error(result, "Bad name or username") result = self.client_post( "/json/users", dict( email='*****@*****.**', password='******', full_name='Romeo Montague', short_name='Romeo', )) self.assert_json_error( result, "Email '*****@*****.**' not allowed for realm 'zulip'") RealmDomain.objects.create(realm=get_realm('zulip'), domain='zulip.net') # HAPPY PATH STARTS HERE valid_params = dict( email='*****@*****.**', password='******', full_name='Romeo Montague', short_name='Romeo', ) result = self.client_post("/json/users", valid_params) self.assert_json_success(result) # Romeo is a newly registered user new_user = get_user('*****@*****.**', get_realm('zulip')) self.assertEqual(new_user.full_name, 'Romeo Montague') self.assertEqual(new_user.short_name, 'Romeo') # One more error condition to test--we can't create # the same user twice. result = self.client_post("/json/users", valid_params) self.assert_json_error(result, "Email '*****@*****.**' already in use")
def handle(self, **options: Any) -> None: if options["percent_huddles"] + options["percent_personals"] > 100: self.stderr.write( "Error! More than 100% of messages allocated.\n") return # Get consistent data for backend tests. if options["test_suite"]: random.seed(0) # If max_topics is not set, we set it proportional to the # number of messages. if options["max_topics"] is None: options["max_topics"] = 1 + options["num_messages"] // 100 if options["delete"]: # Start by clearing all the data in our database clear_database() # Create our three default realms # Could in theory be done via zerver.lib.actions.do_create_realm, but # welcome-bot (needed for do_create_realm) hasn't been created yet create_internal_realm() zulip_realm = Realm.objects.create( string_id="zulip", name="Zulip Dev", emails_restricted_to_domains=False, email_address_visibility=Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS, description= "The Zulip development environment default organization." " It's great for testing!", invite_required=False, org_type=Realm.CORPORATE) RealmDomain.objects.create(realm=zulip_realm, domain="zulip.com") if options["test_suite"]: mit_realm = Realm.objects.create( string_id="zephyr", name="MIT", emails_restricted_to_domains=True, invite_required=False, org_type=Realm.CORPORATE) RealmDomain.objects.create(realm=mit_realm, domain="mit.edu") lear_realm = Realm.objects.create( string_id="lear", name="Lear & Co.", emails_restricted_to_domains=False, invite_required=False, org_type=Realm.CORPORATE) # Create test Users (UserProfiles are automatically created, # as are subscriptions to the ability to receive personals). names = [ ("Zoe", "*****@*****.**"), ("Othello, the Moor of Venice", "*****@*****.**"), ("Iago", "*****@*****.**"), ("Prospero from The Tempest", "*****@*****.**"), ("Cordelia Lear", "*****@*****.**"), ("King Hamlet", "*****@*****.**"), ("aaron", "*****@*****.**"), ("Polonius", "*****@*****.**"), ("Desdemona", "*****@*****.**"), ] # For testing really large batches: # Create extra users with semi realistic names to make search # functions somewhat realistic. We'll still create 1000 users # like Extra222 User for some predicability. num_names = options['extra_users'] num_boring_names = 300 for i in range(min(num_names, num_boring_names)): full_name = f'Extra{i:03} User' names.append((full_name, f'extrauser{i}@zulip.com')) if num_names > num_boring_names: fnames = [ 'Amber', 'Arpita', 'Bob', 'Cindy', 'Daniela', 'Dan', 'Dinesh', 'Faye', 'François', 'George', 'Hank', 'Irene', 'James', 'Janice', 'Jenny', 'Jill', 'John', 'Kate', 'Katelyn', 'Kobe', 'Lexi', 'Manish', 'Mark', 'Matt', 'Mayna', 'Michael', 'Pete', 'Peter', 'Phil', 'Phillipa', 'Preston', 'Sally', 'Scott', 'Sandra', 'Steve', 'Stephanie', 'Vera' ] mnames = ['de', 'van', 'von', 'Shaw', 'T.'] lnames = [ 'Adams', 'Agarwal', 'Beal', 'Benson', 'Bonita', 'Davis', 'George', 'Harden', 'James', 'Jones', 'Johnson', 'Jordan', 'Lee', 'Leonard', 'Singh', 'Smith', 'Patel', 'Towns', 'Wall' ] for i in range(num_boring_names, num_names): fname = random.choice(fnames) + str(i) full_name = fname if random.random() < 0.7: if random.random() < 0.5: full_name += ' ' + random.choice(mnames) full_name += ' ' + random.choice(lnames) email = fname.lower() + '@zulip.com' names.append((full_name, email)) create_users(zulip_realm, names, tos_version=settings.TOS_VERSION) iago = get_user_by_delivery_email("*****@*****.**", zulip_realm) do_change_user_role(iago, UserProfile.ROLE_REALM_ADMINISTRATOR, acting_user=None) iago.is_staff = True iago.save(update_fields=['is_staff']) desdemona = get_user_by_delivery_email("*****@*****.**", zulip_realm) do_change_user_role(desdemona, UserProfile.ROLE_REALM_OWNER, acting_user=None) guest_user = get_user_by_delivery_email("*****@*****.**", zulip_realm) guest_user.role = UserProfile.ROLE_GUEST guest_user.save(update_fields=['role']) # These bots are directly referenced from code and thus # are needed for the test suite. zulip_realm_bots = [ ("Zulip Error Bot", "*****@*****.**"), ("Zulip Default Bot", "*****@*****.**"), ] for i in range(options["extra_bots"]): zulip_realm_bots.append( (f'Extra Bot {i}', f'extrabot{i}@zulip.com')) create_users(zulip_realm, zulip_realm_bots, bot_type=UserProfile.DEFAULT_BOT) zoe = get_user_by_delivery_email("*****@*****.**", zulip_realm) zulip_webhook_bots = [ ("Zulip Webhook Bot", "*****@*****.**"), ] # If a stream is not supplied in the webhook URL, the webhook # will (in some cases) send the notification as a PM to the # owner of the webhook bot, so bot_owner can't be None create_users(zulip_realm, zulip_webhook_bots, bot_type=UserProfile.INCOMING_WEBHOOK_BOT, bot_owner=zoe) aaron = get_user_by_delivery_email("*****@*****.**", zulip_realm) zulip_outgoing_bots = [ ("Outgoing Webhook", "*****@*****.**"), ] create_users(zulip_realm, zulip_outgoing_bots, bot_type=UserProfile.OUTGOING_WEBHOOK_BOT, bot_owner=aaron) outgoing_webhook = get_user("*****@*****.**", zulip_realm) add_service("outgoing-webhook", user_profile=outgoing_webhook, interface=Service.GENERIC, base_url="http://127.0.0.1:5002", token=generate_api_key()) # Add the realm internal bots to each realm. create_if_missing_realm_internal_bots() # Create public streams. stream_list = ["Verona", "Denmark", "Scotland", "Venice", "Rome"] stream_dict: Dict[str, Dict[str, Any]] = { "Verona": { "description": "A city in Italy" }, "Denmark": { "description": "A Scandinavian country" }, "Scotland": { "description": "Located in the United Kingdom" }, "Venice": { "description": "A northeastern Italian city" }, "Rome": { "description": "Yet another Italian city", "is_web_public": True }, } bulk_create_streams(zulip_realm, stream_dict) recipient_streams: List[int] = [ Stream.objects.get(name=name, realm=zulip_realm).id for name in stream_list ] # Create subscriptions to streams. The following # algorithm will give each of the users a different but # deterministic subset of the streams (given a fixed list # of users). For the test suite, we have a fixed list of # subscriptions to make sure test data is consistent # across platforms. subscriptions_list: List[Tuple[UserProfile, Recipient]] = [] profiles: Sequence[ UserProfile] = UserProfile.objects.select_related().filter( is_bot=False).order_by("email") if options["test_suite"]: subscriptions_map = { '*****@*****.**': ['Verona'], '*****@*****.**': ['Verona'], '*****@*****.**': ['Verona', 'Denmark'], '*****@*****.**': ['Verona', 'Denmark', 'Scotland'], '*****@*****.**': ['Verona', 'Denmark', 'Scotland'], '*****@*****.**': ['Verona', 'Denmark', 'Scotland', 'Venice'], '*****@*****.**': ['Verona', 'Denmark', 'Scotland', 'Venice', 'Rome'], '*****@*****.**': ['Verona'], '*****@*****.**': ['Verona', 'Denmark', 'Venice'], } for profile in profiles: email = profile.delivery_email if email not in subscriptions_map: raise Exception( f'Subscriptions not listed for user {email}') for stream_name in subscriptions_map[email]: stream = Stream.objects.get(name=stream_name) r = Recipient.objects.get(type=Recipient.STREAM, type_id=stream.id) subscriptions_list.append((profile, r)) else: num_streams = len(recipient_streams) num_users = len(profiles) for i, profile in enumerate(profiles): # Subscribe to some streams. fraction = float(i) / num_users num_recips = int(num_streams * fraction) + 1 for type_id in recipient_streams[:num_recips]: r = Recipient.objects.get(type=Recipient.STREAM, type_id=type_id) subscriptions_list.append((profile, r)) subscriptions_to_add: List[Subscription] = [] event_time = timezone_now() all_subscription_logs: (List[RealmAuditLog]) = [] i = 0 for profile, recipient in subscriptions_list: i += 1 color = STREAM_ASSIGNMENT_COLORS[i % len(STREAM_ASSIGNMENT_COLORS)] s = Subscription(recipient=recipient, user_profile=profile, color=color) subscriptions_to_add.append(s) log = RealmAuditLog( realm=profile.realm, modified_user=profile, modified_stream_id=recipient.type_id, event_last_message_id=0, event_type=RealmAuditLog.SUBSCRIPTION_CREATED, event_time=event_time) all_subscription_logs.append(log) Subscription.objects.bulk_create(subscriptions_to_add) RealmAuditLog.objects.bulk_create(all_subscription_logs) # Create custom profile field data phone_number = try_add_realm_custom_profile_field( zulip_realm, "Phone number", CustomProfileField.SHORT_TEXT, hint='') biography = try_add_realm_custom_profile_field( zulip_realm, "Biography", CustomProfileField.LONG_TEXT, hint='What are you known for?') favorite_food = try_add_realm_custom_profile_field( zulip_realm, "Favorite food", CustomProfileField.SHORT_TEXT, hint="Or drink, if you'd prefer") field_data: ProfileFieldData = { 'vim': { 'text': 'Vim', 'order': '1' }, 'emacs': { 'text': 'Emacs', 'order': '2' }, } favorite_editor = try_add_realm_custom_profile_field( zulip_realm, "Favorite editor", CustomProfileField.CHOICE, field_data=field_data) birthday = try_add_realm_custom_profile_field( zulip_realm, "Birthday", CustomProfileField.DATE) favorite_website = try_add_realm_custom_profile_field( zulip_realm, "Favorite website", CustomProfileField.URL, hint="Or your personal blog's URL") mentor = try_add_realm_custom_profile_field( zulip_realm, "Mentor", CustomProfileField.USER) github_profile = try_add_realm_default_custom_profile_field( zulip_realm, "github") # Fill in values for Iago and Hamlet hamlet = get_user_by_delivery_email("*****@*****.**", zulip_realm) do_update_user_custom_profile_data_if_changed( iago, [ { "id": phone_number.id, "value": "+1-234-567-8901" }, { "id": biography.id, "value": "Betrayer of Othello." }, { "id": favorite_food.id, "value": "Apples" }, { "id": favorite_editor.id, "value": "emacs" }, { "id": birthday.id, "value": "2000-1-1" }, { "id": favorite_website.id, "value": "https://zulip.readthedocs.io/en/latest/" }, { "id": mentor.id, "value": [hamlet.id] }, { "id": github_profile.id, "value": 'zulip' }, ]) do_update_user_custom_profile_data_if_changed( hamlet, [ { "id": phone_number.id, "value": "+0-11-23-456-7890" }, { "id": biography.id, "value": "I am:\n* The prince of Denmark\n* Nephew to the usurping Claudius", }, { "id": favorite_food.id, "value": "Dark chocolate" }, { "id": favorite_editor.id, "value": "vim" }, { "id": birthday.id, "value": "1900-1-1" }, { "id": favorite_website.id, "value": "https://blog.zulig.org" }, { "id": mentor.id, "value": [iago.id] }, { "id": github_profile.id, "value": 'zulipbot' }, ]) else: zulip_realm = get_realm("zulip") recipient_streams = [ klass.type_id for klass in Recipient.objects.filter(type=Recipient.STREAM) ] # Extract a list of all users user_profiles: List[UserProfile] = list( UserProfile.objects.filter(is_bot=False)) # Create a test realm emoji. IMAGE_FILE_PATH = static_path('images/test-images/checkbox.png') with open(IMAGE_FILE_PATH, 'rb') as fp: check_add_realm_emoji(zulip_realm, 'green_tick', iago, fp) if not options["test_suite"]: # Populate users with some bar data for user in user_profiles: status: int = UserPresence.ACTIVE date = timezone_now() client = get_client("website") if user.full_name[0] <= 'H': client = get_client("ZulipAndroid") UserPresence.objects.get_or_create(user_profile=user, realm_id=user.realm_id, client=client, timestamp=date, status=status) user_profiles_ids = [user_profile.id for user_profile in user_profiles] # Create several initial huddles for i in range(options["num_huddles"]): get_huddle(random.sample(user_profiles_ids, random.randint(3, 4))) # Create several initial pairs for personals personals_pairs = [ random.sample(user_profiles_ids, 2) for i in range(options["num_personals"]) ] create_alert_words(zulip_realm.id) # Generate a new set of test data. create_test_data() # prepopulate the URL preview/embed data for the links present # in the config.generate_data.json data set. This makes it # possible for populate_db to run happily without Internet # access. with open("zerver/tests/fixtures/docs_url_preview_data.json") as f: urls_with_preview_data = ujson.load(f) for url in urls_with_preview_data: cache_set(url, urls_with_preview_data[url], PREVIEW_CACHE_NAME) if options["delete"]: if options["test_suite"]: # Create test users; the MIT ones are needed to test # the Zephyr mirroring codepaths. testsuite_mit_users = [ ("Fred Sipb (MIT)", "*****@*****.**"), ("Athena Consulting Exchange User (MIT)", "*****@*****.**"), ("Esp Classroom (MIT)", "*****@*****.**"), ] create_users(mit_realm, testsuite_mit_users, tos_version=settings.TOS_VERSION) testsuite_lear_users = [ ("King Lear", "*****@*****.**"), ("Cordelia Lear", "*****@*****.**"), ] create_users(lear_realm, testsuite_lear_users, tos_version=settings.TOS_VERSION) if not options["test_suite"]: # To keep the messages.json fixtures file for the test # suite fast, don't add these users and subscriptions # when running populate_db for the test suite zulip_stream_dict: Dict[str, Dict[str, Any]] = { "devel": { "description": "For developing" }, "all": { "description": "For **everything**" }, "announce": { "description": "For announcements", 'stream_post_policy': Stream.STREAM_POST_POLICY_ADMINS }, "design": { "description": "For design" }, "support": { "description": "For support" }, "social": { "description": "For socializing" }, "test": { "description": "For testing `code`" }, "errors": { "description": "For errors" }, "sales": { "description": "For sales discussion" }, } # Calculate the maximum number of digits in any extra stream's # number, since a stream with name "Extra Stream 3" could show # up after "Extra Stream 29". (Used later to pad numbers with # 0s). maximum_digits = len(str(options['extra_streams'] - 1)) for i in range(options['extra_streams']): # Pad the number with 0s based on `maximum_digits`. number_str = str(i).zfill(maximum_digits) extra_stream_name = 'Extra Stream ' + number_str zulip_stream_dict[extra_stream_name] = { "description": "Auto-generated extra stream.", } bulk_create_streams(zulip_realm, zulip_stream_dict) # Now that we've created the notifications stream, configure it properly. zulip_realm.notifications_stream = get_stream( "announce", zulip_realm) zulip_realm.save(update_fields=['notifications_stream']) # Add a few default streams for default_stream_name in [ "design", "devel", "social", "support" ]: DefaultStream.objects.create(realm=zulip_realm, stream=get_stream( default_stream_name, zulip_realm)) # Now subscribe everyone to these streams subscribe_users_to_streams(zulip_realm, zulip_stream_dict) if not options["test_suite"]: # Update pointer of each user to point to the last message in their # UserMessage rows with sender_id=user_profile_id. users = list( UserMessage.objects.filter(message__sender_id=F( 'user_profile_id')).values('user_profile_id').annotate( pointer=Max('message_id'))) for user in users: UserProfile.objects.filter( id=user['user_profile_id']).update( pointer=user['pointer']) create_user_groups() if not options["test_suite"]: # We populate the analytics database here for # development purpose only call_command('populate_analytics_db') threads = options["threads"] jobs: List[Tuple[int, List[List[int]], Dict[str, Any], Callable[[str], int], int]] = [] for i in range(threads): count = options["num_messages"] // threads if i < options["num_messages"] % threads: count += 1 jobs.append((count, personals_pairs, options, self.stdout.write, random.randint(0, 10**10))) for job in jobs: generate_and_send_messages(job) if options["delete"]: if not options['test_suite']: # These bots are not needed by the test suite # Also, we don't want interacting with each other # in dev setup. internal_zulip_users_nosubs = [ ("Zulip Commit Bot", "*****@*****.**"), ("Zulip Trac Bot", "*****@*****.**"), ("Zulip Nagios Bot", "*****@*****.**"), ] create_users(zulip_realm, internal_zulip_users_nosubs, bot_type=UserProfile.DEFAULT_BOT) mark_all_messages_as_read() self.stdout.write("Successfully populated test database.\n")
def mit_user(self, name: str) -> UserProfile: email = self.mit_user_map[name] return get_user(email, get_realm('zephyr'))
def test_people(self) -> None: hamlet = self.example_user("hamlet") realm = get_realm("zulip") self.login_user(hamlet) bots = {} for i in range(3): bots[i] = self.create_bot( owner=hamlet, bot_email=f"bot-{i}@zulip.com", bot_name=f"Bot {i}", ) for i in range(3): defunct_user = self.create_non_active_user( realm=realm, email=f"defunct-{i}@zulip.com", name=f"Defunct User {i}", ) result = self._get_home_page() page_params = self._get_page_params(result) """ We send three lists of users. The first two below are disjoint lists of users, and the records we send for them have identical structure. The realm_bots bucket is somewhat redundant, since all bots will be in one of the first two buckets. They do include fields, however, that normal users don't care about, such as default_sending_stream. """ buckets = [ "realm_users", "realm_non_active_users", "realm_bots", ] for field in buckets: users = page_params[field] self.assertTrue(len(users) >= 3, field) for rec in users: self.assertEqual(rec["user_id"], get_user(rec["email"], realm).id) if field == "realm_bots": self.assertNotIn("is_bot", rec) self.assertIn("is_active", rec) self.assertIn("owner_id", rec) else: self.assertIn("is_bot", rec) self.assertNotIn("is_active", rec) active_ids = {p["user_id"] for p in page_params["realm_users"]} non_active_ids = { p["user_id"] for p in page_params["realm_non_active_users"] } bot_ids = {p["user_id"] for p in page_params["realm_bots"]} self.assertIn(hamlet.id, active_ids) self.assertIn(defunct_user.id, non_active_ids) # Bots can show up in multiple buckets. self.assertIn(bots[2].id, bot_ids) self.assertIn(bots[2].id, active_ids) # Make sure nobody got mis-bucketed. self.assertNotIn(hamlet.id, non_active_ids) self.assertNotIn(defunct_user.id, active_ids) cross_bots = page_params["cross_realm_bots"] self.assertEqual(len(cross_bots), 3) cross_bots.sort(key=lambda d: d["email"]) for cross_bot in cross_bots: # These are either nondeterministic or boring del cross_bot["timezone"] del cross_bot["avatar_url"] del cross_bot["date_joined"] notification_bot = self.notification_bot() email_gateway_bot = get_system_bot(settings.EMAIL_GATEWAY_BOT) welcome_bot = get_system_bot(settings.WELCOME_BOT) by_email = lambda d: d["email"] self.assertEqual( sorted(cross_bots, key=by_email), sorted( [ dict( avatar_version=email_gateway_bot.avatar_version, bot_owner_id=None, bot_type=1, email=email_gateway_bot.email, user_id=email_gateway_bot.id, full_name=email_gateway_bot.full_name, is_active=True, is_bot=True, is_admin=False, is_owner=False, is_cross_realm_bot=True, is_guest=False, ), dict( avatar_version=notification_bot.avatar_version, bot_owner_id=None, bot_type=1, email=notification_bot.email, user_id=notification_bot.id, full_name=notification_bot.full_name, is_active=True, is_bot=True, is_admin=False, is_owner=False, is_cross_realm_bot=True, is_guest=False, ), dict( avatar_version=welcome_bot.avatar_version, bot_owner_id=None, bot_type=1, email=welcome_bot.email, user_id=welcome_bot.id, full_name=welcome_bot.full_name, is_active=True, is_bot=True, is_admin=False, is_owner=False, is_cross_realm_bot=True, is_guest=False, ), ], key=by_email, ), )
def lear_user(self, name: str) -> UserProfile: email = self.lear_user_map[name] return get_user(email, get_realm('lear'))
def save(self, domain_override: Optional[bool] = None, subject_template_name: Text = 'registration/password_reset_subject.txt', email_template_name: Text = 'registration/password_reset_email.html', use_https: bool = False, token_generator: PasswordResetTokenGenerator = default_token_generator, from_email: Optional[Text] = None, request: HttpRequest = None, html_email_template_name: Optional[Text] = None, extra_email_context: Optional[Dict[str, Any]] = None) -> None: """ If the email address has an account in the target realm, generates a one-use only link for resetting password and sends to the user. We send a different email if an associated account does not exist in the database, or an account does exist, but not in the realm. Note: We ignore protocol and the various email template arguments (those are an artifact of using Django's password reset framework). """ email = self.cleaned_data["email"] realm = get_realm(get_subdomain(request)) if not email_auth_enabled(realm): logging.info( "Password reset attempted for %s even though password auth is disabled." % (email, )) return user = None try: user = get_user(email, realm) except UserProfile.DoesNotExist: pass context = { 'email': email, 'realm_uri': realm.uri, } if user is not None: token = token_generator.make_token(user) uid = urlsafe_base64_encode(force_bytes(user.id)) endpoint = reverse( 'django.contrib.auth.views.password_reset_confirm', kwargs=dict(uidb64=uid, token=token)) context['no_account_in_realm'] = False context['reset_url'] = "{}{}".format(user.realm.uri, endpoint) send_email('zerver/emails/password_reset', to_user_id=user.id, from_name="Zulip Account Security", from_address=FromAddress.NOREPLY, context=context) else: context['no_account_in_realm'] = True accounts = UserProfile.objects.filter(email__iexact=email) if accounts: context['accounts'] = accounts context['multiple_accounts'] = accounts.count() != 1 send_email('zerver/emails/password_reset', to_email=email, from_name="Zulip Account Security", from_address=FromAddress.NOREPLY, context=context)