mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-03 21:43:21 +00:00 
			
		
		
		
	reset_password: Modify password reset email if email is in wrong realm.
This fixes a confusing issue where a user might try resetting the password for an email account that in part of a different Zulip organization. Is a useful early step towards making Zulip support reusing an email in multiple realms. Fixes: #4557.
This commit is contained in:
		@@ -1,8 +1,15 @@
 | 
			
		||||
{% if attempted_realm %}
 | 
			
		||||
Someone (possibly you) requested a password reset email for {{ email }} on
 | 
			
		||||
{{ attempted_realm.uri }}, but {{ email }} does not
 | 
			
		||||
have an active account in {{ attempted_realm.uri }}.  However, {{ email }} does
 | 
			
		||||
have an active account in {{ user.realm.uri }} organization; you
 | 
			
		||||
can try logging in or resetting your password there.
 | 
			
		||||
{% else %}
 | 
			
		||||
Psst. Word on the street is that you forgot your password, {{ email }}.
 | 
			
		||||
 | 
			
		||||
It's all good. Follow the link below and we'll take care of the rest:
 | 
			
		||||
 | 
			
		||||
{{ protocol }}://{{ user.realm.host }}{{ url('django.contrib.auth.views.password_reset_confirm', kwargs=dict(uidb64=uid, token=token)) }}
 | 
			
		||||
 | 
			
		||||
{% endif %}
 | 
			
		||||
Thanks,
 | 
			
		||||
Your friends at Zulip HQ
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@ import logging
 | 
			
		||||
import re
 | 
			
		||||
import DNS
 | 
			
		||||
 | 
			
		||||
from typing import Any, Callable, List, Optional, Text
 | 
			
		||||
from typing import Any, Callable, List, Optional, Text, Dict
 | 
			
		||||
 | 
			
		||||
MIT_VALIDATION_ERROR = u'That user does not exist at MIT or is a ' + \
 | 
			
		||||
                       u'<a href="https://ist.mit.edu/email-lists">mailing list</a>. ' + \
 | 
			
		||||
