Run Django password validators against populated user models

Previously the validators were run against empty user models, which made
the UserAttributeSimilarityValidator accept passwords that would
otherwise have been rejected.

Tests have been added to ensure the validator run correctly.
This commit is contained in:
Tim Heap 2018-02-26 12:18:15 +11:00 committed by Matt Westcott
parent 0d4f324d27
commit 9d8563a744
2 changed files with 88 additions and 3 deletions

View file

@ -133,10 +133,26 @@ class UserForm(UsernameForm):
code='password_mismatch',
))
if password1:
return password2
def validate_password(self):
"""
Run the Django password validators against the new password. This must
be called after the user instance in self.instance is populated with
the new data from the form, as some validators rely on attributes on
the user model.
"""
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 == password2:
validate_password(password1, user=self.instance)
return password2
def _post_clean(self):
super()._post_clean()
try:
self.validate_password()
except forms.ValidationError as e:
self.add_error('password2', e)
def _clean_fields(self):
super()._clean_fields()

View file

@ -200,6 +200,40 @@ class TestUserCreateView(TestCase, WagtailTestUtils):
users = get_user_model().objects.filter(username='testuser')
self.assertEqual(users.count(), 0)
@override_settings(
AUTH_PASSWORD_VALIDATORS=[
{'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
],
)
def test_create_with_password_validation(self):
"""
Test that the Django password validators are run when creating a user.
Specifically test that the UserAttributeSimilarityValidator works,
which requires a full-populated user model before the validation works.
"""
# Create a user with a password the same as their name
response = self.post({
'username': "testuser",
'email': "test@user.com",
'first_name': "Example",
'last_name': "Name",
'password1': "example name",
'password2': "example name",
})
# Should remain on page
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'wagtailusers/users/create.html')
# Password field should have an error
errors = response.context['form'].errors.as_data()
self.assertIn('password2', errors)
self.assertEqual(errors['password2'][0].code, 'password_too_similar')
# Check that the user was not created
users = get_user_model().objects.filter(username='testuser')
self.assertEqual(users.count(), 0)
def test_create_with_missing_password(self):
"""Password should be required by default"""
response = self.post({
@ -603,7 +637,7 @@ class TestUserEditView(TestCase, WagtailTestUtils):
self.assertEqual(user.first_name, 'Edited')
self.assertTrue(user.check_password('password'))
def test_validate_password(self):
def test_passwords_match(self):
"""Password fields should be validated if supplied"""
response = self.post({
'username': "testuser",
@ -625,6 +659,41 @@ class TestUserEditView(TestCase, WagtailTestUtils):
self.assertEqual(user.first_name, 'Original')
self.assertTrue(user.check_password('password'))
@override_settings(
AUTH_PASSWORD_VALIDATORS=[
{'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
],
)
def test_edit_with_password_validation(self):
"""
Test that the Django password validators are run when editing a user.
Specifically test that the UserAttributeSimilarityValidator works,
which requires a full-populated user model before the validation works.
"""
# Create a user with a password the same as their name
response = self.post({
'username': "testuser",
'email': "test@user.com",
'first_name': "Edited",
'last_name': "Name",
'password1': "edited name",
'password2': "edited name",
})
# Should remain on page
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'wagtailusers/users/edit.html')
# Password field should have an error
errors = response.context['form'].errors.as_data()
self.assertIn('password2', errors)
self.assertEqual(errors['password2'][0].code, 'password_too_similar')
# Check that the user was not edited
user = get_user_model().objects.get(pk=self.test_user.pk)
self.assertEqual(user.first_name, 'Original')
self.assertTrue(user.check_password('password'))
def test_edit_and_deactivate(self):
response = self.post({
'username': "testuser",