diff --git a/django/contrib/auth/password_validation.py b/django/contrib/auth/password_validation.py index 12cf29cb9b..4ff2c409ec 100644 --- a/django/contrib/auth/password_validation.py +++ b/django/contrib/auth/password_validation.py @@ -147,7 +147,7 @@ class UserAttributeSimilarityValidator(object): continue value_parts = re.split(r'\W+', value) + [value] for value_part in value_parts: - if SequenceMatcher(a=password.lower(), b=value_part.lower()).quick_ratio() > self.max_similarity: + if SequenceMatcher(a=password.lower(), b=value_part.lower()).quick_ratio() >= self.max_similarity: try: verbose_name = force_text(user._meta.get_field(attribute_name).verbose_name) except FieldDoesNotExist: diff --git a/docs/topics/auth/passwords.txt b/docs/topics/auth/passwords.txt index da2bc57df8..7015e65404 100644 --- a/docs/topics/auth/passwords.txt +++ b/docs/topics/auth/passwords.txt @@ -545,11 +545,10 @@ Django includes four validators: is used: ``'username', 'first_name', 'last_name', 'email'``. Attributes that don't exist are ignored. - The maximum similarity the password can have, before it is rejected, can - be set with the ``max_similarity`` parameter, on a scale of 0 to 1. - A setting of 0 will cause all passwords to be rejected, whereas a setting - of 1 will cause it to only reject passwords that are identical to an - attribute's value. + The minimum similarity of a rejected password can be set on a scale of 0 to + 1 with the ``max_similarity`` parameter. A setting of 0 rejects all + passwords, whereas a setting of 1 rejects only passwords that are identical + to an attribute's value. .. class:: CommonPasswordValidator(password_list_path=DEFAULT_PASSWORD_LIST_PATH) diff --git a/tests/auth_tests/test_validators.py b/tests/auth_tests/test_validators.py index 97cf396b1a..e074716f46 100644 --- a/tests/auth_tests/test_validators.py +++ b/tests/auth_tests/test_validators.py @@ -124,7 +124,22 @@ class UserAttributeSimilarityValidatorTest(TestCase): max_similarity=0.3, ).validate('testclient', user=user) self.assertEqual(cm.exception.messages, [expected_error % "first name"]) - + # max_similarity=1 doesn't allow passwords that are identical to the + # attribute's value. + with self.assertRaises(ValidationError) as cm: + UserAttributeSimilarityValidator( + user_attributes=['first_name'], + max_similarity=1, + ).validate(user.first_name, user=user) + self.assertEqual(cm.exception.messages, [expected_error % "first name"]) + # max_similarity=0 rejects all passwords. + with self.assertRaises(ValidationError) as cm: + UserAttributeSimilarityValidator( + user_attributes=['first_name'], + max_similarity=0, + ).validate('XXX', user=user) + self.assertEqual(cm.exception.messages, [expected_error % "first name"]) + # Passes validation. self.assertIsNone( UserAttributeSimilarityValidator(user_attributes=['first_name']).validate('testclient', user=user) )