from django.contrib import admin from django.http import HttpRequest from django.utils.translation import gettext_lazy as _ from axes.conf import settings from axes.models import AccessAttempt, AccessLog, AccessFailureLog from axes.handlers.database import AxesDatabaseHandler 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": return queryset.filter( failures_since_start__gte=settings.AXES_FAILURE_LIMIT ) if self.value() == "no": return queryset.filter(failures_since_start__lt=settings.AXES_FAILURE_LIMIT) return queryset class AccessAttemptAdmin(admin.ModelAdmin): list_display = [ "attempt_time", "ip_address", "user_agent", "username", "path_info", "failures_since_start", ] if settings.AXES_USE_ATTEMPT_EXPIRATION: list_display.append("expiration") list_filter = ["attempt_time", "path_info"] 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") list_filter.append(IsLockedOutFilter) # type: ignore[arg-type] search_fields = ["ip_address", "username", "user_agent", "path_info"] date_hierarchy = "attempt_time" fieldsets = ( ( None, {"fields": ("username", "path_info", "failures_since_start", "expiration")}, ), (_("Form Data"), {"fields": ("get_data", "post_data")}), (_("Meta Data"), {"fields": ("user_agent", "ip_address", "http_accept")}), ) readonly_fields = [ "user_agent", "ip_address", "username", "http_accept", "path_info", "attempt_time", "get_data", "post_data", "failures_since_start", "expiration", ] actions = ["cleanup_expired_attempts"] @admin.action(description=_("Clean up expired attempts")) def cleanup_expired_attempts(self, request, queryset): # noqa 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() def has_add_permission(self, request: HttpRequest) -> bool: return False def expiration(self, obj: AccessAttempt): return obj.expiration.expires_at if hasattr(obj, "expiration") else _("Not set") def status(self, obj: AccessAttempt): return ( f"{settings.AXES_FAILURE_LIMIT - obj.failures_since_start} " + _("Attempt Remaining") if obj.failures_since_start < settings.AXES_FAILURE_LIMIT else _("Locked Out") ) class AccessLogAdmin(admin.ModelAdmin): list_display = ( "attempt_time", "logout_time", "ip_address", "username", "user_agent", "path_info", ) list_filter = ["attempt_time", "logout_time", "path_info"] search_fields = ["ip_address", "user_agent", "username", "path_info"] date_hierarchy = "attempt_time" fieldsets = ( (None, {"fields": ("username", "path_info")}), (_("Meta Data"), {"fields": ("user_agent", "ip_address", "http_accept")}), ) readonly_fields = [ "user_agent", "ip_address", "username", "http_accept", "path_info", "attempt_time", "logout_time", ] def has_add_permission(self, request: HttpRequest) -> bool: return False 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 = ( (None, {"fields": ("username", "path_info")}), (_("Meta Data"), {"fields": ("user_agent", "ip_address", "http_accept")}), ) readonly_fields = [ "user_agent", "ip_address", "username", "http_accept", "path_info", "attempt_time", "locked_out", ] def has_add_permission(self, request: HttpRequest) -> bool: return False if settings.AXES_ENABLE_ADMIN: admin.site.register(AccessAttempt, AccessAttemptAdmin) admin.site.register(AccessLog, AccessLogAdmin) admin.site.register(AccessFailureLog, AccessFailureLogAdmin)