diff --git a/common/test/acceptance/tests/lms/test_lms.py b/common/test/acceptance/tests/lms/test_lms.py index 224b9fc9dc..28e0cc5347 100644 --- a/common/test/acceptance/tests/lms/test_lms.py +++ b/common/test/acceptance/tests/lms/test_lms.py @@ -145,18 +145,15 @@ class LoginFromCombinedPageTest(UniqueCourseTest): # Expect that we're shown a success message self.assertIn("Password Reset Email Sent", self.login_page.wait_for_success()) - def test_password_reset_failure(self): + def test_password_reset_no_user(self): # Navigate to the password reset form self.login_page.visit() # User account does not exist self.login_page.password_reset(email="nobody@nowhere.com") - # Expect that we're shown a failure message - self.assertIn( - "No user with the provided email address exists.", - self.login_page.wait_for_errors() - ) + # Expect that we're shown a success message + self.assertIn("Password Reset Email Sent", self.login_page.wait_for_success()) def test_third_party_login(self): """ diff --git a/lms/djangoapps/student_account/test/test_views.py b/lms/djangoapps/student_account/test/test_views.py index 71613757c2..3612568187 100644 --- a/lms/djangoapps/student_account/test/test_views.py +++ b/lms/djangoapps/student_account/test/test_views.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- """ Tests for student account views. """ +import logging import re from unittest import skipUnless from urllib import urlencode @@ -18,6 +19,7 @@ from django.test.utils import override_settings from django.http import HttpRequest from edx_rest_api_client import exceptions from nose.plugins.attrib import attr +from testfixtures import LogCapture from commerce.models import CommerceConfiguration from commerce.tests import TEST_API_URL, TEST_API_SIGNING_KEY, factories @@ -36,6 +38,9 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from openedx.core.djangoapps.theming.tests.test_util import with_comprehensive_theme_context +LOGGER_NAME = 'audit' + + @ddt.ddt class StudentAccountUpdateTest(CacheIsolationTestCase, UrlResetMixin): """ Tests for the student account views that update the user's account information. """ @@ -175,9 +180,11 @@ class StudentAccountUpdateTest(CacheIsolationTestCase, UrlResetMixin): # Log out the user created during test setup self.client.logout() - # Send the view an email address not tied to any user - response = self._change_password(email=self.NEW_EMAIL) - self.assertEqual(response.status_code, 400) + with LogCapture(LOGGER_NAME, level=logging.INFO) as logger: + # Send the view an email address not tied to any user + response = self._change_password(email=self.NEW_EMAIL) + self.assertEqual(response.status_code, 200) + logger.check((LOGGER_NAME, 'INFO', 'Invalid password reset attempt')) def test_password_change_rate_limited(self): # Log out the user created during test setup, to prevent the view from diff --git a/lms/djangoapps/student_account/views.py b/lms/djangoapps/student_account/views.py index 33f681a6fd..ec8bdda333 100644 --- a/lms/djangoapps/student_account/views.py +++ b/lms/djangoapps/student_account/views.py @@ -110,6 +110,7 @@ def login_and_registration_form(request, initial_mode="login"): 'third_party_auth': _third_party_auth_context(request, redirect_to), 'third_party_auth_hint': third_party_auth_hint or '', 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), + 'support_link': configuration_helpers.get_value('SUPPORT_SITE_LINK', settings.SUPPORT_SITE_LINK), # Include form descriptions retrieved from the user API. # We could have the JS client make these requests directly, @@ -148,8 +149,7 @@ def password_change_request_handler(request): Returns: HttpResponse: 200 if the email was sent successfully - HttpResponse: 400 if there is no 'email' POST parameter, or if no user with - the provided email exists + HttpResponse: 400 if there is no 'email' POST parameter HttpResponse: 403 if the client has been rate limited HttpResponse: 405 if using an unsupported HTTP method @@ -158,6 +158,7 @@ def password_change_request_handler(request): POST /account/password """ + limiter = BadRequestRateLimiter() if limiter.is_rate_limit_exceeded(request): AUDIT_LOG.warning("Password reset rate limit exceeded") @@ -175,8 +176,6 @@ def password_change_request_handler(request): # Increment the rate limit counter limiter.tick_bad_request_counter(request) - return HttpResponseBadRequest(_("No user with the provided email address exists.")) - return HttpResponse(status=200) else: return HttpResponseBadRequest(_("No email address provided.")) diff --git a/lms/static/js/student_account/views/AccessView.js b/lms/static/js/student_account/views/AccessView.js index a2dce464b9..b18edaacc9 100644 --- a/lms/static/js/student_account/views/AccessView.js +++ b/lms/static/js/student_account/views/AccessView.js @@ -70,6 +70,7 @@ }; this.platformName = options.platform_name; + this.supportURL = options.support_link; // The login view listens for 'sync' events from the reset model this.resetModel = new PasswordResetModel({}, { @@ -120,7 +121,8 @@ model: model, resetModel: this.resetModel, thirdPartyAuth: this.thirdPartyAuth, - platformName: this.platformName + platformName: this.platformName, + supportURL: this.supportURL }); // Listen for 'password-help' event to toggle sub-views diff --git a/lms/static/js/student_account/views/LoginView.js b/lms/static/js/student_account/views/LoginView.js index 73f2695aa1..803e6ab5d7 100644 --- a/lms/static/js/student_account/views/LoginView.js +++ b/lms/static/js/student_account/views/LoginView.js @@ -1,13 +1,13 @@ ;(function (define) { 'use strict'; define([ - 'jquery', - 'underscore', - 'gettext', - 'js/student_account/views/FormView' + 'jquery', + 'underscore', + 'gettext', + 'edx-ui-toolkit/js/utils/html-utils', + 'js/student_account/views/FormView' ], - function($, _, gettext, FormView) { - + function($, _, gettext, HtmlUtils, FormView) { return FormView.extend({ el: '#login-form', tpl: '#login-tpl', @@ -29,6 +29,7 @@ this.errorMessage = data.thirdPartyAuth.errorMessage || ''; this.platformName = data.platformName; this.resetModel = data.resetModel; + this.supportURL = data.supportURL; this.listenTo( this.model, 'sync', this.saveSuccess ); this.listenTo( this.resetModel, 'sync', this.resetEmail ); @@ -36,6 +37,13 @@ render: function( html ) { var fields = html || ''; + this.successMessage = HtmlUtils.interpolateHtml( + // eslint-disable-next-line + gettext('We have sent an email message with password reset instructions to the email address you provided. If you do not receive this message, {anchorStart}contact technical support{anchorEnd}.'), { // jshint ignore:line + anchorStart: HtmlUtils.HTML(''), + anchorEnd: HtmlUtils.HTML('') + } + ); $(this.el).html(_.template(this.tpl)({ // We pass the context object to the template so that @@ -86,6 +94,16 @@ resetEmail: function() { this.element.hide( this.$errors ); + this.resetMessage = this.$resetSuccess.find('.message-copy'); + if (this.resetMessage.find('p').length === 0) { + this.resetMessage.append( + HtmlUtils.joinHtml( + HtmlUtils.HTML('

'), + this.successMessage, + HtmlUtils.HTML('

') + ).toString() + ); + } this.element.show( this.$resetSuccess ); }, diff --git a/lms/templates/student_account/login.underscore b/lms/templates/student_account/login.underscore index c28ead2c27..af7615f5d7 100644 --- a/lms/templates/student_account/login.underscore +++ b/lms/templates/student_account/login.underscore @@ -10,9 +10,6 @@