Update sign in email address for continued access

This commit is contained in:
Saleem Latif
2018-12-19 18:20:04 +05:00
parent f44c68fb52
commit eaf93d5978
16 changed files with 398 additions and 6 deletions

View File

@@ -110,6 +110,12 @@ def login_and_registration_form(request, initial_mode="login"):
} for message in messages.get_messages(request) if 'account-activation' in message.tags
]
account_recovery_messages = [
{
'message': message.message, 'tags': message.tags
} for message in messages.get_messages(request) if 'account-recovery' in message.tags
]
# Otherwise, render the combined login/registration page
context = {
'data': {
@@ -123,6 +129,7 @@ def login_and_registration_form(request, initial_mode="login"):
'PASSWORD_RESET_SUPPORT_LINK', settings.PASSWORD_RESET_SUPPORT_LINK
) or settings.SUPPORT_SITE_LINK,
'account_activation_messages': account_activation_messages,
'account_recovery_messages': account_recovery_messages,
# Include form descriptions retrieved from the user API.
# We could have the JS client make these requests directly,

View File

@@ -17,6 +17,7 @@ from django.contrib.messages.middleware import MessageMiddleware
from django.contrib.sessions.middleware import SessionMiddleware
from django.core import mail
from django.core.files.uploadedfile import SimpleUploadedFile
from django.http import Http404
from django.urls import reverse
from django.test import TestCase
from django.test.client import RequestFactory
@@ -28,6 +29,8 @@ from oauth2_provider.models import RefreshToken as dot_refresh_token
from provider.oauth2.models import AccessToken as dop_access_token
from provider.oauth2.models import RefreshToken as dop_refresh_token
from testfixtures import LogCapture
from waffle.models import Switch
from waffle.testutils import override_switch
from course_modes.models import CourseMode
from openedx.core.djangoapps.user_authn.views.login_form import login_and_registration_form
@@ -36,9 +39,11 @@ from openedx.core.djangoapps.site_configuration.tests.mixins import SiteMixin
from openedx.core.djangoapps.theming.tests.test_util import with_comprehensive_theme_context
from openedx.core.djangoapps.user_api.accounts.api import activate_account, create_account
from openedx.core.djangoapps.user_api.errors import UserAPIInternalError
from openedx.core.djangoapps.user_api.accounts.utils import ENABLE_SECONDARY_EMAIL_FEATURE_SWITCH
from openedx.core.djangolib.js_utils import dump_js_escaped_json
from openedx.core.djangolib.markup import HTML, Text
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase, skip_unless_lms
from student.tests.factories import AccountRecoveryFactory
from third_party_auth.tests.testutil import ThirdPartyAuthTestMixin, simulate_running_pipeline
from util.testing import UrlResetMixin
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
@@ -76,6 +81,12 @@ class UserAccountUpdateTest(CacheIsolationTestCase, UrlResetMixin):
activation_key = create_account(self.USERNAME, self.OLD_PASSWORD, self.OLD_EMAIL)
activate_account(activation_key)
self.account_recovery = AccountRecoveryFactory.create(user=User.objects.get(email=self.OLD_EMAIL))
self.enable_account_recovery_switch = Switch.objects.create(
name=ENABLE_SECONDARY_EMAIL_FEATURE_SWITCH,
active=True
)
# Login
result = self.client.login(username=self.USERNAME, password=self.OLD_PASSWORD)
self.assertTrue(result)
@@ -250,6 +261,63 @@ class UserAccountUpdateTest(CacheIsolationTestCase, UrlResetMixin):
response = getattr(self.client, method)(url)
self.assertEqual(response.status_code, 405)
@override_switch(ENABLE_SECONDARY_EMAIL_FEATURE_SWITCH, active=False)
def test_404_if_account_recovery_not_enabled(self):
with mock.patch('openedx.core.djangoapps.user_api.accounts.api.request_account_recovery',
side_effect=UserAPIInternalError):
self._recover_account()
self.assertRaises(Http404)
def test_account_recovery_failure(self):
with mock.patch('openedx.core.djangoapps.user_api.accounts.api.request_account_recovery',
side_effect=UserAPIInternalError):
self._recover_account()
self.assertRaises(UserAPIInternalError)
@override_settings(FEATURES=FEATURES_WITH_FAILED_PASSWORD_RESET_EMAIL)
def test_account_recovery_failure_email(self):
"""Test that a password reset failure email notification is sent, when enabled."""
# Log the user out
self.client.logout()
with LogCapture(LOGGER_NAME, level=logging.INFO) as logger:
bad_email = 'doesnotexist@example.com'
response = self._recover_account(email=bad_email)
self.assertEqual(response.status_code, 200)
logger.check(
(
LOGGER_NAME,
"WARNING", "Account recovery attempt via invalid secondary email '{email}'.".format(
email=bad_email
)
)
)
def test_password_change_rate_limited_during_account_recovery(self):
# Log out the user created during test setup, to prevent the view from
# selecting the logged-in user's email address over the email provided
# in the POST data
self.client.logout()
# Make many consecutive bad requests in an attempt to trigger the rate limiter
for __ in xrange(self.INVALID_ATTEMPTS):
self._recover_account(email=self.NEW_EMAIL)
response = self._recover_account(email=self.NEW_EMAIL)
self.assertEqual(response.status_code, 403)
@ddt.data(
('post', 'account_recovery', []),
)
@ddt.unpack
def test_require_http_method_during_account_recovery(self, correct_method, url_name, args):
wrong_methods = {'get', 'put', 'post', 'head', 'options', 'delete'} - {correct_method}
url = reverse(url_name, args=args)
for method in wrong_methods:
response = getattr(self.client, method)(url)
self.assertEqual(response.status_code, 405)
def _change_password(self, email=None):
"""Request to change the user's password. """
data = {}
@@ -259,6 +327,15 @@ class UserAccountUpdateTest(CacheIsolationTestCase, UrlResetMixin):
return self.client.post(path=reverse('password_change_request'), data=data)
def _recover_account(self, email=None):
"""Request to create the user's password. """
data = {}
if email:
data['email'] = email
return self.client.post(path=reverse('account_recovery'), data=data)
def _create_dop_tokens(self, user=None):
"""Create dop access token for given user if user provided else for default user."""
if not user: