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 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 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 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 # TODO: The below operations should ideally be executed in a single # atomic block to avoid failing with partial changes getting saved. # This can be fixed once we figure out how do_deactivate_user can be run # inside an atomic block. # 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(["./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 = "*****@*****.**" invite_expires_in_minutes = settings.INVITATION_LINK_VALIDITY_MINUTES 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 change_user_is_active(user, False) result = client.post("/accounts/password/reset/", {"email": registered_email}, **host_kwargs) assert result.status_code == 302 change_user_is_active(user, True) # account on different realm assert other_realm is not None 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, "invite_expires_in_minutes": invite_expires_in_minutes, "stream_ids": orjson.dumps([stream.id]).decode(), }, **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, 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, acting_user=None) return redirect(email_page)