diff --git a/templates/registration/password_reset_email.txt b/templates/registration/password_reset_email.txt index 55207d4ebf..3ebc9f3e2a 100644 --- a/templates/registration/password_reset_email.txt +++ b/templates/registration/password_reset_email.txt @@ -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 diff --git a/zerver/forms.py b/zerver/forms.py index b3c3032ebf..8468babdfa 100644 --- a/zerver/forms.py +++ b/zerver/forms.py @@ -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'mailing list. ' + \ @@ -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() diff --git a/zerver/tests/test_signup.py b/zerver/tests/test_signup.py index 5a22cdbaa1..30970969b2 100644 --- a/zerver/tests/test_signup.py +++ b/zerver/tests/test_signup.py @@ -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 '''