mirror of
https://github.com/jazzband/django-axes.git
synced 2026-03-16 22:30:23 +00:00
281 lines
9.2 KiB
ReStructuredText
281 lines
9.2 KiB
ReStructuredText
.. _integration:
|
|
|
|
Integration
|
|
===========
|
|
|
|
Axes is intended to be pluggable and usable with custom authentication solutions.
|
|
This document describes the integration with some popular 3rd party packages
|
|
such as Django Allauth, Django REST Framework, and other tools.
|
|
|
|
In the following table
|
|
**Compatible** means that a component should be fully compatible out-of-the-box,
|
|
**Functional** means that a component should be functional after configuration, and
|
|
**Incompatible** means that a component has been reported as non-functional with Axes.
|
|
|
|
======================= ============= ============ ============ ==============
|
|
Project Version Compatible Functional Incompatible
|
|
======================= ============= ============ ============ ==============
|
|
Django REST Framework |check|
|
|
Django Allauth |check|
|
|
Django Simple Captcha |check|
|
|
Django OAuth Toolkit |check|
|
|
Django Reversion |check|
|
|
Django Auth LDAP |check|
|
|
======================= ============= ============ ============ ==============
|
|
|
|
.. |check| unicode:: U+2713
|
|
.. |lt| unicode:: U+003C
|
|
.. |lte| unicode:: U+2264
|
|
.. |gte| unicode:: U+2265
|
|
.. |gt| unicode:: U+003E
|
|
|
|
Please note that project compatibility depends on multiple different factors
|
|
such as Django version, Axes version, and 3rd party package versions and
|
|
their unique combinations per project.
|
|
|
|
.. note::
|
|
This documentation is mostly provided by Axes users.
|
|
If you have your own compatibility tweaks and customizations
|
|
that enable you to use Axes with other tools or have better
|
|
implementations than the solutions provided here, please do
|
|
feel free to open an issue or a pull request in GitHub!
|
|
|
|
|
|
Integration with Django Allauth
|
|
-------------------------------
|
|
|
|
Axes relies on having login information stored under ``AXES_USERNAME_FORM_FIELD`` key
|
|
both in ``request.POST`` and in ``credentials`` dict passed to
|
|
``user_login_failed`` signal.
|
|
|
|
This is not the case with Allauth. Allauth always uses the ``login`` key in post POST data
|
|
but it becomes ``username`` key in ``credentials`` dict in signal handler.
|
|
|
|
To overcome this you need to use custom login form that duplicates the value
|
|
of ``username`` key under a ``login`` key in that dict and set ``AXES_USERNAME_FORM_FIELD = 'login'``.
|
|
|
|
You also need to decorate ``dispatch()`` and ``form_invalid()`` methods of the Allauth login view.
|
|
|
|
``settings.py``::
|
|
|
|
AXES_USERNAME_FORM_FIELD = 'login'
|
|
|
|
``example/forms.py``::
|
|
|
|
from allauth.account.forms import LoginForm
|
|
|
|
class AxesLoginForm(LoginForm):
|
|
"""
|
|
Extended login form class that supplied the
|
|
user credentials for Axes compatibility.
|
|
"""
|
|
|
|
def user_credentials(self):
|
|
credentials = super().user_credentials()
|
|
credentials['login'] = credentials.get('email') or credentials.get('username')
|
|
return credentials
|
|
|
|
``example/urls.py``::
|
|
|
|
from django.utils.decorators import method_decorator
|
|
|
|
from allauth.account.views import LoginView
|
|
|
|
from axes.decorators import axes_dispatch
|
|
from axes.decorators import axes_form_invalid
|
|
|
|
from example.forms import AxesLoginForm
|
|
|
|
LoginView.dispatch = method_decorator(axes_dispatch)(LoginView.dispatch)
|
|
LoginView.form_invalid = method_decorator(axes_form_invalid)(LoginView.form_invalid)
|
|
|
|
urlpatterns = [
|
|
# Override allauth default login view with a patched view
|
|
path('accounts/login/', LoginView.as_view(form_class=AxesLoginForm), name='account_login'),
|
|
path('accounts/', include('allauth.urls')),
|
|
]
|
|
|
|
|
|
Integration with Django REST Framework
|
|
--------------------------------------
|
|
|
|
.. warning::
|
|
The following guide only covers authentication schemes that rely on
|
|
Django's ``authenticate()`` function. Other schemes (e.g.
|
|
``TokenAuthentication``) are currently not supported.
|
|
|
|
Django Axes requires REST Framework to be connected
|
|
via lockout signals for correct functionality.
|
|
|
|
You can use the following snippet in your project signals such as ``example/signals.py``::
|
|
|
|
from django.dispatch import receiver
|
|
|
|
from axes.signals import user_locked_out
|
|
from rest_framework.exceptions import PermissionDenied
|
|
|
|
|
|
@receiver(user_locked_out)
|
|
def raise_permission_denied(*args, **kwargs):
|
|
raise PermissionDenied("Too many failed login attempts")
|
|
|
|
And then configure your application to load it in ``examples/apps.py``::
|
|
|
|
from django import apps
|
|
|
|
|
|
class AppConfig(apps.AppConfig):
|
|
name = "example"
|
|
|
|
def ready(self):
|
|
from example import signals # noqa
|
|
|
|
Please check the Django signals documentation for more information:
|
|
|
|
https://docs.djangoproject.com/en/3.2/topics/signals/
|
|
|
|
When a user login fails a signal is emitted and PermissionDenied
|
|
raises a HTTP 403 reply which interrupts the login process.
|
|
|
|
This functionality was handled in the middleware for a time,
|
|
but that resulted in extra database requests being made for
|
|
each and every web request, and was migrated to signals.
|
|
|
|
|
|
Integration with Django Simple Captcha
|
|
--------------------------------------
|
|
|
|
Axes supports Captcha with the Django Simple Captcha package in the following manner.
|
|
|
|
``settings.py``::
|
|
|
|
AXES_LOCKOUT_URL = '/locked'
|
|
|
|
``example/urls.py``::
|
|
|
|
url(r'^locked/$', locked_out, name='locked_out'),
|
|
|
|
``example/forms.py``::
|
|
|
|
class AxesCaptchaForm(forms.Form):
|
|
captcha = CaptchaField()
|
|
|
|
``example/views.py``::
|
|
|
|
from axes.utils import reset_request
|
|
from django.http.response import HttpResponseRedirect
|
|
from django.shortcuts import render
|
|
from django.urls import reverse_lazy
|
|
|
|
from .forms import AxesCaptchaForm
|
|
|
|
|
|
def locked_out(request):
|
|
if request.POST:
|
|
form = AxesCaptchaForm(request.POST)
|
|
if form.is_valid():
|
|
reset_request(request)
|
|
return HttpResponseRedirect(reverse_lazy('auth_login'))
|
|
else:
|
|
form = AxesCaptchaForm()
|
|
|
|
return render(request, 'accounts/captcha.html', {'form': form})
|
|
|
|
``example/templates/example/captcha.html``::
|
|
|
|
<form action="" method="post">
|
|
{% csrf_token %}
|
|
|
|
{{ form.captcha.errors }}
|
|
{{ form.captcha }}
|
|
|
|
<div class="form-actions">
|
|
<input type="submit" value="Submit" />
|
|
</div>
|
|
</form>
|
|
|
|
|
|
Integration with Django OAuth Toolkit
|
|
-------------------------------------
|
|
|
|
Django OAuth toolkit is not designed to work with Axes,
|
|
but some users have reported that they have configured
|
|
validator classes to function correctly.
|
|
|
|
|
|
``example/validators.py``::
|
|
|
|
from django.contrib.auth import authenticate
|
|
from django.http import HttpRequest, QueryDict
|
|
|
|
from oauth2_provider.oauth2_validators import OAuth2Validator
|
|
|
|
from axes.helpers import get_client_ip_address, get_client_user_agent
|
|
|
|
|
|
class AxesOAuth2Validator(OAuth2Validator):
|
|
def validate_user(self, username, password, client, request, *args, **kwargs):
|
|
"""
|
|
Check username and password correspond to a valid and active User
|
|
|
|
Set defaults for necessary request object attributes for Axes compatibility.
|
|
The ``request`` argument is not a Django ``HttpRequest`` object.
|
|
"""
|
|
|
|
_request = request
|
|
if request and not isinstance(request, HttpRequest):
|
|
request = HttpRequest()
|
|
|
|
request.uri = _request.uri
|
|
request.method = request.http_method = _request.http_method
|
|
request.META = request.headers = _request.headers
|
|
request._params = _request._params
|
|
request.decoded_body = _request.decoded_body
|
|
|
|
request.axes_ip_address = get_client_ip_address(request)
|
|
request.axes_user_agent = get_client_user_agent(request)
|
|
|
|
body = QueryDict(str(_request.body), mutable=True)
|
|
if request.method == 'GET':
|
|
request.GET = body
|
|
elif request.method == 'POST':
|
|
request.POST = body
|
|
|
|
user = authenticate(request=request, username=username, password=password)
|
|
if user is not None and user.is_active:
|
|
request = _request
|
|
request.user = user
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
``settings.py``::
|
|
|
|
OAUTH2_PROVIDER = {
|
|
'OAUTH2_VALIDATOR_CLASS': 'example.validators.AxesOAuth2Validator',
|
|
'SCOPES': {'read': 'Read scope', 'write': 'Write scope'},
|
|
}
|
|
|
|
|
|
Integration with Django Reversion
|
|
---------------------------------
|
|
|
|
Django Reversion is not designed to work with Axes,
|
|
but some users have reported that they have configured
|
|
a workaround with a monkeypatch function that functions correctly.
|
|
|
|
``example/monkeypatch.py``::
|
|
|
|
from django.urls import resolve
|
|
|
|
from reversion import views
|
|
|
|
def _request_creates_revision(request):
|
|
view_name = resolve(request.path_info).url_name
|
|
if view_name and view_name.endswith('login'):
|
|
return False
|
|
|
|
return request.method not in ["OPTIONS", "GET", "HEAD"]
|
|
|
|
views._request_creates_revision = _request_creates_revision
|