Example #1
0
    def test_send_deactivated_realm(self):
        """
        rest_dispatch rejects requests in a deactivated realm, both /json and api

        """
        realm = get_realm("zulip.com")
        do_deactivate_realm(get_realm("zulip.com"))

        result = self.client_post("/json/messages", {"type": "private",
                                                     "content": "Test message",
                                                     "client": "test suite",
                                                     "to": "*****@*****.**"})
        self.assert_json_error_contains(result, "Not logged in", status_code=401)

        # Even if a logged-in session was leaked, it still wouldn't work
        realm.deactivated = False
        realm.save()
        self.login("*****@*****.**")
        realm.deactivated = True
        realm.save()

        result = self.client_post("/json/messages", {"type": "private",
                                                     "content": "Test message",
                                                     "client": "test suite",
                                                     "to": "*****@*****.**"})
        self.assert_json_error_contains(result, "has been deactivated", status_code=400)

        result = self.client_post("/api/v1/messages", {"type": "private",
                                                       "content": "Test message",
                                                       "client": "test suite",
                                                       "to": "*****@*****.**"},
                                  **self.api_auth("*****@*****.**"))
        self.assert_json_error_contains(result, "has been deactivated", status_code=401)
Example #2
0
    def test_create_realm_domain(self) -> None:
        self.login(self.example_email("iago"))
        data = {'domain': ujson.dumps(''),
                'allow_subdomains': ujson.dumps(True)}
        result = self.client_post("/json/realm/domains", info=data)
        self.assert_json_error(result, 'Invalid domain: Domain can\'t be empty.')

        data['domain'] = ujson.dumps('acme.com')
        result = self.client_post("/json/realm/domains", info=data)
        self.assert_json_success(result)
        realm = get_realm('zulip')
        self.assertTrue(RealmDomain.objects.filter(realm=realm, domain='acme.com',
                                                   allow_subdomains=True).exists())

        result = self.client_post("/json/realm/domains", info=data)
        self.assert_json_error(result, 'The domain acme.com is already a part of your organization.')

        mit_user_profile = self.mit_user("sipbtest")
        self.login(mit_user_profile.email, realm=get_realm("zephyr"))

        do_change_is_admin(mit_user_profile, True)

        result = self.client_post("/json/realm/domains", info=data,
                                  HTTP_HOST=mit_user_profile.realm.host)
        self.assert_json_success(result)
Example #3
0
    def test_change_signup_notifications_stream(self) -> None:
        # We need an admin user.
        email = '*****@*****.**'
        self.login(email)

        disabled_signup_notifications_stream_id = -1
        req = dict(signup_notifications_stream_id = ujson.dumps(disabled_signup_notifications_stream_id))
        result = self.client_patch('/json/realm', req)
        self.assert_json_success(result)
        realm = get_realm('zulip')
        self.assertEqual(realm.signup_notifications_stream, None)

        new_signup_notifications_stream_id = 4
        req = dict(signup_notifications_stream_id = ujson.dumps(new_signup_notifications_stream_id))

        result = self.client_patch('/json/realm', req)
        self.assert_json_success(result)
        realm = get_realm('zulip')
        self.assertEqual(realm.signup_notifications_stream.id, new_signup_notifications_stream_id)

        invalid_signup_notifications_stream_id = 1234
        req = dict(signup_notifications_stream_id = ujson.dumps(invalid_signup_notifications_stream_id))
        result = self.client_patch('/json/realm', req)
        self.assert_json_error(result, 'Invalid stream id')
        realm = get_realm('zulip')
        self.assertNotEqual(realm.signup_notifications_stream.id, invalid_signup_notifications_stream_id)
Example #4
0
 def test_delete_all_user_sessions(self) -> None:
     self.do_test_session(self.example_email("hamlet"),
                          lambda: delete_all_user_sessions(),
                          get_realm("zulip"), True)
     self.do_test_session(self.mit_email("sipbtest"),
                          lambda: delete_all_user_sessions(),
                          get_realm("zephyr"), True)
Example #5
0
    def test_change_video_chat_provider(self) -> None:
        self.assertEqual(get_realm('zulip').video_chat_provider, "Jitsi")
        email = self.example_email("iago")
        self.login(email)

        req = {"video_chat_provider": ujson.dumps("Google Hangouts")}
        result = self.client_patch('/json/realm', req)
        self.assert_json_error(result, "Invalid domain: Domain can't be empty.")

        req = {
            "video_chat_provider": ujson.dumps("Google Hangouts"),
            "google_hangouts_domain": ujson.dumps("invaliddomain"),
        }
        result = self.client_patch('/json/realm', req)
        self.assert_json_error(result, "Invalid domain: Domain must have at least one dot (.)")

        req = {
            "video_chat_provider": ujson.dumps("Google Hangouts"),
            "google_hangouts_domain": ujson.dumps("zulip.com"),
        }
        result = self.client_patch('/json/realm', req)
        self.assert_json_success(result)
        self.assertEqual(get_realm('zulip').video_chat_provider, "Google Hangouts")

        req = {"video_chat_provider": ujson.dumps("Jitsi")}
        result = self.client_patch('/json/realm', req)
        self.assert_json_success(result)
        self.assertEqual(get_realm('zulip').video_chat_provider, "Jitsi")
Example #6
0
    def test_realm_message_content_allowed_in_email_notifications(self) -> None:
        user = self.example_user("hamlet")

        # When message content is allowed at realm level
        realm = get_realm("zulip")
        realm.message_content_allowed_in_email_notifications = True
        realm.save(update_fields=['message_content_allowed_in_email_notifications'])

        # Emails have missed message content when message content is enabled by the user
        do_change_notification_settings(user, "message_content_in_email_notifications", True)
        mail.outbox = []
        self._extra_context_in_personal_missed_stream_messages(False, show_message_content=True)

        # Emails don't have missed message content when message content is disabled by the user
        do_change_notification_settings(user, "message_content_in_email_notifications", False)
        mail.outbox = []
        self._extra_context_in_personal_missed_stream_messages(False, show_message_content=False)

        # When message content is not allowed at realm level
        # Emails don't have missed message irrespective of message content setting of the user
        realm = get_realm("zulip")
        realm.message_content_allowed_in_email_notifications = False
        realm.save(update_fields=['message_content_allowed_in_email_notifications'])

        do_change_notification_settings(user, "message_content_in_email_notifications", True)
        mail.outbox = []
        self._extra_context_in_personal_missed_stream_messages(False, show_message_content=False)

        do_change_notification_settings(user, "message_content_in_email_notifications", False)
        mail.outbox = []
        self._extra_context_in_personal_missed_stream_messages(False, show_message_content=False)
Example #7
0
    def test_valid_user_id(self) -> None:
        realm = get_realm("zulip")
        hamlet = self.example_user('hamlet')
        othello = self.example_user('othello')
        bot = self.example_user("welcome_bot")

        # Invalid user ID
        invalid_uid = 1000
        self.assertEqual(check_valid_user_id(realm.id, invalid_uid),
                         "Invalid user ID: %d" % (invalid_uid))
        self.assertEqual(check_valid_user_id(realm.id, "abc"),
                         "User id is not an integer")
        self.assertEqual(check_valid_user_id(realm.id, str(othello.id)),
                         "User id is not an integer")

        # User is in different realm
        self.assertEqual(check_valid_user_id(get_realm("zephyr").id, hamlet.id),
                         "Invalid user ID: %d" % (hamlet.id))

        # User is not active
        hamlet.is_active = False
        hamlet.save()
        self.assertEqual(check_valid_user_id(realm.id, hamlet.id),
                         "User is deactivated")
        self.assertEqual(check_valid_user_id(realm.id, hamlet.id, allow_deactivated=True),
                         None)

        # User is bot
        self.assertEqual(check_valid_user_id(realm.id, bot.id),
                         "User with id %d is bot" % (bot.id))

        # Succesfully get non-bot, active user belong to your realm
        self.assertEqual(check_valid_user_id(realm.id, othello.id), None)
Example #8
0
    def test_get_user_by_id_in_realm_including_cross_realm(self) -> None:
        realm = get_realm('zulip')
        hamlet = self.example_user('hamlet')
        othello = self.example_user('othello')
        bot = self.example_user('welcome_bot')

        # Pass in the ID of a cross-realm bot and a valid realm
        cross_realm_bot = get_user_by_id_in_realm_including_cross_realm(
            bot.id, realm)
        self.assertEqual(cross_realm_bot.email, bot.email)
        self.assertEqual(cross_realm_bot.id, bot.id)

        # Pass in the ID of a cross-realm bot but with a invalid realm,
        # note that the realm should be irrelevant here
        cross_realm_bot = get_user_by_id_in_realm_including_cross_realm(
            bot.id, get_realm('invalid'))
        self.assertEqual(cross_realm_bot.email, bot.email)
        self.assertEqual(cross_realm_bot.id, bot.id)

        # Pass in the ID of a non-cross-realm user with a realm
        user_profile = get_user_by_id_in_realm_including_cross_realm(
            othello.id, realm)
        self.assertEqual(user_profile.email, othello.email)
        self.assertEqual(user_profile.id, othello.id)

        # If the realm doesn't match, or if the ID is not that of a
        # cross-realm bot, UserProfile.DoesNotExist is raised
        with self.assertRaises(UserProfile.DoesNotExist):
            get_user_by_id_in_realm_including_cross_realm(
                hamlet.id, get_realm('invalid'))
Example #9
0
 def test_delete_user_sessions(self) -> None:
     user_profile = self.example_user('hamlet')
     email = user_profile.email
     self.do_test_session(str(email), lambda: delete_user_sessions(user_profile),
                          get_realm("zulip"), True)
     self.do_test_session(str(self.example_email("othello")),
                          lambda: delete_user_sessions(user_profile),
                          get_realm("zulip"), False)
Example #10
0
 def test_delete_realm_user_sessions(self) -> None:
     realm = get_realm('zulip')
     self.do_test_session(self.example_email("hamlet"),
                          lambda: delete_realm_user_sessions(realm),
                          get_realm("zulip"), True)
     self.do_test_session(self.mit_email("sipbtest"),
                          lambda: delete_realm_user_sessions(realm),
                          get_realm("zephyr"), False)
