def confirm_email_change(request: HttpRequest, confirmation_key: str) -> HttpResponse: try: email_change_object = get_object_from_key(confirmation_key, Confirmation.EMAIL_CHANGE) except ConfirmationKeyException as exception: return render_confirmation_key_error(request, exception) new_email = email_change_object.new_email old_email = email_change_object.old_email user_profile = email_change_object.user_profile if user_profile.realm.email_changes_disabled and not user_profile.is_realm_admin: raise JsonableError(_("Email address changes are disabled in this organization.")) do_change_user_delivery_email(user_profile, new_email) context = {'realm_name': user_profile.realm.name, 'new_email': new_email} send_email('zerver/emails/notify_change_in_email', to_emails=[old_email], from_name="Zulip Account Security", from_address=FromAddress.SUPPORT, language=user_profile.default_language, context=context) ctx = { 'new_email': new_email, 'old_email': old_email, } return render(request, 'confirmation/confirm_email_change.html', context=ctx)
def confirm_email_change(request: HttpRequest, confirmation_key: str) -> HttpResponse: try: email_change_object = get_object_from_key(confirmation_key, Confirmation.EMAIL_CHANGE) except ConfirmationKeyException as exception: return render_confirmation_key_error(request, exception) new_email = email_change_object.new_email old_email = email_change_object.old_email user_profile = email_change_object.user_profile if user_profile.realm.email_changes_disabled and not user_profile.is_realm_admin: raise JsonableError(_("Email address changes are disabled in this organization.")) do_change_user_delivery_email(user_profile, new_email) context = {'realm_name': user_profile.realm.name, 'new_email': new_email} language = user_profile.default_language send_email('zerver/emails/notify_change_in_email', to_emails=[old_email], from_name=FromAddress.security_email_from_name(user_profile=user_profile), from_address=FromAddress.SUPPORT, language=language, context=context, realm=user_profile.realm) ctx = { 'new_email_html_tag': SafeString(f'<a href="mailto:{escape(new_email)}">{escape(new_email)}</a>'), 'old_email_html_tag': SafeString(f'<a href="mailto:{escape(old_email)}">{escape(old_email)}</a>'), } return render(request, 'confirmation/confirm_email_change.html', context=ctx)
def handle(self, *args: Any, **options: str) -> None: old_email = options['old_email'] new_email = options['new_email'] realm = self.get_realm(options) user_profile = self.get_user(old_email, realm) do_change_user_delivery_email(user_profile, new_email)
def test_change_email(self) -> None: now = timezone_now() user = self.example_user('hamlet') new_email = '*****@*****.**' do_change_user_delivery_email(user, new_email) self.assertEqual(RealmAuditLog.objects.filter(event_type=RealmAuditLog.USER_EMAIL_CHANGED, event_time__gte=now).count(), 1) self.assertEqual(new_email, user.delivery_email) # Test the RealmAuditLog stringification audit_entry = RealmAuditLog.objects.get(event_type=RealmAuditLog.USER_EMAIL_CHANGED, event_time__gte=now) self.assertTrue(str(audit_entry).startswith(f"<RealmAuditLog: <UserProfile: {user.email} {user.realm}> {RealmAuditLog.USER_EMAIL_CHANGED} "))
def test_change_email(self) -> None: now = timezone_now() user = self.example_user('hamlet') email = '*****@*****.**' do_change_user_delivery_email(user, email) self.assertEqual(RealmAuditLog.objects.filter(event_type=RealmAuditLog.USER_EMAIL_CHANGED, event_time__gte=now).count(), 1) self.assertEqual(email, user.email) # Test the RealmAuditLog stringification audit_entry = RealmAuditLog.objects.get(event_type=RealmAuditLog.USER_EMAIL_CHANGED, event_time__gte=now) self.assertTrue(str(audit_entry).startswith("<RealmAuditLog: <UserProfile: [email protected] <Realm: zulip 1>> user_email_changed "))
def confirm_email_change(request: HttpRequest, confirmation_key: str) -> HttpResponse: try: email_change_object = get_object_from_key(confirmation_key, [Confirmation.EMAIL_CHANGE]) except ConfirmationKeyException as exception: return render_confirmation_key_error(request, exception) new_email = email_change_object.new_email old_email = email_change_object.old_email user_profile = email_change_object.user_profile if user_profile.realm.deactivated: return redirect_to_deactivation_notice() if not user_profile.is_active: # TODO: Make this into a user-facing error, not JSON raise UserDeactivatedError() if user_profile.realm.email_changes_disabled and not user_profile.is_realm_admin: raise JsonableError( _("Email address changes are disabled in this organization.")) do_change_user_delivery_email(user_profile, new_email) context = {"realm_name": user_profile.realm.name, "new_email": new_email} language = user_profile.default_language send_email( "zerver/emails/notify_change_in_email", to_emails=[old_email], from_name=FromAddress.security_email_from_name( user_profile=user_profile), from_address=FromAddress.SUPPORT, language=language, context=context, realm=user_profile.realm, ) ctx = { "new_email_html_tag": SafeString( f'<a href="mailto:{escape(new_email)}">{escape(new_email)}</a>'), "old_email_html_tag": SafeString( f'<a href="mailto:{escape(old_email)}">{escape(old_email)}</a>'), } return render(request, "confirmation/confirm_email_change.html", context=ctx)
def generate_all_emails(request: HttpRequest) -> HttpResponse: if not settings.TEST_SUITE: # nocoverage # It's really convenient to automatically inline the email CSS # here, since that saves a step when testing out changes to # the email CSS. But we don't run this inside the test suite, # because by role, the tests shouldn't be doing a provision-like thing. subprocess.check_call(["./scripts/setup/inline_email_css.py"]) # We import the Django test client inside the view function, # because it isn't needed in production elsewhere, and not # importing it saves ~50ms of unnecessary manage.py startup time. from django.test import Client client = Client() # write fake data for all variables registered_email = "*****@*****.**" unregistered_email_1 = "*****@*****.**" unregistered_email_2 = "*****@*****.**" realm = get_realm("zulip") other_realm = Realm.objects.exclude(string_id='zulip').first() user = get_user_by_delivery_email(registered_email, realm) host_kwargs = {'HTTP_HOST': realm.host} # Password reset emails # active account in realm result = client.post('/accounts/password/reset/', {'email': registered_email}, **host_kwargs) assert result.status_code == 302 # deactivated user user.is_active = False user.save(update_fields=['is_active']) result = client.post('/accounts/password/reset/', {'email': registered_email}, **host_kwargs) assert result.status_code == 302 user.is_active = True user.save(update_fields=['is_active']) # account on different realm result = client.post('/accounts/password/reset/', {'email': registered_email}, HTTP_HOST=other_realm.host) assert result.status_code == 302 # no account anywhere result = client.post('/accounts/password/reset/', {'email': unregistered_email_1}, **host_kwargs) assert result.status_code == 302 # Confirm account email result = client.post('/accounts/home/', {'email': unregistered_email_1}, **host_kwargs) assert result.status_code == 302 # Find account email result = client.post('/accounts/find/', {'emails': registered_email}, **host_kwargs) assert result.status_code == 302 # New login email logged_in = client.login(dev_auth_username=registered_email, realm=realm) assert logged_in # New user invite and reminder emails stream = get_realm_stream("Denmark", user.realm.id) result = client.post( "/json/invites", { "invitee_emails": unregistered_email_2, "stream_ids": ujson.dumps([stream.id]) }, **host_kwargs) assert result.status_code == 200 # Verification for new email result = client.patch( '/json/settings', urllib.parse.urlencode({'email': '*****@*****.**'}), **host_kwargs) assert result.status_code == 200 # Email change successful key = Confirmation.objects.filter( type=Confirmation.EMAIL_CHANGE).latest('id').confirmation_key url = confirmation_url(key, realm.host, Confirmation.EMAIL_CHANGE) user_profile = get_user_by_delivery_email(registered_email, realm) result = client.get(url) assert result.status_code == 200 # Reset the email value so we can run this again do_change_user_delivery_email(user_profile, registered_email) # Follow up day1 day2 emails for normal user enqueue_welcome_emails(user_profile) # Follow up day1 day2 emails for admin user enqueue_welcome_emails(get_user_by_delivery_email("*****@*****.**", realm), realm_creation=True) # Realm reactivation email do_send_realm_reactivation_email(realm) return redirect(email_page)
def save(self) -> None: """ This method is called at the end of operations modifying a user, and is responsible for actually applying the requested changes, writing them to the database. """ realm = RequestNotes.get_notes(self._request).realm assert realm is not None email_new_value = getattr(self, "_email_new_value", None) is_active_new_value = getattr(self, "_is_active_new_value", None) full_name_new_value = getattr(self, "_full_name_new_value", None) password = getattr(self, "_password_set_to", None) # Clean up the internal "pending change" state, now that we've # fetched the values: self._email_new_value = None self._is_active_new_value = None self._full_name_new_value = None self._password_set_to = None if email_new_value: try: # Note that the validate_email check that usually # appears adjacent to email_allowed_for_realm is # present in save(). email_allowed_for_realm(email_new_value, realm) except DomainNotAllowedForRealmError: raise scim_exceptions.BadRequestError( "This email domain isn't allowed in this organization." ) except DisposableEmailError: # nocoverage raise scim_exceptions.BadRequestError( "Disposable email domains are not allowed for this realm." ) except EmailContainsPlusError: # nocoverage raise scim_exceptions.BadRequestError("Email address can't contain + characters.") try: validate_email_not_already_in_realm(realm, email_new_value) except ValidationError as e: raise ConflictError("Email address already in use: " + str(e)) if self.is_new_user(): assert full_name_new_value is not None self.obj = do_create_user( email_new_value, password, realm, full_name_new_value, acting_user=None, ) return with transaction.atomic(): # We process full_name first here, since it's the only one that can fail. if full_name_new_value: check_change_full_name(self.obj, full_name_new_value, acting_user=None) if email_new_value: do_change_user_delivery_email(self.obj, email_new_value) if is_active_new_value is not None and is_active_new_value: do_reactivate_user(self.obj, acting_user=None) elif is_active_new_value is not None and not is_active_new_value: do_deactivate_user(self.obj, acting_user=None)
def generate_all_emails(request: HttpRequest) -> HttpResponse: if not settings.TEST_SUITE: # nocoverage # It's really convenient to automatically inline the email CSS # here, since that saves a step when testing out changes to # the email CSS. But we don't run this inside the test suite, # because by role, the tests shouldn't be doing a provision-like thing. subprocess.check_call(["./tools/inline-email-css"]) # We import the Django test client inside the view function, # because it isn't needed in production elsewhere, and not # importing it saves ~50ms of unnecessary manage.py startup time. from django.test import Client client = Client() # write fake data for all variables registered_email = "*****@*****.**" unregistered_email_1 = "*****@*****.**" unregistered_email_2 = "*****@*****.**" realm = get_realm("zulip") other_realm = Realm.objects.exclude(string_id='zulip').first() user = get_user_by_delivery_email(registered_email, realm) host_kwargs = {'HTTP_HOST': realm.host} # Password reset emails # active account in realm result = client.post('/accounts/password/reset/', {'email': registered_email}, **host_kwargs) assert result.status_code == 302 # deactivated user user.is_active = False user.save(update_fields=['is_active']) result = client.post('/accounts/password/reset/', {'email': registered_email}, **host_kwargs) assert result.status_code == 302 user.is_active = True user.save(update_fields=['is_active']) # account on different realm result = client.post('/accounts/password/reset/', {'email': registered_email}, HTTP_HOST=other_realm.host) assert result.status_code == 302 # no account anywhere result = client.post('/accounts/password/reset/', {'email': unregistered_email_1}, **host_kwargs) assert result.status_code == 302 # Confirm account email result = client.post('/accounts/home/', {'email': unregistered_email_1}, **host_kwargs) assert result.status_code == 302 # Find account email result = client.post('/accounts/find/', {'emails': registered_email}, **host_kwargs) assert result.status_code == 302 # New login email logged_in = client.login(dev_auth_username=registered_email, realm=realm) assert logged_in # New user invite and reminder emails result = client.post("/json/invites", {"invitee_emails": unregistered_email_2, "stream": ["Denmark"]}, **host_kwargs) assert result.status_code == 200 # Verification for new email result = client.patch('/json/settings', urllib.parse.urlencode({'email': '*****@*****.**'}), **host_kwargs) assert result.status_code == 200 # Email change successful key = Confirmation.objects.filter(type=Confirmation.EMAIL_CHANGE).latest('id').confirmation_key url = confirmation_url(key, realm.host, Confirmation.EMAIL_CHANGE) user_profile = get_user_by_delivery_email(registered_email, realm) result = client.get(url) assert result.status_code == 200 # Reset the email value so we can run this again do_change_user_delivery_email(user_profile, registered_email) # Follow up day1 day2 emails for normal user enqueue_welcome_emails(user_profile) # Follow up day1 day2 emails for admin user enqueue_welcome_emails(get_user_by_delivery_email("*****@*****.**", realm), realm_creation=True) # Realm reactivation email do_send_realm_reactivation_email(realm) return redirect(email_page)