mirror of
https://github.com/jazzband/django-axes.git
synced 2026-05-11 09:03:12 +00:00
Merge ef91ba9b61 into e27ce891ea
This commit is contained in:
commit
e4842012fd
3 changed files with 50 additions and 2 deletions
|
|
@ -461,6 +461,12 @@ def get_lockout_message() -> str:
|
|||
return settings.AXES_PERMALOCK_MESSAGE
|
||||
|
||||
|
||||
def _set_retry_after_header(response: HttpResponse, request: HttpRequest) -> None:
|
||||
cool_off = get_cool_off(request)
|
||||
if cool_off is not None:
|
||||
response["Retry-After"] = str(int(cool_off.total_seconds()))
|
||||
|
||||
|
||||
def get_lockout_response(
|
||||
request: HttpRequest,
|
||||
original_response: Optional[HttpResponse] = None,
|
||||
|
|
@ -513,10 +519,15 @@ def get_lockout_response(
|
|||
json_response["Access-Control-Allow-Headers"] = (
|
||||
"Origin, Content-Type, Accept, Authorization, x-requested-with"
|
||||
)
|
||||
_set_retry_after_header(json_response, request)
|
||||
return json_response
|
||||
|
||||
if settings.AXES_LOCKOUT_TEMPLATE:
|
||||
return render(request, settings.AXES_LOCKOUT_TEMPLATE, context, status=status)
|
||||
response = render(
|
||||
request, settings.AXES_LOCKOUT_TEMPLATE, context, status=status
|
||||
)
|
||||
_set_retry_after_header(response, request)
|
||||
return response
|
||||
|
||||
if settings.AXES_LOCKOUT_URL:
|
||||
lockout_url = settings.AXES_LOCKOUT_URL
|
||||
|
|
@ -524,7 +535,9 @@ def get_lockout_response(
|
|||
url = f"{lockout_url}?{query_string}"
|
||||
return redirect(url)
|
||||
|
||||
return HttpResponse(get_lockout_message(), status=status)
|
||||
response = HttpResponse(get_lockout_message(), status=status)
|
||||
_set_retry_after_header(response, request)
|
||||
return response
|
||||
|
||||
|
||||
def is_ip_address_in_whitelist(ip_address: str) -> bool:
|
||||
|
|
|
|||
|
|
@ -86,6 +86,13 @@ The following ``settings.py`` options are available for customizing Axes behavio
|
|||
| AXES_LOCKOUT_PARAMETERS | ["ip_address"] | A list of parameters that Axes uses to lock out users. It can also be callable, which takes an http request or AccesAttempt object and credentials and returns a list of parameters. Each parameter can be a string (a single parameter) or a list of strings (a combined parameter). For example, if you configure ``AXES_LOCKOUT_PARAMETERS = ["ip_address", ["username", "user_agent"]]``, axes will block clients by ip and/or username and user agent combination. See :ref:`customizing-lockout-parameters` for more details. |
|
||||
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
|
||||
.. note::
|
||||
When ``AXES_COOLOFF_TIME`` is configured, lockout responses automatically include a
|
||||
``Retry-After`` HTTP header (`RFC 7231 <https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.3>`_)
|
||||
with the cool-off duration in seconds. This applies to JSON, template-rendered, and
|
||||
plain-text lockout responses, but not to redirects (``AXES_LOCKOUT_URL``) or custom
|
||||
callables (``AXES_LOCKOUT_CALLABLE``).
|
||||
|
||||
The configuration option precedences for the access attempt monitoring are:
|
||||
|
||||
1. Default: only use IP address.
|
||||
|
|
|
|||
|
|
@ -946,6 +946,34 @@ class LockoutResponseTestCase(AxesTestCase):
|
|||
response = get_lockout_response(request=self.request)
|
||||
self.assertEqual(type(response), HttpResponse)
|
||||
|
||||
@override_settings(AXES_COOLOFF_TIME=2)
|
||||
def test_get_lockout_response_retry_after_header(self):
|
||||
response = get_lockout_response(request=self.request)
|
||||
self.assertEqual(response["Retry-After"], "7200")
|
||||
|
||||
@override_settings(AXES_COOLOFF_TIME=None)
|
||||
def test_get_lockout_response_retry_after_no_cooloff(self):
|
||||
response = get_lockout_response(request=self.request)
|
||||
self.assertFalse(response.has_header("Retry-After"))
|
||||
|
||||
@override_settings(AXES_COOLOFF_TIME=2)
|
||||
def test_get_lockout_response_retry_after_json(self):
|
||||
self.request.META["HTTP_X_REQUESTED_WITH"] = "XMLHttpRequest"
|
||||
response = get_lockout_response(request=self.request)
|
||||
self.assertEqual(response["Retry-After"], "7200")
|
||||
|
||||
@override_settings(AXES_COOLOFF_TIME=2, AXES_LOCKOUT_TEMPLATE="example.html")
|
||||
@patch("axes.helpers.render")
|
||||
def test_get_lockout_response_retry_after_template(self, mock_render):
|
||||
mock_render.return_value = HttpResponse(status=429)
|
||||
response = get_lockout_response(request=self.request)
|
||||
self.assertEqual(response["Retry-After"], "7200")
|
||||
|
||||
@override_settings(AXES_COOLOFF_TIME=2, AXES_LOCKOUT_URL="https://example.com")
|
||||
def test_get_lockout_response_retry_after_redirect_absent(self):
|
||||
response = get_lockout_response(request=self.request)
|
||||
self.assertFalse(response.has_header("Retry-After"))
|
||||
|
||||
|
||||
def mock_get_cool_off_str(req):
|
||||
return timedelta(seconds=30)
|
||||
|
|
|
|||
Loading…
Reference in a new issue