Example #11
0
    def test_upgrade_where_subscription_save_fails_at_first(
            self, mock5: Mock, mock4: Mock, mock3: Mock, mock2: Mock, mock1: Mock) -> None:
        user = self.example_user("hamlet")
        self.login(user.email)
        # From https://stripe.com/docs/testing#cards: Attaching this card to
        # a Customer object succeeds, but attempts to charge the customer fail.
        self.client_post("/upgrade/", {'stripeToken': stripe_create_token('4000000000000341').id,
                                       'signed_seat_count': self.signed_seat_count,
                                       'salt': self.salt,
                                       'plan': Plan.CLOUD_ANNUAL})
        # Check that we created a Customer object with has_billing_relationship False
        customer = Customer.objects.get(realm=get_realm('zulip'))
        self.assertFalse(customer.has_billing_relationship)
        original_stripe_customer_id = customer.stripe_customer_id
        # Check that we created a customer in stripe, with no subscription
        stripe_customer = stripe_get_customer(customer.stripe_customer_id)
        self.assertFalse(extract_current_subscription(stripe_customer))
        # Check that we correctly populated RealmAuditLog
        audit_log_entries = list(RealmAuditLog.objects.filter(acting_user=user)
                                 .values_list('event_type', flat=True).order_by('id'))
        self.assertEqual(audit_log_entries, [RealmAuditLog.STRIPE_CUSTOMER_CREATED,
                                             RealmAuditLog.STRIPE_CARD_CHANGED])
        # Check that we did not update Realm
        realm = get_realm("zulip")
        self.assertFalse(realm.has_seat_based_plan)
        # Check that we still get redirected to /upgrade
        response = self.client_get("/billing/")
        self.assertEqual(response.status_code, 302)
        self.assertEqual('/upgrade/', response.url)

        # Try again, with a valid card
        self.client_post("/upgrade/", {'stripeToken': stripe_create_token().id,
                                       'signed_seat_count': self.signed_seat_count,
                                       'salt': self.salt,
                                       'plan': Plan.CLOUD_ANNUAL})
        customer = Customer.objects.get(realm=get_realm('zulip'))
        # Impossible to create two Customers, but check that we didn't
        # change stripe_customer_id and that we updated has_billing_relationship
        self.assertEqual(customer.stripe_customer_id, original_stripe_customer_id)
        self.assertTrue(customer.has_billing_relationship)
        # Check that we successfully added a subscription
        stripe_customer = stripe_get_customer(customer.stripe_customer_id)
        self.assertTrue(extract_current_subscription(stripe_customer))
        # Check that we correctly populated RealmAuditLog
        audit_log_entries = list(RealmAuditLog.objects.filter(acting_user=user)
                                 .values_list('event_type', flat=True).order_by('id'))
        self.assertEqual(audit_log_entries, [RealmAuditLog.STRIPE_CUSTOMER_CREATED,
                                             RealmAuditLog.STRIPE_CARD_CHANGED,
                                             RealmAuditLog.STRIPE_CARD_CHANGED,
                                             RealmAuditLog.STRIPE_PLAN_CHANGED,
                                             RealmAuditLog.REALM_PLAN_TYPE_CHANGED])
        # Check that we correctly updated Realm
        realm = get_realm("zulip")
        self.assertTrue(realm.has_seat_based_plan)
        # Check that we can no longer access /upgrade
        response = self.client_get("/upgrade/")
        self.assertEqual(response.status_code, 302)
        self.assertEqual('/billing/', response.url)
Example #12
0
 def test_realm_icon_version(self) -> None:
     self.login(self.example_email("iago"))
     realm = get_realm('zulip')
     icon_version = realm.icon_version
     self.assertEqual(icon_version, 1)
     with get_test_image_file(self.correct_files[0][0]) as fp:
         self.client_post("/json/realm/icon", {'file': fp})
     realm = get_realm('zulip')
     self.assertEqual(realm.icon_version, icon_version + 1)
Example #13
0
 def test_realm_reactivation_link(self) -> None:
     realm = get_realm('zulip')
     do_deactivate_realm(realm)
     self.assertTrue(realm.deactivated)
     confirmation_url = create_confirmation_link(realm, realm.host, Confirmation.REALM_REACTIVATION)
     response = self.client_get(confirmation_url)
     self.assert_in_success_response(['Your organization has been successfully reactivated'], response)
     realm = get_realm('zulip')
     self.assertFalse(realm.deactivated)
Example #14
0
    def test_upgrade_where_subscription_save_fails_at_first(self, create_customer: mock.Mock) -> None:
        user = self.example_user("hamlet")
        self.login(user.email)
        with mock.patch('stripe.Subscription.create',
                        side_effect=stripe.error.CardError('message', 'param', 'code', json_body={})):
            self.client_post("/upgrade/", {'stripeToken': self.token,
                                           'signed_seat_count': self.signed_seat_count,
                                           'salt': self.salt,
                                           'plan': Plan.CLOUD_ANNUAL})
        # Check that we created a customer in stripe
        create_customer.assert_called()
        create_customer.reset_mock()
        # Check that we created a Customer with has_billing_relationship=False
        self.assertTrue(Customer.objects.filter(
            stripe_customer_id=self.stripe_customer_id, has_billing_relationship=False).exists())
        # Check that we correctly populated RealmAuditLog
        audit_log_entries = list(RealmAuditLog.objects.filter(acting_user=user)
                                 .values_list('event_type', flat=True).order_by('id'))
        self.assertEqual(audit_log_entries, [RealmAuditLog.STRIPE_CUSTOMER_CREATED,
                                             RealmAuditLog.STRIPE_CARD_ADDED])
        # Check that we did not update Realm
        realm = get_realm("zulip")
        self.assertFalse(realm.has_seat_based_plan)
        # Check that we still get redirected to /upgrade
        response = self.client_get("/billing/")
        self.assertEqual(response.status_code, 302)
        self.assertEqual('/upgrade/', response.url)

        # mock_create_customer just returns a customer with no subscription object
        with mock.patch("stripe.Subscription.create", side_effect=mock_customer_with_subscription):
            with mock.patch("stripe.Customer.retrieve", side_effect=mock_create_customer):
                with mock.patch("stripe.Customer.save", side_effect=mock_create_customer):
                    self.client_post("/upgrade/", {'stripeToken': self.token,
                                                   'signed_seat_count': self.signed_seat_count,
                                                   'salt': self.salt,
                                                   'plan': Plan.CLOUD_ANNUAL})
        # Check that we do not create a new customer in stripe
        create_customer.assert_not_called()
        # Impossible to create two Customers, but check that we updated has_billing_relationship
        self.assertTrue(Customer.objects.filter(
            stripe_customer_id=self.stripe_customer_id, has_billing_relationship=True).exists())
        # Check that we correctly populated RealmAuditLog
        audit_log_entries = list(RealmAuditLog.objects.filter(acting_user=user)
                                 .values_list('event_type', flat=True).order_by('id'))
        self.assertEqual(audit_log_entries, [RealmAuditLog.STRIPE_CUSTOMER_CREATED,
                                             RealmAuditLog.STRIPE_CARD_ADDED,
                                             RealmAuditLog.STRIPE_CARD_ADDED,
                                             RealmAuditLog.STRIPE_PLAN_CHANGED,
                                             RealmAuditLog.REALM_PLAN_TYPE_CHANGED])
        # Check that we correctly updated Realm
        realm = get_realm("zulip")
        self.assertTrue(realm.has_seat_based_plan)
        # Check that we can no longer access /upgrade
        response = self.client_get("/upgrade/")
        self.assertEqual(response.status_code, 302)
        self.assertEqual('/billing/', response.url)
Example #15
0
    def test_initial_plan_type(self) -> None:
        with self.settings(BILLING_ENABLED=True):
            self.assertEqual(do_create_realm('hosted', 'hosted').plan_type, Realm.LIMITED)
            self.assertEqual(get_realm("hosted").max_invites, settings.INVITES_DEFAULT_REALM_DAILY_MAX)
            self.assertEqual(get_realm("hosted").message_visibility_limit, Realm.MESSAGE_VISIBILITY_LIMITED)

        with self.settings(BILLING_ENABLED=False):
            self.assertEqual(do_create_realm('onpremise', 'onpremise').plan_type, Realm.SELF_HOSTED)
            self.assertEqual(get_realm('onpremise').max_invites, settings.INVITES_DEFAULT_REALM_DAILY_MAX)
            self.assertEqual(get_realm('onpremise').message_visibility_limit, None)
Example #16
0
 def test_do_set_realm_name_caching(self) -> None:
     """The main complicated thing about setting realm names is fighting the
     cache, and we start by populating the cache for Hamlet, and we end
     by checking the cache to ensure that the new value is there."""
     self.example_user('hamlet')
     realm = get_realm('zulip')
     new_name = u'Zed You Elle Eye Pea'
     do_set_realm_property(realm, 'name', new_name)
     self.assertEqual(get_realm(realm.string_id).name, new_name)
     self.assert_user_profile_cache_gets_new_name(self.example_user('hamlet'), new_name)
Example #17
0
    def test_deactivate_realm_by_admin(self) -> None:
        email = self.example_email('iago')
        self.login(email)
        realm = get_realm('zulip')
        self.assertFalse(realm.deactivated)

        result = self.client_post('/json/realm/deactivate')
        self.assert_json_success(result)
        realm = get_realm('zulip')
        self.assertTrue(realm.deactivated)
Example #18
0
    def test_deactivate_realm_by_non_admin(self) -> None:
        email = self.example_email('hamlet')
        self.login(email)
        realm = get_realm('zulip')
        self.assertFalse(realm.deactivated)

        result = self.client_post('/json/realm/deactivate')
        self.assert_json_error(result, "Must be an organization administrator")
        realm = get_realm('zulip')
        self.assertFalse(realm.deactivated)
Example #19
0
    def test_user_ids_to_users(self) -> None:
        real_user_ids = [
            self.example_user('hamlet').id,
            self.example_user('cordelia').id,
        ]

        user_ids_to_users(real_user_ids, get_realm("zulip"))
        with self.assertRaises(JsonableError):
            user_ids_to_users([1234], get_realm("zephyr"))
        with self.assertRaises(JsonableError):
            user_ids_to_users(real_user_ids, get_realm("zephyr"))
Example #20
0
    def test_delete_all_realm_domains(self) -> None:
        self.login(self.example_email("iago"))
        realm = get_realm('zulip')
        query = RealmDomain.objects.filter(realm=realm)

        self.assertTrue(realm.restricted_to_domain)
        for realm_domain in query.all():
            do_remove_realm_domain(realm_domain)
        self.assertEqual(query.count(), 0)
        # Deleting last realm_domain should set `restricted_to_domain` to False.
        # This should be tested on a fresh instance, since the cached objects
        # would not be updated.
        self.assertFalse(get_realm('zulip').restricted_to_domain)
Example #21
0
    def test_realm_filter_events(self):
        schema_checker = check_dict([
            ('type', equals('realm_filters')),
            ('realm_filters', check_list(None)), # TODO: validate tuples in the list
        ])
        events = self.do_test(lambda: do_add_realm_filter(get_realm("zulip.com"), "#[123]",
                                                          "https://realm.com/my_realm_filter/%(id)s"))
        error = schema_checker('events[0]', events[0])
        self.assert_on_error(error)

        self.do_test(lambda: do_remove_realm_filter(get_realm("zulip.com"), "#[123]"))
        error = schema_checker('events[0]', events[0])
        self.assert_on_error(error)
