2008-11-05 22:52:40 +00:00
|
|
|
from django.contrib import admin
|
2022-04-26 13:09:10 +00:00
|
|
|
from django.http import HttpRequest
|
2018-07-17 13:57:24 +00:00
|
|
|
from django.utils.translation import gettext_lazy as _
|
2013-03-16 22:13:35 +00:00
|
|
|
|
2020-09-12 13:18:47 +00:00
|
|
|
from axes.conf import settings
|
2022-03-15 09:42:24 +00:00
|
|
|
from axes.models import AccessAttempt, AccessLog, AccessFailureLog
|
2025-07-08 14:17:34 +00:00
|
|
|
from axes.handlers.database import AxesDatabaseHandler
|
2008-11-05 22:52:40 +00:00
|
|
|
|
2011-04-12 21:04:51 +00:00
|
|
|
|
2025-06-22 07:46:53 +00:00
|
|
|
class IsLockedOutFilter(admin.SimpleListFilter):
|
|
|
|
|
title = _("Locked Out")
|
|
|
|
|
parameter_name = "locked_out"
|
|
|
|
|
|
|
|
|
|
def lookups(self, request, model_admin):
|
|
|
|
|
return (
|
|
|
|
|
("yes", _("Yes")),
|
|
|
|
|
("no", _("No")),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def queryset(self, request, queryset):
|
|
|
|
|
if self.value() == "yes":
|
2026-02-11 19:54:13 +00:00
|
|
|
return queryset.filter(
|
|
|
|
|
failures_since_start__gte=settings.AXES_FAILURE_LIMIT
|
|
|
|
|
)
|
2026-02-11 20:06:59 +00:00
|
|
|
if self.value() == "no":
|
2025-06-22 07:46:53 +00:00
|
|
|
return queryset.filter(failures_since_start__lt=settings.AXES_FAILURE_LIMIT)
|
|
|
|
|
return queryset
|
|
|
|
|
|
|
|
|
|
|
2008-11-05 22:52:40 +00:00
|
|
|
class AccessAttemptAdmin(admin.ModelAdmin):
|
2025-06-22 07:10:04 +00:00
|
|
|
list_display = [
|
|
|
|
|
"attempt_time",
|
|
|
|
|
"ip_address",
|
|
|
|
|
"user_agent",
|
|
|
|
|
"username",
|
|
|
|
|
"path_info",
|
|
|
|
|
"failures_since_start",
|
|
|
|
|
]
|
2026-02-11 19:54:13 +00:00
|
|
|
|
2025-06-07 13:13:14 +00:00
|
|
|
if settings.AXES_USE_ATTEMPT_EXPIRATION:
|
2026-02-11 19:54:13 +00:00
|
|
|
list_display.append("expiration")
|
2025-05-27 19:21:50 +00:00
|
|
|
|
2019-09-28 16:27:50 +00:00
|
|
|
list_filter = ["attempt_time", "path_info"]
|
2013-03-16 22:13:35 +00:00
|
|
|
|
2025-06-22 07:46:25 +00:00
|
|
|
if isinstance(settings.AXES_FAILURE_LIMIT, int) and settings.AXES_FAILURE_LIMIT > 0:
|
|
|
|
|
# This will only add the status field if AXES_FAILURE_LIMIT is set to a positive integer
|
|
|
|
|
# Because callable failure limit requires scope of request object
|
|
|
|
|
list_display.append("status")
|
2026-02-11 19:53:12 +00:00
|
|
|
list_filter.append(IsLockedOutFilter) # type: ignore[arg-type]
|
2025-06-22 07:46:25 +00:00
|
|
|
|
2019-09-28 16:27:50 +00:00
|
|
|
search_fields = ["ip_address", "username", "user_agent", "path_info"]
|
2013-03-16 22:13:35 +00:00
|
|
|
|
2019-09-28 16:27:50 +00:00
|
|
|
date_hierarchy = "attempt_time"
|
2013-03-16 22:13:35 +00:00
|
|
|
|
2008-11-05 22:52:40 +00:00
|
|
|
fieldsets = (
|
2026-02-11 19:54:13 +00:00
|
|
|
(
|
|
|
|
|
None,
|
|
|
|
|
{"fields": ("username", "path_info", "failures_since_start", "expiration")},
|
|
|
|
|
),
|
2019-09-28 16:27:50 +00:00
|
|
|
(_("Form Data"), {"fields": ("get_data", "post_data")}),
|
|
|
|
|
(_("Meta Data"), {"fields": ("user_agent", "ip_address", "http_accept")}),
|
2008-11-05 22:52:40 +00:00
|
|
|
)
|
|
|
|
|
|
2016-08-18 10:11:28 +00:00
|
|
|
readonly_fields = [
|
2019-09-28 16:27:50 +00:00
|
|
|
"user_agent",
|
|
|
|
|
"ip_address",
|
|
|
|
|
"username",
|
|
|
|
|
"http_accept",
|
|
|
|
|
"path_info",
|
|
|
|
|
"attempt_time",
|
|
|
|
|
"get_data",
|
|
|
|
|
"post_data",
|
|
|
|
|
"failures_since_start",
|
2025-06-08 08:44:02 +00:00
|
|
|
"expiration",
|
2016-08-18 10:11:28 +00:00
|
|
|
]
|
|
|
|
|
|
2026-02-11 19:54:13 +00:00
|
|
|
actions = ["cleanup_expired_attempts"]
|
2025-07-08 14:17:34 +00:00
|
|
|
|
2026-02-11 19:54:13 +00:00
|
|
|
@admin.action(description=_("Clean up expired attempts"))
|
2026-02-11 20:06:59 +00:00
|
|
|
def cleanup_expired_attempts(self, request, queryset): # noqa
|
2025-07-08 14:17:34 +00:00
|
|
|
count = self.handler.clean_expired_user_attempts(request=request)
|
|
|
|
|
self.message_user(request, _(f"Cleaned up {count} expired access attempts."))
|
|
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
self.handler = AxesDatabaseHandler()
|
|
|
|
|
|
2022-04-26 13:09:10 +00:00
|
|
|
def has_add_permission(self, request: HttpRequest) -> bool:
|
2016-08-18 10:11:28 +00:00
|
|
|
return False
|
|
|
|
|
|
2025-06-08 08:44:02 +00:00
|
|
|
def expiration(self, obj: AccessAttempt):
|
2025-06-10 15:03:56 +00:00
|
|
|
return obj.expiration.expires_at if hasattr(obj, "expiration") else _("Not set")
|
2026-02-11 19:54:13 +00:00
|
|
|
|
2025-06-22 07:46:25 +00:00
|
|
|
def status(self, obj: AccessAttempt):
|
2026-02-11 19:54:13 +00:00
|
|
|
return (
|
|
|
|
|
f"{settings.AXES_FAILURE_LIMIT - obj.failures_since_start} "
|
|
|
|
|
+ _("Attempt Remaining")
|
|
|
|
|
if obj.failures_since_start < settings.AXES_FAILURE_LIMIT
|
|
|
|
|
else _("Locked Out")
|
|
|
|
|
)
|
|
|
|
|
|
2018-05-26 17:28:11 +00:00
|
|
|
|
2012-11-25 20:46:45 +00:00
|
|
|
class AccessLogAdmin(admin.ModelAdmin):
|
2013-03-16 22:13:35 +00:00
|
|
|
list_display = (
|
2019-09-28 16:27:50 +00:00
|
|
|
"attempt_time",
|
|
|
|
|
"logout_time",
|
|
|
|
|
"ip_address",
|
|
|
|
|
"username",
|
|
|
|
|
"user_agent",
|
|
|
|
|
"path_info",
|
2013-03-16 22:13:35 +00:00
|
|
|
)
|
|
|
|
|
|
2019-09-28 16:27:50 +00:00
|
|
|
list_filter = ["attempt_time", "logout_time", "path_info"]
|
2013-03-16 22:13:35 +00:00
|
|
|
|
2019-09-28 16:27:50 +00:00
|
|
|
search_fields = ["ip_address", "user_agent", "username", "path_info"]
|
2013-03-16 22:13:35 +00:00
|
|
|
|
2019-09-28 16:27:50 +00:00
|
|
|
date_hierarchy = "attempt_time"
|
2013-03-16 22:13:35 +00:00
|
|
|
|
2012-11-25 20:46:45 +00:00
|
|
|
fieldsets = (
|
2023-06-15 12:14:59 +00:00
|
|
|
(None, {"fields": ("username", "path_info")}),
|
2019-09-28 16:27:50 +00:00
|
|
|
(_("Meta Data"), {"fields": ("user_agent", "ip_address", "http_accept")}),
|
2012-11-25 20:46:45 +00:00
|
|
|
)
|
|
|
|
|
|
2016-08-18 10:11:28 +00:00
|
|
|
readonly_fields = [
|
2019-09-28 16:27:50 +00:00
|
|
|
"user_agent",
|
|
|
|
|
"ip_address",
|
|
|
|
|
"username",
|
|
|
|
|
"http_accept",
|
|
|
|
|
"path_info",
|
|
|
|
|
"attempt_time",
|
|
|
|
|
"logout_time",
|
2016-08-18 10:11:28 +00:00
|
|
|
]
|
|
|
|
|
|
2022-04-26 13:09:10 +00:00
|
|
|
def has_add_permission(self, request: HttpRequest) -> bool:
|
2016-08-18 10:11:28 +00:00
|
|
|
return False
|
2019-10-09 16:02:45 +00:00
|
|
|
|
|
|
|
|
|
2022-03-15 09:42:24 +00:00
|
|
|
class AccessFailureLogAdmin(admin.ModelAdmin):
|
|
|
|
|
list_display = (
|
|
|
|
|
"attempt_time",
|
|
|
|
|
"ip_address",
|
|
|
|
|
"username",
|
|
|
|
|
"user_agent",
|
|
|
|
|
"path_info",
|
|
|
|
|
"locked_out",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
list_filter = ["attempt_time", "locked_out", "path_info"]
|
|
|
|
|
|
|
|
|
|
search_fields = ["ip_address", "user_agent", "username", "path_info"]
|
|
|
|
|
|
|
|
|
|
date_hierarchy = "attempt_time"
|
|
|
|
|
|
|
|
|
|
fieldsets = (
|
2023-06-15 12:14:59 +00:00
|
|
|
(None, {"fields": ("username", "path_info")}),
|
2022-03-15 09:42:24 +00:00
|
|
|
(_("Meta Data"), {"fields": ("user_agent", "ip_address", "http_accept")}),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
readonly_fields = [
|
|
|
|
|
"user_agent",
|
|
|
|
|
"ip_address",
|
|
|
|
|
"username",
|
|
|
|
|
"http_accept",
|
|
|
|
|
"path_info",
|
|
|
|
|
"attempt_time",
|
|
|
|
|
"locked_out",
|
|
|
|
|
]
|
|
|
|
|
|
2022-04-26 13:09:10 +00:00
|
|
|
def has_add_permission(self, request: HttpRequest) -> bool:
|
2022-03-15 09:42:24 +00:00
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
2019-10-09 16:02:45 +00:00
|
|
|
if settings.AXES_ENABLE_ADMIN:
|
|
|
|
|
admin.site.register(AccessAttempt, AccessAttemptAdmin)
|
|
|
|
|
admin.site.register(AccessLog, AccessLogAdmin)
|
2022-03-15 09:42:24 +00:00
|
|
|
admin.site.register(AccessFailureLog, AccessFailureLogAdmin)
|