def json_change_notify_settings( request: HttpRequest, user_profile: UserProfile, enable_stream_desktop_notifications: Optional[bool]=REQ(validator=check_bool, default=None), enable_stream_email_notifications: Optional[bool]=REQ(validator=check_bool, default=None), enable_stream_push_notifications: Optional[bool]=REQ(validator=check_bool, default=None), enable_stream_audible_notifications: Optional[bool]=REQ(validator=check_bool, default=None), notification_sound: Optional[str]=REQ(validator=check_string, default=None), enable_desktop_notifications: Optional[bool]=REQ(validator=check_bool, default=None), enable_sounds: Optional[bool]=REQ(validator=check_bool, default=None), enable_offline_email_notifications: Optional[bool]=REQ(validator=check_bool, default=None), enable_offline_push_notifications: Optional[bool]=REQ(validator=check_bool, default=None), enable_online_push_notifications: Optional[bool]=REQ(validator=check_bool, default=None), enable_digest_emails: Optional[bool]=REQ(validator=check_bool, default=None), enable_login_emails: Optional[bool]=REQ(validator=check_bool, default=None), message_content_in_email_notifications: Optional[bool]=REQ(validator=check_bool, default=None), pm_content_in_desktop_notifications: Optional[bool]=REQ(validator=check_bool, default=None), desktop_icon_count_display: Optional[int]=REQ(validator=check_int, default=None), realm_name_in_notifications: Optional[bool]=REQ(validator=check_bool, default=None) ) -> HttpResponse: result = {} # Stream notification settings. if (notification_sound is not None and notification_sound not in get_available_notification_sounds()): raise JsonableError(_("Invalid notification sound '%s'") % (notification_sound,)) req_vars = {k: v for k, v in list(locals().items()) if k in user_profile.notification_setting_types} for k, v in list(req_vars.items()): if v is not None and getattr(user_profile, k) != v: do_change_notification_settings(user_profile, k, v) result[k] = v return json_success(result)
def json_change_notify_settings(request, user_profile, enable_stream_desktop_notifications=REQ(validator=check_bool, default=None), enable_stream_push_notifications=REQ(validator=check_bool, default=None), enable_stream_sounds=REQ(validator=check_bool, default=None), enable_desktop_notifications=REQ(validator=check_bool, default=None), enable_sounds=REQ(validator=check_bool, default=None), enable_offline_email_notifications=REQ(validator=check_bool, default=None), enable_offline_push_notifications=REQ(validator=check_bool, default=None), enable_online_push_notifications=REQ(validator=check_bool, default=None), enable_digest_emails=REQ(validator=check_bool, default=None), pm_content_in_desktop_notifications=REQ(validator=check_bool, default=None)): # type: (HttpRequest, UserProfile, Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool]) -> HttpResponse result = {} # Stream notification settings. req_vars = {k: v for k, v in list(locals().items()) if k in user_profile.notification_setting_types} for k, v in list(req_vars.items()): if v is not None and getattr(user_profile, k) != v: do_change_notification_settings(user_profile, k, v) result[k] = v return json_success(result)
def test_change_notification_settings(self) -> None: user = self.example_user("hamlet") value: Union[bool, int, str] for setting, v in user.notification_setting_types.items(): if setting == "notification_sound": value = "ding" elif setting == "desktop_icon_count_display": value = 3 else: value = False now = timezone_now() old_value = getattr(user, setting) do_change_notification_settings(user, setting, value, acting_user=user) expected_extra_data = { RealmAuditLog.OLD_VALUE: old_value, RealmAuditLog.NEW_VALUE: value, "property": setting, } self.assertEqual( RealmAuditLog.objects.filter( realm=user.realm, event_type=RealmAuditLog.USER_NOTIFICATION_SETTINGS_CHANGED, event_time__gte=now, acting_user=user, modified_user=user, extra_data=orjson.dumps(expected_extra_data).decode(), ).count(), 1, ) self.assertEqual(getattr(user, setting), value)
def json_change_notify_settings( request: HttpRequest, user_profile: UserProfile, enable_stream_desktop_notifications: Optional[bool]=REQ(validator=check_bool, default=None), enable_stream_email_notifications: Optional[bool]=REQ(validator=check_bool, default=None), enable_stream_push_notifications: Optional[bool]=REQ(validator=check_bool, default=None), enable_stream_sounds: Optional[bool]=REQ(validator=check_bool, default=None), enable_desktop_notifications: Optional[bool]=REQ(validator=check_bool, default=None), enable_sounds: Optional[bool]=REQ(validator=check_bool, default=None), enable_offline_email_notifications: Optional[bool]=REQ(validator=check_bool, default=None), enable_offline_push_notifications: Optional[bool]=REQ(validator=check_bool, default=None), enable_online_push_notifications: Optional[bool]=REQ(validator=check_bool, default=None), enable_digest_emails: Optional[bool]=REQ(validator=check_bool, default=None), enable_login_emails: Optional[bool]=REQ(validator=check_bool, default=None), message_content_in_email_notifications: Optional[bool]=REQ(validator=check_bool, default=None), pm_content_in_desktop_notifications: Optional[bool]=REQ(validator=check_bool, default=None), realm_name_in_notifications: Optional[bool]=REQ(validator=check_bool, default=None) ) -> HttpResponse: result = {} # Stream notification settings. req_vars = {k: v for k, v in list(locals().items()) if k in user_profile.notification_setting_types} for k, v in list(req_vars.items()): if v is not None and getattr(user_profile, k) != v: do_change_notification_settings(user_profile, k, v) result[k] = v return json_success(result)
def handle(self, **options): # type: (**str) -> None realm = self.get_realm(options) if realm is None and options["users"] is None: self.print_help("./manage.py", "turn_off_digests") exit(1) if realm and not options["users"]: user_profiles = UserProfile.objects.filter(realm=realm) else: emails = set( [email.strip() for email in options["users"].split(",")]) user_profiles = [] for email in emails: user_profiles.append(self.get_user(email, realm)) print("Turned off digest emails for:") for user_profile in user_profiles: already_disabled_prefix = "" if user_profile.enable_digest_emails: do_change_notification_settings(user_profile, 'enable_digest_emails', False) else: already_disabled_prefix = "(already off) " print("%s%s <%s>" % (already_disabled_prefix, user_profile.full_name, user_profile.email))
def test_enable_login_emails_user_setting(self) -> None: user = self.example_user("hamlet") mock_time = datetime.datetime(year=2018, month=1, day=1, tzinfo=datetime.timezone.utc) user.timezone = "US/Pacific" user.date_joined = mock_time - datetime.timedelta( seconds=JUST_CREATED_THRESHOLD + 1) user.save() do_change_notification_settings(user, "enable_login_emails", False, acting_user=None) self.assertFalse(user.enable_login_emails) with mock.patch("zerver.signals.timezone_now", return_value=mock_time): self.login_user(user) self.assert_length(mail.outbox, 0) do_change_notification_settings(user, "enable_login_emails", True, acting_user=None) self.assertTrue(user.enable_login_emails) with mock.patch("zerver.signals.timezone_now", return_value=mock_time): self.login_user(user) self.assert_length(mail.outbox, 1)
def test_message_content_disabled_in_missed_message_notifications(self) -> None: # Test when user disabled message content in email notifications. do_change_notification_settings(self.example_user("hamlet"), "message_content_in_email_notifications", False) self._extra_context_in_missed_stream_messages_mention(False, show_message_content=False) mail.outbox = [] self._extra_context_in_personal_missed_stream_messages(False, show_message_content=False) mail.outbox = [] self._extra_context_in_huddle_missed_stream_messages_two_others(False, show_message_content=False)
def test_message_content_disabled_in_missed_message_notifications(self) -> None: # Test when user disabled message content in email notifications. do_change_notification_settings(self.example_user("hamlet"), "message_content_in_email_notifications", False) self._extra_context_in_missed_stream_messages_mention(False, show_message_content=False) mail.outbox = [] self._extra_context_in_personal_missed_stream_messages(False, show_message_content=False) mail.outbox = [] self._extra_context_in_huddle_missed_stream_messages_two_others(False, show_message_content=False)
def handle(self, **options: str) -> None: realm = self.get_realm(options) user_profiles = self.get_users(options, realm) print("Turned off digest emails for:") for user_profile in user_profiles: already_disabled_prefix = "" if user_profile.enable_digest_emails: do_change_notification_settings(user_profile, 'enable_digest_emails', False) else: already_disabled_prefix = "(already off) " print(f"{already_disabled_prefix}{user_profile.full_name} <{user_profile.delivery_email}>")
def handle(self, **options: str) -> None: realm = self.get_realm(options) user_profiles = self.get_users(options, realm) print("Turned off digest emails for:") for user_profile in user_profiles: already_disabled_prefix = "" if user_profile.enable_digest_emails: do_change_notification_settings(user_profile, 'enable_digest_emails', False) else: already_disabled_prefix = "(already off) " print("%s%s <%s>" % (already_disabled_prefix, user_profile.full_name, user_profile.email))
def handle(self, **options): # type: (**str) -> None realm = self.get_realm(options) user_profiles = self.get_users(options, realm) print("Turned off digest emails for:") for user_profile in user_profiles: already_disabled_prefix = "" if user_profile.enable_digest_emails: do_change_notification_settings(user_profile, 'enable_digest_emails', False) else: already_disabled_prefix = "(already off) " print("%s%s <%s>" % (already_disabled_prefix, user_profile.full_name, user_profile.email))
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)
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)
def test_enable_login_emails_user_setting(self) -> None: user = self.example_user('hamlet') utc = get_timezone('utc') mock_time = datetime.datetime(year=2018, month=1, day=1, tzinfo=utc) user.timezone = 'US/Pacific' user.date_joined = mock_time - datetime.timedelta(seconds=JUST_CREATED_THRESHOLD + 1) user.save() do_change_notification_settings(user, "enable_login_emails", False) self.assertFalse(user.enable_login_emails) with mock.patch('zerver.signals.timezone_now', return_value=mock_time): self.login(user.email) self.assertEqual(len(mail.outbox), 0) do_change_notification_settings(user, "enable_login_emails", True) self.assertTrue(user.enable_login_emails) with mock.patch('zerver.signals.timezone_now', return_value=mock_time): self.login(user.email) self.assertEqual(len(mail.outbox), 1)
def handle(self, **options): # type: (**str) -> None if options["string_id"] is None and options["users"] is None: self.print_help("./manage.py", "turn_off_digests") exit(1) if options["string_id"]: realm = get_realm(options["string_id"]) user_profiles = UserProfile.objects.filter(realm=realm) else: emails = set([email.strip() for email in options["users"].split(",")]) user_profiles = [] for email in emails: user_profiles.append(get_user_profile_by_email(email)) print("Turned off digest emails for:") for user_profile in user_profiles: already_disabled_prefix = "" if user_profile.enable_digest_emails: do_change_notification_settings(user_profile, 'enable_digest_emails', False) else: already_disabled_prefix = "(already off) " print("%s%s <%s>" % (already_disabled_prefix, user_profile.full_name, user_profile.email))
def do_missedmessage_unsubscribe(user_profile): # type: (UserProfile) -> None do_change_notification_settings(user_profile, 'enable_offline_email_notifications', False)
def do_login_unsubscribe(user_profile: UserProfile) -> None: do_change_notification_settings(user_profile, 'enable_login_emails', False, acting_user=user_profile)
def do_missedmessage_unsubscribe(user_profile: UserProfile) -> None: do_change_notification_settings(user_profile, 'enable_offline_email_notifications', False, acting_user=user_profile)
def do_marketing_unsubscribe(user_profile: UserProfile) -> None: do_change_notification_settings(user_profile, "enable_marketing_emails", False, acting_user=user_profile)
def json_change_settings( request: HttpRequest, user_profile: UserProfile, full_name: str = REQ(default=""), email: str = REQ(default=""), old_password: str = REQ(default=""), new_password: str = REQ(default=""), twenty_four_hour_time: Optional[bool] = REQ(json_validator=check_bool, default=None), dense_mode: Optional[bool] = REQ(json_validator=check_bool, default=None), starred_message_counts: Optional[bool] = REQ(json_validator=check_bool, default=None), fluid_layout_width: Optional[bool] = REQ(json_validator=check_bool, default=None), high_contrast_mode: Optional[bool] = REQ(json_validator=check_bool, default=None), color_scheme: Optional[int] = REQ(json_validator=check_int_in( UserProfile.COLOR_SCHEME_CHOICES), default=None), translate_emoticons: Optional[bool] = REQ(json_validator=check_bool, default=None), default_language: Optional[str] = REQ(default=None), default_view: Optional[str] = REQ( str_validator=check_string_in(default_view_options), default=None), left_side_userlist: Optional[bool] = REQ(json_validator=check_bool, default=None), emojiset: Optional[str] = REQ( str_validator=check_string_in(emojiset_choices), default=None), demote_inactive_streams: Optional[int] = REQ(json_validator=check_int_in( UserProfile.DEMOTE_STREAMS_CHOICES), default=None), timezone: Optional[str] = REQ(str_validator=check_string_in( pytz.all_timezones_set), default=None), email_notifications_batching_period_seconds: Optional[int] = REQ( json_validator=check_int, default=None), enable_stream_desktop_notifications: Optional[bool] = REQ( json_validator=check_bool, default=None), enable_stream_email_notifications: Optional[bool] = REQ( json_validator=check_bool, default=None), enable_stream_push_notifications: Optional[bool] = REQ( json_validator=check_bool, default=None), enable_stream_audible_notifications: Optional[bool] = REQ( json_validator=check_bool, default=None), wildcard_mentions_notify: Optional[bool] = REQ(json_validator=check_bool, default=None), notification_sound: Optional[str] = REQ(default=None), enable_desktop_notifications: Optional[bool] = REQ( json_validator=check_bool, default=None), enable_sounds: Optional[bool] = REQ(json_validator=check_bool, default=None), enable_offline_email_notifications: Optional[bool] = REQ( json_validator=check_bool, default=None), enable_offline_push_notifications: Optional[bool] = REQ( json_validator=check_bool, default=None), enable_online_push_notifications: Optional[bool] = REQ( json_validator=check_bool, default=None), enable_digest_emails: Optional[bool] = REQ(json_validator=check_bool, default=None), enable_login_emails: Optional[bool] = REQ(json_validator=check_bool, default=None), enable_marketing_emails: Optional[bool] = REQ(json_validator=check_bool, default=None), message_content_in_email_notifications: Optional[bool] = REQ( json_validator=check_bool, default=None), pm_content_in_desktop_notifications: Optional[bool] = REQ( json_validator=check_bool, default=None), desktop_icon_count_display: Optional[int] = REQ(json_validator=check_int, default=None), realm_name_in_notifications: Optional[bool] = REQ( json_validator=check_bool, default=None), presence_enabled: Optional[bool] = REQ(json_validator=check_bool, default=None), enter_sends: Optional[bool] = REQ(json_validator=check_bool, default=None), ) -> HttpResponse: # We can't use REQ for this widget because # get_available_language_codes requires provisioning to be # complete. if default_language is not None and default_language not in get_available_language_codes( ): raise JsonableError(_("Invalid default_language")) if (notification_sound is not None and notification_sound not in get_available_notification_sounds() and notification_sound != "none"): raise JsonableError( _("Invalid notification sound '{}'").format(notification_sound)) if email_notifications_batching_period_seconds is not None and ( email_notifications_batching_period_seconds <= 0 or email_notifications_batching_period_seconds > 7 * 24 * 60 * 60): # We set a limit of one week for the batching period raise JsonableError( _("Invalid email batching period: {} seconds").format( email_notifications_batching_period_seconds)) if new_password != "": return_data: Dict[str, Any] = {} if email_belongs_to_ldap(user_profile.realm, user_profile.delivery_email): raise JsonableError(_("Your Zulip password is managed in LDAP")) try: if not authenticate( request, username=user_profile.delivery_email, password=old_password, realm=user_profile.realm, return_data=return_data, ): raise JsonableError(_("Wrong password!")) except RateLimited as e: assert e.secs_to_freedom is not None secs_to_freedom = int(e.secs_to_freedom) raise JsonableError( _("You're making too many attempts! Try again in {} seconds."). format(secs_to_freedom), ) if not check_password_strength(new_password): raise JsonableError(_("New password is too weak!")) do_change_password(user_profile, new_password) # In Django 1.10, password changes invalidates sessions, see # https://docs.djangoproject.com/en/1.10/topics/auth/default/#session-invalidation-on-password-change # for details. To avoid this logging the user out of their own # session (which would provide a confusing UX at best), we # update the session hash here. update_session_auth_hash(request, user_profile) # We also save the session to the DB immediately to mitigate # race conditions. In theory, there is still a race condition # and to completely avoid it we will have to use some kind of # mutex lock in `django.contrib.auth.get_user` where session # is verified. To make that lock work we will have to control # the AuthenticationMiddleware which is currently controlled # by Django, request.session.save() result: Dict[str, Any] = {} new_email = email.strip() if user_profile.delivery_email != new_email and new_email != "": if user_profile.realm.email_changes_disabled and not user_profile.is_realm_admin: raise JsonableError( _("Email address changes are disabled in this organization.")) error = validate_email_is_valid( new_email, get_realm_email_validator(user_profile.realm), ) if error: raise JsonableError(error) try: validate_email_not_already_in_realm( user_profile.realm, new_email, verbose=False, ) except ValidationError as e: raise JsonableError(e.message) do_start_email_change_process(user_profile, new_email) if user_profile.full_name != full_name and full_name.strip() != "": if name_changes_disabled( user_profile.realm) and not user_profile.is_realm_admin: # Failingly silently is fine -- they can't do it through the UI, so # they'd have to be trying to break the rules. pass else: # Note that check_change_full_name strips the passed name automatically check_change_full_name(user_profile, full_name, user_profile) # Loop over user_profile.property_types request_settings = { k: v for k, v in list(locals().items()) if k in user_profile.property_types } for k, v in list(request_settings.items()): if v is not None and getattr(user_profile, k) != v: do_set_user_display_setting(user_profile, k, v) req_vars = { k: v for k, v in list(locals().items()) if k in user_profile.notification_setting_types } for k, v in list(req_vars.items()): if v is not None and getattr(user_profile, k) != v: do_change_notification_settings(user_profile, k, v, acting_user=user_profile) if timezone is not None and user_profile.timezone != timezone: do_set_user_display_setting(user_profile, "timezone", timezone) # TODO: Do this more generally. from zerver.lib.request import get_request_notes request_notes = get_request_notes(request) for req_var in request.POST: if req_var not in request_notes.processed_parameters: request_notes.ignored_parameters.add(req_var) if len(request_notes.ignored_parameters) > 0: result["ignored_parameters_unsupported"] = list( request_notes.ignored_parameters) return json_success(result)
def do_digest_unsubscribe(user_profile: UserProfile) -> None: do_change_notification_settings(user_profile, 'enable_digest_emails', False)
def do_digest_unsubscribe(user_profile): # type: (UserProfile) -> None do_change_notification_settings(user_profile, 'enable_digest_emails', False)
def do_login_unsubscribe(user_profile: UserProfile) -> None: do_change_notification_settings(user_profile, 'enable_login_emails', False)