converted logistration password reset views from function to DRF APIVIEW (#25582)

* converted logistration password reset views from function to DRF APIVIEW
VAN-31
This commit is contained in:
jawad khan
2020-11-23 15:35:11 +05:00
committed by GitHub
parent 56e2d32a83
commit c46aebd6d9
3 changed files with 103 additions and 98 deletions

View File

@@ -65,13 +65,13 @@ urlpatterns = [
url(r'^account/password$', password_reset.password_change_request_handler, name='password_change_request'),
# logistration MFE flow
url(r'^user_api/v1/account/password_reset/token/validate/$', password_reset.password_reset_token_validate,
url(r'^user_api/v1/account/password_reset/token/validate/$', password_reset.PasswordResetTokenValidation.as_view(),
name="user_api_password_reset_token_validate"),
# logistration MFE reset flow
url(
r'^password/reset/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$',
password_reset.password_reset_logistration,
password_reset.LogistrationPasswordResetView.as_view(),
name='logistration_password_reset',
),
]

View File

@@ -25,6 +25,7 @@ from edx_ace import ace
from edx_ace.recipient import Recipient
from eventtracking import tracker
from ratelimit.decorators import ratelimit
from rest_framework.response import Response
from rest_framework.views import APIView
from common.djangoapps.edxmako.shortcuts import render_to_string
@@ -641,107 +642,106 @@ def password_change_request_handler(request):
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})
class PasswordResetTokenValidation(APIView):
user = User.objects.get(id=uid_int)
if UserRetirementRequest.has_user_requested_retirement(user):
return JsonResponse({'is_valid': is_valid})
def post(self, request):
""" HTTP end-point to validate password reset token. """
is_valid = False
token = request.data.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 Response({'is_valid': is_valid})
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")
user = User.objects.get(id=uid_int)
if UserRetirementRequest.has_user_requested_retirement(user):
return Response({'is_valid': is_valid})
return JsonResponse({'is_valid': is_valid})
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 Response({'is_valid': is_valid})
def _check_token_has_required_values(uidb36, token):
"""
Helper function to test that token
string passed has the required kwargs needed
to process token validation.
"""
class LogistrationPasswordResetView(APIView):
if not uidb36 or not token:
return False, None
try:
uid_int = base36_to_int(uidb36)
except ValueError:
return False, None
return True, uid_int
def post(self, request, **kwargs):
""" Reset learner password using passed token and new credentials """
reset_status = False
uidb36 = kwargs.get('uidb36')
token = kwargs.get('token')
@require_POST
@ensure_csrf_cookie
def password_reset_logistration(request, **kwargs):
"""Reset learner password using passed token and new credentials"""
has_required_values, uid_int = self._check_token_has_required_values(uidb36, token)
if not has_required_values:
AUDIT_LOG.exception("Invalid password reset confirm token")
return Response({'reset_status': reset_status})
reset_status = False
uidb36 = kwargs.get('uidb36')
token = kwargs.get('token')
request.data._mutable = True
request.data['new_password1'] = normalize_password(request.data['new_password1'])
request.data['new_password2'] = normalize_password(request.data['new_password2'])
has_required_values, uid_int = _check_token_has_required_values(uidb36, token)
if not has_required_values:
AUDIT_LOG.exception("Invalid password reset confirm token")
return JsonResponse({'reset_status': reset_status})
password = request.data['new_password1']
try:
user = User.objects.get(id=uid_int)
if not default_token_generator.check_token(user, token):
AUDIT_LOG.exception("Token validation failed")
return Response({'reset_status': reset_status})
request.POST = request.POST.copy()
request.POST['new_password1'] = normalize_password(request.POST['new_password1'])
request.POST['new_password2'] = normalize_password(request.POST['new_password2'])
validate_password(password, user=user)
form = SetPasswordForm(user, request.data)
if form.is_valid():
form.save()
reset_status = True
password = request.POST['new_password1']
try:
user = User.objects.get(id=uid_int)
if not default_token_generator.check_token(user, token):
AUDIT_LOG.exception("Token validation failed")
return JsonResponse({'reset_status': reset_status})
if 'is_account_recovery' in request.GET:
try:
old_primary_email = user.email
user.email = user.account_recovery.secondary_email
user.account_recovery.delete()
# emit an event that the user changed their secondary email to the primary email
tracker.emit(
SETTING_CHANGE_INITIATED,
{
"setting": "email",
"old": old_primary_email,
"new": user.email,
"user_id": user.id,
}
)
user.save()
send_password_reset_success_email(user, request)
except ObjectDoesNotExist:
err = 'Account recovery process initiated without AccountRecovery instance for user {username}'
log.error(err.format(username=user.username))
except ValidationError as err:
AUDIT_LOG.exception("Password validation failed")
error_status = {
'reset_status': reset_status,
'err_msg': ' '.join(err.messages)
}
return Response(error_status)
except Exception: # pylint: disable=broad-except
AUDIT_LOG.exception("Setting new password failed")
validate_password(password, user=user)
form = SetPasswordForm(user, request.POST)
if form.is_valid():
form.save()
reset_status = True
return Response({'reset_status': reset_status})
if 'is_account_recovery' in request.GET:
try:
old_primary_email = user.email
user.email = user.account_recovery.secondary_email
user.account_recovery.delete()
# emit an event that the user changed their secondary email to the primary email
tracker.emit(
SETTING_CHANGE_INITIATED,
{
"setting": "email",
"old": old_primary_email,
"new": user.email,
"user_id": user.id,
}
)
user.save()
send_password_reset_success_email(user, request)
except ObjectDoesNotExist:
log.error('Account recovery process initiated without AccountRecovery instance for user {username}'
.format(username=user.username))
except ValidationError as err:
AUDIT_LOG.exception("Password validation failed")
error_status = {
'reset_status': reset_status,
'err_msg': ' '.join(err.messages)
}
return JsonResponse(error_status)
except Exception: # pylint: disable=broad-except
AUDIT_LOG.exception("Setting new password failed")
def _check_token_has_required_values(self, uidb36, token):
"""
Helper function to test that token
string passed has the required kwargs needed
to process token validation.
"""
return JsonResponse({'reset_status': reset_status})
if not uidb36 or not token:
return False, None
try:
uid_int = base36_to_int(uidb36)
except ValueError:
return False, None
return True, uid_int

