From 0a90a7d075400316f1c4f97aed2207db40daee4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksi=20Ha=CC=88kli?= Date: Wed, 1 May 2019 18:28:29 +0300 Subject: [PATCH] Improve documentation --- axes/backends.py | 8 +++++--- axes/handlers/base.py | 12 +++++++----- axes/middleware.py | 38 ++++++++++++++++++++++++++------------ axes/request.py | 25 ++++++++++++++++++++++++- docs/1_requirements.rst | 4 ++-- docs/2_installation.rst | 6 +++--- docs/3_usage.rst | 6 +++--- docs/4_configuration.rst | 22 +++++++++++++++------- docs/5_customization.rst | 20 ++++++++++++++++---- docs/6_integration.rst | 10 +++++----- docs/7_architecture.rst | 4 ++-- docs/8_reference.rst | 17 +++++++++++------ docs/9_development.rst | 33 ++++++++++++++++++++------------- docs/index.rst | 1 + 14 files changed, 140 insertions(+), 66 deletions(-) diff --git a/axes/backends.py b/axes/backends.py index 1347ac0..79b334f 100644 --- a/axes/backends.py +++ b/axes/backends.py @@ -13,13 +13,13 @@ class AxesBackend(ModelBackend): Use this class as the first item of ``AUTHENTICATION_BACKENDS`` to prevent locked out users from being logged in by the Django authentication flow. - **Note:** this backend does not log your user in and delegates login to the - backends that are configured after it in the ``AUTHENTICATION_BACKENDS`` list. + .. note:: This backend does not log your user in. It monitors login attempts. + Authentication is handled by the following backends that are configured in ``AUTHENTICATION_BACKENDS``. """ def authenticate(self, request: AxesHttpRequest, username: str = None, password: str = None, **kwargs: dict): """ - Checks user lockout status and raise a PermissionDenied if user is not allowed to log in. + Checks user lockout status and raises an exception if user is not allowed to log in. This method interrupts the login flow and inserts error message directly to the ``response_context`` attribute that is supplied as a keyword argument. @@ -57,4 +57,6 @@ class AxesBackend(ModelBackend): class AxesModelBackend(AxesBackend): """ Backwards compatibility class for version 4 to version 5 migration. + + See the ``AxesBackend`` class documentation and implementation. """ diff --git a/axes/handlers/base.py b/axes/handlers/base.py index ad0d4a4..60312de 100644 --- a/axes/handlers/base.py +++ b/axes/handlers/base.py @@ -9,12 +9,14 @@ from axes.request import AxesHttpRequest class AxesHandler: # pylint: disable=unused-argument """ - Virtual handler API definition for subclassing handlers that can be used with the ``AxesProxyHandler``. + Handler API definition for implementations that are used by the ``AxesProxyHandler``. - If you wish to implement your own handler class just override the methods you wish to specialize - and define the class to be used with ``settings.AXES_HANDLER = 'path.to.YourClass'``. + If you wish to specialize your own handler class, override the necessary methods + and configure the class for use by setting ``settings.AXES_HANDLER = 'module.path.to.YourClass'``. - The default implementation that is actually used by Axes is the ``axes.handlers.database.AxesDatabaseHandler``. + The default implementation that is actually used by Axes is ``axes.handlers.database.AxesDatabaseHandler``. + + .. note:: This is a virtual class and **can not be used without specialization**. """ def is_allowed(self, request: AxesHttpRequest, credentials: dict = None) -> bool: @@ -23,7 +25,7 @@ class AxesHandler: # pylint: disable=unused-argument This method is abstract and other backends can specialize it as needed, but the default implementation checks if the user has attempted to authenticate into the site too many times through the - Django authentication backends and returns ``False``if user exceeds the configured Axes thresholds. + Django authentication backends and returns ``False`` if user exceeds the configured Axes thresholds. This checker can implement arbitrary checks such as IP whitelisting or blacklisting, request frequency checking, failed attempt monitoring or similar functions. diff --git a/axes/middleware.py b/axes/middleware.py index f05a357..675b7b3 100644 --- a/axes/middleware.py +++ b/axes/middleware.py @@ -16,16 +16,26 @@ from axes.request import AxesHttpRequest class AxesMiddleware: """ - Middleware that maps lockout signals into readable HTTP 403 Forbidden responses. + Middleware that calculates necessary HTTP request attributes for attempt monitoring + and maps lockout signals into readable HTTP 403 Forbidden responses. - Without this middleware the backend returns HTTP 403 errors with the - ``django.views.defaults.permission_denied`` view that renders the ``403.html`` - template from the root template directory if found. - This middleware uses the ``axes.helpers.get_lockout_response`` handler - for returning a context aware lockout message to the end user. + By default Django server returns ``PermissionDenied`` exceptions as HTTP 403 errors + with the ``django.views.defaults.permission_denied`` view that renders + the ``403.html`` template from the root template directory if found. - To customize the error rendering, you can subclass this middleware + This middleware recognizes the specialized attempt monitoring and lockout exceptions + and uses the ``axes.helpers.get_lockout_response`` handler for returning + customizable and context aware lockout message to the end user. + + To customize the error handling behaviour further, you can subclass this middleware and change the ``process_exception`` handler to your own liking. + + Please see the following configuration flags before customizing this handler: + + - ``AXES_LOCKOUT_TEMPLATE``, + - ``AXES_LOCKOUT_URL``, + - ``AXES_COOLOFF_MESSAGE``, and + - ``AXES_PERMALOCK_MESSAGE``. """ def __init__(self, get_response: Callable): @@ -37,9 +47,11 @@ class AxesMiddleware: def update_request(self, request: HttpRequest): """ - Update given Django ``HttpRequest`` with necessary attributes - before passing it on the ``get_response`` for further - Django middleware and view processing. + Construct an ``AxesHttpRequest`` from the given ``HttpRequest`` + by updating the request with necessary attempt tracking attributes. + + This method is called by the middleware class ``__call__`` method + when iterating over the middleware stack. """ request.axes_attempt_time = now() @@ -50,9 +62,11 @@ class AxesMiddleware: def process_exception(self, request: AxesHttpRequest, exception): # pylint: disable=inconsistent-return-statements """ - Exception handler that processes exceptions raised by the Axes signal handler when request fails with login. + Handle exceptions raised by the Axes signal handler class when requests fail checks. - Only ``axes.exceptions.AxesSignalPermissionDenied`` exception is handled by this middleware. + Note that only ``AxesSignalPermissionDenied`` is handled by this middleware class. + + :return: Configured ``HttpResponse`` for failed authentication attempts and lockouts. """ if isinstance(exception, AxesSignalPermissionDenied): diff --git a/axes/request.py b/axes/request.py index 649c19b..978c392 100644 --- a/axes/request.py +++ b/axes/request.py @@ -5,7 +5,30 @@ from django.http import HttpRequest class AxesHttpRequest(HttpRequest): """ - Type definition for the HTTP request Axes uses. + Extended Django ``HttpRequest`` with custom Axes attributes. + + This request is constructed by the ``AxesMiddleware`` class + where the custom attributes are inserted into the request. + + .. note:: The ``str`` type variables have a maximum length of 255 + characters and they are calculated in the middleware layer. + If the HTTP request attributes can not be resolved + they are assigned default value of ````. + + :var axes_attempt_time: Timestamp of the request on the server side. + :vartype axes_attempt_time: datetime + + :var axes_ip_address: Request IP address as resolved by django-axes and django-ipware configurations. + :vartype axes_ip_address: str + + :var axes_user_agent: Request agent from ``request.META['HTTP_USER_AGENT']``. + :vartype axes_user_agent: str + + :var axes_path_info: Request path from ``request.META['PATH_INFO']``. + :vartype axes_path_info: str + + :var axes_http_accept: Request ``Accept`` header from ``request.META['HTTP_ACCEPT']``. + :vartype axes_http_accept: str """ axes_attempt_time: datetime diff --git a/docs/1_requirements.rst b/docs/1_requirements.rst index ab1b5a2..8dab0ab 100644 --- a/docs/1_requirements.rst +++ b/docs/1_requirements.rst @@ -1,7 +1,7 @@ .. _requirements: -1. Requirements -=============== +Requirements +============ Axes requires a supported Django version and runs on Python and PyPy versions 3.5 and above. diff --git a/docs/2_installation.rst b/docs/2_installation.rst index 37e38c3..1a3e762 100644 --- a/docs/2_installation.rst +++ b/docs/2_installation.rst @@ -1,7 +1,7 @@ .. _installation: -2. Installation -=============== +Installation +============ Axes is easy to install from the PyPI package:: @@ -56,7 +56,7 @@ After installing the package, the project settings need to be configured. Axes is now functional with the default settings and is saving user attempts into your database and locking users out if they exceed the maximum attempts. -You should use the ``python manage.py check`` command to verify the correct configuration in both +You should use the ``python manage.py check`` command to verify the correct configuration in development, staging, 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. diff --git a/docs/3_usage.rst b/docs/3_usage.rst index f8dd4c2..a430713 100644 --- a/docs/3_usage.rst +++ b/docs/3_usage.rst @@ -1,7 +1,7 @@ .. _usage: -3. Usage -======== +Usage +===== Once Axes is is installed and configured, you can login and logout of your application via the ``django.contrib.auth`` views. @@ -31,4 +31,4 @@ In your code, you can use the ``axes.utils.reset`` function. Please note that if you give both ``username`` and ``ip`` arguments to ``reset`` that attempts that have both the set IP and username are reset. -The effective behaviour of ``reset`` is to ``and`` the terms instead of ``or``ing them. +The effective behaviour of ``reset`` is to ``and`` the terms instead of ``or`` ing them. diff --git a/docs/4_configuration.rst b/docs/4_configuration.rst index 46d4053..2999317 100644 --- a/docs/4_configuration.rst +++ b/docs/4_configuration.rst @@ -1,7 +1,7 @@ .. _configuration: -4. Configuration -================ +Configuration +============= Minimal Axes configuration is done with just ``settings.py`` updates. @@ -46,7 +46,7 @@ The following ``settings.py`` options are available for customizing Axes behavio user is locked out. Template receives ``cooloff_time`` and ``failure_limit`` as context variables. Default: ``None`` * ``AXES_LOCKOUT_URL``: If set, specifies a URL to redirect to on lockout. If - both AXES_LOCKOUT_TEMPLATE and AXES_LOCKOUT_URL are set, the template will + both ``AXES_LOCKOUT_TEMPLATE`` and ``AXES_LOCKOUT_URL`` are set, the template will be used. Default: ``None`` * ``AXES_VERBOSE``: If ``True``, you'll see slightly more logging for Axes. Default: ``True`` @@ -100,10 +100,10 @@ following settings to suit your set up to correctly resolve client IP addresses: Configuring handlers -------------------- -Axes uses and provides handlers for processing signals and events +Axes uses handlers for processing signals and events from Django authentication and login attempts. -The following handlers are offered by default and can be configured +The following handlers are implemented by Axes and can be configured with the ``AXES_HANDLER`` setting in project configuration: - ``axes.handlers.database.AxesDatabaseHandler`` @@ -126,6 +126,12 @@ with the ``AXES_HANDLER`` setting in project configuration: and is meant to be used on e.g. local development setups and testing deployments where login monitoring is not wanted. +To switch to cache based attempt tracking you can do the following:: + + AXES_HANDLER = 'axes.handlers.cache.AxesCacheHandler' + +See the cache configuration section for suitable cache backends. + Configuring caches ------------------ @@ -140,8 +146,10 @@ resets made in the command line might not remove lock-outs that are in a sepate processes in-memory cache such as the web server serving your login or admin page. To circumvent this problem, please use somethings else than -``django.core.cache.backends.locmem.LocMemCache`` as your -cache backend in Django cache ``BACKEND`` setting. +``django.core.cache.backends.dummy.DummyCache``, +``django.core.cache.backends.locmem.LocMemCache``, or +``django.core.cache.backends.filebased.FileBasedCache`` +as your cache backend in Django cache ``BACKEND`` setting. If changing the ``'default'`` cache is not an option, you can add a cache specifically for use with Axes. This is a two step process. First you need to diff --git a/docs/5_customization.rst b/docs/5_customization.rst index bc536cf..d08a832 100644 --- a/docs/5_customization.rst +++ b/docs/5_customization.rst @@ -1,9 +1,20 @@ .. customization: -5. Customization -================ +Customization +============= -Axes can be customized and extended by using the correct signals. +Axes has multiple options for customization including customizing the +attempt tracking and lockout handling logic and lockout response formatting. + +There are public APIs and the whole Axes tracking system is pluggable. +You can swap the authentication backend, attempt tracker, failure handlers, +database or cache backends and error formatters as you see fit. + +Check the API reference section for further inspiration on +implementing custom authentication backends, middleware, and handlers. + +Axes uses the stock Django signals for login monitoring and +can be customized and extended by using them correctly. Axes listens to the following signals from ``django.contrib.auth.signals`` to log access attempts: @@ -15,6 +26,7 @@ You can also use Axes with your own auth module, but you'll need to ensure that it sends the correct signals in order for Axes to log the access attempts. + Customizing authentication views -------------------------------- @@ -140,4 +152,4 @@ into ``my_namespace-username``: NOTE: You still have to make these modifications yourself before calling authenticate. If you want to re-use the same function for consistency, that's fine, but Axes does not inject these changes into the authentication flow -for you. \ No newline at end of file +for you. diff --git a/docs/6_integration.rst b/docs/6_integration.rst index e37b071..0157587 100644 --- a/docs/6_integration.rst +++ b/docs/6_integration.rst @@ -1,7 +1,7 @@ .. _integration: -6. Integration -============== +Integration +=========== Axes is intended to be pluggable and usable with 3rd party authentication solutions. @@ -9,7 +9,7 @@ This document describes the integration with some commonly used 3rd party packag such as Django Allauth and Django REST Framework. -Integrating with Django Allauth +Integration with Django Allauth ------------------------------- Axes relies on having login information stored under ``AXES_USERNAME_FORM_FIELD`` key @@ -64,7 +64,7 @@ You also need to decorate ``dispatch()`` and ``form_invalid()`` methods of the A ] -Integrating with Django REST Framework +Integration with Django REST Framework -------------------------------------- Modern versions of Django REST Framework after 3.7.0 work normally with Axes. @@ -108,7 +108,7 @@ require the request object to be passed for authentication. return (user, None) -Integrating with Django Simple Captcha +Integration with Django Simple Captcha -------------------------------------- Axes supports Captcha with the Django Simple Captcha package in the following manner. diff --git a/docs/7_architecture.rst b/docs/7_architecture.rst index 54ea028..320f148 100644 --- a/docs/7_architecture.rst +++ b/docs/7_architecture.rst @@ -1,7 +1,7 @@ .. _architecture: -7. Architecture -================ +Architecture +============ Axes is based on the existing Django authentication backend architecture and framework for recognizing users and aims to be diff --git a/docs/8_reference.rst b/docs/8_reference.rst index 6503eac..2efe381 100644 --- a/docs/8_reference.rst +++ b/docs/8_reference.rst @@ -1,17 +1,22 @@ .. _reference: -8. API reference -================ +API reference +============= -Axes offers extendable APIs that you can customize to your liking. -You can specialize the following base classes or alternatively implement -your own classes based on top of the following base implementations. +Axes offers extensible APIs that you can customize to your liking. +You can specialize the following base classes or alternatively use +third party modules as long as they implement the following APIs. + +.. automodule:: axes.handlers.base + :members: .. automodule:: axes.backends :members: + :show-inheritance: .. automodule:: axes.middleware :members: -.. automodule:: axes.handlers.base +.. automodule:: axes.request :members: + :show-inheritance: diff --git a/docs/9_development.rst b/docs/9_development.rst index 89ba195..89ea9de 100644 --- a/docs/9_development.rst +++ b/docs/9_development.rst @@ -1,24 +1,27 @@ .. _development: -9. Development -============== +Development +=========== You can contribute to this project forking it from GitHub and sending pull requests. +First `fork `_ the +`repository `_ and then clone it:: -Setting up a development environment ------------------------------------- + $ git clone git@github.com:/django-axes.git -Fork and clone the repository, initialize a virtual environment and install the requirements:: +Initialize a virtual environment for development purposes:: - $ git clone git@github.com:/django-axes.git - $ cd django-axes - $ mkdir ~/.virtualenvs + $ mkdir -p ~/.virtualenvs $ python3 -m venv ~/.virtualenvs/django-axes - $ source ~/.virtualenvs/bin/activate + $ source ~/.virtualenvs/django-axes/bin/activate + +Then install the necessary requirements:: + + $ cd django-axes $ pip install -r requirements.txt -Unit tests that are in the `axes/tests` folder can be run easily with the ``axes.tests.settings`` configuration:: +Unit tests are located in the ``axes/tests`` folder and can be easily run with the pytest tool:: $ pytest @@ -30,10 +33,14 @@ Mypy runs static typing checks to verify the source code type annotations and co $ mypy . -Before committing, you can run all the tests against all supported Django versions with tox:: +Before committing, you can run all the above tests against all supported Python and Django versions with tox:: $ tox -Tox runs the same tests that are run by Travis, and your code should be good to go if it passes. +Tox runs the same test set that is run by Travis, and your code should be good to go if it passes. -After you have made your changes, open a pull request on GitHub for getting your code upstreamed. +If you wish to limit the testing to specific environment(s), you can parametrize the tox run:: + + $ tox -e py37-django21 + +After you have pushed your changes, open a pull request on GitHub for getting your code upstreamed. diff --git a/docs/index.rst b/docs/index.rst index 4ba0ac9..7ae0444 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,6 +8,7 @@ Contents .. toctree:: :maxdepth: 2 + :numbered: 1_requirements 2_installation