Example #22
0
 def test_do_change_realm_subdomain_clears_user_realm_cache(self) -> None:
     """The main complicated thing about changing realm subdomains is
     updating the cache, and we start by populating the cache for
     Hamlet, and we end by checking the cache to ensure that his
     realm appears to be deactivated.  You can make this test fail
     by disabling cache.flush_realm()."""
     user = get_user_profile_by_email('*****@*****.**')
     realm = get_realm('zulip')
     do_change_realm_subdomain(realm, "newzulip")
     user = get_user_profile_by_email('*****@*****.**')
     self.assertEqual(user.realm.string_id, "newzulip")
     # This doesn't use a cache right now, but may later.
     self.assertIsNone(get_realm("zulip"))
Example #23
0
    def test_user_ids_to_users(self) -> None:
        real_user_ids = [
            self.example_user('hamlet').id,
            self.example_user('cordelia').id,
        ]

        self.assertEqual(user_ids_to_users([], get_realm("zulip")), [])
        self.assertEqual(set([user_profile.id for user_profile in user_ids_to_users(real_user_ids, get_realm("zulip"))]),
                         set(real_user_ids))
        with self.assertRaises(JsonableError):
            user_ids_to_users([1234], get_realm("zephyr"))
        with self.assertRaises(JsonableError):
            user_ids_to_users(real_user_ids, get_realm("zephyr"))
Example #24
0
    def test_realm_emoji_events(self):
        schema_checker = check_dict([
            ('type', equals('realm_emoji')),
            ('op', equals('update')),
            ('realm_emoji', check_dict([])),
        ])
        events = self.do_test(lambda: do_add_realm_emoji(get_realm("zulip.com"), "my_emoji",
                                                         "https://realm.com/my_emoji"))
        error = schema_checker('events[0]', events[0])
        self.assert_on_error(error)

        events = self.do_test(lambda: do_remove_realm_emoji(get_realm("zulip.com"), "my_emoji"))
        error = schema_checker('events[0]', events[0])
        self.assert_on_error(error)
Example #25
0
 def subscribe_to_stream(self, email, stream_name, realm=None):
     realm = get_realm(resolve_email_to_domain(email))
     stream = get_stream(stream_name, realm)
     if stream is None:
         stream, _ = create_stream_if_needed(realm, stream_name)
     user_profile = get_user_profile_by_email(email)
     do_add_subscription(user_profile, stream, no_log=True)
Example #26
0
    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)
Example #27
0
 def clean_realm_subdomain(self):
     # type: () -> str
     if settings.REALMS_HAVE_SUBDOMAINS:
         error_strings = {
             'too short': _("Subdomain needs to have length 3 or greater."),
             'extremal dash': _("Subdomain cannot start or end with a '-'."),
             'bad character': _("Subdomain can only have lowercase letters, numbers, and '-'s."),
             'unavailable': _("Subdomain unavailable. Please choose a different one.")}
     else:
         error_strings = {
             'too short': _("Short name needs at least 3 characters."),
             'extremal dash': _("Short name cannot start or end with a '-'."),
             'bad character': _("Short name can only have lowercase letters, numbers, and '-'s."),
             'unavailable': _("Short name unavailable. Please choose a different one.")}
     subdomain = self.cleaned_data['realm_subdomain']
     if not subdomain:
         return ''
     if len(subdomain) < 3:
         raise ValidationError(error_strings['too short'])
     if subdomain[0] == '-' or subdomain[-1] == '-':
         raise ValidationError(error_strings['extremal dash'])
     if not re.match('^[a-z0-9-]*$', subdomain):
         raise ValidationError(error_strings['bad character'])
     if is_reserved_subdomain(subdomain) or \
        get_realm(subdomain) is not None:
         raise ValidationError(error_strings['unavailable'])
     return subdomain
Example #28
0
    def handle(self, *args, **options):
        # type: (*Any, **str) -> None
        if options['realms']:
            try:
                realms = [get_realm(string_id) for string_id in options['realms']]
            except Realm.DoesNotExist as e:
                print(e)
                exit(1)
        else:
            realms = Realm.objects.all()

        for realm in realms:
            print(realm.string_id)
            print("------------")
            print("%25s %15s %10s" % ("stream", "subscribers", "messages"))
            streams = Stream.objects.filter(realm=realm).exclude(Q(name__istartswith="tutorial-"))
            invite_only_count = 0
            for stream in streams:
                if stream.invite_only:
                    invite_only_count += 1
                    continue
                print("%25s" % (stream.name,), end=' ')
                recipient = Recipient.objects.filter(type=Recipient.STREAM, type_id=stream.id)
                print("%10d" % (len(Subscription.objects.filter(recipient=recipient, active=True)),), end=' ')
                num_messages = len(Message.objects.filter(recipient=recipient))
                print("%12d" % (num_messages,))
            print("%d invite-only streams" % (invite_only_count,))
            print("")
Example #29
0
    def users_subscribed_to_stream(self, stream_name, realm_domain):
        realm = get_realm(realm_domain)
        stream = Stream.objects.get(name=stream_name, realm=realm)
        recipient = Recipient.objects.get(type_id=stream.id, type=Recipient.STREAM)
        subscriptions = Subscription.objects.filter(recipient=recipient, active=True)

        return [subscription.user_profile for subscription in subscriptions]
Example #30
0
    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)
Example #31
0
    def test_analytics_not_running(self) -> None:
        realm = get_realm("zulip")

        self.assertEqual(FillState.objects.count(), 0)

        realm.date_created = timezone_now() - timedelta(days=3)
        realm.save(update_fields=["date_created"])
        with self.assertLogs(level="WARNING") as m:
            result = self.client_get("/json/analytics/chart_data",
                                     {"chart_name": "messages_sent_over_time"})
            self.assertEqual(
                m.output,
                [
                    f"WARNING:root:User from realm zulip attempted to access /stats, but the computed start time: {realm.date_created} (creation of realm or installation) is later than the computed end time: 0001-01-01 00:00:00+00:00 (last successful analytics update). Is the analytics cron job running?"
                ],
            )

        self.assert_json_error_contains(result, "No analytics data available")

        realm.date_created = timezone_now() - timedelta(days=1, hours=2)
        realm.save(update_fields=["date_created"])
        with self.assertLogs(level="WARNING") as m:
            result = self.client_get("/json/analytics/chart_data",
                                     {"chart_name": "messages_sent_over_time"})
            self.assertEqual(
                m.output,
                [
                    f"WARNING:root:User from realm zulip attempted to access /stats, but the computed start time: {realm.date_created} (creation of realm or installation) is later than the computed end time: 0001-01-01 00:00:00+00:00 (last successful analytics update). Is the analytics cron job running?"
                ],
            )

        self.assert_json_error_contains(result, "No analytics data available")

        realm.date_created = timezone_now() - timedelta(days=1, minutes=10)
        realm.save(update_fields=["date_created"])
        result = self.client_get("/json/analytics/chart_data",
                                 {"chart_name": "messages_sent_over_time"})
        self.assert_json_success(result)

        realm.date_created = timezone_now() - timedelta(hours=10)
        realm.save(update_fields=["date_created"])
        result = self.client_get("/json/analytics/chart_data",
                                 {"chart_name": "messages_sent_over_time"})
        self.assert_json_success(result)

        end_time = timezone_now() - timedelta(days=5)
        fill_state = FillState.objects.create(
            property="messages_sent:is_bot:hour",
            end_time=end_time,
            state=FillState.DONE)

        realm.date_created = timezone_now() - timedelta(days=3)
        realm.save(update_fields=["date_created"])
        with self.assertLogs(level="WARNING") as m:
            result = self.client_get("/json/analytics/chart_data",
                                     {"chart_name": "messages_sent_over_time"})
            self.assertEqual(
                m.output,
                [
                    f"WARNING:root:User from realm zulip attempted to access /stats, but the computed start time: {realm.date_created} (creation of realm or installation) is later than the computed end time: {end_time} (last successful analytics update). Is the analytics cron job running?"
                ],
            )

        self.assert_json_error_contains(result, "No analytics data available")

        realm.date_created = timezone_now() - timedelta(days=1, minutes=10)
        realm.save(update_fields=["date_created"])
        result = self.client_get("/json/analytics/chart_data",
                                 {"chart_name": "messages_sent_over_time"})
        self.assert_json_success(result)

        end_time = timezone_now() - timedelta(days=2)
        fill_state.end_time = end_time
        fill_state.save(update_fields=["end_time"])

        realm.date_created = timezone_now() - timedelta(days=3)
        realm.save(update_fields=["date_created"])
        result = self.client_get("/json/analytics/chart_data",
                                 {"chart_name": "messages_sent_over_time"})
        self.assert_json_success(result)

        realm.date_created = timezone_now() - timedelta(days=1, hours=2)
        realm.save(update_fields=["date_created"])
        with self.assertLogs(level="WARNING") as m:
            result = self.client_get("/json/analytics/chart_data",
                                     {"chart_name": "messages_sent_over_time"})
            self.assertEqual(
                m.output,
                [
                    f"WARNING:root:User from realm zulip attempted to access /stats, but the computed start time: {realm.date_created} (creation of realm or installation) is later than the computed end time: {end_time} (last successful analytics update). Is the analytics cron job running?"
                ],
            )

        self.assert_json_error_contains(result, "No analytics data available")

        realm.date_created = timezone_now() - timedelta(days=1, minutes=10)
        realm.save(update_fields=["date_created"])
        result = self.client_get("/json/analytics/chart_data",
                                 {"chart_name": "messages_sent_over_time"})
        self.assert_json_success(result)
Example #32
0
 def mit_user(self, name):
     # type: (str) -> UserProfile
     email = self.mit_user_map[name]
     return get_user(email, get_realm('zephyr'))
Example #33
0
 def nonreg_user(self, name: str) -> UserProfile:
     email = self.nonreg_user_map[name]
     return get_user(email, get_realm("zulip"))
