mirror of
https://github.com/jazzband/django-axes.git
synced 2026-05-19 13:01:08 +00:00
Add option to keep current behavior for cooloff reset
This commit is contained in:
parent
1015bad451
commit
9c2ceb7eb7
6 changed files with 49 additions and 8 deletions
|
|
@ -1,3 +1,4 @@
|
|||
from django.conf import settings
|
||||
from django.contrib.auth.backends import ModelBackend
|
||||
|
||||
from axes.exceptions import (
|
||||
|
|
@ -52,7 +53,8 @@ class AxesBackend(ModelBackend):
|
|||
response_context["error"] = error_msg
|
||||
|
||||
# This flag can be used later to check if it was Axes that denied the login attempt.
|
||||
request.axes_locked_out = True
|
||||
if not settings.AXES_RESET_COOL_OFF_ON_FAILURE_DURING_LOCKOUT:
|
||||
request.axes_locked_out = True
|
||||
|
||||
# Raise an error that stops the authentication flows at django.contrib.auth.authenticate.
|
||||
# This error stops bubbling up at the authenticate call which catches backend PermissionDenied errors.
|
||||
|
|
|
|||
|
|
@ -136,3 +136,8 @@ settings.AXES_CLIENT_STR_CALLABLE = getattr(settings, "AXES_CLIENT_STR_CALLABLE"
|
|||
|
||||
# set the HTTP response code given by too many requests
|
||||
settings.AXES_HTTP_RESPONSE_CODE = getattr(settings, "AXES_HTTP_RESPONSE_CODE", 403)
|
||||
|
||||
# If True, a failed login attempt during lockout will reset the cool off period
|
||||
settings.AXES_RESET_COOL_OFF_ON_FAILURE_DURING_LOCKOUT = getattr(
|
||||
settings, "AXES_RESET_COOL_OFF_ON_FAILURE_DURING_LOCKOUT", True
|
||||
)
|
||||
|
|
|
|||
|
|
@ -84,7 +84,10 @@ class AxesCacheHandler(AbstractAxesHandler, AxesBaseHandler):
|
|||
return
|
||||
|
||||
# If axes denied access, don't record the failed attempt as that would reset the lockout time.
|
||||
if request.axes_locked_out:
|
||||
if (
|
||||
not settings.AXES_RESET_COOL_OFF_ON_FAILURE_DURING_LOCKOUT
|
||||
and request.axes_locked_out
|
||||
):
|
||||
request.axes_credentials = credentials
|
||||
user_locked_out.send(
|
||||
"axes",
|
||||
|
|
|
|||
|
|
@ -140,7 +140,10 @@ class AxesDatabaseHandler(AbstractAxesHandler, AxesBaseHandler):
|
|||
)
|
||||
|
||||
# If axes denied access, don't record the failed attempt as that would reset the lockout time.
|
||||
if request.axes_locked_out:
|
||||
if (
|
||||
not settings.AXES_RESET_COOL_OFF_ON_FAILURE_DURING_LOCKOUT
|
||||
and request.axes_locked_out
|
||||
):
|
||||
request.axes_credentials = credentials
|
||||
user_locked_out.send(
|
||||
"axes",
|
||||
|
|
|
|||
|
|
@ -125,6 +125,9 @@ The following ``settings.py`` options are available for customizing Axes behavio
|
|||
reached.
|
||||
For example: ``AXES_HTTP_RESPONSE_CODE = 429``
|
||||
Default: ``403``
|
||||
* ``AXES_RESET_COOL_OFF_ON_FAILURE_DURING_LOCKOUT``: If ``True``, a failed login attempt during lockout will
|
||||
reset the cool off period.
|
||||
Default: ``True``
|
||||
|
||||
The configuration option precedences for the access attempt monitoring are:
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from django.test import override_settings, TestCase
|
|||
from django.urls import reverse
|
||||
|
||||
from axes.conf import settings
|
||||
from axes.helpers import get_cache, make_cache_key_list, get_cool_off
|
||||
from axes.helpers import get_cache, make_cache_key_list, get_cool_off, get_failure_limit
|
||||
from axes.models import AccessAttempt
|
||||
from tests.base import AxesTestCase
|
||||
|
||||
|
|
@ -632,20 +632,45 @@ class DatabaseLoginTestCase(AxesTestCase):
|
|||
response = self.client.get(reverse("admin:login"), REMOTE_ADDR=self.IP_1)
|
||||
self.assertContains(response, self.LOGIN_FORM_KEY, status_code=200, html=True)
|
||||
|
||||
@override_settings(AXES_COOLOFF_TIME=timedelta(seconds=1))
|
||||
@override_settings(
|
||||
AXES_COOLOFF_TIME=timedelta(seconds=1),
|
||||
AXES_RESET_COOL_OFF_ON_FAILURE_DURING_LOCKOUT=False,
|
||||
AXES_FAILURE_LIMIT=2,
|
||||
)
|
||||
def test_login_during_lockout_doesnt_reset_cool_off_time(self):
|
||||
# Lockout
|
||||
self.lockout()
|
||||
for _ in range(get_failure_limit(None, None)):
|
||||
self.login(self.USER_1)
|
||||
|
||||
# Attempt during lockout
|
||||
sleep_time = get_cool_off().total_seconds() / 2
|
||||
sleep(sleep_time)
|
||||
self.login()
|
||||
self.login(self.USER_1)
|
||||
sleep(sleep_time)
|
||||
|
||||
# New attempt after initial lockout period: should work
|
||||
response = self.login(is_valid_username=True, is_valid_password=True)
|
||||
self.assertNotContains(response, self.LOCKED_MESSAGE, status_code=302)
|
||||
self.assertNotContains(response, self.LOCKED_MESSAGE, status_code=self.ALLOWED)
|
||||
|
||||
@override_settings(
|
||||
AXES_COOLOFF_TIME=timedelta(seconds=1),
|
||||
AXES_RESET_COOL_OFF_ON_FAILURE_DURING_LOCKOUT=True,
|
||||
AXES_FAILURE_LIMIT=2,
|
||||
)
|
||||
def test_login_during_lockout_does_reset_cool_off_time(self):
|
||||
# Lockout
|
||||
for _ in range(get_failure_limit(None, None)):
|
||||
self.login(self.USER_1)
|
||||
|
||||
# Attempt during lockout
|
||||
sleep_time = get_cool_off().total_seconds() / 2
|
||||
sleep(sleep_time)
|
||||
self.login(self.USER_1)
|
||||
sleep(sleep_time)
|
||||
|
||||
# New attempt after initial lockout period: should not work
|
||||
response = self.login(is_valid_username=True, is_valid_password=True)
|
||||
self.assertContains(response, self.LOCKED_MESSAGE, status_code=self.BLOCKED)
|
||||
|
||||
|
||||
# Test the same logic with cache handler
|
||||
|
|
|
|||
Loading…
Reference in a new issue