Merge pull request #15699 from edx/tasawer/learner-1494/add-password-complexity-on-reset-page
length complexity added on password reset page
This commit is contained in:
@@ -13,6 +13,7 @@ from django.contrib.auth.tokens import default_token_generator
|
|||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
|
from django.test.utils import override_settings
|
||||||
from django.utils.http import int_to_base36
|
from django.utils.http import int_to_base36
|
||||||
from edx_oauth2_provider.tests.factories import AccessTokenFactory, ClientFactory, RefreshTokenFactory
|
from edx_oauth2_provider.tests.factories import AccessTokenFactory, ClientFactory, RefreshTokenFactory
|
||||||
from mock import Mock, patch
|
from mock import Mock, patch
|
||||||
@@ -287,6 +288,35 @@ class ResetPasswordTests(EventTestMixin, CacheIsolationTestCase):
|
|||||||
self.assertEqual(resp.status_code, 200)
|
self.assertEqual(resp.status_code, 200)
|
||||||
self.assertFalse(User.objects.get(pk=self.user.pk).is_active)
|
self.assertFalse(User.objects.get(pk=self.user.pk).is_active)
|
||||||
|
|
||||||
|
@override_settings(PASSWORD_MIN_LENGTH=2)
|
||||||
|
@override_settings(PASSWORD_MAX_LENGTH=10)
|
||||||
|
@ddt.data(
|
||||||
|
{
|
||||||
|
'password': '1',
|
||||||
|
'error_message': 'Password: Invalid Length (must be 2 characters or more)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'password': '01234567891',
|
||||||
|
'error_message': 'Password: Invalid Length (must be 10 characters or fewer)'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def test_password_reset_with_invalid_length(self, password_dict):
|
||||||
|
"""Tests that if we provide password characters less then PASSWORD_MIN_LENGTH,
|
||||||
|
or more than PASSWORD_MAX_LENGTH, password reset will fail with error message.
|
||||||
|
"""
|
||||||
|
|
||||||
|
url = reverse(
|
||||||
|
'password_reset_confirm',
|
||||||
|
kwargs={'uidb36': self.uidb36, 'token': self.token}
|
||||||
|
)
|
||||||
|
request_params = {'new_password1': password_dict['password'], 'new_password2': password_dict['password']}
|
||||||
|
confirm_request = self.request_factory.post(url, data=request_params)
|
||||||
|
|
||||||
|
# Make a password reset request with minimum/maximum passwords characters.
|
||||||
|
response = password_reset_confirm_wrapper(confirm_request, self.uidb36, self.token)
|
||||||
|
|
||||||
|
self.assertEqual(response.context_data['err_msg'], password_dict['error_message'])
|
||||||
|
|
||||||
@patch('student.views.password_reset_confirm')
|
@patch('student.views.password_reset_confirm')
|
||||||
@patch("openedx.core.djangoapps.site_configuration.helpers.get_value", fake_get_value)
|
@patch("openedx.core.djangoapps.site_configuration.helpers.get_value", fake_get_value)
|
||||||
def test_reset_password_good_token_configuration_override(self, reset_confirm):
|
def test_reset_password_good_token_configuration_override(self, reset_confirm):
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ from util.bad_request_rate_limiter import BadRequestRateLimiter
|
|||||||
from util.db import outer_atomic
|
from util.db import outer_atomic
|
||||||
from util.json_request import JsonResponse
|
from util.json_request import JsonResponse
|
||||||
from util.milestones_helpers import get_pre_requisite_courses_not_completed
|
from util.milestones_helpers import get_pre_requisite_courses_not_completed
|
||||||
from util.password_policy_validators import validate_password_strength
|
from util.password_policy_validators import validate_password_length, validate_password_strength
|
||||||
from xmodule.modulestore.django import modulestore
|
from xmodule.modulestore.django import modulestore
|
||||||
|
|
||||||
log = logging.getLogger("edx.student")
|
log = logging.getLogger("edx.student")
|
||||||
@@ -2476,7 +2476,30 @@ def uidb36_to_uidb64(uidb36):
|
|||||||
return uidb64
|
return uidb64
|
||||||
|
|
||||||
|
|
||||||
def validate_password(user, password):
|
def validate_password(password):
|
||||||
|
"""
|
||||||
|
Validate password overall strength if ENFORCE_PASSWORD_POLICY is enable
|
||||||
|
otherwise only validate the length of the password.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
password: the user's proposed new password.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
err_msg: an error message if there's a violation of one of the password
|
||||||
|
checks. Otherwise, `None`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
if settings.FEATURES.get('ENFORCE_PASSWORD_POLICY', False):
|
||||||
|
validate_password_strength(password)
|
||||||
|
else:
|
||||||
|
validate_password_length(password)
|
||||||
|
|
||||||
|
except ValidationError as err:
|
||||||
|
return _('Password: ') + '; '.join(err.messages)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_password_security_policy(user, password):
|
||||||
"""
|
"""
|
||||||
Tie in password policy enforcement as an optional level of
|
Tie in password policy enforcement as an optional level of
|
||||||
security protection
|
security protection
|
||||||
@@ -2486,19 +2509,11 @@ def validate_password(user, password):
|
|||||||
password: the user's proposed new password.
|
password: the user's proposed new password.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
is_valid_password: a boolean indicating if the new password
|
|
||||||
passes the validation.
|
|
||||||
err_msg: an error message if there's a violation of one of the password
|
err_msg: an error message if there's a violation of one of the password
|
||||||
checks. Otherwise, `None`.
|
checks. Otherwise, `None`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
err_msg = None
|
err_msg = None
|
||||||
|
|
||||||
if settings.FEATURES.get('ENFORCE_PASSWORD_POLICY', False):
|
|
||||||
try:
|
|
||||||
validate_password_strength(password)
|
|
||||||
except ValidationError as err:
|
|
||||||
err_msg = _('Password: ') + '; '.join(err.messages)
|
|
||||||
|
|
||||||
# also, check the password reuse policy
|
# also, check the password reuse policy
|
||||||
if not PasswordHistory.is_allowable_password_reuse(user, password):
|
if not PasswordHistory.is_allowable_password_reuse(user, password):
|
||||||
if user.is_staff:
|
if user.is_staff:
|
||||||
@@ -2524,9 +2539,7 @@ def validate_password(user, password):
|
|||||||
num_days
|
num_days
|
||||||
).format(num=num_days)
|
).format(num=num_days)
|
||||||
|
|
||||||
is_password_valid = err_msg is None
|
return err_msg
|
||||||
|
|
||||||
return is_password_valid, err_msg
|
|
||||||
|
|
||||||
|
|
||||||
def password_reset_confirm_wrapper(request, uidb36=None, token=None):
|
def password_reset_confirm_wrapper(request, uidb36=None, token=None):
|
||||||
@@ -2552,16 +2565,24 @@ def password_reset_confirm_wrapper(request, uidb36=None, token=None):
|
|||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
password = request.POST['new_password1']
|
password = request.POST['new_password1']
|
||||||
is_password_valid, password_err_msg = validate_password(user, password)
|
valid_link = False
|
||||||
if not is_password_valid:
|
error_message = validate_password_security_policy(user, password)
|
||||||
|
if not error_message:
|
||||||
|
# if security is not violated, we need to validate password
|
||||||
|
error_message = validate_password(password)
|
||||||
|
if error_message:
|
||||||
|
# password reset link will be valid if there is no security violation
|
||||||
|
valid_link = True
|
||||||
|
|
||||||
|
if error_message:
|
||||||
# We have a password reset attempt which violates some security
|
# We have a password reset attempt which violates some security
|
||||||
# policy. Use the existing Django template to communicate that
|
# policy, or any other validation. Use the existing Django template to communicate that
|
||||||
# back to the user.
|
# back to the user.
|
||||||
context = {
|
context = {
|
||||||
'validlink': False,
|
'validlink': valid_link,
|
||||||
'form': None,
|
'form': None,
|
||||||
'title': _('Password reset unsuccessful'),
|
'title': _('Password reset unsuccessful'),
|
||||||
'err_msg': password_err_msg,
|
'err_msg': error_message,
|
||||||
}
|
}
|
||||||
context.update(platform_name)
|
context.update(platform_name)
|
||||||
return TemplateResponse(
|
return TemplateResponse(
|
||||||
|
|||||||
Reference in New Issue
Block a user