Example #34
0
    def handle(self, **options):
        # type: (**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")

            # 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", "*****@*****.**"),
            ]
            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'])

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

            zulip_webhook_bots = [
                ("Zulip Webhook Bot", "*****@*****.**"),
            ]
            create_users(zulip_realm,
                         zulip_webhook_bots,
                         bot_type=UserProfile.INCOMING_WEBHOOK_BOT)
            zulip_outgoing_bots = [("Outgoing Webhook",
                                    "*****@*****.**")]
            aaron = get_user("*****@*****.**", zulip_realm)
            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).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)
        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')
        UPLOADED_EMOJI_FILE_NAME = 'green_tick.png'
        with open(IMAGE_FILE_PATH, 'rb') as fp:
            upload_backend.upload_emoji_image(fp, UPLOADED_EMOJI_FILE_NAME,
                                              iago)
            RealmEmoji.objects.create(
                name='green_tick',
                author=iago,
                realm=zulip_realm,
                deactivated=False,
                file_name=UPLOADED_EMOJI_FILE_NAME,
            )

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

        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)

            create_simple_community_realm()

            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]]
                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'])

            self.stdout.write("Successfully populated test database.\n")
Example #35
0
 def create_user_group_for_test(self, group_name: str,
                                realm: Realm=get_realm('zulip')) -> UserGroup:
     members = [self.example_user('othello')]
     return create_user_group(group_name, members, realm)
Example #36
0
def support(
    request: HttpRequest,
    realm_id: Optional[int] = REQ(default=None, converter=to_non_negative_int),
    plan_type: Optional[int] = REQ(default=None, converter=to_non_negative_int),
    discount: Optional[Decimal] = REQ(default=None, converter=to_decimal),
    new_subdomain: Optional[str] = REQ(default=None),
    status: Optional[str] = REQ(default=None, str_validator=check_string_in(VALID_STATUS_VALUES)),
    billing_method: Optional[str] = REQ(
        default=None, str_validator=check_string_in(VALID_BILLING_METHODS)
    ),
    sponsorship_pending: Optional[bool] = REQ(default=None, json_validator=check_bool),
    approve_sponsorship: Optional[bool] = REQ(default=None, json_validator=check_bool),
    downgrade_method: Optional[str] = REQ(
        default=None, str_validator=check_string_in(VALID_DOWNGRADE_METHODS)
    ),
    scrub_realm: Optional[bool] = REQ(default=None, json_validator=check_bool),
    query: Optional[str] = REQ("q", default=None),
) -> HttpResponse:
    context: Dict[str, Any] = {}

    if "success_message" in request.session:
        context["success_message"] = request.session["success_message"]
        del request.session["success_message"]

    if settings.BILLING_ENABLED and request.method == "POST":
        # We check that request.POST only has two keys in it: The
        # realm_id and a field to change.
        keys = set(request.POST.keys())
        if "csrfmiddlewaretoken" in keys:
            keys.remove("csrfmiddlewaretoken")
        if len(keys) != 2:
            raise JsonableError(_("Invalid parameters"))

        realm = Realm.objects.get(id=realm_id)

        acting_user = request.user
        assert isinstance(acting_user, UserProfile)
        if plan_type is not None:
            current_plan_type = realm.plan_type
            do_change_plan_type(realm, plan_type, acting_user=acting_user)
            msg = f"Plan type of {realm.string_id} changed from {get_plan_name(current_plan_type)} to {get_plan_name(plan_type)} "
            context["success_message"] = msg
        elif discount is not None:
            current_discount = get_discount_for_realm(realm) or 0
            attach_discount_to_realm(realm, discount, acting_user=acting_user)
            context[
                "success_message"
            ] = f"Discount of {realm.string_id} changed to {discount}% from {current_discount}%."
        elif new_subdomain is not None:
            old_subdomain = realm.string_id
            try:
                check_subdomain_available(new_subdomain)
            except ValidationError as error:
                context["error_message"] = error.message
            else:
                do_change_realm_subdomain(realm, new_subdomain, acting_user=acting_user)
                request.session[
                    "success_message"
                ] = f"Subdomain changed from {old_subdomain} to {new_subdomain}"
                return HttpResponseRedirect(
                    reverse("support") + "?" + urlencode({"q": new_subdomain})
                )
        elif status is not None:
            if status == "active":
                do_send_realm_reactivation_email(realm, acting_user=acting_user)
                context[
                    "success_message"
                ] = f"Realm reactivation email sent to admins of {realm.string_id}."
            elif status == "deactivated":
                do_deactivate_realm(realm, acting_user=acting_user)
                context["success_message"] = f"{realm.string_id} deactivated."
        elif billing_method is not None:
            if billing_method == "send_invoice":
                update_billing_method_of_current_plan(
                    realm, charge_automatically=False, acting_user=acting_user
                )
                context[
                    "success_message"
                ] = f"Billing method of {realm.string_id} updated to pay by invoice."
            elif billing_method == "charge_automatically":
                update_billing_method_of_current_plan(
                    realm, charge_automatically=True, acting_user=acting_user
                )
                context[
                    "success_message"
                ] = f"Billing method of {realm.string_id} updated to charge automatically."
        elif sponsorship_pending is not None:
            if sponsorship_pending:
                update_sponsorship_status(realm, True, acting_user=acting_user)
                context["success_message"] = f"{realm.string_id} marked as pending sponsorship."
            else:
                update_sponsorship_status(realm, False, acting_user=acting_user)
                context["success_message"] = f"{realm.string_id} is no longer pending sponsorship."
        elif approve_sponsorship:
            do_approve_sponsorship(realm, acting_user=acting_user)
            context["success_message"] = f"Sponsorship approved for {realm.string_id}"
        elif downgrade_method is not None:
            if downgrade_method == "downgrade_at_billing_cycle_end":
                downgrade_at_the_end_of_billing_cycle(realm)
                context[
                    "success_message"
                ] = f"{realm.string_id} marked for downgrade at the end of billing cycle"
            elif downgrade_method == "downgrade_now_without_additional_licenses":
                downgrade_now_without_creating_additional_invoices(realm)
                context[
                    "success_message"
                ] = f"{realm.string_id} downgraded without creating additional invoices"
            elif downgrade_method == "downgrade_now_void_open_invoices":
                downgrade_now_without_creating_additional_invoices(realm)
                voided_invoices_count = void_all_open_invoices(realm)
                context[
                    "success_message"
                ] = f"{realm.string_id} downgraded and voided {voided_invoices_count} open invoices"
        elif scrub_realm:
            do_scrub_realm(realm, acting_user=acting_user)
            context["success_message"] = f"{realm.string_id} scrubbed."

    if query:
        key_words = get_invitee_emails_set(query)

        users = set(UserProfile.objects.filter(delivery_email__in=key_words))
        realms = set(Realm.objects.filter(string_id__in=key_words))

        for key_word in key_words:
            try:
                URLValidator()(key_word)
                parse_result = urllib.parse.urlparse(key_word)
                hostname = parse_result.hostname
                assert hostname is not None
                if parse_result.port:
                    hostname = f"{hostname}:{parse_result.port}"
                subdomain = get_subdomain_from_hostname(hostname)
                try:
                    realms.add(get_realm(subdomain))
                except Realm.DoesNotExist:
                    pass
            except ValidationError:
                users.update(UserProfile.objects.filter(full_name__iexact=key_word))

        for realm in realms:
            realm.customer = get_customer_by_realm(realm)

            current_plan = get_current_plan_by_realm(realm)
            if current_plan is not None:
                new_plan, last_ledger_entry = make_end_of_cycle_updates_if_needed(
                    current_plan, timezone_now()
                )
                if last_ledger_entry is not None:
                    if new_plan is not None:
                        realm.current_plan = new_plan
                    else:
                        realm.current_plan = current_plan
                    realm.current_plan.licenses = last_ledger_entry.licenses
                    realm.current_plan.licenses_used = get_latest_seat_count(realm)

        # full_names can have , in them
        users.update(UserProfile.objects.filter(full_name__iexact=query))

        context["users"] = users
        context["realms"] = realms

        confirmations: List[Dict[str, Any]] = []

        preregistration_users = PreregistrationUser.objects.filter(email__in=key_words)
        confirmations += get_confirmations(
            [Confirmation.USER_REGISTRATION, Confirmation.INVITATION, Confirmation.REALM_CREATION],
            preregistration_users,
            hostname=request.get_host(),
        )

        multiuse_invites = MultiuseInvite.objects.filter(realm__in=realms)
        confirmations += get_confirmations([Confirmation.MULTIUSE_INVITE], multiuse_invites)

        confirmations += get_confirmations(
            [Confirmation.REALM_REACTIVATION], [realm.id for realm in realms]
        )

        context["confirmations"] = confirmations

    def get_realm_owner_emails_as_string(realm: Realm) -> str:
        return ", ".join(
            realm.get_human_owner_users()
            .order_by("delivery_email")
            .values_list("delivery_email", flat=True)
        )

    def get_realm_admin_emails_as_string(realm: Realm) -> str:
        return ", ".join(
            realm.get_human_admin_users(include_realm_owners=False)
            .order_by("delivery_email")
            .values_list("delivery_email", flat=True)
        )

    context["get_realm_owner_emails_as_string"] = get_realm_owner_emails_as_string
    context["get_realm_admin_emails_as_string"] = get_realm_admin_emails_as_string
    context["get_discount_for_realm"] = get_discount_for_realm
    context["get_org_type_display_name"] = get_org_type_display_name
    context["realm_icon_url"] = realm_icon_url
    context["Confirmation"] = Confirmation
    return render(request, "analytics/support.html", context=context)
Example #37
0
 def setUp(self) -> None:
     super().setUp()
     self.realm = get_realm("zulip")
     self.scim_client = SCIMClient.objects.create(
         realm=self.realm,
         name=settings.SCIM_CONFIG["zulip"]["scim_client_name"])
Example #38
0
    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=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_owner=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_owner=False,
                    is_cross_realm_bot=True,
                    is_guest=False,
                ),
            ],
                   key=by_email))
