django-axes/axes/handlers/proxy.py
Aleksi Häkli 3152b4d7e9 Improve lockout and request handling
The old architecture used exceptions in the signal handler
which prevented transactions from running smoothly
and signal handlers from running after Axes handlers.

The new architecture changes the request approach to request flagging
and moves the exception handling into the middleware call method.

This allows users to more flexibly run their own signal handlers
and optionally use the Axes middleware if they want to do so.

Fixes #440
Fixes #442
2019-05-19 18:32:40 +03:00

97 lines
3.4 KiB
Python

from logging import getLogger
from django.utils.module_loading import import_string
from django.utils.timezone import now
from axes.conf import settings
from axes.handlers.base import AxesHandler
from axes.helpers import (
get_client_ip_address,
get_client_user_agent,
get_client_path_info,
get_client_http_accept,
toggleable,
)
log = getLogger(settings.AXES_LOGGER)
class AxesProxyHandler(AxesHandler):
"""
Proxy interface for configurable Axes signal handler class.
If you wish to implement a custom version of this handler,
you can override the settings.AXES_HANDLER configuration string
with a class that implements a compatible interface and methods.
Defaults to using axes.handlers.proxy.AxesProxyHandler if not overridden.
Refer to axes.handlers.proxy.AxesProxyHandler for default implementation.
"""
implementation = None # type: AxesHandler
@classmethod
def get_implementation(cls, force: bool = False) -> AxesHandler:
"""
Fetch and initialize configured handler implementation and memoize it to avoid reinitialization.
This method is re-entrant and can be called multiple times from e.g. Django application loader.
"""
if force or not cls.implementation:
cls.implementation = import_string(settings.AXES_HANDLER)()
return cls.implementation
@staticmethod
def update_request(request):
"""
Update request attributes before passing them into the selected handler class.
"""
if request is None:
log.error('AXES: AxesProxyHandler.update_request can not set request attributes to a None request')
return
request.axes_locked_out = False
request.axes_attempt_time = now()
request.axes_ip_address = get_client_ip_address(request)
request.axes_user_agent = get_client_user_agent(request)
request.axes_path_info = get_client_path_info(request)
request.axes_http_accept = get_client_http_accept(request)
@classmethod
def is_locked(cls, request, credentials: dict = None) -> bool:
cls.update_request(request)
return cls.get_implementation().is_locked(request, credentials)
@classmethod
def is_allowed(cls, request, credentials: dict = None) -> bool:
cls.update_request(request)
return cls.get_implementation().is_allowed(request, credentials)
@classmethod
@toggleable
def user_login_failed(cls, sender, credentials: dict, request=None, **kwargs):
cls.update_request(request)
return cls.get_implementation().user_login_failed(sender, credentials, request, **kwargs)
@classmethod
@toggleable
def user_logged_in(cls, sender, request, user, **kwargs):
cls.update_request(request)
return cls.get_implementation().user_logged_in(sender, request, user, **kwargs)
@classmethod
@toggleable
def user_logged_out(cls, sender, request, user, **kwargs):
cls.update_request(request)
return cls.get_implementation().user_logged_out(sender, request, user, **kwargs)
@classmethod
@toggleable
def post_save_access_attempt(cls, instance, **kwargs):
return cls.get_implementation().post_save_access_attempt(instance, **kwargs)
@classmethod
def post_delete_access_attempt(cls, instance, **kwargs):
return cls.get_implementation().post_delete_access_attempt(instance, **kwargs)