1
0
mirror of https://github.com/django/django.git synced 2024-12-22 17:16:24 +00:00

Fixed #35782 -- Allowed overriding password validation error messages.

This commit is contained in:
Ben Cail 2024-09-26 10:11:41 -04:00 committed by Sarah Boyce
parent 06bf06a911
commit ec7d69035a
4 changed files with 131 additions and 18 deletions

View File

@ -106,17 +106,16 @@ class MinimumLengthValidator:
def validate(self, password, user=None):
if len(password) < self.min_length:
raise ValidationError(
ngettext(
"This password is too short. It must contain at least "
"%(min_length)d character.",
"This password is too short. It must contain at least "
"%(min_length)d characters.",
self.min_length,
),
code="password_too_short",
params={"min_length": self.min_length},
)
raise ValidationError(self.get_error_message(), code="password_too_short")
def get_error_message(self):
return ngettext(
"This password is too short. It must contain at least %d character."
% self.min_length,
"This password is too short. It must contain at least %d characters."
% self.min_length,
self.min_length,
)
def get_help_text(self):
return ngettext(
@ -203,11 +202,14 @@ class UserAttributeSimilarityValidator:
except FieldDoesNotExist:
verbose_name = attribute_name
raise ValidationError(
_("The password is too similar to the %(verbose_name)s."),
self.get_error_message(),
code="password_too_similar",
params={"verbose_name": verbose_name},
)
def get_error_message(self):
return _("The password is too similar to the %(verbose_name)s.")
def get_help_text(self):
return _(
"Your password cant be too similar to your other personal information."
@ -242,10 +244,13 @@ class CommonPasswordValidator:
def validate(self, password, user=None):
if password.lower().strip() in self.passwords:
raise ValidationError(
_("This password is too common."),
self.get_error_message(),
code="password_too_common",
)
def get_error_message(self):
return _("This password is too common.")
def get_help_text(self):
return _("Your password cant be a commonly used password.")
@ -258,9 +263,12 @@ class NumericPasswordValidator:
def validate(self, password, user=None):
if password.isdigit():
raise ValidationError(
_("This password is entirely numeric."),
self.get_error_message(),
code="password_entirely_numeric",
)
def get_error_message(self):
return _("This password is entirely numeric.")
def get_help_text(self):
return _("Your password cant be entirely numeric.")

View File

@ -82,6 +82,10 @@ Minor features
improves performance. See :ref:`adding an async interface
<writing-authentication-backends-async-interface>` for more details.
* The :ref:`password validator classes <included-password-validators>`
now have a new method ``get_error_message()``, which can be overridden in
subclasses to customize the error messages.
:mod:`django.contrib.contenttypes`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -590,6 +590,8 @@ has no settings.
The help texts and any errors from password validators are always returned in
the order they are listed in :setting:`AUTH_PASSWORD_VALIDATORS`.
.. _included-password-validators:
Included validators
-------------------
@ -600,10 +602,18 @@ Django includes four validators:
Validates that the password is of a minimum length.
The minimum length can be customized with the ``min_length`` parameter.
.. method:: get_error_message()
.. versionadded:: 5.2
A hook for customizing the ``ValidationError`` error message. Defaults
to ``"This password is too short. It must contain at least <min_length>
characters."``.
.. method:: get_help_text()
A hook for customizing the validator's help text. Defaults to ``"Your
password must contain at least <min_length> characters."``
password must contain at least <min_length> characters."``.
.. class:: UserAttributeSimilarityValidator(user_attributes=DEFAULT_USER_ATTRIBUTES, max_similarity=0.7)
@ -622,10 +632,17 @@ Django includes four validators:
``user_attributes``, whereas a value of 1.0 rejects only passwords that are
identical to an attribute's value.
.. method:: get_error_message()
.. versionadded:: 5.2
A hook for customizing the ``ValidationError`` error message. Defaults
to ``"The password is too similar to the <user_attribute>."``.
.. method:: get_help_text()
A hook for customizing the validator's help text. Defaults to ``"Your
password cant be too similar to your other personal information."``
password cant be too similar to your other personal information."``.
.. class:: CommonPasswordValidator(password_list_path=DEFAULT_PASSWORD_LIST_PATH)
@ -638,19 +655,33 @@ Django includes four validators:
common passwords. This file should contain one lowercase password per line
and may be plain text or gzipped.
.. method:: get_error_message()
.. versionadded:: 5.2
A hook for customizing the ``ValidationError`` error message. Defaults
to ``"This password is too common."``.
.. method:: get_help_text()
A hook for customizing the validator's help text. Defaults to ``"Your
password cant be a commonly used password."``
password cant be a commonly used password."``.
.. class:: NumericPasswordValidator()
Validate that the password is not entirely numeric.
.. method:: get_error_message()
.. versionadded:: 5.2
A hook for customizing the ``ValidationError`` error message. Defaults
to ``"This password is entirely numeric."``.
.. method:: get_help_text()
A hook for customizing the validator's help text. Defaults to ``"Your
password cant be entirely numeric."``
password cant be entirely numeric."``.
Integrating validation
----------------------

View File

@ -144,6 +144,20 @@ class MinimumLengthValidatorTest(SimpleTestCase):
"Your password must contain at least 8 characters.",
)
def test_custom_error(self):
class CustomMinimumLengthValidator(MinimumLengthValidator):
def get_error_message(self):
return "Your password must be %d characters long" % self.min_length
expected_error = "Your password must be %d characters long"
with self.assertRaisesMessage(ValidationError, expected_error % 8) as cm:
CustomMinimumLengthValidator().validate("1234567")
self.assertEqual(cm.exception.error_list[0].code, "password_too_short")
with self.assertRaisesMessage(ValidationError, expected_error % 3) as cm:
CustomMinimumLengthValidator(min_length=3).validate("12")
class UserAttributeSimilarityValidatorTest(TestCase):
def test_validate(self):
@ -213,6 +227,42 @@ class UserAttributeSimilarityValidatorTest(TestCase):
"Your password cant be too similar to your other personal information.",
)
def test_custom_error(self):
class CustomUserAttributeSimilarityValidator(UserAttributeSimilarityValidator):
def get_error_message(self):
return "The password is too close to the %(verbose_name)s."
user = User.objects.create_user(
username="testclient",
password="password",
email="testclient@example.com",
first_name="Test",
last_name="Client",
)
expected_error = "The password is too close to the %s."
with self.assertRaisesMessage(ValidationError, expected_error % "username"):
CustomUserAttributeSimilarityValidator().validate("testclient", user=user)
def test_custom_error_verbose_name_not_used(self):
class CustomUserAttributeSimilarityValidator(UserAttributeSimilarityValidator):
def get_error_message(self):
return "The password is too close to a user attribute."
user = User.objects.create_user(
username="testclient",
password="password",
email="testclient@example.com",
first_name="Test",
last_name="Client",
)
expected_error = "The password is too close to a user attribute."
with self.assertRaisesMessage(ValidationError, expected_error):
CustomUserAttributeSimilarityValidator().validate("testclient", user=user)
class CommonPasswordValidatorTest(SimpleTestCase):
def test_validate(self):
@ -247,6 +297,16 @@ class CommonPasswordValidatorTest(SimpleTestCase):
"Your password cant be a commonly used password.",
)
def test_custom_error(self):
class CustomCommonPasswordValidator(CommonPasswordValidator):
def get_error_message(self):
return "This password has been used too much."
expected_error = "This password has been used too much."
with self.assertRaisesMessage(ValidationError, expected_error):
CustomCommonPasswordValidator().validate("godzilla")
class NumericPasswordValidatorTest(SimpleTestCase):
def test_validate(self):
@ -264,6 +324,16 @@ class NumericPasswordValidatorTest(SimpleTestCase):
"Your password cant be entirely numeric.",
)
def test_custom_error(self):
class CustomNumericPasswordValidator(NumericPasswordValidator):
def get_error_message(self):
return "This password is all digits."
expected_error = "This password is all digits."
with self.assertRaisesMessage(ValidationError, expected_error):
CustomNumericPasswordValidator().validate("42424242")
class UsernameValidatorsTests(SimpleTestCase):
def test_unicode_validator(self):