mirror of
https://github.com/jazzband/django-axes.git
synced 2026-03-16 22:30:23 +00:00
Merge 061f605d59 into a5d14cd630
This commit is contained in:
commit
b141f29b4a
3 changed files with 82 additions and 3 deletions
|
|
@ -5,6 +5,7 @@ from string import Template
|
|||
from typing import Callable, Optional, Type, Union, List
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.cache import BaseCache, caches
|
||||
from django.http import HttpRequest, HttpResponse, JsonResponse, QueryDict
|
||||
from django.shortcuts import redirect, render
|
||||
|
|
@ -164,14 +165,37 @@ def get_client_username(
|
|||
log.debug(
|
||||
"Using parameter credentials to get username with key settings.AXES_USERNAME_FORM_FIELD"
|
||||
)
|
||||
return credentials.get(settings.AXES_USERNAME_FORM_FIELD, None) # type: ignore[return-value]
|
||||
username = credentials.get(settings.AXES_USERNAME_FORM_FIELD, None)
|
||||
if username is None:
|
||||
# Fallback to Django's USERNAME_FIELD for compatibility with
|
||||
# Django's user_login_failed signal which uses USERNAME_FIELD as the key
|
||||
# See: https://github.com/jazzband/django-axes/issues/1159
|
||||
username_field = get_user_model().USERNAME_FIELD
|
||||
if username_field != str(settings.AXES_USERNAME_FORM_FIELD):
|
||||
log.debug(
|
||||
"Falling back to USERNAME_FIELD '%s' for credentials lookup",
|
||||
username_field,
|
||||
)
|
||||
username = credentials.get(username_field, None)
|
||||
return username # type: ignore[return-value]
|
||||
|
||||
log.debug(
|
||||
"Using parameter request.POST to get username with key settings.AXES_USERNAME_FORM_FIELD"
|
||||
)
|
||||
|
||||
request_data = getattr(request, "data", request.POST)
|
||||
return request_data.get(settings.AXES_USERNAME_FORM_FIELD, None)
|
||||
username = request_data.get(settings.AXES_USERNAME_FORM_FIELD, None)
|
||||
if username is None:
|
||||
# Fallback to Django's USERNAME_FIELD for compatibility
|
||||
# See: https://github.com/jazzband/django-axes/issues/1159
|
||||
username_field = get_user_model().USERNAME_FIELD
|
||||
if username_field != str(settings.AXES_USERNAME_FORM_FIELD):
|
||||
log.debug(
|
||||
"Falling back to USERNAME_FIELD '%s' for request data lookup",
|
||||
username_field,
|
||||
)
|
||||
username = request_data.get(username_field, None)
|
||||
return username # type: ignore[return-value]
|
||||
|
||||
|
||||
def get_client_ip_address(
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ The following ``settings.py`` options are available for customizing Axes behavio
|
|||
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| AXES_VERBOSE | True | If ``True``, you'll see slightly more logging for Axes. |
|
||||
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| AXES_USERNAME_FORM_FIELD | 'settings.AUTH_USER_MODEL.USERNAME_FIELD' | The name of the form field that contains your users usernames. |
|
||||
| AXES_USERNAME_FORM_FIELD | 'settings.AUTH_USER_MODEL.USERNAME_FIELD' | The name of the form field that contains your users usernames. When looking up the username from credentials or request data, Axes will first try this field, then fall back to Django's ``USERNAME_FIELD`` if not found. This ensures compatibility with Django's ``user_login_failed`` signal which uses ``USERNAME_FIELD``. |
|
||||
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| AXES_USERNAME_CALLABLE | None | A callable or a string path to callable that takes two arguments for user lookups: ``def get_username(request: HttpRequest, credentials: dict) -> str: ...``. This can be any callable such as ``AXES_USERNAME_CALLABLE = lambda request, credentials: 'username'`` or a full Python module path to callable such as ``AXES_USERNAME_CALLABLE = 'example.get_username``. The ``request`` is a HttpRequest like object and the ``credentials`` is a dictionary like object. ``credentials`` are the ones that were passed to Django ``authenticate()`` in the login flow. If no function is supplied, Axes fetches the username from the ``credentials`` or ``request.POST`` dictionaries based on ``AXES_USERNAME_FORM_FIELD``. |
|
||||
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
|
|
|
|||
|
|
@ -792,6 +792,61 @@ class UsernameTestCase(AxesTestCase):
|
|||
def test_get_client_username_str(self):
|
||||
self.assertEqual(get_client_username(HttpRequest(), {}), "username")
|
||||
|
||||
@override_settings(AXES_USERNAME_FORM_FIELD="auth-username")
|
||||
def test_get_client_username_fallback_to_username_field_from_credentials(self):
|
||||
"""
|
||||
Test that when AXES_USERNAME_FORM_FIELD is set to a custom value that
|
||||
doesn't exist in credentials, we fallback to Django's USERNAME_FIELD.
|
||||
|
||||
This fixes https://github.com/jazzband/django-axes/issues/1159
|
||||
"""
|
||||
expected = "test-username"
|
||||
|
||||
request = HttpRequest()
|
||||
# Credentials from Django's user_login_failed signal use USERNAME_FIELD (e.g., "username")
|
||||
# not the custom AXES_USERNAME_FORM_FIELD value
|
||||
credentials = {"username": expected}
|
||||
|
||||
actual = get_client_username(request, credentials)
|
||||
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
@override_settings(AXES_USERNAME_FORM_FIELD="auth-username")
|
||||
def test_get_client_username_fallback_to_username_field_from_request(self):
|
||||
"""
|
||||
Test that when AXES_USERNAME_FORM_FIELD is set to a custom value that
|
||||
doesn't exist in request.POST, we fallback to Django's USERNAME_FIELD.
|
||||
|
||||
This fixes https://github.com/jazzband/django-axes/issues/1159
|
||||
"""
|
||||
expected = "test-username"
|
||||
|
||||
request = HttpRequest()
|
||||
# POST data might use Django's USERNAME_FIELD instead of custom form field
|
||||
request.POST["username"] = expected
|
||||
|
||||
actual = get_client_username(request)
|
||||
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
@override_settings(AXES_USERNAME_FORM_FIELD="auth-username")
|
||||
def test_get_client_username_custom_field_takes_priority(self):
|
||||
"""
|
||||
Test that AXES_USERNAME_FORM_FIELD takes priority when it exists in credentials.
|
||||
"""
|
||||
expected = "custom-field-username"
|
||||
fallback = "username-field-value"
|
||||
|
||||
request = HttpRequest()
|
||||
credentials = {
|
||||
"auth-username": expected, # Custom field - should be used
|
||||
"username": fallback, # Django's USERNAME_FIELD - should be ignored
|
||||
}
|
||||
|
||||
actual = get_client_username(request, credentials)
|
||||
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
|
||||
def get_username(request, credentials: dict) -> str:
|
||||
return "username"
|
||||
|
|
|
|||
Loading…
Reference in a new issue