Example #39
0
    def test_fix_unreads(self) -> None:
        user = self.example_user('hamlet')
        realm = get_realm('zulip')

        def send_message(stream_name: str, topic_name: str) -> int:
            msg_id = self.send_stream_message(self.example_email("othello"),
                                              stream_name,
                                              topic_name=topic_name)
            um = UserMessage.objects.get(user_profile=user, message_id=msg_id)
            return um.id

        def assert_read(user_message_id: int) -> None:
            um = UserMessage.objects.get(id=user_message_id)
            self.assertTrue(um.flags.read)

        def assert_unread(user_message_id: int) -> None:
            um = UserMessage.objects.get(id=user_message_id)
            self.assertFalse(um.flags.read)

        def mute_stream(stream_name: str) -> None:
            stream = get_stream(stream_name, realm)
            recipient = get_stream_recipient(stream.id)
            subscription = Subscription.objects.get(user_profile=user,
                                                    recipient=recipient)
            subscription.in_home_view = False
            subscription.save()

        def mute_topic(stream_name: str, topic_name: str) -> None:
            stream = get_stream(stream_name, realm)
            recipient = get_stream_recipient(stream.id)

            add_topic_mute(
                user_profile=user,
                stream_id=stream.id,
                recipient_id=recipient.id,
                topic_name=topic_name,
            )

        def force_unsubscribe(stream_name: str) -> None:
            '''
            We don't want side effects here, since the eventual
            unsubscribe path may mark messages as read, defeating
            the test setup here.
            '''
            sub = get_subscription(stream_name, user)
            sub.active = False
            sub.save()

        # The data setup here is kind of funny, because some of these
        # conditions should not actually happen in practice going forward,
        # but we may have had bad data from the past.

        mute_stream('Denmark')
        mute_topic('Verona', 'muted_topic')

        um_normal_id = send_message('Verona', 'normal')
        um_muted_topic_id = send_message('Verona', 'muted_topic')
        um_muted_stream_id = send_message('Denmark', 'whatever')

        user.pointer = self.get_last_message().id
        user.save()

        um_post_pointer_id = send_message('Verona', 'muted_topic')

        self.subscribe(user, 'temporary')
        um_unsubscribed_id = send_message('temporary', 'whatever')
        force_unsubscribe('temporary')

        # verify data setup
        assert_unread(um_normal_id)
        assert_unread(um_muted_topic_id)
        assert_unread(um_muted_stream_id)
        assert_unread(um_post_pointer_id)
        assert_unread(um_unsubscribed_id)

        with connection.cursor() as cursor:
            fix_pre_pointer(cursor, user)

        # The only message that should have been fixed is the "normal"
        # unumuted message before the pointer.
        assert_read(um_normal_id)

        # We don't "fix" any messages that are either muted or after the
        # pointer, because they can be legitimately unread.
        assert_unread(um_muted_topic_id)
        assert_unread(um_muted_stream_id)
        assert_unread(um_post_pointer_id)
        assert_unread(um_unsubscribed_id)

        # fix unsubscribed
        with connection.cursor() as cursor:
            fix_unsubscribed(cursor, user)

        # Most messages don't change.
        assert_unread(um_muted_topic_id)
        assert_unread(um_muted_stream_id)
        assert_unread(um_post_pointer_id)

        # The unsubscribed entry should change.
        assert_read(um_unsubscribed_id)

        # test idempotency
        fix(user)

        assert_read(um_normal_id)
        assert_unread(um_muted_topic_id)
        assert_unread(um_muted_stream_id)
        assert_unread(um_post_pointer_id)
        assert_read(um_unsubscribed_id)
Example #40
0
 def test_realm_realm_domains_uniqueness(self) -> None:
     realm = get_realm('zulip')
     with self.assertRaises(IntegrityError):
         RealmDomain.objects.create(realm=realm,
                                    domain='zulip.com',
                                    allow_subdomains=True)
Example #41
0
 def example_user(self, name: str) -> UserProfile:
     email = self.example_user_map[name]
     return get_user(email, get_realm('zulip'))
Example #42
0
 def test_success_stream(self) -> None:
     stream = get_stream("Denmark", get_realm("zulip"))
     stream_to_address = encode_email_address(stream)
     result = self.send_offline_message(stream_to_address,
                                        self.example_email('hamlet'))
     self.assert_json_success(result)
Example #43
0
    def test_encode_decode(self) -> None:
        realm = get_realm('zulip')
        stream_name = 'dev. help'
        stream = ensure_stream(realm, stream_name)
        email_address = encode_email_address(stream)
        self.assertTrue(email_address.startswith('dev%0046%0032help'))
        self.assertTrue(email_address.endswith('@testserver'))
        tup = decode_email_address(email_address)
        assert tup is not None
        (decoded_stream_name, token, show_sender) = tup
        self.assertFalse(show_sender)
        self.assertEqual(decoded_stream_name, stream_name)
        self.assertEqual(token, stream.email_token)

        parts = email_address.split('@')
        parts[0] += "+show-sender"
        email_address_show = '@'.join(parts)
        tup = decode_email_address(email_address_show)
        assert tup is not None
        (decoded_stream_name, token, show_sender) = tup
        self.assertTrue(show_sender)
        self.assertEqual(decoded_stream_name, stream_name)
        self.assertEqual(token, stream.email_token)

        email_address_dots = email_address.replace('+', '.')
        tup = decode_email_address(email_address_dots)
        assert tup is not None
        (decoded_stream_name, token, show_sender) = tup
        self.assertFalse(show_sender)
        self.assertEqual(decoded_stream_name, stream_name)
        self.assertEqual(token, stream.email_token)

        email_address_dots_show = email_address_show.replace('+', '.')
        tup = decode_email_address(email_address_dots_show)
        assert tup is not None
        (decoded_stream_name, token, show_sender) = tup
        self.assertTrue(show_sender)
        self.assertEqual(decoded_stream_name, stream_name)
        self.assertEqual(token, stream.email_token)

        email_address = email_address.replace('@testserver', '@zulip.org')
        email_address_show = email_address_show.replace(
            '@testserver', '@zulip.org')
        self.assertEqual(decode_email_address(email_address), None)
        self.assertEqual(decode_email_address(email_address_show), None)

        with self.settings(EMAIL_GATEWAY_EXTRA_PATTERN_HACK='@zulip.org'):
            tup = decode_email_address(email_address)
            assert tup is not None
            (decoded_stream_name, token, show_sender) = tup
            self.assertFalse(show_sender)
            self.assertEqual(decoded_stream_name, stream_name)
            self.assertEqual(token, stream.email_token)

            tup = decode_email_address(email_address_show)
            assert tup is not None
            (decoded_stream_name, token, show_sender) = tup
            self.assertTrue(show_sender)
            self.assertEqual(decoded_stream_name, stream_name)
            self.assertEqual(token, stream.email_token)

        self.assertEqual(decode_email_address('bogus'), None)
Example #44
0
def api_fetch_api_key(
    request: HttpRequest, username: str = REQ(), password: str = REQ()
) -> HttpResponse:
    return_data = {}  # type: Dict[str, bool]
    subdomain = get_subdomain(request)
    realm = get_realm(subdomain)
    if username == "google-oauth2-token":
        # This code path is auth for the legacy Android app
        user_profile = authenticate(google_oauth2_token=password,
                                    realm=realm,
                                    return_data=return_data)
    else:
        if not ldap_auth_enabled(realm=get_realm_from_request(request)):
            # In case we don't authenticate against LDAP, check for a valid
            # email. LDAP backend can authenticate against a non-email.
            validate_login_email(username)

        user_profile = authenticate(username=username,
                                    password=password,
                                    realm=realm,
                                    return_data=return_data)
    if return_data.get("inactive_user"):
        return json_error(_("Your account has been disabled."),
                          data={"reason": "user disable"},
                          status=403)
    if return_data.get("inactive_realm"):
        return json_error(_("This organization has been deactivated."),
                          data={"reason": "realm deactivated"},
                          status=403)
    if return_data.get("password_auth_disabled"):
        return json_error(_("Password auth is disabled in your team."),
                          data={"reason": "password auth disabled"},
                          status=403)
    if user_profile is None:
        if return_data.get("valid_attestation"):
            # We can leak that the user is unregistered iff
            # they present a valid authentication string for the user.
            return json_error(
                _("This user is not registered; do so from a browser."),
                data={"reason": "unregistered"},
                status=403)
        return json_error(_("Your username or password is incorrect."),
                          data={"reason": "incorrect_creds"},
                          status=403)

    # Maybe sending 'user_logged_in' signal is the better approach:
    #   user_logged_in.send(sender=user_profile.__class__, request=request, user=user_profile)
    # Not doing this only because over here we don't add the user information
    # in the session. If the signal receiver assumes that we do then that
    # would cause problems.
    email_on_new_login(sender=user_profile.__class__,
                       request=request,
                       user=user_profile)

    # Mark this request as having a logged-in user for our server logs.
    process_client(request, user_profile)
    request._email = user_profile.email

    api_key = get_api_key(user_profile)
    return json_success({
        "api_key": api_key,
        "email": user_profile.delivery_email
    })
Example #45
0
    def test_encode_email_addr(self) -> None:
        stream = get_stream("Denmark", get_realm("zulip"))

        with self.settings(EMAIL_GATEWAY_PATTERN=''):
            test_address = encode_email_address(stream)
            self.assertEqual(test_address, '')
Example #46
0
    def test_slack_import_to_existing_database(
            self, mock_get_slack_api_data: mock.Mock,
            mock_build_avatar_url: mock.Mock, mock_build_avatar: mock.Mock,
            mock_process_uploads: mock.Mock, mock_attachment: mock.Mock,
            mock_requests_get: mock.Mock) -> None:
        test_slack_dir = os.path.join(settings.DEPLOY_ROOT, "zerver", "tests",
                                      "fixtures", "slack_fixtures")
        test_slack_zip_file = os.path.join(test_slack_dir,
                                           "test_slack_importer.zip")
        test_slack_unzipped_file = os.path.join(test_slack_dir,
                                                "test_slack_importer")

        test_realm_subdomain = 'test-slack-import'
        output_dir = os.path.join(settings.DEPLOY_ROOT, "var",
                                  "test-slack-importer-data")
        token = 'valid-token'

        # If the test fails, the 'output_dir' would not be deleted and hence it would give an
        # error when we run the tests next time, as 'do_convert_data' expects an empty 'output_dir'
        # hence we remove it before running 'do_convert_data'
        self.rm_tree(output_dir)
        # Also the unzipped data file should be removed if the test fails at 'do_convert_data'
        self.rm_tree(test_slack_unzipped_file)

        user_data_fixture = orjson.loads(
            self.fixture_data('user_data.json', type='slack_fixtures'))
        team_info_fixture = orjson.loads(
            self.fixture_data('team_info.json', type='slack_fixtures'))
        mock_get_slack_api_data.side_effect = [
            user_data_fixture['members'], {}, team_info_fixture["team"]
        ]
        mock_requests_get.return_value.raw = get_test_image_file("img.png")

        with self.assertLogs(level="INFO"):
            do_convert_data(test_slack_zip_file, output_dir, token)

        self.assertTrue(os.path.exists(output_dir))
        self.assertTrue(os.path.exists(output_dir + '/realm.json'))

        realm_icons_path = os.path.join(output_dir, 'realm_icons')
        realm_icon_records_path = os.path.join(realm_icons_path,
                                               'records.json')

        self.assertTrue(os.path.exists(realm_icon_records_path))
        with open(realm_icon_records_path, "rb") as f:
            records = orjson.loads(f.read())
            self.assertEqual(len(records), 2)
            self.assertEqual(records[0]["path"], "0/icon.original")
            self.assertTrue(
                os.path.exists(
                    os.path.join(realm_icons_path, records[0]["path"])))

            self.assertEqual(records[1]["path"], "0/icon.png")
            self.assertTrue(
                os.path.exists(
                    os.path.join(realm_icons_path, records[1]["path"])))

        # test import of the converted slack data into an existing database
        with self.settings(BILLING_ENABLED=False), self.assertLogs(
                level="INFO"):
            do_import_realm(output_dir, test_realm_subdomain)
        realm = get_realm(test_realm_subdomain)
        self.assertTrue(realm.name, test_realm_subdomain)
        self.assertEqual(realm.icon_source, Realm.ICON_UPLOADED)

        # test RealmAuditLog
        realmauditlog = RealmAuditLog.objects.filter(realm=realm)
        realmauditlog_event_type = {log.event_type for log in realmauditlog}
        self.assertEqual(
            realmauditlog_event_type, {
                RealmAuditLog.SUBSCRIPTION_CREATED,
                RealmAuditLog.REALM_PLAN_TYPE_CHANGED
            })

        Realm.objects.filter(name=test_realm_subdomain).delete()

        remove_folder(output_dir)
        # remove tar file created in 'do_convert_data' function
        os.remove(output_dir + '.tar.gz')
        self.assertFalse(os.path.exists(output_dir))
 def test_delete_no_author(self) -> None:
     self.login("iago")
     realm = get_realm("zulip")
     self.create_test_emoji_with_no_author("my_emoji", realm)
     result = self.client_delete("/json/realm/emoji/my_emoji")
     self.assert_json_success(result)