View File

@@ -34,7 +34,7 @@ from openedx.core.djangoapps.user_api.models import UserRetirementRequest
from openedx.core.djangoapps.user_api.tests.test_views import UserAPITestCase
from openedx.core.djangoapps.user_api.accounts import EMAIL_MAX_LENGTH, EMAIL_MIN_LENGTH
from openedx.core.djangoapps.user_authn.views.password_reset import (
SETTING_CHANGE_INITIATED, password_reset, password_reset_logistration,
SETTING_CHANGE_INITIATED, password_reset, LogistrationPasswordResetView,
PasswordResetConfirmWrapper)
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
from common.djangoapps.student.tests.factories import TEST_PASSWORD, UserFactory
@@ -754,7 +754,7 @@ class ResetPasswordAPITests(EventTestMixin, CacheIsolationTestCase):
"logistration_password_reset",
kwargs={"uidb36": uidb36, "token": token}
) + query_param,
request_param
request_param, format='json'
)
return post_request
@@ -771,7 +771,8 @@ class ResetPasswordAPITests(EventTestMixin, CacheIsolationTestCase):
post_request = self.create_reset_request(uidb36, token, False)
post_request.user = AnonymousUser()
json_response = password_reset_logistration(post_request, uidb36=uidb36, token=token)
reset_view = LogistrationPasswordResetView.as_view()
json_response = reset_view(post_request, uidb36=uidb36, token=token).render()
json_response = json.loads(json_response.content.decode('utf-8'))
self.assertEqual(json_response.get('reset_status'), status)
@@ -784,7 +785,8 @@ class ResetPasswordAPITests(EventTestMixin, CacheIsolationTestCase):
post_request = self.create_reset_request(self.uidb36, self.token, False)
post_request.user = AnonymousUser()
self.assertRaises(Exception, password_reset_logistration(post_request, uidb36=uidb36, token=token))
reset_view = LogistrationPasswordResetView.as_view()
self.assertRaises(Exception, reset_view(post_request, uidb36=uidb36, token=token))
def test_password_mismatch_in_reset_request(self):
"""
@@ -792,7 +794,8 @@ class ResetPasswordAPITests(EventTestMixin, CacheIsolationTestCase):
"""
post_request = self.create_reset_request(self.uidb36, self.token, False, 'new_password2')
post_request.user = AnonymousUser()
json_response = password_reset_logistration(post_request, uidb36=self.uidb36, token=self.token)
reset_view = LogistrationPasswordResetView.as_view()
json_response = reset_view(post_request, uidb36=self.uidb36, token=self.token).render()
json_response = json.loads(json_response.content.decode('utf-8'))
self.assertFalse(json_response.get('reset_status'))
@@ -803,7 +806,8 @@ class ResetPasswordAPITests(EventTestMixin, CacheIsolationTestCase):
"""
post_request = self.create_reset_request(self.uidb36, self.token, True)
post_request.user = AnonymousUser()
password_reset_logistration(post_request, uidb36=self.uidb36, token=self.token)
reset_view = LogistrationPasswordResetView.as_view()
reset_view(post_request, uidb36=self.uidb36, token=self.token)
updated_user = User.objects.get(id=self.user.id)
self.assertEqual(updated_user.email, self.secondary_email)
@@ -824,7 +828,8 @@ class ResetPasswordAPITests(EventTestMixin, CacheIsolationTestCase):
post_request = self.create_reset_request(self.uidb36, self.token, True)
post_request.user = AnonymousUser()
post_request.site = Mock(domain='example.com')
password_reset_logistration(post_request, uidb36=self.uidb36, token=self.token)
reset_view = LogistrationPasswordResetView.as_view()
reset_view(post_request, uidb36=self.uidb36, token=self.token)
updated_user = User.objects.get(id=self.user.id)
from_email = configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL)