Deprecate old signature for AXES_USERNAME_CALLABLE and update documentation

Signed-off-by: Aleksi Häkli <aleksi.hakli@iki.fi>
This commit is contained in:
Aleksi Häkli 2019-02-07 18:25:42 +02:00
parent 715dedc069
commit fcef40748a
No known key found for this signature in database
GPG key ID: 3E7146964D726BBE
6 changed files with 38 additions and 48 deletions

View file

@ -26,6 +26,9 @@ Changes
- Drop support for Python 2.7 and Python 3.4.
[aleksihakli]
- Drop old single-argument signature format for ``AXES_USERNAME_CALLABLE``.
[aleksihakli]
4.5.4 (2019-01-15)
------------------

View file

@ -172,22 +172,9 @@ class UtilsTest(TestCase):
self.assertEqual(expected_in_credentials, actual)
def sample_customize_username(request):
def sample_customize_username(request, credentials):
return 'prefixed-' + request.POST.get('username')
@override_settings(AXES_USERNAME_FORM_FIELD='username')
@override_settings(AXES_USERNAME_CALLABLE=sample_customize_username)
def test_custom_get_client_username(self):
provided = 'test-username'
expected = 'prefixed-' + provided
request = HttpRequest()
request.POST['username'] = provided
actual = get_client_username(request)
self.assertEqual(expected, actual)
@override_settings(AXES_USERNAME_FORM_FIELD='username')
@override_settings(AXES_USERNAME_CALLABLE=sample_customize_username)
def test_custom_get_client_username_from_request(self):
@ -223,18 +210,30 @@ class UtilsTest(TestCase):
self.assertEqual(expected_in_credentials, actual)
def sample_get_client_username_too_few_arguments():
def sample_get_client_username(request, credentials):
return 'example'
@override_settings(AXES_USERNAME_CALLABLE=sample_get_client_username)
def test_get_client_username(self):
self.assertEqual('example', get_client_username(HttpRequest(), {}))
@override_settings(AXES_USERNAME_CALLABLE=sample_get_client_username)
def test_get_client_username_too_many_arguments(self):
with self.assertRaises(TypeError):
actual = get_client_username(HttpRequest(), {}, None)
def sample_get_client_username_too_few_arguments(request):
pass
@override_settings(AXES_USERNAME_CALLABLE=sample_get_client_username_too_few_arguments)
def test_get_client_username_too_few_arguments_invalid_callable(self):
def test_get_client_username_invalid_callable_too_few_arguments(self):
with self.assertRaises(TypeError):
actual = get_client_username(HttpRequest(), {})
def sample_get_client_username_too_many_arguments(one, two, three):
def sample_get_client_username_too_many_arguments(request, credentials, extra_argument):
pass
@override_settings(AXES_USERNAME_CALLABLE=sample_get_client_username_too_many_arguments)
def test_get_client_username_too_many_arguments_invalid_callable(self):
def test_get_client_username_invalid_callable_too_many_arguments(self):
with self.assertRaises(TypeError):
actual = get_client_username(HttpRequest(), {})

View file

