diff --git a/CHANGES.txt b/CHANGES.txt index e372e7f..6170d97 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -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) ------------------ diff --git a/axes/tests/test_utils.py b/axes/tests/test_utils.py index cc2eb66..03732c7 100644 --- a/axes/tests/test_utils.py +++ b/axes/tests/test_utils.py @@ -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(), {}) diff --git a/axes/utils.py b/axes/utils.py index fd93395..b4df189 100644 --- a/axes/utils.py +++ b/axes/utils.py @@ -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') diff --git a/docs/configuration.rst b/docs/configuration.rst index b83c1ef..9050ceb 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -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`` diff --git a/docs/migration.rst b/docs/migration.rst index d26fafd..1f292f0 100644 --- a/docs/migration.rst +++ b/docs/migration.rst @@ -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``. diff --git a/docs/usage.rst b/docs/usage.rst index 9c13e84..11b24ac 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -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.