Example #48
0
 def notification_bot(self):
     # type: () -> UserProfile
     return get_user('*****@*****.**', get_realm('zulip'))
Example #49
0
def maybe_send_to_registration(request: HttpRequest,
                               email: str,
                               full_name: str = '',
                               is_signup: bool = False,
                               password_required: bool = True,
                               multiuse_object_key: str = '') -> HttpResponse:
    """Given a successful authentication for an email address (i.e. we've
    confirmed the user controls the email address) that does not
    currently have a Zulip account in the target realm, send them to
    the registration flow or the "continue to registration" flow,
    depending on is_signup, whether the email address can join the
    organization (checked in HomepageForm), and similar details.
    """
    realm = get_realm(get_subdomain(request))
    from_multiuse_invite = False
    multiuse_obj = None
    streams_to_subscribe = None
    invited_as = PreregistrationUser.INVITE_AS['MEMBER']
    if multiuse_object_key:
        from_multiuse_invite = True
        multiuse_obj = Confirmation.objects.get(
            confirmation_key=multiuse_object_key).content_object
        realm = multiuse_obj.realm
        streams_to_subscribe = multiuse_obj.streams.all()
        invited_as = multiuse_obj.invited_as

    form = HomepageForm({'email': email},
                        realm=realm,
                        from_multiuse_invite=from_multiuse_invite)
    if form.is_valid():
        # If the email address is allowed to sign up for an account in
        # this organization, construct a PreregistrationUser and
        # Confirmation objects, and then send the user to account
        # creation or confirm-continue-registration depending on
        # is_signup.
        prereg_user = None
        if settings.ONLY_SSO:
            try:
                prereg_user = PreregistrationUser.objects.filter(
                    email__iexact=email, realm=realm).latest("invited_at")
            except PreregistrationUser.DoesNotExist:
                prereg_user = create_preregistration_user(
                    email, request, password_required=password_required)
        else:
            prereg_user = create_preregistration_user(
                email, request, password_required=password_required)

        if multiuse_object_key:
            request.session.modified = True
            if streams_to_subscribe is not None:
                prereg_user.streams.set(streams_to_subscribe)
            prereg_user.invited_as = invited_as
            prereg_user.save()

        confirmation_link = create_confirmation_link(
            prereg_user, request.get_host(), Confirmation.USER_REGISTRATION)
        if is_signup:
            return redirect(confirmation_link)

        context = {
            'email': email,
            'continue_link': confirmation_link,
            'full_name': full_name
        }
        return render(request,
                      'zerver/confirm_continue_registration.html',
                      context=context)

    # This email address it not allowed to join this organization, so
    # just send the user back to the registration page.
    url = reverse('register')
    context = login_context(request)
    extra_context = {
        'form': form,
        'current_url': lambda: url,
        'from_multiuse_invite': from_multiuse_invite,
        'multiuse_object_key': multiuse_object_key
    }  # type: Mapping[str, Any]
    context.update(extra_context)
    return render(request, 'zerver/accounts_home.html', context=context)
Example #50
0
def log_into_subdomain(request: HttpRequest, token: str) -> HttpResponse:
    """Given a valid signed authentication token (generated by
    redirect_and_log_into_subdomain called on auth.zulip.example.com),
    call login_or_register_remote_user, passing all the authentication
    result data that had been encoded in the signed token.
    """

    try:
        data = signing.loads(token, salt=_subdomain_token_salt, max_age=15)
    except signing.SignatureExpired as e:
        logging.warning('Subdomain cookie: {}'.format(e))
        return HttpResponse(status=400)
    except signing.BadSignature:
        logging.warning('Subdomain cookie: Bad signature.')
        return HttpResponse(status=400)

    subdomain = get_subdomain(request)
    if data['subdomain'] != subdomain:
        logging.warning('Login attempt on invalid subdomain')
        return HttpResponse(status=400)

    email_address = data['email']
    full_name = data['name']
    is_signup = data['is_signup']
    redirect_to = data['next']

    if 'multiuse_object_key' in data:
        multiuse_object_key = data['multiuse_object_key']
    else:
        multiuse_object_key = ''

    # We cannot pass the actual authenticated user_profile object that
    # was fetched by the original authentication backend and passed
    # into redirect_and_log_into_subdomain through a signed URL token,
    # so we need to re-fetch it from the database.
    if is_signup:
        # If we are creating a new user account, user_profile will
        # always have been None, so we set that here.  In the event
        # that a user account with this email was somehow created in a
        # race, the eventual registration code will catch that and
        # throw an error, so we don't need to check for that here.
        user_profile = None
        return_data = {}  # type: Dict[str, Any]
    else:
        # We're just trying to login.  We can be reasonably confident
        # that this subdomain actually has a corresponding active
        # realm, since the signed cookie proves there was one very
        # recently.  But as part of fetching the UserProfile object
        # for the target user, we use DummyAuthBackend, which
        # conveniently re-validates that the realm and user account
        # were not deactivated in the meantime.

        # Note: Ideally, we'd have a nice user-facing error message
        # for the case where this auth fails (because e.g. the realm
        # or user was deactivated since the signed cookie was
        # generated < 15 seconds ago), but the authentication result
        # is correct in those cases and such a race would be very
        # rare, so a nice error message is low priority.
        realm = get_realm(subdomain)
        user_profile, return_data = authenticate_remote_user(
            realm, email_address)

    invalid_subdomain = bool(return_data.get('invalid_subdomain'))
    return login_or_register_remote_user(
        request,
        email_address,
        user_profile,
        full_name,
        invalid_subdomain=invalid_subdomain,
        is_signup=is_signup,
        redirect_to=redirect_to,
        multiuse_object_key=multiuse_object_key)
Example #51
0
def login_page(
    request: HttpRequest,
    next: str = REQ(default="/"),
    **kwargs: Any,
) -> HttpResponse:
    if settings.SOCIAL_AUTH_SUBDOMAIN == get_subdomain(request):
        return social_auth_subdomain_login_page(request)

    # To support previewing the Zulip login pages, we have a special option
    # that disables the default behavior of redirecting logged-in users to the
    # logged-in app.
    is_preview = "preview" in request.GET
    if settings.TWO_FACTOR_AUTHENTICATION_ENABLED:
        if request.user and request.user.is_verified():
            return HttpResponseRedirect(request.user.realm.uri)
    elif request.user.is_authenticated and not is_preview:
        return HttpResponseRedirect(request.user.realm.uri)
    if is_subdomain_root_or_alias(request) and settings.ROOT_DOMAIN_LANDING_PAGE:
        redirect_url = reverse("realm_redirect")
        if request.GET:
            redirect_url = add_query_to_redirect_url(redirect_url, request.GET.urlencode())
        return HttpResponseRedirect(redirect_url)

    realm = get_realm_from_request(request)
    if realm and realm.deactivated:
        return redirect_to_deactivation_notice()

    extra_context = kwargs.pop("extra_context", {})
    extra_context["next"] = next
    if dev_auth_enabled() and kwargs.get("template_name") == "zerver/development/dev_login.html":
        from zerver.views.development.dev_login import add_dev_login_context

        if "new_realm" in request.POST:
            try:
                realm = get_realm(request.POST["new_realm"])
            except Realm.DoesNotExist:
                realm = None

        add_dev_login_context(realm, extra_context)
        if realm and "new_realm" in request.POST:
            # If we're switching realms, redirect to that realm, but
            # only if it actually exists.
            return HttpResponseRedirect(realm.uri)

    if "username" in request.POST:
        extra_context["email"] = request.POST["username"]
    extra_context.update(login_context(request))

    if settings.TWO_FACTOR_AUTHENTICATION_ENABLED:
        return start_two_factor_auth(request, extra_context=extra_context, **kwargs)

    try:
        template_response = DjangoLoginView.as_view(
            authentication_form=OurAuthenticationForm, extra_context=extra_context, **kwargs
        )(request)
    except ZulipLDAPConfigurationError as e:
        assert len(e.args) > 1
        return redirect_to_misconfigured_ldap_notice(request, e.args[1])

    if isinstance(template_response, SimpleTemplateResponse):
        # Only those responses that are rendered using a template have
        # context_data attribute. This attribute doesn't exist otherwise. It is
        # added in SimpleTemplateResponse class, which is a derived class of
        # HttpResponse. See django.template.response.SimpleTemplateResponse,
        # https://github.com/django/django/blob/2.0/django/template/response.py#L19
        update_login_page_context(request, template_response.context_data)

    assert isinstance(template_response, HttpResponse)
    return template_response
Example #52
0
def redirect_and_log_into_subdomain(result: ExternalAuthResult) -> HttpResponse:
    token = result.store_data()
    realm = get_realm(result.data_dict["subdomain"])
    subdomain_login_uri = realm.uri + reverse(log_into_subdomain, args=[token])
    return redirect(subdomain_login_uri)