@ -1,5 +1,4 @@
from datetime import timedelta
from inspect import getargspec
from logging import getLogger
from socket import error, inet_pton, AF_INET6
@ -76,30 +75,17 @@ def get_client_username(request, credentials=None):
The order of preference for fetching the username is as follows:
1. If configured, use `AXES_USERNAME_CALLABLE`, and supply either `request` or `request, credentials` as arguments
depending on the function argument count (multiple signatures are supported for backwards compatibility)
2. If given, use `credentials` and fetch username from `AXES_USERNAME_FORM_FIELD` (defaults to `username`)
3. Use request.POST and fetch username from `AXES_USERNAME_FORM_FIELD` (defaults to `username`)
1. If configured, use ``AXES_USERNAME_CALLABLE``, and supply ``request, credentials`` as arguments
2. If given, use ``credentials`` and fetch username from ``AXES_USERNAME_FORM_FIELD`` (defaults to ``username``)
3. Use request.POST and fetch username from ``AXES_USERNAME_FORM_FIELD`` (defaults to ``username``)
:param request: incoming Django `HttpRequest` or similar object from authentication backend or other source
:param credentials: incoming credentials `dict` or similar object from authentication backend or other source
:param request: incoming Django ``HttpRequest`` or similar object from authentication backend or other source
:param credentials: incoming credentials ``dict`` or similar object from authentication backend or other source
"""
if settings.AXES_USERNAME_CALLABLE:
num_args = len(
getargspec(settings.AXES_USERNAME_CALLABLE).args # pylint: disable=deprecated-method
)
if num_args == 2:
logger.debug('Using AXES_USERNAME_CALLABLE for username with two arguments: request, credentials')
return settings.AXES_USERNAME_CALLABLE(request, credentials)
if num_args == 1:
logger.debug('Using AXES_USERNAME_CALLABLE for username with one argument: request')
return settings.AXES_USERNAME_CALLABLE(request)
logger.error('Using AXES_USERNAME_CALLABLE for username failed: wrong number of arguments %s', num_args)
raise TypeError('Wrong number of arguments in function call to AXES_USERNAME_CALLABLE', num_args)
logger.debug('Using AXES_USERNAME_CALLABLE to get username')
return settings.AXES_USERNAME_CALLABLE(request, credentials)
if credentials:
logger.debug('Using `credentials` to get username with key AXES_USERNAME_FORM_FIELD')

View file

@ -145,8 +145,8 @@ Use the ``python manage.py check`` command to verify the correct configuration i
development and production environments. It is probably best to use this step as part
of your regular CI workflows to verify that your project is not misconfigured.
django-axes uses the checks to verify your cache configuration to see that your caches
should be functional with the configuration axes. Many people have different configurations
Axes uses the checks to verify your cache configuration to see that your caches
should be functional with the configuration of Axes. Many people have different configurations
for their development and production environments.
@ -185,7 +185,7 @@ add an extra cache to ``CACHES`` with a name of your choice::
}
}
The next step is to tell axes to use this cache through adding ``AXES_CACHE``
The next step is to tell Axes to use this cache through adding ``AXES_CACHE``
to your ``settings.py`` file::
AXES_CACHE = 'axes_cache'
@ -221,7 +221,7 @@ Customizing Axes
You have a couple options available to you to customize ``django-axes`` a bit.
These should be defined in your ``settings.py`` file.
* ``AXES_CACHE``: The name of the cache for axes to use.
* ``AXES_CACHE``: The name of the cache for Axes to use.
Default: ``'default'``
* ``AXES_FAILURE_LIMIT``: The number of login attempts allowed before a
record is created for the failed logins. Default: ``3``
@ -235,7 +235,7 @@ These should be defined in your ``settings.py`` file.
old failed login attempts will be forgotten. Can be set to a python
timedelta object or an integer. If an integer, will be interpreted as a
number of hours. Default: ``None``
* ``AXES_LOGGER``: If set, specifies a logging mechanism for axes to use.
* ``AXES_LOGGER``: If set, specifies a logging mechanism for Axes to use.
Default: ``'axes.watch_login'``
* ``AXES_LOCKOUT_TEMPLATE``: If set, specifies a template to render when a
user is locked out. Template receives cooloff_time and failure_limit as
@ -247,11 +247,11 @@ These should be defined in your ``settings.py`` file.
Default: ``True``
* ``AXES_USERNAME_FORM_FIELD``: the name of the form field that contains your
users usernames. Default: ``username``
* ``AXES_USERNAME_CALLABLE``: A callable function that takes either one or two arguments:
``AXES_USERNAME_CALLABLE(request)`` or ``AXES_USERNAME_CALLABLE(request, credentials)``.
* ``AXES_USERNAME_CALLABLE``: A callable function that takes two arguments:
``AXES_USERNAME_CALLABLE(request, credentials)``.
The ``request`` is a HttpRequest like object and the ``credentials`` is a dictionary like object.
``credentials`` are the ones that were passed to Django ``authenticate()`` in the login flow.
If no function is supplied, axes fetches the username from the ``credentials`` or ``request.POST``
If no function is supplied, Axes fetches the username from the ``credentials`` or ``request.POST``
dictionaries based on ``AXES_USERNAME_FORM_FIELD``. Default: ``None``
* ``AXES_PASSWORD_FORM_FIELD``: the name of the form or credentials field that contains your
users password. Default: ``password``

View file

@ -33,3 +33,5 @@ API endpoints or names for compatibility reasons.
``axes.exceptions.AxesSignalPermissionDenied`` on lockouts.
- ``AxesMiddleware`` was added to return lockout responses.
The middleware handles ``axes.exception.AxesSignalPermissionDenied``.
- ``AXES_USERNAME_CALLABLE`` is now always called with two arguments,
``request`` and ``credentials`` instead of ``request``.

View file

@ -21,7 +21,7 @@ Once ``axes`` is in your ``INSTALLED_APPS`` in your project settings file, you c
login and logout of your application via the ``django.contrib.auth`` views.
The attempts will be logged and visible in the "Access Attempts" section in admin.
By default, django-axes will lock out repeated access attempts from the same IP address.
By default, Axes will lock out repeated access attempts from the same IP address.
You can allow this IP to attempt again by deleting relevant ``AccessAttempt`` records.
Records can be deleted, for example, by using the Django admin application.