Merge pull request #19050 from edx/diana/password-reset-email
Send password reset email after password compliance failure.
This commit is contained in:
@@ -30,6 +30,34 @@ from student.models import CourseEnrollmentAllowed, email_exists_or_retired
|
||||
from util.password_policy_validators import password_max_length, password_min_length, validate_password
|
||||
|
||||
|
||||
def send_password_reset_email_for_user(user, request):
|
||||
"""
|
||||
Send out a password reset email for the given user.
|
||||
"""
|
||||
site = get_current_site()
|
||||
message_context = get_base_template_context(site)
|
||||
message_context.update({
|
||||
'request': request, # Used by google_analytics_tracking_pixel
|
||||
# TODO: This overrides `platform_name` from `get_base_template_context` to make the tests passes
|
||||
'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME),
|
||||
'reset_link': '{protocol}://{site}{link}'.format(
|
||||
protocol='https' if request.is_secure() else 'http',
|
||||
site=configuration_helpers.get_value('SITE_NAME', settings.SITE_NAME),
|
||||
link=reverse('password_reset_confirm', kwargs={
|
||||
'uidb36': int_to_base36(user.id),
|
||||
'token': default_token_generator.make_token(user),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
msg = PasswordReset().personalize(
|
||||
recipient=Recipient(user.username, user.email),
|
||||
language=get_user_preference(user, LANGUAGE_KEY),
|
||||
user_context=message_context,
|
||||
)
|
||||
ace.send(msg)
|
||||
|
||||
|
||||
class PasswordResetFormNoActive(PasswordResetForm):
|
||||
error_messages = {
|
||||
'unknown': _("That e-mail address doesn't have an associated "
|
||||
@@ -64,29 +92,7 @@ class PasswordResetFormNoActive(PasswordResetForm):
|
||||
user.
|
||||
"""
|
||||
for user in self.users_cache:
|
||||
site = get_current_site()
|
||||
message_context = get_base_template_context(site)
|
||||
|
||||
message_context.update({
|
||||
'request': request, # Used by google_analytics_tracking_pixel
|
||||
# TODO: This overrides `platform_name` from `get_base_template_context` to make the tests passes
|
||||
'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME),
|
||||
'reset_link': '{protocol}://{site}{link}'.format(
|
||||
protocol='https' if use_https else 'http',
|
||||
site=configuration_helpers.get_value('SITE_NAME', settings.SITE_NAME),
|
||||
link=reverse('password_reset_confirm', kwargs={
|
||||
'uidb36': int_to_base36(user.id),
|
||||
'token': token_generator.make_token(user),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
msg = PasswordReset().personalize(
|
||||
recipient=Recipient(user.username, user.email),
|
||||
language=get_user_preference(user, LANGUAGE_KEY),
|
||||
user_context=message_context,
|
||||
)
|
||||
ace.send(msg)
|
||||
send_password_reset_email_for_user(user, request)
|
||||
|
||||
|
||||
class TrueCheckbox(widgets.CheckboxInput):
|
||||
|
||||
@@ -20,6 +20,11 @@ class AceCommonConfig(AppConfig):
|
||||
SettingsType.AWS: {PluginSettings.RELATIVE_PATH: u'settings.aws'},
|
||||
SettingsType.COMMON: {PluginSettings.RELATIVE_PATH: u'settings.common'},
|
||||
SettingsType.DEVSTACK: {PluginSettings.RELATIVE_PATH: u'settings.devstack'},
|
||||
},
|
||||
ProjectType.CMS: {
|
||||
SettingsType.AWS: {PluginSettings.RELATIVE_PATH: u'settings.aws'},
|
||||
SettingsType.COMMON: {PluginSettings.RELATIVE_PATH: u'settings.common'},
|
||||
SettingsType.DEVSTACK: {PluginSettings.RELATIVE_PATH: u'settings.devstack'},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
Context dictionary for templates that use the ace_common base template.
|
||||
"""
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import NoReverseMatch
|
||||
from django.urls import reverse
|
||||
|
||||
from edxmako.shortcuts import marketing_link
|
||||
@@ -12,10 +13,17 @@ def get_base_template_context(site):
|
||||
"""
|
||||
Dict with entries needed for all templates that use the base template.
|
||||
"""
|
||||
# When on LMS and a dashboard is available, use that as the dashboard url.
|
||||
# Otherwise, use the home url instead.
|
||||
try:
|
||||
dashboard_url = reverse('dashboard')
|
||||
except NoReverseMatch:
|
||||
dashboard_url = reverse('home')
|
||||
|
||||
return {
|
||||
# Platform information
|
||||
'homepage_url': marketing_link('ROOT'),
|
||||
'dashboard_url': reverse('dashboard'),
|
||||
'dashboard_url': dashboard_url,
|
||||
'template_revision': getattr(settings, 'EDX_PLATFORM_REVISION', None),
|
||||
'platform_name': get_config_value_from_site_or_settings(
|
||||
'PLATFORM_NAME',
|
||||
|
||||
@@ -83,8 +83,7 @@ def enforce_compliance_on_login(user, password):
|
||||
_capitalize_first(_(
|
||||
'{platform_name} now requires more complex passwords. Your current password does not meet the new '
|
||||
'requirements. You must change your password by {deadline} to be able to continue using the site. '
|
||||
'To change your password, select the dropdown menu icon next to your username, then select "Account". '
|
||||
'You can reset your password from this page. Thank you for helping us keep your data safe.'
|
||||
'A password reset e-mail has been sent to the address associated with this account.'
|
||||
).format(
|
||||
platform_name=settings.PLATFORM_NAME,
|
||||
deadline=strftime_localized(deadline, DEFAULT_SHORT_DATE_FORMAT)
|
||||
|
||||
@@ -32,6 +32,7 @@ from student.models import (
|
||||
PasswordHistory,
|
||||
)
|
||||
from student.views import send_reactivation_email_for_user
|
||||
from student.forms import send_password_reset_email_for_user
|
||||
import third_party_auth
|
||||
from third_party_auth import pipeline, provider
|
||||
from util.json_request import JsonResponse
|
||||
@@ -146,6 +147,7 @@ def _enforce_password_policy_compliance(request, user):
|
||||
# Allow login, but warn the user that they will be required to reset their password soon.
|
||||
PageLevelMessages.register_warning_message(request, e.message)
|
||||
except password_policy_compliance.NonCompliantPasswordException as e:
|
||||
send_password_reset_email_for_user(user, request)
|
||||
# Prevent the login attempt.
|
||||
raise AuthFailedError(e.message)
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import unittest
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.cache import cache
|
||||
from django.core import mail
|
||||
from django.urls import NoReverseMatch, reverse
|
||||
from django.http import HttpResponse, HttpResponseBadRequest
|
||||
from django.test.client import Client
|
||||
@@ -462,6 +463,8 @@ class LoginTest(CacheIsolationTestCase):
|
||||
)
|
||||
response_content = json.loads(response.content)
|
||||
self.assertFalse(response_content.get('success'))
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertIn('Password reset', mail.outbox[0].subject)
|
||||
|
||||
@override_settings(PASSWORD_POLICY_COMPLIANCE_ROLLOUT_CONFIG={'ENFORCE_COMPLIANCE_ON_LOGIN': True})
|
||||
def test_check_password_policy_compliance_warning(self):
|
||||
|
||||
Reference in New Issue
Block a user