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:
Dillon Dumesnil
2018-10-02 14:21:07 -04:00
parent 4fa27f98dc
commit deb758c808
30 changed files with 482 additions and 469 deletions

View File

@@ -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"

View File

@@ -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):