mirror of
https://github.com/jazzband/django-axes.git
synced 2026-03-16 22:30:23 +00:00
parent
7f95777e28
commit
d3da797020
4 changed files with 69 additions and 15 deletions
|
|
@ -41,6 +41,9 @@ class AxesAppConf(AppConf):
|
|||
# determine if given user should be always allowed to attempt authentication
|
||||
WHITELIST_CALLABLE = None
|
||||
|
||||
# return custom lockout response if configured
|
||||
LOCKOUT_CALLABLE = None
|
||||
|
||||
# reset the number of failed attempts after one successful attempt
|
||||
RESET_ON_SUCCESS = False
|
||||
|
||||
|
|
|
|||
|
|
@ -308,6 +308,15 @@ def get_lockout_message() -> str:
|
|||
|
||||
|
||||
def get_lockout_response(request, credentials: dict = None) -> HttpResponse:
|
||||
if settings.AXES_LOCKOUT_CALLABLE:
|
||||
if callable(settings.AXES_LOCKOUT_CALLABLE):
|
||||
return settings.AXES_LOCKOUT_CALLABLE(request, credentials)
|
||||
if isinstance(settings.AXES_LOCKOUT_CALLABLE, str):
|
||||
return import_string(settings.AXES_LOCKOUT_CALLABLE)(request, credentials)
|
||||
raise TypeError(
|
||||
"settings.AXES_LOCKOUT_CALLABLE needs to be a string, callable, or None."
|
||||
)
|
||||
|
||||
status = 403
|
||||
context = {
|
||||
"failure_limit": get_failure_limit(request, credentials),
|
||||
|
|
|
|||
|
|
@ -1,22 +1,18 @@
|
|||
from datetime import timedelta
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.http import HttpRequest
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.test import override_settings
|
||||
|
||||
from axes.helpers import get_cool_off, is_user_attempt_whitelisted
|
||||
from axes.helpers import get_cool_off, get_lockout_response, is_user_attempt_whitelisted
|
||||
from axes.tests.base import AxesTestCase
|
||||
|
||||
|
||||
def get_cool_off_str():
|
||||
def mock_get_cool_off_str():
|
||||
return timedelta(seconds=30)
|
||||
|
||||
|
||||
def is_whitelisted(request, credentials):
|
||||
return True
|
||||
|
||||
|
||||
class AxesHelpersTestCase(AxesTestCase):
|
||||
class AxesCoolOffTestCase(AxesTestCase):
|
||||
@override_settings(AXES_COOLOFF_TIME=None)
|
||||
def test_get_cool_off_none(self):
|
||||
self.assertIsNone(get_cool_off())
|
||||
|
|
@ -29,12 +25,18 @@ class AxesHelpersTestCase(AxesTestCase):
|
|||
def test_get_cool_off_callable(self):
|
||||
self.assertEqual(get_cool_off(), timedelta(seconds=30))
|
||||
|
||||
@override_settings(AXES_COOLOFF_TIME="axes.tests.test_helpers.get_cool_off_str")
|
||||
def test_get_cool_off_str(self):
|
||||
@override_settings(
|
||||
AXES_COOLOFF_TIME="axes.tests.test_helpers.mock_get_cool_off_str"
|
||||
)
|
||||
def test_get_cool_off_path(self):
|
||||
self.assertEqual(get_cool_off(), timedelta(seconds=30))
|
||||
|
||||
|
||||
class UserWhitelistTestCase(AxesTestCase):
|
||||
def mock_is_whitelisted(request, credentials):
|
||||
return True
|
||||
|
||||
|
||||
class AxesWhitelistTestCase(AxesTestCase):
|
||||
def setUp(self):
|
||||
self.user_model = get_user_model()
|
||||
self.user = self.user_model.objects.create(username="jane.doe")
|
||||
|
|
@ -44,11 +46,13 @@ class UserWhitelistTestCase(AxesTestCase):
|
|||
def test_is_whitelisted(self):
|
||||
self.assertFalse(is_user_attempt_whitelisted(self.request, self.credentials))
|
||||
|
||||
@override_settings(AXES_WHITELIST_CALLABLE=is_whitelisted)
|
||||
def test_is_whitelisted_override(self):
|
||||
@override_settings(AXES_WHITELIST_CALLABLE=mock_is_whitelisted)
|
||||
def test_is_whitelisted_override_callable(self):
|
||||
self.assertTrue(is_user_attempt_whitelisted(self.request, self.credentials))
|
||||
|
||||
@override_settings(AXES_WHITELIST_CALLABLE="axes.tests.test_helpers.is_whitelisted")
|
||||
@override_settings(
|
||||
AXES_WHITELIST_CALLABLE="axes.tests.test_helpers.mock_is_whitelisted"
|
||||
)
|
||||
def test_is_whitelisted_override_path(self):
|
||||
self.assertTrue(is_user_attempt_whitelisted(self.request, self.credentials))
|
||||
|
||||
|
|
@ -56,3 +60,34 @@ class UserWhitelistTestCase(AxesTestCase):
|
|||
def test_is_whitelisted_override_invalid(self):
|
||||
with self.assertRaises(TypeError):
|
||||
is_user_attempt_whitelisted(self.request, self.credentials)
|
||||
|
||||
|
||||
def mock_get_lockout_response(request, credentials):
|
||||
return HttpResponse(status=400)
|
||||
|
||||
|
||||
class AxesLockoutTestCase(AxesTestCase):
|
||||
def setUp(self):
|
||||
self.request = HttpRequest()
|
||||
self.credentials = dict()
|
||||
|
||||
def test_get_lockout_response(self):
|
||||
response = get_lockout_response(self.request, self.credentials)
|
||||
self.assertEqual(403, response.status_code)
|
||||
|
||||
@override_settings(AXES_LOCKOUT_CALLABLE=mock_get_lockout_response)
|
||||
def test_get_lockout_response_override_callable(self):
|
||||
response = get_lockout_response(self.request, self.credentials)
|
||||
self.assertEqual(400, response.status_code)
|
||||
|
||||
@override_settings(
|
||||
AXES_LOCKOUT_CALLABLE="axes.tests.test_helpers.mock_get_lockout_response"
|
||||
)
|
||||
def test_get_lockout_response_override_path(self):
|
||||
response = get_lockout_response(self.request, self.credentials)
|
||||
self.assertEqual(400, response.status_code)
|
||||
|
||||
@override_settings(AXES_LOCKOUT_CALLABLE=42)
|
||||
def test_get_lockout_response_override_invalid(self):
|
||||
with self.assertRaises(TypeError):
|
||||
get_lockout_response(self.request, self.credentials)
|
||||
|
|
|
|||
|
|
@ -80,7 +80,14 @@ The following ``settings.py`` options are available for customizing Axes behavio
|
|||
two arguments for whitelisting determination and returns True,
|
||||
if user should be whitelisted:
|
||||
``def is_whilisted(request: HttpRequest, credentials: dict) -> bool: ...``.
|
||||
This can be any callable similarly to ``AXES_USERNAME_CALLABLE``
|
||||
This can be any callable similarly to ``AXES_USERNAME_CALLABLE``.
|
||||
Default: ``None``
|
||||
* ``AXES_LOCKOUT_CALLABLE``: A callable or a string path to callable that takes
|
||||
two arguments returns a response. For example:
|
||||
``def generate_lockout_response(request: HttpRequest, credentials: dict) -> HttpResponse: ...``.
|
||||
This can be any callable similarly to ``AXES_USERNAME_CALLABLE``.
|
||||
If not callable is defined, then the default implementation in ``axes.helpers.get_lockout_response``
|
||||
is used for determining the correct lockout response that is sent to the requesting client.
|
||||
Default: ``None``
|
||||
* ``AXES_PASSWORD_FORM_FIELD``: the name of the form or credentials field that contains your users password.
|
||||
Default: ``password``
|
||||
|
|
|
|||
Loading…
Reference in a new issue