mirror of
https://github.com/jazzband/django-axes.git
synced 2026-03-16 22:30:23 +00:00
Don't reset cooloff time in case of login attempt during lockout
This commit is contained in:
parent
246d884b84
commit
1015bad451
5 changed files with 46 additions and 5 deletions
|
|
@ -51,6 +51,9 @@ class AxesBackend(ModelBackend):
|
|||
response_context = kwargs.get("response_context", {})
|
||||
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
|
||||
|
||||
# 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.
|
||||
# After this error is caught by authenticate it emits a signal indicating user login failed,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ class AxesCacheHandler(AbstractAxesHandler, AxesBaseHandler):
|
|||
|
||||
def __init__(self):
|
||||
self.cache = get_cache()
|
||||
self.cache_timeout = get_cache_timeout()
|
||||
|
||||
def reset_attempts(
|
||||
self,
|
||||
|
|
@ -84,6 +83,17 @@ 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:
|
||||
request.axes_credentials = credentials
|
||||
user_locked_out.send(
|
||||
"axes",
|
||||
request=request,
|
||||
username=username,
|
||||
ip_address=request.axes_ip_address,
|
||||
)
|
||||
return
|
||||
|
||||
client_str = get_client_str(
|
||||
username,
|
||||
request.axes_ip_address,
|
||||
|
|
@ -115,7 +125,7 @@ class AxesCacheHandler(AbstractAxesHandler, AxesBaseHandler):
|
|||
cache_keys = get_client_cache_key(request, credentials)
|
||||
for cache_key in cache_keys:
|
||||
failures = self.cache.get(cache_key, default=0)
|
||||
self.cache.set(cache_key, failures + 1, self.cache_timeout)
|
||||
self.cache.set(cache_key, failures + 1, get_cache_timeout())
|
||||
|
||||
if (
|
||||
settings.AXES_LOCK_OUT_AT_FAILURE
|
||||
|
|
|
|||
|
|
@ -139,6 +139,17 @@ class AxesDatabaseHandler(AbstractAxesHandler, AxesBaseHandler):
|
|||
request,
|
||||
)
|
||||
|
||||
# If axes denied access, don't record the failed attempt as that would reset the lockout time.
|
||||
if request.axes_locked_out:
|
||||
request.axes_credentials = credentials
|
||||
user_locked_out.send(
|
||||
"axes",
|
||||
request=request,
|
||||
username=username,
|
||||
ip_address=request.axes_ip_address,
|
||||
)
|
||||
return
|
||||
|
||||
# This replaces null byte chars that crash saving failures.
|
||||
get_data = get_query_str(request.GET).replace("\0", "0x00")
|
||||
post_data = get_query_str(request.POST).replace("\0", "0x00")
|
||||
|
|
|
|||
|
|
@ -78,7 +78,8 @@ class AxesProxyHandler(AbstractAxesHandler, AxesBaseHandler):
|
|||
)
|
||||
return
|
||||
if not hasattr(request, "axes_updated"):
|
||||
request.axes_locked_out = False
|
||||
if not hasattr(request, "axes_locked_out"):
|
||||
request.axes_locked_out = False
|
||||
request.axes_attempt_time = now()
|
||||
request.axes_ip_address = get_client_ip_address(request)
|
||||
request.axes_user_agent = get_client_user_agent(request)
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@ Integration tests for the login handling.
|
|||
|
||||
TODO: Clean up the tests in this module.
|
||||
"""
|
||||
|
||||
from datetime import timedelta
|
||||
from importlib import import_module
|
||||
from time import sleep
|
||||
|
||||
from django.contrib.auth import get_user_model, login, logout
|
||||
from django.http import HttpRequest
|
||||
|
|
@ -12,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
|
||||
from axes.helpers import get_cache, make_cache_key_list, get_cool_off
|
||||
from axes.models import AccessAttempt
|
||||
from tests.base import AxesTestCase
|
||||
|
||||
|
|
@ -631,6 +632,21 @@ 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))
|
||||
def test_login_during_lockout_doesnt_reset_cool_off_time(self):
|
||||
# Lockout
|
||||
self.lockout()
|
||||
|
||||
# Attempt during lockout
|
||||
sleep_time = get_cool_off().total_seconds() / 2
|
||||
sleep(sleep_time)
|
||||
self.login()
|
||||
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)
|
||||
|
||||
|
||||
# Test the same logic with cache handler
|
||||
@override_settings(AXES_HANDLER="axes.handlers.cache.AxesCacheHandler")
|
||||
|
|
|
|||
Loading…
Reference in a new issue