Skip to content

Commit

Permalink
Allow user to set the message in Django settings.
Browse files Browse the repository at this point in the history
  • Loading branch information
mic159 committed May 16, 2018
1 parent 9e03714 commit 73f9192
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 7 deletions.
36 changes: 36 additions & 0 deletions docs/validator.rst
Expand Up @@ -46,3 +46,39 @@ Using the password validator
changes a user's password in other ways. If you manipulate user
passwords through means other than the high-level APIs listed
above, you'll need to manually check passwords.


Changing the error message
==========================

To change the error or help message shown to the user, you can set it
in the ``OPTIONS`` dictionary like so:

.. code-block:: python
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'pwned_passwords_django.validators.PwnedPasswordsValidator',
'OPTIONS': {
'error_message': 'That password was pwned',
'help_message': 'Your password can\'t be a commonly used password.',
}
},
]
The amount of times the password has appeared in a breach can also be included
in the error message, including a plural form:

.. code-block:: python
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'pwned_passwords_django.validators.PwnedPasswordsValidator',
'OPTIONS': {
'error_message': (
'Pwned %(amount)d time',
'Pwned %(amount)d times',
)
}
},
]
52 changes: 51 additions & 1 deletion pwned_passwords_django/tests/test_validators.py
Expand Up @@ -2,6 +2,7 @@

from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError
from django.test import override_settings

from .. import api
from ..validators import PwnedPasswordsValidator
Expand Down Expand Up @@ -29,7 +30,7 @@ def test_compromised(self):
with mock.patch('requests.get', request_mock):
with self.assertRaisesMessage(
ValidationError,
PwnedPasswordsValidator.PWNED_MESSAGE
PwnedPasswordsValidator.DEFAULT_PWNED_MESSAGE
):
validate_password(self.sample_password)
request_mock.assert_called_with(
Expand Down Expand Up @@ -57,3 +58,52 @@ def test_not_compromised(self):
),
timeout=api.REQUEST_TIMEOUT,
)

@override_settings(AUTH_PASSWORD_VALIDATORS=[{
'NAME': 'pwned_passwords_django.validators.PwnedPasswordsValidator',
'OPTIONS': {'error_message': 'Pwned'}
}])
def test_message_override(self):
"""
Custom message is shown.
"""
request_mock = self._get_mock()
with mock.patch('requests.get', request_mock):
with self.assertRaisesMessage(
ValidationError,
'Pwned'
):
validate_password(self.sample_password)

@override_settings(AUTH_PASSWORD_VALIDATORS=[{
'NAME': 'pwned_passwords_django.validators.PwnedPasswordsValidator',
'OPTIONS': {
'error_message': (
'Pwned %(amount)d time',
'Pwned %(amount)d times',
),
},
}])
def test_message_number(self):
"""
Custom message can show the amount of times pwned.
"""
request_mock_plural = self._get_mock()
request_mock_singular = self._get_mock(
response_text='{}:1'.format(self.sample_password_suffix)
)

with mock.patch('requests.get', request_mock_plural):
with self.assertRaisesMessage(
ValidationError,
'Pwned 3 times'
):
validate_password(self.sample_password)
with mock.patch('requests.get', request_mock_singular):
with self.assertRaisesMessage(
ValidationError,
'Pwned 1 time'
):
validate_password(self.sample_password)
32 changes: 26 additions & 6 deletions pwned_passwords_django/validators.py
@@ -1,5 +1,5 @@
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext as _, ungettext

from . import api

Expand All @@ -9,14 +9,34 @@ class PwnedPasswordsValidator(object):
Password validator which checks the Pwned Passwords database.
"""
HELP_MESSAGE = _("Your password can't be a commonly used password.")
PWNED_MESSAGE = _(
DEFAULT_HELP_MESSAGE = _(
"Your password can't be a commonly used password."
)
DEFAULT_PWNED_MESSAGE = (
"This password is known to have appeared in a public data breach."
)

def __init__(self, error_message=None, help_message=None):
self.help_message = help_message or self.DEFAULT_HELP_MESSAGE
error_message = error_message or self.DEFAULT_PWNED_MESSAGE
# If there is no plural, use the same message for both forms
if not isinstance(error_message, (list, tuple)):
self.error_message = (error_message, error_message)
else:
self.error_message = error_message

def validate(self, password, user=None):
if api.pwned_password(password):
raise ValidationError(self.PWNED_MESSAGE, code='pwned_password')
amount = api.pwned_password(password)
if amount:
raise ValidationError(
ungettext(
self.error_message[0],
self.error_message[1],
amount,
),
params={'amount': amount},
code='pwned_password',
)

def get_help_text(self):
return self.HELP_MESSAGE # pragma: no cover
return self.help_message # pragma: no cover

0 comments on commit 73f9192

Please sign in to comment.