Example #53
0
    def test_mirror_worker_rate_limiting(self, mock_mirror_email: MagicMock,
                                         mock_warn: MagicMock) -> None:
        fake_client = self.FakeClient()
        realm = get_realm('zulip')
        RateLimitedRealmMirror(realm).clear_history()
        stream = get_stream('Denmark', realm)
        stream_to_address = encode_email_address(stream)
        data = [
            dict(
                msg_base64=base64.b64encode(b'\xf3test').decode(),
                time=time.time(),
                rcpt_to=stream_to_address,
            ),
        ] * 5
        for element in data:
            fake_client.queue.append(('email_mirror', element))

        with simulated_queue_client(lambda: fake_client), \
                self.assertLogs('zerver.worker.queue_processors', level='WARNING') as warn_logs:
            start_time = time.time()
            with patch('time.time', return_value=start_time):
                worker = queue_processors.MirrorWorker()
                worker.setup()
                worker.start()
                # Of the first 5 messages, only 2 should be processed
                # (the rest being rate-limited):
                self.assertEqual(mock_mirror_email.call_count, 2)

                # If a new message is sent into the stream mirror, it will get rejected:
                fake_client.queue.append(('email_mirror', data[0]))
                worker.start()
                self.assertEqual(mock_mirror_email.call_count, 2)

                # However, missed message emails don't get rate limited:
                with self.settings(EMAIL_GATEWAY_PATTERN="*****@*****.**"):
                    address = 'mm' + ('x' * 32) + '@example.com'
                    event = dict(
                        msg_base64=base64.b64encode(b'\xf3test').decode(),
                        time=time.time(),
                        rcpt_to=address,
                    )
                    fake_client.queue.append(('email_mirror', event))
                    worker.start()
                    self.assertEqual(mock_mirror_email.call_count, 3)

            # After some times passes, emails get accepted again:
            with patch('time.time', return_value=(start_time + 11.0)):
                fake_client.queue.append(('email_mirror', data[0]))
                worker.start()
                self.assertEqual(mock_mirror_email.call_count, 4)

                # If RateLimiterLockingException is thrown, we rate-limit the new message:
                with patch('zerver.lib.rate_limiter.RedisRateLimiterBackend.incr_ratelimit',
                           side_effect=RateLimiterLockingException):
                    fake_client.queue.append(('email_mirror', data[0]))
                    worker.start()
                    self.assertEqual(mock_mirror_email.call_count, 4)
                    mock_warn.assert_called_with(
                        "Deadlock trying to incr_ratelimit for %s",
                        f"RateLimitedRealmMirror:{realm.string_id}",
                    )
        self.assertEqual(warn_logs.output, [
            'WARNING:zerver.worker.queue_processors:MirrorWorker: Rejecting an email from: None to realm: Zulip Dev - rate limited.'
        ] * 5)
Example #54
0
def maybe_send_to_registration(
    request: HttpRequest,
    email: str,
    full_name: str = "",
    mobile_flow_otp: Optional[str] = None,
    desktop_flow_otp: Optional[str] = None,
    is_signup: bool = False,
    password_required: bool = True,
    multiuse_object_key: str = "",
    full_name_validated: bool = False,
) -> HttpResponse:
    """Given a successful authentication for an email address (i.e. we've
    confirmed the user controls the email address) that does not
    currently have a Zulip account in the target realm, send them to
    the registration flow or the "continue to registration" flow,
    depending on is_signup, whether the email address can join the
    organization (checked in HomepageForm), and similar details.
    """

    # In the desktop and mobile registration flows, the sign up
    # happens in the browser so the user can use their
    # already-logged-in social accounts.  Then at the end, with the
    # user account created, we pass the appropriate data to the app
    # via e.g. a `zulip://` redirect.  We store the OTP keys for the
    # mobile/desktop flow in the session with 1-hour expiry, because
    # we want this configuration of having a successful authentication
    # result in being logged into the app to persist if the user makes
    # mistakes while trying to authenticate (E.g. clicks the wrong
    # Google account, hits back, etc.) during a given browser session,
    # rather than just logging into the web app in the target browser.
    #
    # We can't use our usual pre-account-creation state storage
    # approach of putting something in PreregistrationUser, because
    # that would apply to future registration attempts on other
    # devices, e.g. just creating an account on the web on their laptop.
    assert not (mobile_flow_otp and desktop_flow_otp)
    if mobile_flow_otp:
        set_expirable_session_var(
            request.session, "registration_mobile_flow_otp", mobile_flow_otp, expiry_seconds=3600
        )
    elif desktop_flow_otp:
        set_expirable_session_var(
            request.session, "registration_desktop_flow_otp", desktop_flow_otp, expiry_seconds=3600
        )

    multiuse_obj: Optional[MultiuseInvite] = None
    realm: Optional[Realm] = None
    from_multiuse_invite = False
    if multiuse_object_key:
        from_multiuse_invite = True
        try:
            multiuse_obj = get_object_from_key(multiuse_object_key, Confirmation.MULTIUSE_INVITE)
        except ConfirmationKeyException:
            return render(request, "zerver/confirmation_link_expired_error.html", status=404)

        assert multiuse_obj is not None
        realm = multiuse_obj.realm
        invited_as = multiuse_obj.invited_as
    else:
        try:
            realm = get_realm(get_subdomain(request))
        except Realm.DoesNotExist:
            pass
        invited_as = PreregistrationUser.INVITE_AS["MEMBER"]

    form = HomepageForm({"email": email}, realm=realm, from_multiuse_invite=from_multiuse_invite)
    if form.is_valid():
        # If the email address is allowed to sign up for an account in
        # this organization, construct a PreregistrationUser and
        # Confirmation objects, and then send the user to account
        # creation or confirm-continue-registration depending on
        # is_signup.
        try:
            prereg_user = filter_to_valid_prereg_users(
                PreregistrationUser.objects.filter(email__iexact=email, realm=realm)
            ).latest("invited_at")

            # password_required and full_name data passed here as argument should take precedence
            # over the defaults with which the existing PreregistrationUser that we've just fetched
            # was created.
            prereg_user.password_required = password_required
            update_fields = ["password_required"]
            if full_name:
                prereg_user.full_name = full_name
                prereg_user.full_name_validated = full_name_validated
                update_fields.extend(["full_name", "full_name_validated"])
            prereg_user.save(update_fields=update_fields)
        except PreregistrationUser.DoesNotExist:
            prereg_user = create_preregistration_user(
                email,
                request,
                password_required=password_required,
                full_name=full_name,
                full_name_validated=full_name_validated,
            )

        if multiuse_obj is not None:
            request.session.modified = True
            streams_to_subscribe = list(multiuse_obj.streams.all())
            prereg_user.streams.set(streams_to_subscribe)
            prereg_user.invited_as = invited_as
            prereg_user.save()

        confirmation_link = create_confirmation_link(prereg_user, Confirmation.USER_REGISTRATION)
        if is_signup:
            return redirect(confirmation_link)

        context = {"email": email, "continue_link": confirmation_link, "full_name": full_name}
        return render(request, "zerver/confirm_continue_registration.html", context=context)

    # This email address it not allowed to join this organization, so
    # just send the user back to the registration page.
    url = reverse("register")
    context = login_context(request)
    extra_context: Mapping[str, Any] = {
        "form": form,
        "current_url": lambda: url,
        "from_multiuse_invite": from_multiuse_invite,
        "multiuse_object_key": multiuse_object_key,
        "mobile_flow_otp": mobile_flow_otp,
        "desktop_flow_otp": desktop_flow_otp,
    }
    context.update(extra_context)
    return render(request, "zerver/accounts_home.html", context=context)
Example #55
0
    def test_billing_quantity_changes_end_to_end(
            self, mock_customer_with_subscription: mock.Mock,
            mock_create_subscription: mock.Mock,
            mock_create_customer: mock.Mock) -> None:
        self.login(self.example_email("hamlet"))
        processor = BillingProcessor.objects.create(
            log_row=RealmAuditLog.objects.order_by('id').first(),
            state=BillingProcessor.DONE)

        def check_billing_processor_update(event_type: str,
                                           quantity: int) -> None:
            def check_subscription_save(subscription: stripe.Subscription,
                                        idempotency_key: str) -> None:
                self.assertEqual(subscription.quantity, quantity)
                log_row = RealmAuditLog.objects.filter(
                    event_type=event_type,
                    requires_billing_update=True).order_by('-id').first()
                self.assertEqual(
                    idempotency_key,
                    'process_billing_log_entry:%s' % (log_row.id, ))
                self.assertEqual(subscription.proration_date,
                                 datetime_to_timestamp(log_row.event_time))

            with mock.patch.object(stripe.Subscription,
                                   'save',
                                   autospec=True,
                                   side_effect=check_subscription_save):
                run_billing_processor_one_step(processor)

        # Test STRIPE_PLAN_QUANTITY_RESET
        new_seat_count = 123
        # change the seat count while the user is going through the upgrade flow
        with mock.patch('zilencer.lib.stripe.get_seat_count',
                        return_value=new_seat_count):
            self.client_post(
                "/upgrade/", {
                    'stripeToken': self.token,
                    'signed_seat_count': self.signed_seat_count,
                    'salt': self.salt,
                    'plan': Plan.CLOUD_ANNUAL
                })
        check_billing_processor_update(
            RealmAuditLog.STRIPE_PLAN_QUANTITY_RESET, new_seat_count)

        # Test USER_CREATED
        user = do_create_user('*****@*****.**', 'password',
                              get_realm('zulip'), 'full name', 'short name')
        check_billing_processor_update(RealmAuditLog.USER_CREATED,
                                       self.quantity + 1)

        # Test USER_DEACTIVATED
        do_deactivate_user(user)
        check_billing_processor_update(RealmAuditLog.USER_DEACTIVATED,
                                       self.quantity - 1)

        # Test USER_REACTIVATED
        do_reactivate_user(user)
        check_billing_processor_update(RealmAuditLog.USER_REACTIVATED,
                                       self.quantity + 1)

        # Test USER_ACTIVATED
        # Not a proper use of do_activate_user, but it's fine to call it like this for this test
        do_activate_user(user)
        check_billing_processor_update(RealmAuditLog.USER_ACTIVATED,
                                       self.quantity + 1)
