Merge pull request #315 from markddavidoff/add-middleware

use an authentication backend to check if user is locked out.
This commit is contained in:
Aleksi Häkli 2018-04-11 20:02:29 +03:00 committed by GitHub
commit d08aaa5602
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 80 additions and 9 deletions

42
axes/backends.py Normal file
View file

@ -0,0 +1,42 @@
from django.contrib.auth.backends import ModelBackend
from django.core.exceptions import PermissionDenied
from axes.attempts import is_already_locked
from axes.utils import get_lockout_message
class AxesModelBackend(ModelBackend):
class RequestParameterRequired(Exception):
msg = 'DjangoAxesModelBackend requires calls to authenticate to pass `request`'
def __init__(self):
super(AxesModelBackend.RequestParameterRequired, self).__init__(
AxesModelBackend.RequestParameterRequired.msg)
def authenticate(self, request, username=None, password=None, **kwargs):
"""
Add django-axes handling and add allow adding errors directly to a passed return_context.
Will never actually authenticate a user, just blocks locked out uses so don't use this as your only back end.
:param request: see ModelBackend.authenticate
:param username: see ModelBackend.authenticate
:param password: see ModelBackend.authenticate
:keyword response_context: context dict that will be returned/used in the response template.
NOTE: will overwrite 'error' field in dict
:param kwargs: see ModelBackend.authenticate
:raises PermissionDenied: if user is already locked out.
:return: Nothing, but will update return_context with lockout message if user is locked out.
"""
if request is None:
raise AxesModelBackend.RequestParameterRequired()
if is_already_locked(request):
# locked out, don't try to authenticate, just update return_context and return
# Its a bit weird to pass a context and expect a response value but its nice to get a "why" back.
error_msg = get_lockout_message()
response_context = kwargs.get('response_context', {})
response_context['error'] = error_msg
raise PermissionDenied(error_msg)
# No-op

View file

@ -47,3 +47,9 @@ class MyAppConf(AppConf):
IP_WHITELIST = None
IP_BLACKLIST = None
# message to show when locked out and have cooloff enabled
AXES_COOLOFF_MESSAGE = 'Account locked: too many login attempts. Please try again later'
# message to show when locked out and have cooloff disabled
AXES_PERMALOCK_MESSAGE = 'Account locked: too many login attempts. Contact an admin to unlock your account.'

View file

@ -10,8 +10,7 @@ from django.shortcuts import render
from axes import get_version
from axes.conf import settings
from axes.attempts import is_already_locked
from axes.utils import iso8601
from axes.utils import iso8601, get_lockout_message
log = logging.getLogger(settings.AXES_LOGGER)
if settings.AXES_VERBOSE:
@ -77,10 +76,6 @@ def lockout_response(request):
return HttpResponseRedirect(settings.AXES_LOCKOUT_URL)
else:
msg = 'Account locked: too many login attempts. {0}'
if settings.AXES_COOLOFF_TIME:
msg = msg.format('Please try again later.')
else:
msg = msg.format('Contact an admin to unlock your account.')
msg = get_lockout_message()
return HttpResponse(msg, status=403)

View file

@ -92,3 +92,10 @@ def iso8601(timestamp):
if value]
)
return 'P' + date + ('T' + time if time else '')
def get_lockout_message():
if settings.AXES_COOLOFF_TIME:
return settings.AXES_COOLOFF_MESSAGE
else:
return settings.AXES_PERMALOCK_MESSAGE

View file

@ -3,7 +3,9 @@
Configuration
=============
Just add `axes` to your ``INSTALLED_APPS``::
3 Simple Steps!
- add `axes` to your ``INSTALLED_APPS``::
INSTALLED_APPS = (
'django.contrib.admin',
@ -16,7 +18,26 @@ Just add `axes` to your ``INSTALLED_APPS``::
...
)
Remember to run ``python manage.py migrate`` to sync the database.
- add Axes to the top of ``AUTHENTICATION_BACKENDS``::
AUTHENTICATION_BACKENDS = [
'axes.middleware.DjangoAxesAuthBackend',
...
'django.contrib.auth.backends.ModelBackend',
...
]
- run ``python manage.py migrate`` to sync the database.
Things to you might need to change in your code, especially if you get a ``AxesModelBackend.RequestParameterRequired``:
- make sure any calls to ``django.contrib.auth.authenticate`` pass the request.
- make sure any auth libraries you use that call the authentication middleware stack pass request. Notably Django Rest
Framework (DRF) ``BasicAuthentication`` does not pass request. `Here is an example workaround for DRF`_.
.. _Here is an example workaround for DRF: https://gist.github.com/markddavidoff/7e442b1ea2a2e68d390e76731c35afe7
Known configuration problems
----------------------------