mirror of
https://github.com/jazzband/django-axes.git
synced 2026-04-26 17:54:47 +00:00
Merge pull request #315 from markddavidoff/add-middleware
use an authentication backend to check if user is locked out.
This commit is contained in:
commit
d08aaa5602
5 changed files with 80 additions and 9 deletions
42
axes/backends.py
Normal file
42
axes/backends.py
Normal 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
|
||||
|
|
@ -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.'
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
----------------------------
|
||||
|
|
|
|||
Loading…
Reference in a new issue