Added in tests for the new password validation. Fixed old tests that
relied on the old configuration values and old way of validating passwords. Also improved registration page by always showing error messages rather than hiding them on leaving the field.
This commit is contained in:
@@ -16,9 +16,9 @@ from mock import patch
|
||||
from openedx.core.djangoapps.external_auth.models import ExternalAuthMap
|
||||
from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory
|
||||
from openedx.core.djangoapps.user_authn.views.deprecated import create_account
|
||||
from util.password_policy_validators import create_validator_config
|
||||
|
||||
|
||||
@patch.dict("django.conf.settings.FEATURES", {'ENFORCE_PASSWORD_POLICY': True})
|
||||
class TestPasswordPolicy(TestCase):
|
||||
"""
|
||||
Go through some password policy tests to make sure things are properly working
|
||||
@@ -35,7 +35,9 @@ class TestPasswordPolicy(TestCase):
|
||||
'honor_code': 'true',
|
||||
}
|
||||
|
||||
@override_settings(PASSWORD_MIN_LENGTH=6)
|
||||
@override_settings(AUTH_PASSWORD_VALIDATORS=[
|
||||
create_validator_config('util.password_policy_validators.MinimumLengthValidator', {'min_length': 6})
|
||||
])
|
||||
def test_password_length_too_short(self):
|
||||
self.url_params['password'] = 'aaa'
|
||||
response = self.client.post(self.url, self.url_params)
|
||||
@@ -43,10 +45,12 @@ class TestPasswordPolicy(TestCase):
|
||||
obj = json.loads(response.content)
|
||||
self.assertEqual(
|
||||
obj['value'],
|
||||
"Enter a password with at least 6 characters.",
|
||||
"This password is too short. It must contain at least 6 characters.",
|
||||
)
|
||||
|
||||
@override_settings(PASSWORD_MIN_LENGTH=6)
|
||||
@override_settings(AUTH_PASSWORD_VALIDATORS=[
|
||||
create_validator_config('util.password_policy_validators.MinimumLengthValidator', {'min_length': 6})
|
||||
])
|
||||
def test_password_length_long_enough(self):
|
||||
self.url_params['password'] = 'ThisIsALongerPassword'
|
||||
response = self.client.post(self.url, self.url_params)
|
||||
@@ -54,7 +58,9 @@ class TestPasswordPolicy(TestCase):
|
||||
obj = json.loads(response.content)
|
||||
self.assertTrue(obj['success'])
|
||||
|
||||
@override_settings(PASSWORD_MAX_LENGTH=12)
|
||||
@override_settings(AUTH_PASSWORD_VALIDATORS=[
|
||||
create_validator_config('util.password_policy_validators.MaximumLengthValidator', {'max_length': 12})
|
||||
])
|
||||
def test_password_length_too_long(self):
|
||||
self.url_params['password'] = 'ThisPasswordIsWayTooLong'
|
||||
response = self.client.post(self.url, self.url_params)
|
||||
@@ -62,10 +68,12 @@ class TestPasswordPolicy(TestCase):
|
||||
obj = json.loads(response.content)
|
||||
self.assertEqual(
|
||||
obj['value'],
|
||||
"Enter a password with at most 12 characters.",
|
||||
"This password is too long. It must contain no more than 12 characters.",
|
||||
)
|
||||
|
||||
@patch.dict("django.conf.settings.PASSWORD_COMPLEXITY", {'UPPER': 3})
|
||||
@override_settings(AUTH_PASSWORD_VALIDATORS=[
|
||||
create_validator_config('util.password_policy_validators.UppercaseValidator', {'min_upper': 3})
|
||||
])
|
||||
def test_password_not_enough_uppercase(self):
|
||||
self.url_params['password'] = 'thisshouldfail'
|
||||
response = self.client.post(self.url, self.url_params)
|
||||
@@ -73,10 +81,12 @@ class TestPasswordPolicy(TestCase):
|
||||
obj = json.loads(response.content)
|
||||
self.assertEqual(
|
||||
obj['value'],
|
||||
"Enter a password with at least 3 uppercase letters.",
|
||||
"This password must contain at least 3 uppercase letters.",
|
||||
)
|
||||
|
||||
@patch.dict("django.conf.settings.PASSWORD_COMPLEXITY", {'UPPER': 3})
|
||||
@override_settings(AUTH_PASSWORD_VALIDATORS=[
|
||||
create_validator_config('util.password_policy_validators.UppercaseValidator', {'min_upper': 3})
|
||||
])
|
||||
def test_password_enough_uppercase(self):
|
||||
self.url_params['password'] = 'ThisShouldPass'
|
||||
response = self.client.post(self.url, self.url_params)
|
||||
@@ -84,7 +94,9 @@ class TestPasswordPolicy(TestCase):
|
||||
obj = json.loads(response.content)
|
||||
self.assertTrue(obj['success'])
|
||||
|
||||
@patch.dict("django.conf.settings.PASSWORD_COMPLEXITY", {'LOWER': 3})
|
||||
@override_settings(AUTH_PASSWORD_VALIDATORS=[
|
||||
create_validator_config('util.password_policy_validators.LowercaseValidator', {'min_lower': 3})
|
||||
])
|
||||
def test_password_not_enough_lowercase(self):
|
||||
self.url_params['password'] = 'THISSHOULDFAIL'
|
||||
response = self.client.post(self.url, self.url_params)
|
||||
@@ -92,10 +104,12 @@ class TestPasswordPolicy(TestCase):
|
||||
obj = json.loads(response.content)
|
||||
self.assertEqual(
|
||||
obj['value'],
|
||||
"Enter a password with at least 3 lowercase letters.",
|
||||
"This password must contain at least 3 lowercase letters.",
|
||||
)
|
||||
|
||||
@patch.dict("django.conf.settings.PASSWORD_COMPLEXITY", {'LOWER': 3})
|
||||
@override_settings(AUTH_PASSWORD_VALIDATORS=[
|
||||
create_validator_config('util.password_policy_validators.LowercaseValidator', {'min_lower': 3})
|
||||
])
|
||||
def test_password_enough_lowercase(self):
|
||||
self.url_params['password'] = 'ThisShouldPass'
|
||||
response = self.client.post(self.url, self.url_params)
|
||||
@@ -103,26 +117,9 @@ class TestPasswordPolicy(TestCase):
|
||||
obj = json.loads(response.content)
|
||||
self.assertTrue(obj['success'])
|
||||
|
||||
@patch.dict("django.conf.settings.PASSWORD_COMPLEXITY", {'DIGITS': 3})
|
||||
def test_not_enough_digits(self):
|
||||
self.url_params['password'] = 'thishasnodigits'
|
||||
response = self.client.post(self.url, self.url_params)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
obj = json.loads(response.content)
|
||||
self.assertEqual(
|
||||
obj['value'],
|
||||
"Enter a password with at least 3 digits.",
|
||||
)
|
||||
|
||||
@patch.dict("django.conf.settings.PASSWORD_COMPLEXITY", {'DIGITS': 3})
|
||||
def test_enough_digits(self):
|
||||
self.url_params['password'] = 'Th1sSh0uldPa88'
|
||||
response = self.client.post(self.url, self.url_params)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
obj = json.loads(response.content)
|
||||
self.assertTrue(obj['success'])
|
||||
|
||||
@patch.dict("django.conf.settings.PASSWORD_COMPLEXITY", {'PUNCTUATION': 3})
|
||||
@override_settings(AUTH_PASSWORD_VALIDATORS=[
|
||||
create_validator_config('util.password_policy_validators.PunctuationValidator', {'min_punctuation': 3})
|
||||
])
|
||||
def test_not_enough_punctuations(self):
|
||||
self.url_params['password'] = 'thisshouldfail'
|
||||
response = self.client.post(self.url, self.url_params)
|
||||
@@ -130,10 +127,12 @@ class TestPasswordPolicy(TestCase):
|
||||
obj = json.loads(response.content)
|
||||
self.assertEqual(
|
||||
obj['value'],
|
||||
"Enter a password with at least 3 punctuation marks.",
|
||||
"This password must contain at least 3 punctuation marks.",
|
||||
)
|
||||
|
||||
@patch.dict("django.conf.settings.PASSWORD_COMPLEXITY", {'PUNCTUATION': 3})
|
||||
@override_settings(AUTH_PASSWORD_VALIDATORS=[
|
||||
create_validator_config('util.password_policy_validators.PunctuationValidator', {'min_punctuation': 3})
|
||||
])
|
||||
def test_enough_punctuations(self):
|
||||
self.url_params['password'] = 'Th!sSh.uldPa$*'
|
||||
response = self.client.post(self.url, self.url_params)
|
||||
@@ -141,26 +140,9 @@ class TestPasswordPolicy(TestCase):
|
||||
obj = json.loads(response.content)
|
||||
self.assertTrue(obj['success'])
|
||||
|
||||
@patch.dict("django.conf.settings.PASSWORD_COMPLEXITY", {'WORDS': 3})
|
||||
def test_not_enough_words(self):
|
||||
self.url_params['password'] = 'thisshouldfail'
|
||||
response = self.client.post(self.url, self.url_params)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
obj = json.loads(response.content)
|
||||
self.assertEqual(
|
||||
obj['value'],
|
||||
"Enter a password with at least 3 words.",
|
||||
)
|
||||
|
||||
@patch.dict("django.conf.settings.PASSWORD_COMPLEXITY", {'WORDS': 3})
|
||||
def test_enough_wordss(self):
|
||||
self.url_params['password'] = u'this should pass'
|
||||
response = self.client.post(self.url, self.url_params)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
obj = json.loads(response.content)
|
||||
self.assertTrue(obj['success'])
|
||||
|
||||
@patch.dict("django.conf.settings.PASSWORD_COMPLEXITY", {'NUMERIC': 3})
|
||||
@override_settings(AUTH_PASSWORD_VALIDATORS=[
|
||||
create_validator_config('util.password_policy_validators.NumericValidator', {'min_numeric': 3})
|
||||
])
|
||||
def test_not_enough_numeric_characters(self):
|
||||
self.url_params['password'] = u'thishouldfail½2'
|
||||
response = self.client.post(self.url, self.url_params)
|
||||
@@ -168,18 +150,23 @@ class TestPasswordPolicy(TestCase):
|
||||
obj = json.loads(response.content)
|
||||
self.assertEqual(
|
||||
obj['value'],
|
||||
"Enter a password with at least 3 numbers.",
|
||||
"This password must contain at least 3 numbers.",
|
||||
)
|
||||
|
||||
@patch.dict("django.conf.settings.PASSWORD_COMPLEXITY", {'NUMERIC': 3})
|
||||
@override_settings(AUTH_PASSWORD_VALIDATORS=[
|
||||
create_validator_config('util.password_policy_validators.NumericValidator', {'min_numeric': 3})
|
||||
])
|
||||
def test_enough_numeric_characters(self):
|
||||
self.url_params['password'] = u'thisShouldPass½33' # This unicode 1/2 should count as a numeric value here
|
||||
# This unicode 1/2 should count as a numeric value here
|
||||
self.url_params['password'] = u'thisShouldPass½33'
|
||||
response = self.client.post(self.url, self.url_params)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
obj = json.loads(response.content)
|
||||
self.assertTrue(obj['success'])
|
||||
|
||||
@patch.dict("django.conf.settings.PASSWORD_COMPLEXITY", {'ALPHABETIC': 3})
|
||||
@override_settings(AUTH_PASSWORD_VALIDATORS=[
|
||||
create_validator_config('util.password_policy_validators.AlphabeticValidator', {'min_alphabetic': 3})
|
||||
])
|
||||
def test_not_enough_alphabetic_characters(self):
|
||||
self.url_params['password'] = '123456ab'
|
||||
response = self.client.post(self.url, self.url_params)
|
||||
@@ -187,10 +174,12 @@ class TestPasswordPolicy(TestCase):
|
||||
obj = json.loads(response.content)
|
||||
self.assertEqual(
|
||||
obj['value'],
|
||||
"Enter a password with at least 3 letters.",
|
||||
"This password must contain at least 3 letters.",
|
||||
)
|
||||
|
||||
@patch.dict("django.conf.settings.PASSWORD_COMPLEXITY", {'ALPHABETIC': 3})
|
||||
@override_settings(AUTH_PASSWORD_VALIDATORS=[
|
||||
create_validator_config('util.password_policy_validators.AlphabeticValidator', {'min_alphabetic': 3})
|
||||
])
|
||||
def test_enough_alphabetic_characters(self):
|
||||
self.url_params['password'] = u'𝒯𝓗Ï𝓼𝒫å𝓼𝓼𝔼𝓼'
|
||||
response = self.client.post(self.url, self.url_params)
|
||||
@@ -198,86 +187,65 @@ class TestPasswordPolicy(TestCase):
|
||||
obj = json.loads(response.content)
|
||||
self.assertTrue(obj['success'])
|
||||
|
||||
@patch.dict("django.conf.settings.PASSWORD_COMPLEXITY", {
|
||||
'PUNCTUATION': 3,
|
||||
'WORDS': 3,
|
||||
'DIGITS': 3,
|
||||
'LOWER': 3,
|
||||
'UPPER': 3,
|
||||
})
|
||||
@override_settings(AUTH_PASSWORD_VALIDATORS=[
|
||||
create_validator_config('util.password_policy_validators.MinimumLengthValidator', {'min_length': 3}),
|
||||
create_validator_config('util.password_policy_validators.UppercaseValidator', {'min_upper': 3}),
|
||||
create_validator_config('util.password_policy_validators.NumericValidator', {'min_numeric': 3}),
|
||||
create_validator_config('util.password_policy_validators.PunctuationValidator', {'min_punctuation': 3}),
|
||||
])
|
||||
def test_multiple_errors_fail(self):
|
||||
self.url_params['password'] = 'thisshouldfail'
|
||||
response = self.client.post(self.url, self.url_params)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
obj = json.loads(response.content)
|
||||
errstring = (
|
||||
"Enter a password with at least "
|
||||
"3 uppercase letters & "
|
||||
"3 digits & "
|
||||
"3 punctuation marks & "
|
||||
"3 words."
|
||||
"This password must contain at least 3 uppercase letters. "
|
||||
"This password must contain at least 3 numbers. "
|
||||
"This password must contain at least 3 punctuation marks."
|
||||
)
|
||||
self.assertEqual(obj['value'], errstring)
|
||||
|
||||
@patch.dict("django.conf.settings.PASSWORD_COMPLEXITY", {
|
||||
'PUNCTUATION': 3,
|
||||
'WORDS': 3,
|
||||
'DIGITS': 3,
|
||||
'LOWER': 3,
|
||||
'UPPER': 3,
|
||||
})
|
||||
@override_settings(AUTH_PASSWORD_VALIDATORS=[
|
||||
create_validator_config('util.password_policy_validators.MinimumLengthValidator', {'min_length': 3}),
|
||||
create_validator_config('util.password_policy_validators.UppercaseValidator', {'min_upper': 3}),
|
||||
create_validator_config('util.password_policy_validators.LowercaseValidator', {'min_lower': 3}),
|
||||
create_validator_config('util.password_policy_validators.NumericValidator', {'min_numeric': 3}),
|
||||
create_validator_config('util.password_policy_validators.PunctuationValidator', {'min_punctuation': 3}),
|
||||
])
|
||||
def test_multiple_errors_pass(self):
|
||||
self.url_params['password'] = u'tH1s Sh0u!d P3#$'
|
||||
self.url_params['password'] = u'tH1s Sh0u!d P3#$!'
|
||||
response = self.client.post(self.url, self.url_params)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
obj = json.loads(response.content)
|
||||
self.assertTrue(obj['success'])
|
||||
|
||||
@override_settings(PASSWORD_DICTIONARY=['foo', 'bar'])
|
||||
@override_settings(PASSWORD_DICTIONARY_EDIT_DISTANCE_THRESHOLD=1)
|
||||
def test_dictionary_similarity_fail1(self):
|
||||
self.url_params['password'] = 'foo'
|
||||
@override_settings(AUTH_PASSWORD_VALIDATORS=[
|
||||
create_validator_config('django.contrib.auth.password_validation.CommonPasswordValidator')
|
||||
])
|
||||
def test_common_password_fail(self):
|
||||
self.url_params['password'] = 'password'
|
||||
response = self.client.post(self.url, self.url_params)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
obj = json.loads(response.content)
|
||||
self.assertEqual(
|
||||
obj['value'],
|
||||
"Password is too similar to a dictionary word.",
|
||||
"This password is too common.",
|
||||
)
|
||||
|
||||
@override_settings(PASSWORD_DICTIONARY=['foo', 'bar'])
|
||||
@override_settings(PASSWORD_DICTIONARY_EDIT_DISTANCE_THRESHOLD=1)
|
||||
def test_dictionary_similarity_fail2(self):
|
||||
self.url_params['password'] = 'bar'
|
||||
response = self.client.post(self.url, self.url_params)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
obj = json.loads(response.content)
|
||||
self.assertEqual(
|
||||
obj['value'],
|
||||
"Password is too similar to a dictionary word.",
|
||||
)
|
||||
|
||||
@override_settings(PASSWORD_DICTIONARY=['foo', 'bar'])
|
||||
@override_settings(PASSWORD_DICTIONARY_EDIT_DISTANCE_THRESHOLD=1)
|
||||
def test_dictionary_similarity_fail3(self):
|
||||
self.url_params['password'] = 'fo0'
|
||||
response = self.client.post(self.url, self.url_params)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
obj = json.loads(response.content)
|
||||
self.assertEqual(
|
||||
obj['value'],
|
||||
"Password is too similar to a dictionary word.",
|
||||
)
|
||||
|
||||
@override_settings(PASSWORD_DICTIONARY=['foo', 'bar'])
|
||||
@override_settings(PASSWORD_DICTIONARY_EDIT_DISTANCE_THRESHOLD=1)
|
||||
def test_dictionary_similarity_pass(self):
|
||||
@override_settings(AUTH_PASSWORD_VALIDATORS=[
|
||||
create_validator_config('django.contrib.auth.password_validation.CommonPasswordValidator')
|
||||
])
|
||||
def test_common_password_pass(self):
|
||||
self.url_params['password'] = 'this_is_ok'
|
||||
response = self.client.post(self.url, self.url_params)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
obj = json.loads(response.content)
|
||||
self.assertTrue(obj['success'])
|
||||
|
||||
@override_settings(AUTH_PASSWORD_VALIDATORS=[
|
||||
create_validator_config('util.password_policy_validators.MinimumLengthValidator', {'min_length': 6}),
|
||||
create_validator_config('util.password_policy_validators.MaximumLengthValidator', {'max_length': 75}),
|
||||
])
|
||||
def test_with_unicode(self):
|
||||
self.url_params['password'] = u'四節比分和七年前'
|
||||
response = self.client.post(self.url, self.url_params)
|
||||
@@ -285,7 +253,9 @@ class TestPasswordPolicy(TestCase):
|
||||
obj = json.loads(response.content)
|
||||
self.assertTrue(obj['success'])
|
||||
|
||||
@override_settings(PASSWORD_MIN_LENGTH=6, SESSION_ENGINE='django.contrib.sessions.backends.cache')
|
||||
@override_settings(AUTH_PASSWORD_VALIDATORS=[
|
||||
create_validator_config('util.password_policy_validators.MinimumLengthValidator', {'min_length': 6})
|
||||
], SESSION_ENGINE='django.contrib.sessions.backends.cache')
|
||||
def test_ext_auth_password_length_too_short(self):
|
||||
"""
|
||||
Tests that even if password policy is enforced, ext_auth registrations aren't subject to it
|
||||
@@ -325,6 +295,9 @@ class TestUsernamePasswordNonmatch(TestCase):
|
||||
'honor_code': 'true',
|
||||
}
|
||||
|
||||
@override_settings(AUTH_PASSWORD_VALIDATORS=[
|
||||
create_validator_config('django.contrib.auth.password_validation.UserAttributeSimilarityValidator')
|
||||
])
|
||||
def test_with_username_password_match(self):
|
||||
self.url_params['username'] = "foobar"
|
||||
self.url_params['password'] = "foobar"
|
||||
@@ -333,9 +306,12 @@ class TestUsernamePasswordNonmatch(TestCase):
|
||||
obj = json.loads(response.content)
|
||||
self.assertEqual(
|
||||
obj['value'],
|
||||
"Password cannot be the same as the username.",
|
||||
"The password is too similar to the username.",
|
||||
)
|
||||
|
||||
@override_settings(AUTH_PASSWORD_VALIDATORS=[
|
||||
create_validator_config('django.contrib.auth.password_validation.UserAttributeSimilarityValidator')
|
||||
])
|
||||
def test_with_username_password_nonmatch(self):
|
||||
self.url_params['username'] = "foobar"
|
||||
self.url_params['password'] = "nonmatch"
|
||||
|
||||
@@ -29,6 +29,7 @@ from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
||||
from student.tests.factories import UserFactory
|
||||
from student.tests.test_email import mock_render_to_string
|
||||
from student.views import SETTING_CHANGE_INITIATED, password_reset, password_reset_confirm_wrapper
|
||||
from util.password_policy_validators import create_validator_config
|
||||
from util.testing import EventTestMixin
|
||||
|
||||
from .test_configuration_overrides import fake_get_value
|
||||
@@ -350,16 +351,18 @@ class ResetPasswordTests(EventTestMixin, CacheIsolationTestCase):
|
||||
self.user.refresh_from_db()
|
||||
assert not self.user.is_active
|
||||
|
||||
@override_settings(PASSWORD_MIN_LENGTH=2)
|
||||
@override_settings(PASSWORD_MAX_LENGTH=10)
|
||||
@override_settings(AUTH_PASSWORD_VALIDATORS=[
|
||||
create_validator_config('util.password_policy_validators.MinimumLengthValidator', {'min_length': 2}),
|
||||
create_validator_config('util.password_policy_validators.MaximumLengthValidator', {'max_length': 10})
|
||||
])
|
||||
@ddt.data(
|
||||
{
|
||||
'password': '1',
|
||||
'error_message': 'Enter a password with at least 2 characters.',
|
||||
'error_message': 'This password is too short. It must contain at least 2 characters.',
|
||||
},
|
||||
{
|
||||
'password': '01234567891',
|
||||
'error_message': 'Enter a password with at most 10 characters.',
|
||||
'error_message': 'This password is too long. It must contain no more than 10 characters.',
|
||||
}
|
||||
)
|
||||
def test_password_reset_with_invalid_length(self, password_dict):
|
||||
|
||||
Reference in New Issue
Block a user