mirror of
https://github.com/zulip/zulip.git
synced 2025-11-13 10:26:28 +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 }}.
|
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:
|
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)) }}
|
{{ protocol }}://{{ user.realm.host }}{{ url('django.contrib.auth.views.password_reset_confirm', kwargs=dict(uidb64=uid, token=token)) }}
|
||||||
|
{% endif %}
|
||||||
Thanks,
|
Thanks,
|
||||||
Your friends at Zulip HQ
|
Your friends at Zulip HQ
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import logging
|
|||||||
import re
|
import re
|
||||||
import DNS
|
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 ' + \
|
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>. ' + \
|
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,))
|
logging.info("Password reset attempted for %s; no active account." % (email,))
|
||||||
return result
|
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):
|
class CreateUserForm(forms.Form):
|
||||||
full_name = forms.CharField(max_length=100)
|
full_name = forms.CharField(max_length=100)
|
||||||
email = forms.EmailField()
|
email = forms.EmailField()
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ from zerver.management.commands.deliver_email import send_email_job
|
|||||||
from zerver.lib.actions import (
|
from zerver.lib.actions import (
|
||||||
set_default_streams,
|
set_default_streams,
|
||||||
do_change_is_admin,
|
do_change_is_admin,
|
||||||
get_stream
|
get_stream,
|
||||||
|
do_create_realm,
|
||||||
)
|
)
|
||||||
|
|
||||||
from zerver.lib.initial_password import initial_password
|
from zerver.lib.initial_password import initial_password
|
||||||
@@ -151,6 +152,63 @@ class PasswordResetTest(ZulipTestCase):
|
|||||||
# make sure old password no longer works
|
# make sure old password no longer works
|
||||||
self.login(email, password=old_password, fails=True)
|
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):
|
def test_redirect_endpoints(self):
|
||||||
# type: () -> None
|
# type: () -> None
|
||||||
'''
|
'''
|
||||||
|
|||||||
Reference in New Issue
Block a user