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:
Umair Khan 2017-04-24 15:19:54 +05:00 committed by Tim Abbott
parent a8521dc988
commit 556264f3d7
3 changed files with 110 additions and 3 deletions

View File

@ -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

View File

@ -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()

View File

@ -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
'''