@@ -185,6 +185,48 @@ class ZulipPasswordResetForm(PasswordResetForm):
 | 
			
		||||
            logging.info("Password reset attempted for %s; no active account." % (email,))
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
    def send_mail(self, subject_template_name, email_template_name,
 | 
			
		||||
                  context, from_email, to_email, html_email_template_name=None):
 | 
			
		||||
        # type: (str, str, Dict[str, Any], str, str, str) -> None
 | 
			
		||||
        """
 | 
			
		||||
        Currently we don't support accounts in multiple subdomains using
 | 
			
		||||
        a single email addresss. We override this function so that we do
 | 
			
		||||
        not send a reset link to an email address if the reset attempt is
 | 
			
		||||
        done on the subdomain which does not match user.realm.subdomain.
 | 
			
		||||
 | 
			
		||||
        Once we start supporting accounts with the same email in
 | 
			
		||||
        multiple subdomains, we may be able to delete or refactor this
 | 
			
		||||
        function.
 | 
			
		||||
        """
 | 
			
		||||
        user_realm = get_user_profile_by_email(to_email).realm
 | 
			
		||||
        attempted_subdomain = get_subdomain(getattr(self, 'request'))
 | 
			
		||||
        context['attempted_realm'] = False
 | 
			
		||||
        if not check_subdomain(user_realm.subdomain, attempted_subdomain):
 | 
			
		||||
            context['attempted_realm'] = get_realm(attempted_subdomain)
 | 
			
		||||
 | 
			
		||||
        super(ZulipPasswordResetForm, self).send_mail(
 | 
			
		||||
            subject_template_name,
 | 
			
		||||
            email_template_name,
 | 
			
		||||
            context,
 | 
			
		||||
            from_email,
 | 
			
		||||
            to_email,
 | 
			
		||||
            html_email_template_name=html_email_template_name
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def save(self, *args, **kwargs):
 | 
			
		||||
        # type: (*Any, **Any) -> None
 | 
			
		||||
        """Currently we don't support accounts in multiple subdomains using
 | 
			
		||||
        a single email addresss. We override this function so that we can
 | 
			
		||||
        inject request parameter in context. This parameter will be used
 | 
			
		||||
        by send_mail function.
 | 
			
		||||
 | 
			
		||||
        Once we start supporting accounts with the same email in
 | 
			
		||||
        multiple subdomains, we may be able to delete or refactor this
 | 
			
		||||
        function.
 | 
			
		||||
        """
 | 
			
		||||
        setattr(self, 'request', kwargs.get('request'))
 | 
			
		||||
        super(ZulipPasswordResetForm, self).save(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
class CreateUserForm(forms.Form):
 | 
			
		||||
    full_name = forms.CharField(max_length=100)
 | 
			
		||||
    email = forms.EmailField()
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,8 @@ from zerver.management.commands.deliver_email import send_email_job
 | 
			
		||||
from zerver.lib.actions import (
 | 
			
		||||
    set_default_streams,
 | 
			
		||||
    do_change_is_admin,
 | 
			
		||||
    get_stream
 | 
			
		||||
    get_stream,
 | 
			
		||||
    do_create_realm,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
from zerver.lib.initial_password import initial_password
 | 
			
		||||
@@ -151,6 +152,63 @@ class PasswordResetTest(ZulipTestCase):
 | 
			
		||||
        # make sure old password no longer works
 | 
			
		||||
        self.login(email, password=old_password, fails=True)
 | 
			
		||||
 | 
			
		||||
    def test_invalid_subdomain(self):
 | 
			
		||||
        # type: () -> None
 | 
			
		||||
        email = 'hamlet@zulip.com'
 | 
			
		||||
        string_id = 'hamlet'
 | 
			
		||||
        name = 'Hamlet'
 | 
			
		||||
        do_create_realm(
 | 
			
		||||
            string_id,
 | 
			
		||||
            name,
 | 
			
		||||
            restricted_to_domain=False,
 | 
			
		||||
            invite_required=False
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        with self.settings(REALMS_HAVE_SUBDOMAINS=True):
 | 
			
		||||
            with patch('zerver.forms.get_subdomain', return_value=string_id):
 | 
			
		||||
                # start the password reset process by supplying an email address
 | 
			
		||||
                result = self.client_post(
 | 
			
		||||
                    '/accounts/password/reset/', {'email': email})
 | 
			
		||||
 | 
			
		||||
        # check the redirect link telling you to check mail for password reset link
 | 
			
		||||
        self.assertEqual(result.status_code, 302)
 | 
			
		||||
        self.assertTrue(result["Location"].endswith(
 | 
			
		||||
            "/accounts/password/reset/done/"))
 | 
			
		||||
        result = self.client_get(result["Location"])
 | 
			
		||||
 | 
			
		||||
        self.assert_in_response("Check your email to finish the process.", result)
 | 
			
		||||
 | 
			
		||||
        from django.core.mail import outbox
 | 
			
		||||
        self.assertEqual(len(outbox), 1)
 | 
			
		||||
        message = outbox.pop()
 | 
			
		||||
        self.assertIn("hamlet@zulip.com does not\nhave an active account in http://",
 | 
			
		||||
                      message.body)
 | 
			
		||||
 | 
			
		||||
    def test_correct_subdomain(self):
 | 
			
		||||
        # type: () -> None
 | 
			
		||||
        email = 'hamlet@zulip.com'
 | 
			
		||||
        string_id = 'zulip'
 | 
			
		||||
 | 
			
		||||
        with self.settings(REALMS_HAVE_SUBDOMAINS=True):
 | 
			
		||||
            with patch('zerver.forms.get_subdomain', return_value=string_id):
 | 
			
		||||
                # start the password reset process by supplying an email address
 | 
			
		||||
                result = self.client_post(
 | 
			
		||||
                    '/accounts/password/reset/', {'email': email})
 | 
			
		||||
 | 
			
		||||
        # check the redirect link telling you to check mail for password reset link
 | 
			
		||||
        self.assertEqual(result.status_code, 302)
 | 
			
		||||
        self.assertTrue(result["Location"].endswith(
 | 
			
		||||
            "/accounts/password/reset/done/"))
 | 
			
		||||
        result = self.client_get(result["Location"])
 | 
			
		||||
 | 
			
		||||
        self.assert_in_response("Check your email to finish the process.", result)
 | 
			
		||||
 | 
			
		||||
        from django.core.mail import outbox
 | 
			
		||||
        self.assertEqual(len(outbox), 1)
 | 
			
		||||
        message = outbox.pop()
 | 
			
		||||
        self.assertIn("Psst. Word on the street is that you forgot your password,",
 | 
			
		||||
                      message.body)
 | 
			
		||||
 | 
			
		||||
    def test_redirect_endpoints(self):
 | 
			
		||||
        # type: () -> None
 | 
			
		||||
        '''
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user