Example #56
0
def send_message_backend(request,
                         user_profile,
                         message_type_name=REQ('type'),
                         message_to=REQ('to',
                                        converter=extract_recipients,
                                        default=[]),
                         forged=REQ(default=False),
                         subject_name=REQ('subject', lambda x: x.strip(),
                                          None),
                         message_content=REQ('content'),
                         domain=REQ('domain', default=None),
                         local_id=REQ(default=None),
                         queue_id=REQ(default=None)):
    # type: (HttpRequest, UserProfile, text_type, List[text_type], bool, Optional[text_type], text_type, Optional[text_type], Optional[text_type], Optional[text_type]) -> HttpResponse
    client = request.client
    is_super_user = request.user.is_api_super_user
    if forged and not is_super_user:
        return json_error(_("User not authorized for this query"))

    realm = None
    if domain and domain != user_profile.realm.domain:
        if not is_super_user:
            # The email gateway bot needs to be able to send messages in
            # any realm.
            return json_error(_("User not authorized for this query"))
        realm = get_realm(domain)
        if not realm:
            return json_error(_("Unknown domain %s") % (domain, ))

    if client.name in [
            "zephyr_mirror", "irc_mirror", "jabber_mirror", "JabberMirror"
    ]:
        # Here's how security works for mirroring:
        #
        # For private messages, the message must be (1) both sent and
        # received exclusively by users in your realm, and (2)
        # received by the forwarding user.
        #
        # For stream messages, the message must be (1) being forwarded
        # by an API superuser for your realm and (2) being sent to a
        # mirrored stream (any stream for the Zephyr and Jabber
        # mirrors, but only streams with names starting with a "#" for
        # IRC mirrors)
        #
        # The security checks are split between the below code
        # (especially create_mirrored_message_users which checks the
        # same-realm constraint) and recipient_for_emails (which
        # checks that PMs are received by the forwarding user)
        if "sender" not in request.POST:
            return json_error(_("Missing sender"))
        if message_type_name != "private" and not is_super_user:
            return json_error(_("User not authorized for this query"))
        (valid_input, mirror_sender) = \
            create_mirrored_message_users(request, user_profile, message_to)
        if not valid_input:
            return json_error(_("Invalid mirrored message"))
        if client.name == "zephyr_mirror" and not user_profile.realm.is_zephyr_mirror_realm:
            return json_error(_("Invalid mirrored realm"))
        if (client.name == "irc_mirror" and message_type_name != "private"
                and not message_to[0].startswith("#")):
            return json_error(_("IRC stream names must start with #"))
        sender = mirror_sender
    else:
        sender = user_profile

    ret = check_send_message(sender,
                             client,
                             message_type_name,
                             message_to,
                             subject_name,
                             message_content,
                             forged=forged,
                             forged_timestamp=request.POST.get('time'),
                             forwarder_user_profile=user_profile,
                             realm=realm,
                             local_id=local_id,
                             sender_queue_id=queue_id)
    return json_success({"id": ret})
Example #57
0
    def test_upgrade_where_subscription_save_fails_at_first(
            self, create_customer: mock.Mock) -> None:
        user = self.example_user("hamlet")
        self.login(user.email)
        with mock.patch('stripe.Subscription.create',
                        side_effect=stripe.error.CardError('message',
                                                           'param',
                                                           'code',
                                                           json_body={})):
            self.client_post(
                "/upgrade/", {
                    'stripeToken': self.token,
                    'signed_seat_count': self.signed_seat_count,
                    'salt': self.salt,
                    'plan': Plan.CLOUD_ANNUAL
                })
        # Check that we created a customer in stripe
        create_customer.assert_called()
        create_customer.reset_mock()
        # Check that we created a Customer with has_billing_relationship=False
        self.assertTrue(
            Customer.objects.filter(stripe_customer_id=self.stripe_customer_id,
                                    has_billing_relationship=False).exists())
        # Check that we correctly populated RealmAuditLog
        audit_log_entries = list(
            RealmAuditLog.objects.filter(acting_user=user).values_list(
                'event_type', flat=True).order_by('id'))
        self.assertEqual(audit_log_entries, [
            RealmAuditLog.STRIPE_CUSTOMER_CREATED,
            RealmAuditLog.STRIPE_CARD_ADDED
        ])
        # Check that we did not update Realm
        realm = get_realm("zulip")
        self.assertFalse(realm.has_seat_based_plan)
        # Check that we still get redirected to /upgrade
        response = self.client_get("/billing/")
        self.assertEqual(response.status_code, 302)
        self.assertEqual('/upgrade/', response.url)

        # mock_create_customer just returns a customer with no subscription object
        with mock.patch("stripe.Subscription.create",
                        side_effect=mock_customer_with_subscription):
            with mock.patch("stripe.Customer.retrieve",
                            side_effect=mock_create_customer):
                with mock.patch("stripe.Customer.save",
                                side_effect=mock_create_customer):
                    self.client_post(
                        "/upgrade/", {
                            'stripeToken': self.token,
                            'signed_seat_count': self.signed_seat_count,
                            'salt': self.salt,
                            'plan': Plan.CLOUD_ANNUAL
                        })
        # Check that we do not create a new customer in stripe
        create_customer.assert_not_called()
        # Impossible to create two Customers, but check that we updated has_billing_relationship
        self.assertTrue(
            Customer.objects.filter(stripe_customer_id=self.stripe_customer_id,
                                    has_billing_relationship=True).exists())
        # Check that we correctly populated RealmAuditLog
        audit_log_entries = list(
            RealmAuditLog.objects.filter(acting_user=user).values_list(
                'event_type', flat=True).order_by('id'))
        self.assertEqual(audit_log_entries, [
            RealmAuditLog.STRIPE_CUSTOMER_CREATED,
            RealmAuditLog.STRIPE_CARD_ADDED, RealmAuditLog.STRIPE_CARD_ADDED,
            RealmAuditLog.STRIPE_PLAN_CHANGED,
            RealmAuditLog.REALM_PLAN_TYPE_CHANGED
        ])
        # Check that we correctly updated Realm
        realm = get_realm("zulip")
        self.assertTrue(realm.has_seat_based_plan)
        # Check that we can no longer access /upgrade
        response = self.client_get("/upgrade/")
        self.assertEqual(response.status_code, 302)
        self.assertEqual('/billing/', response.url)
Example #58
0
    def test_initial_upgrade(self, mock_create_subscription: mock.Mock,
                             mock_create_customer: mock.Mock) -> None:
        user = self.example_user("hamlet")
        self.login(user.email)
        response = self.client_get("/upgrade/")
        self.assert_in_success_response(['We can also bill by invoice'],
                                        response)
        self.assertFalse(user.realm.has_seat_based_plan)
        self.assertNotEqual(user.realm.plan_type, Realm.PREMIUM)

        # Click "Make payment" in Stripe Checkout
        self.client_post(
            "/upgrade/", {
                'stripeToken':
                self.token,
                'signed_seat_count':
                self.get_signed_seat_count_from_response(response),
                'salt':
                self.get_salt_from_response(response),
                'plan':
                Plan.CLOUD_ANNUAL
            })
        # Check that we created a customer and subscription in stripe
        mock_create_customer.assert_called_once_with(
            description="zulip (Zulip Dev)",
            email=user.email,
            metadata={
                'realm_id': user.realm.id,
                'realm_str': 'zulip'
            },
            source=self.token,
            coupon=None)
        mock_create_subscription.assert_called_once_with(
            customer=self.stripe_customer_id,
            billing='charge_automatically',
            items=[{
                'plan': self.stripe_plan_id,
                'quantity': self.quantity,
            }],
            prorate=True,
            tax_percent=0)
        # Check that we correctly populated Customer and RealmAuditLog in Zulip
        self.assertEqual(
            1,
            Customer.objects.filter(stripe_customer_id=self.stripe_customer_id,
                                    realm=user.realm).count())
        audit_log_entries = list(
            RealmAuditLog.objects.filter(acting_user=user).values_list(
                'event_type', 'event_time').order_by('id'))
        self.assertEqual(audit_log_entries, [
            (RealmAuditLog.STRIPE_CUSTOMER_CREATED,
             timestamp_to_datetime(self.customer_created)),
            (RealmAuditLog.STRIPE_CARD_ADDED,
             timestamp_to_datetime(self.customer_created)),
            (RealmAuditLog.STRIPE_PLAN_CHANGED,
             timestamp_to_datetime(self.subscription_created)),
            (RealmAuditLog.REALM_PLAN_TYPE_CHANGED, Kandra()),
        ])
        # Check that we correctly updated Realm
        realm = get_realm("zulip")
        self.assertTrue(realm.has_seat_based_plan)
        self.assertEqual(realm.plan_type, Realm.PREMIUM)
        self.assertEqual(realm.max_invites, Realm.MAX_INVITES_PREMIUM)
        # Check that we can no longer access /upgrade
        response = self.client_get("/upgrade/")
        self.assertEqual(response.status_code, 302)
        self.assertEqual('/billing/', response.url)
Example #59
0
def reset_emails_in_zulip_realm() -> None:
    realm = get_realm('zulip')
    do_set_realm_property(realm, 'email_address_visibility',
                          Realm.EMAIL_ADDRESS_VISIBILITY_EVERYONE)
    def test_do_auto_soft_deactivate_users(self) -> None:
        users = [
            self.example_user('iago'),
            self.example_user('cordelia'),
            self.example_user('ZOE'),
            self.example_user('othello'),
            self.example_user('prospero'),
            self.example_user('aaron'),
            self.example_user('polonius'),
            self.example_user('desdemona'),
        ]
        sender = self.example_user('hamlet')
        realm = get_realm('zulip')
        stream_name = 'announce'
        for user in users + [sender]:
            self.subscribe(user, stream_name)

        client, _ = Client.objects.get_or_create(name='website')
        query = '/some/random/endpoint'
        last_visit = timezone_now()
        count = 150
        for user_profile in users:
            UserActivity.objects.get_or_create(
                user_profile=user_profile,
                client=client,
                query=query,
                count=count,
                last_visit=last_visit,
            )

        with mock.patch('logging.info'):
            users_deactivated = do_auto_soft_deactivate_users(-1, realm)
        self.assert_length(users_deactivated, len(users))
        for user in users:
            self.assertTrue(user in users_deactivated)

        # Verify that deactivated users are caught up automatically

        message_id = self.send_stream_message(sender, stream_name)
        received_count = UserMessage.objects.filter(
            user_profile__in=users, message_id=message_id).count()
        self.assertEqual(0, received_count)

        with mock.patch('logging.info'):
            users_deactivated = do_auto_soft_deactivate_users(-1, realm)

        self.assert_length(users_deactivated,
                           0)  # all users are already deactivated
        received_count = UserMessage.objects.filter(
            user_profile__in=users, message_id=message_id).count()
        self.assertEqual(len(users), received_count)

        # Verify that deactivated users are NOT caught up if
        # AUTO_CATCH_UP_SOFT_DEACTIVATED_USERS is off

        message_id = self.send_stream_message(sender, stream_name)
        received_count = UserMessage.objects.filter(
            user_profile__in=users, message_id=message_id).count()
        self.assertEqual(0, received_count)

        with self.settings(AUTO_CATCH_UP_SOFT_DEACTIVATED_USERS=False):
            with mock.patch('logging.info'):
                users_deactivated = do_auto_soft_deactivate_users(-1, realm)

        self.assert_length(users_deactivated,
                           0)  # all users are already deactivated
        received_count = UserMessage.objects.filter(
            user_profile__in=users, message_id=message_id).count()
        self.assertEqual(0, received_count)