Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
E
edx-platform-release
Manage
Activity
Members
Labels
Plan
Issues
0
Issue boards
Milestones
Wiki
Code
Merge requests
1
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Deploy
Releases
Package Registry
Model registry
Operate
Terraform modules
Monitor
Incidents
Service Desk
Analyze
Value stream analytics
Contributor analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Hsin-Yu Chien
edx-platform-release
Commits
30bf95b0
Commit
30bf95b0
authored
3 years ago
by
Shafqat Farhan
Browse files
Options
Downloads
Patches
Plain Diff
VAN-437 - Unlocking the learners upon successful password reset
parent
fe37651d
No related branches found
Branches containing commit
No related tags found
Tags containing commit
No related merge requests found
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
openedx/core/djangoapps/user_authn/views/password_reset.py
+10
-1
10 additions, 1 deletion
openedx/core/djangoapps/user_authn/views/password_reset.py
openedx/core/djangoapps/user_authn/views/tests/test_reset_password.py
+111
-1
111 additions, 1 deletion
.../djangoapps/user_authn/views/tests/test_reset_password.py
with
121 additions
and
2 deletions
openedx/core/djangoapps/user_authn/views/password_reset.py
+
10
−
1
View file @
30bf95b0
...
...
@@ -43,7 +43,7 @@ from openedx.core.djangoapps.user_api.preferences.api import get_user_preference
from
openedx.core.djangoapps.user_authn.message_types
import
PasswordReset
,
PasswordResetSuccess
from
openedx.core.djangolib.markup
import
HTML
from
common.djangoapps.student.forms
import
send_account_recovery_email_for_user
from
common.djangoapps.student.models
import
AccountRecovery
from
common.djangoapps.student.models
import
AccountRecovery
,
LoginFailures
from
common.djangoapps.util.json_request
import
JsonResponse
from
common.djangoapps.util.password_policy_validators
import
normalize_password
,
validate_password
...
...
@@ -505,6 +505,10 @@ class PasswordResetConfirmWrapper(PasswordResetConfirmView):
if
password_reset_successful
and
is_account_recovery
:
self
.
_handle_password_creation
(
request
,
updated_user
)
# Handles clearing the failed login counter upon password reset.
if
LoginFailures
.
is_feature_enabled
():
LoginFailures
.
clear_lockout_counter
(
updated_user
)
send_password_reset_success_email
(
updated_user
,
request
)
return
response
...
...
@@ -763,6 +767,11 @@ class LogistrationPasswordResetView(APIView): # lint-amnesty, pylint: disable=m
except
ObjectDoesNotExist
:
err
=
'
Account recovery process initiated without AccountRecovery instance for user {username}
'
log
.
error
(
err
.
format
(
username
=
user
.
username
))
# Handles clearing the failed login counter upon password reset.
if
LoginFailures
.
is_feature_enabled
():
LoginFailures
.
clear_lockout_counter
(
user
)
send_password_reset_success_email
(
user
,
request
)
except
ValidationError
as
err
:
AUDIT_LOG
.
exception
(
"
Password validation failed
"
)
...
...
This diff is collapsed.
Click to expand it.
openedx/core/djangoapps/user_authn/views/tests/test_reset_password.py
+
111
−
1
View file @
30bf95b0
...
...
@@ -41,7 +41,7 @@ from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
from
common.djangoapps.student.tests.factories
import
TEST_PASSWORD
,
UserFactory
from
common.djangoapps.student.tests.test_configuration_overrides
import
fake_get_value
from
common.djangoapps.student.tests.test_email
import
mock_render_to_string
from
common.djangoapps.student.models
import
AccountRecovery
from
common.djangoapps.student.models
import
AccountRecovery
,
LoginFailures
from
common.djangoapps.util.password_policy_validators
import
create_validator_config
from
common.djangoapps.util.testing
import
EventTestMixin
...
...
@@ -611,6 +611,66 @@ class ResetPasswordTests(EventTestMixin, CacheIsolationTestCase):
self
.
assertRaises
(
Http404
,
PasswordResetConfirmWrapper
.
as_view
(),
reset_request
,
uidb36
=
self
.
uidb36
,
token
=
self
.
token
)
@override_settings
(
FEATURES
=
{
'
ENABLE_MAX_FAILED_LOGIN_ATTEMPTS
'
:
True
},
MAX_FAILED_LOGIN_ATTEMPTS_ALLOWED
=
1
)
def
test_password_reset_with_login_failures_feature_enabled
(
self
):
"""
Tests that user
'
s login failures lockout counter is reset upon successful password reset.
"""
# Adding an entry in LoginFailures to verify the password reset endpoint
# reset the user's login failures lockout counter.
LoginFailures
.
increment_lockout_counter
(
self
.
user
)
request_params
=
{
'
new_password1
'
:
'
password1
'
,
'
new_password2
'
:
'
password1
'
}
confirm_request
=
self
.
request_factory
.
post
(
self
.
password_reset_confirm_url
,
data
=
request_params
)
self
.
setup_request_session_with_token
(
confirm_request
)
confirm_request
.
user
=
self
.
user
# Make a password reset request.
resp
=
PasswordResetConfirmWrapper
.
as_view
()(
confirm_request
,
uidb36
=
self
.
uidb36
,
token
=
self
.
token
)
# Verify the response status code is 302 with password reset success and also verify that
# the user's login failures lockout count is reset.
assert
resp
.
status_code
==
302
assert
not
LoginFailures
.
is_user_locked_out
(
confirm_request
.
user
)
# Verify that the user's login failures lockout counter is not reset upon
# password reset failure.
LoginFailures
.
increment_lockout_counter
(
self
.
user
)
request_params
=
{
'
new_password1
'
:
'
password1
'
,
'
new_password2
'
:
'
password2
'
}
confirm_request
=
self
.
request_factory
.
post
(
self
.
password_reset_confirm_url
,
data
=
request_params
)
self
.
setup_request_session_with_token
(
confirm_request
)
confirm_request
.
user
=
self
.
user
# Make a password reset request with mismatching passwords.
resp
=
PasswordResetConfirmWrapper
.
as_view
()(
confirm_request
,
uidb36
=
self
.
uidb36
,
token
=
self
.
token
)
assert
resp
.
status_code
==
200
assert
LoginFailures
.
is_user_locked_out
(
self
.
user
)
@override_settings
(
MAX_FAILED_LOGIN_ATTEMPTS_ALLOWED
=
1
)
def
test_password_reset_with_login_failures_feature_disabled
(
self
):
"""
Tests that user
'
s login failures lockout counter is not reset upon successful password reset.
"""
# Adding an entry in LoginFailures to verify the password reset endpoint
# does not reset the user's login failures lockout counter.
LoginFailures
.
increment_lockout_counter
(
self
.
user
)
request_params
=
{
'
new_password1
'
:
'
password1
'
,
'
new_password2
'
:
'
password1
'
}
confirm_request
=
self
.
request_factory
.
post
(
self
.
password_reset_confirm_url
,
data
=
request_params
)
self
.
setup_request_session_with_token
(
confirm_request
)
confirm_request
.
user
=
self
.
user
# Make a password reset request.
resp
=
PasswordResetConfirmWrapper
.
as_view
()(
confirm_request
,
uidb36
=
self
.
uidb36
,
token
=
self
.
token
)
# Verify that the user's login failures lockout count is not reset.
assert
resp
.
status_code
==
302
assert
not
LoginFailures
.
is_feature_enabled
()
assert
LoginFailures
.
is_user_locked_out
(
confirm_request
.
user
)
@ddt.ddt
@skip_unless_lms
...
...
@@ -872,3 +932,53 @@ class ResetPasswordAPITests(EventTestMixin, CacheIsolationTestCase):
assert
response
.
status_code
!=
429
response
=
self
.
client
.
post
(
path
,
request_param
)
assert
response
.
status_code
==
429
@override_settings
(
FEATURES
=
{
'
ENABLE_MAX_FAILED_LOGIN_ATTEMPTS
'
:
True
},
MAX_FAILED_LOGIN_ATTEMPTS_ALLOWED
=
1
)
def
test_password_reset_request_with_login_failures_feature_enabled
(
self
):
"""
Tests that user
'
s login failures lockout counter is reset upon successful password reset.
"""
# Adding an entry in LoginFailures to verify the password reset endpoint
# reset the user's login failures lockout counter.
LoginFailures
.
increment_lockout_counter
(
self
.
user
)
post_request
=
self
.
create_reset_request
(
self
.
uidb36
,
self
.
token
,
False
)
post_request
.
user
=
AnonymousUser
()
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
'
))
# Verify that the user's login failures lockout count is reset.
assert
json_response
.
get
(
'
reset_status
'
)
assert
not
LoginFailures
.
is_user_locked_out
(
self
.
user
)
# Verify that the user's login failures lockout counter is not reset upon
# password reset failure.
LoginFailures
.
increment_lockout_counter
(
self
.
user
)
post_request
=
self
.
create_reset_request
(
self
.
uidb36
,
self
.
token
,
False
,
'
new_password2
'
)
post_request
.
user
=
AnonymousUser
()
reset_view
=
LogistrationPasswordResetView
.
as_view
()
reset_view
(
post_request
,
uidb36
=
self
.
uidb36
,
token
=
self
.
token
).
render
()
assert
LoginFailures
.
is_user_locked_out
(
self
.
user
)
@override_settings
(
MAX_FAILED_LOGIN_ATTEMPTS_ALLOWED
=
1
)
def
test_password_reset_request_with_login_failures_feature_disabled
(
self
):
"""
Tests that user
'
s login failures lockout counter is not reset upon successful password reset.
"""
# Adding an entry in LoginFailures to verify the password reset endpoint
# does not reset the user's login failures lockout counter.
LoginFailures
.
increment_lockout_counter
(
self
.
user
)
post_request
=
self
.
create_reset_request
(
self
.
uidb36
,
self
.
token
,
False
)
post_request
.
user
=
AnonymousUser
()
reset_view
=
LogistrationPasswordResetView
.
as_view
()
reset_view
(
post_request
,
uidb36
=
self
.
uidb36
,
token
=
self
.
token
).
render
()
# Verify that the user's login failures lockout count is not reset.
assert
not
LoginFailures
.
is_feature_enabled
()
assert
LoginFailures
.
is_user_locked_out
(
self
.
user
)
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment