Fix #1159: Fallback to USERNAME_FIELD when AXES_USERNAME_FORM_FIELD not in credentials

This commit is contained in:
Adeleke Omotayo 2026-03-10 14:22:29 +00:00
parent e27ce891ea
commit 856d74aef3
2 changed files with 81 additions and 2 deletions

View file

@ -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(

View file

@ -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"