diff --git a/openedx/core/djangoapps/user_authn/urls_common.py b/openedx/core/djangoapps/user_authn/urls_common.py index 20d0982eec8e..826c937916ba 100644 --- a/openedx/core/djangoapps/user_authn/urls_common.py +++ b/openedx/core/djangoapps/user_authn/urls_common.py @@ -63,6 +63,8 @@ name='password_reset_confirm', ), url(r'^account/password$', password_reset.password_change_request_handler, name='password_change_request'), + url(r'^user_api/v1/account/password_reset/token/validate/$', password_reset.password_reset_token_validate, + name="user_api_password_reset_token_validate"), ] diff --git a/openedx/core/djangoapps/user_authn/views/password_reset.py b/openedx/core/djangoapps/user_authn/views/password_reset.py index 17d5276b8aee..83815243b999 100644 --- a/openedx/core/djangoapps/user_authn/views/password_reset.py +++ b/openedx/core/djangoapps/user_authn/views/password_reset.py @@ -598,3 +598,26 @@ def password_change_request_handler(request): return HttpResponse(status=200) else: return HttpResponseBadRequest(_("No email address provided.")) + + +@require_POST +@ensure_csrf_cookie +def password_reset_token_validate(request): + """HTTP end-point to validate password reset token. """ + is_valid = False + token = request.POST.get('token') + try: + token = token.split('-', 1) + uid_int = base36_to_int(token[0]) + if request.user.is_authenticated and request.user.id != uid_int: + return JsonResponse({'is_valid': is_valid}) + + user = User.objects.get(id=uid_int) + is_valid = default_token_generator.check_token(user, token[1]) + if is_valid and not user.is_active: + user.is_active = True + user.save() + except Exception: # pylint: disable=broad-except + AUDIT_LOG.exception("Invalid password reset confirm token") + + return JsonResponse({'is_valid': is_valid}) diff --git a/openedx/core/djangoapps/user_authn/views/tests/test_reset_password.py b/openedx/core/djangoapps/user_authn/views/tests/test_reset_password.py index 3776028e7cca..7ac768e9fb6c 100644 --- a/openedx/core/djangoapps/user_authn/views/tests/test_reset_password.py +++ b/openedx/core/djangoapps/user_authn/views/tests/test_reset_password.py @@ -39,7 +39,7 @@ SETTING_CHANGE_INITIATED, password_reset, PasswordResetConfirmWrapper) from openedx.core.djangolib.testing.utils import CacheIsolationTestCase -from student.tests.factories import UserFactory +from student.tests.factories import TEST_PASSWORD, UserFactory from student.tests.test_configuration_overrides import fake_get_value from student.tests.test_email import mock_render_to_string @@ -629,3 +629,52 @@ def test_password_reset_form(self): "supplementalLink": "", } ]) + + +@skip_unless_lms +class PasswordResetTokenValidateViewTest(UserAPITestCase): + """Tests of the user API's password reset endpoint. """ + + def setUp(self): + super(PasswordResetTokenValidateViewTest, self).setUp() + self.user = UserFactory.create() + self.user.is_active = False + self.user.save() + self.token = '{uidb36}-{token}'.format( + uidb36=int_to_base36(self.user.id), + token=default_token_generator.make_token(self.user) + ) + self.url = reverse("user_api_password_reset_token_validate") + + def test_reset_password_valid_token(self): + """ + Verify that API valid token response and activate user if not active. + """ + response = self.client.post(self.url, data={'token': self.token}) + json_response = json.loads(response.content.decode('utf-8')) + self.assertTrue(json_response.get('is_valid')) + + self.user = User.objects.get(pk=self.user.pk) + self.assertTrue(self.user.is_active) + + def test_reset_password_invalid_token(self): + """ + Verify that API invalid token response if token is invalid. + """ + response = self.client.post(self.url, data={'token': 'invalid-token'}) + json_response = json.loads(response.content.decode('utf-8')) + self.assertFalse(json_response.get('is_valid')) + + def test_reset_password_token_with_other_user(self): + """ + Verify that API returns invalid token response if the user different. + """ + different_user = UserFactory.create(password=TEST_PASSWORD) + self.client.login(username=different_user.username, password=TEST_PASSWORD) + + response = self.client.post(self.url, {'token': self.token}) + json_response = json.loads(response.content.decode('utf-8')) + self.assertFalse(json_response.get('is_valid')) + + self.user = User.objects.get(pk=self.user.pk) + self.assertFalse(self.user.is_active)