Add optional password reset failure emails
This change allows Open edX operators to enable password reset failure email notifications. This is mainly useful for explicitly informing users that there's no account associated with their email, so that users can be sure that there wasn't a problem with the email system. OSPR-1832
This commit is contained in:
@@ -276,6 +276,10 @@ FEATURES = {
|
||||
# Whether or not the dynamic EnrollmentTrackUserPartition should be registered.
|
||||
'ENABLE_ENROLLMENT_TRACK_USER_PARTITION': True,
|
||||
|
||||
# Whether to send an email for failed password reset attempts or not. This is mainly useful for notifying users
|
||||
# that they don't have an account associated with email addresses they believe they've registered with.
|
||||
'ENABLE_PASSWORD_RESET_FAILURE_EMAIL': False,
|
||||
|
||||
# Whether archived courses (courses with end dates in the past) should be
|
||||
# shown in Studio in a separate list.
|
||||
'ENABLE_SEPARATE_ARCHIVED_COURSES': True,
|
||||
|
||||
@@ -51,6 +51,9 @@ from openedx.core.djangoapps.user_api.errors import UserAPIInternalError
|
||||
LOGGER_NAME = 'audit'
|
||||
User = get_user_model() # pylint:disable=invalid-name
|
||||
|
||||
FEATURES_WITH_FAILED_PASSWORD_RESET_EMAIL = settings.FEATURES.copy()
|
||||
FEATURES_WITH_FAILED_PASSWORD_RESET_EMAIL['ENABLE_PASSWORD_RESET_FAILURE_EMAIL'] = True
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class StudentAccountUpdateTest(CacheIsolationTestCase, UrlResetMixin):
|
||||
@@ -140,6 +143,28 @@ class StudentAccountUpdateTest(CacheIsolationTestCase, UrlResetMixin):
|
||||
self._change_password()
|
||||
self.assertRaises(UserAPIInternalError)
|
||||
|
||||
@override_settings(FEATURES=FEATURES_WITH_FAILED_PASSWORD_RESET_EMAIL)
|
||||
def test_password_reset_failure_email(self):
|
||||
"""Test that a password reset failure email notification is sent, when enabled."""
|
||||
# Log the user out
|
||||
self.client.logout()
|
||||
|
||||
bad_email = 'doesnotexist@example.com'
|
||||
response = self._change_password(email=bad_email)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Check that an email was sent
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
|
||||
# Verify that the body contains the failed password reset message
|
||||
email_body = mail.outbox[0].body
|
||||
self.assertIn(
|
||||
'However, there is currently no user account associated with your email address: {email}'.format(
|
||||
email=bad_email
|
||||
),
|
||||
email_body,
|
||||
)
|
||||
|
||||
@ddt.data(True, False)
|
||||
def test_password_change_logged_out(self, send_email):
|
||||
# Log the user out
|
||||
|
||||
@@ -9,9 +9,11 @@ from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
|
||||
from django.core.mail import send_mail
|
||||
from django.core.urlresolvers import resolve, reverse
|
||||
from django.http import HttpRequest, HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
|
||||
from django.shortcuts import redirect
|
||||
from django.template import loader
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from django.views.decorators.http import require_http_methods
|
||||
@@ -216,6 +218,25 @@ def password_change_request_handler(request):
|
||||
AUDIT_LOG.info("Invalid password reset attempt")
|
||||
# Increment the rate limit counter
|
||||
limiter.tick_bad_request_counter(request)
|
||||
|
||||
# If enabled, send an email saying that a password reset was attempted, but that there is
|
||||
# no user associated with the email
|
||||
if configuration_helpers.get_value('ENABLE_PASSWORD_RESET_FAILURE_EMAIL',
|
||||
settings.FEATURES['ENABLE_PASSWORD_RESET_FAILURE_EMAIL']):
|
||||
context = {
|
||||
'failed': True,
|
||||
'email_address': email,
|
||||
'platform_name': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME),
|
||||
|
||||
}
|
||||
subject = loader.render_to_string('emails/password_reset_subject.txt', context)
|
||||
subject = ''.join(subject.splitlines())
|
||||
message = loader.render_to_string('registration/password_reset_email.html', context)
|
||||
from_email = configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL)
|
||||
try:
|
||||
send_mail(subject, message, from_email, [email])
|
||||
except Exception: # pylint: disable=broad-except
|
||||
log.exception(u'Unable to send password reset failure email notification from "%s"', from_email)
|
||||
except UserAPIInternalError as err:
|
||||
log.exception('Error occured during password change for user {email}: {error}'
|
||||
.format(email=email, error=err))
|
||||
|
||||
@@ -418,6 +418,10 @@ FEATURES = {
|
||||
|
||||
# Whether HTML XBlocks/XModules return HTML content with the Course Blocks API student_view_data
|
||||
'ENABLE_HTML_XBLOCK_STUDENT_VIEW_DATA': False,
|
||||
|
||||
# Whether to send an email for failed password reset attempts or not. This is mainly useful for notifying users
|
||||
# that they don't have an account associated with email addresses they believe they've registered with.
|
||||
'ENABLE_PASSWORD_RESET_FAILURE_EMAIL': False,
|
||||
}
|
||||
|
||||
# Settings for the course reviews tool template and identification key, set either to None to disable course reviews
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
{% load i18n %}{% autoescape off %}
|
||||
{% blocktrans %}You're receiving this e-mail because you requested a password reset for your user account at {{ platform_name }}.{% endblocktrans %}
|
||||
|
||||
{% if failed %}
|
||||
{% blocktrans %}However, there is currently no user account associated with your email address: {{ email_address }}.{% endblocktrans %}
|
||||
|
||||
{% trans "If you did not request this change, you can ignore this email." %}
|
||||
{% else %}
|
||||
{% trans "Please go to the following page and choose a new password:" %}
|
||||
{% block reset_link %}
|
||||
{{ protocol }}://{{ site_name }}{% url 'password_reset_confirm' uidb36=uid token=token %}
|
||||
@@ -9,6 +14,7 @@
|
||||
{% trans "If you didn't request this change, you can disregard this email - we have not yet reset your password." %}
|
||||
|
||||
{% trans "Thanks for using our site!" %}
|
||||
{% endif %}
|
||||
|
||||
{% blocktrans %}The {{ platform_name }} Team{% endblocktrans %}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user