diff --git a/common/djangoapps/student/forms.py b/common/djangoapps/student/forms.py index 87a5042b59..8ca0a6f4c9 100644 --- a/common/djangoapps/student/forms.py +++ b/common/djangoapps/student/forms.py @@ -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): diff --git a/openedx/core/djangoapps/ace_common/apps.py b/openedx/core/djangoapps/ace_common/apps.py index 3c671cc070..9a4c1f4e3f 100644 --- a/openedx/core/djangoapps/ace_common/apps.py +++ b/openedx/core/djangoapps/ace_common/apps.py @@ -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'}, } } } diff --git a/openedx/core/djangoapps/ace_common/template_context.py b/openedx/core/djangoapps/ace_common/template_context.py index 8a255e9df5..b2254ba2ae 100644 --- a/openedx/core/djangoapps/ace_common/template_context.py +++ b/openedx/core/djangoapps/ace_common/template_context.py @@ -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', diff --git a/openedx/core/djangoapps/password_policy/compliance.py b/openedx/core/djangoapps/password_policy/compliance.py index 55cf6caa0f..2c20364f11 100644 --- a/openedx/core/djangoapps/password_policy/compliance.py +++ b/openedx/core/djangoapps/password_policy/compliance.py @@ -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) diff --git a/openedx/core/djangoapps/user_authn/views/login.py b/openedx/core/djangoapps/user_authn/views/login.py index c19d620dac..be7a339a2c 100644 --- a/openedx/core/djangoapps/user_authn/views/login.py +++ b/openedx/core/djangoapps/user_authn/views/login.py @@ -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) diff --git a/openedx/core/djangoapps/user_authn/views/tests/test_login.py b/openedx/core/djangoapps/user_authn/views/tests/test_login.py index 6c6f8e49b6..6f5eae5385 100644 --- a/openedx/core/djangoapps/user_authn/views/tests/test_login.py +++ b/openedx/core/djangoapps/user_authn/views/tests/test_login.py @@ -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):