diff --git a/axes/handlers/base.py b/axes/handlers/base.py index 5b20d89..1e26481 100644 --- a/axes/handlers/base.py +++ b/axes/handlers/base.py @@ -14,7 +14,45 @@ from axes.helpers import ( ) -class AxesHandler(ABC): # pylint: disable=unused-argument +class AbstractAxesHandler(ABC): + """ + Contract that all handlers need to follow + """ + + @abstractmethod + def user_login_failed(self, sender, credentials: dict, request=None, **kwargs): + """ + Handles the Django ``django.contrib.auth.signals.user_login_failed`` authentication signal. + """ + pass + + @abstractmethod + def user_logged_in(self, sender, request, user, **kwargs): + """ + Handles the Django ``django.contrib.auth.signals.user_logged_in`` authentication signal. + """ + pass + + @abstractmethod + def user_logged_out(self, sender, request, user, **kwargs): + """ + Handles the Django ``django.contrib.auth.signals.user_logged_out`` authentication signal. + """ + pass + + @abstractmethod + def get_failures(self, request, credentials: dict = None) -> int: + """ + Checks the number of failures associated to the given request and credentials. + + This is a virtual method that needs an implementation in the handler subclass + if the ``settings.AXES_LOCK_OUT_AT_FAILURE`` flag is set to ``True``. + """ + pass + + + +class AxesHandler(AbstractAxesHandler): # pylint: disable=unused-argument """ Handler API definition for implementations that are used by the ``AxesProxyHandler``. @@ -26,19 +64,29 @@ class AxesHandler(ABC): # pylint: disable=unused-argument .. note:: This is a virtual class and **can not be used without specialization**. """ - @abstractmethod + def reset_attempts(self, *, ip_address: str = None, username: str = None) -> int: """ Resets access attempts that match the given IP address or username. - """ - pass - @abstractmethod + :raises NotImplementedError: if the handler does not support resetting attempts. + """ + + raise NotImplementedError( + "Reset for access attempts is not supported on this backend" + ) + + def reset_logs(self, *, age_days: int = None) -> int: """ Resets access logs that are older than given number of days. + + :raises NotImplementedError: if the handler does not support resetting logs. """ - pass + + raise NotImplementedError( + "Reset for access logs is not supported on this backend" + ) def is_allowed(self, request, credentials: dict = None) -> bool: @@ -70,40 +118,29 @@ class AxesHandler(ABC): # pylint: disable=unused-argument return True - @abstractmethod - def user_login_failed(self, sender, credentials: dict, request=None, **kwargs): - """ - Handles the Django ``django.contrib.auth.signals.user_login_failed`` authentication signal. - """ - pass - - @abstractmethod - def user_logged_in(self, sender, request, user, **kwargs): - """ - Handles the Django ``django.contrib.auth.signals.user_logged_in`` authentication signal. - """ - pass - - @abstractmethod - def user_logged_out(self, sender, request, user, **kwargs): - """ - Handles the Django ``django.contrib.auth.signals.user_logged_out`` authentication signal. - """ - pass - - @abstractmethod def post_save_access_attempt(self, instance, **kwargs): """ Handles the ``axes.models.AccessAttempt`` object post save signal. - """ - pass - @abstractmethod + :raises NotImplementedError: if the handler does not support post save signal. + """ + + raise NotImplementedError( + "Post save signal callback is not supported on this backend" + ) + + def post_delete_access_attempt(self, instance, **kwargs): """ Handles the ``axes.models.AccessAttempt`` object post delete signal. + + :raises NotImplementedError: if the handler does not support post delete signal. """ - pass + + raise NotImplementedError( + "Post delete signal callback is not supported on this backend" + ) + def is_blacklisted( self, request, credentials: dict = None @@ -147,16 +184,6 @@ class AxesHandler(ABC): # pylint: disable=unused-argument return False - @abstractmethod - def get_failures(self, request, credentials: dict = None) -> int: - """ - Checks the number of failures associated to the given request and credentials. - - This is a virtual method that needs an implementation in the handler subclass - if the ``settings.AXES_LOCK_OUT_AT_FAILURE`` flag is set to ``True``. - """ - pass - def is_admin_site(self, request) -> bool: """ Checks if the request is for admin site. @@ -169,3 +196,51 @@ class AxesHandler(ABC): # pylint: disable=unused-argument return not re.match("^%s" % admin_url, request.path) return False + + + def user_login_failed(self, sender, credentials: dict, request=None, **kwargs): + """ + Handles the Django ``django.contrib.auth.signals.user_login_failed`` authentication signal. + + :raises NotImplementedError: if the handler does not support user_login_failed callback. + """ + + raise NotImplementedError( + "user_login_failed callback is not supported on this backend" + ) + + def user_logged_in(self, sender, request, user, **kwargs): + """ + Handles the Django ``django.contrib.auth.signals.user_logged_in`` authentication signal. + + :raises NotImplementedError: if the handler does not support user_logged_in callback. + """ + + raise NotImplementedError( + "user_logged_in callback is not supported on this backend" + ) + + def user_logged_out(self, sender, request, user, **kwargs): + """ + Handles the Django ``django.contrib.auth.signals.user_logged_out`` authentication signal. + + :raises NotImplementedError: if the handler does not support user_logged_out callback. + """ + + raise NotImplementedError( + "user_logged_out callback is not supported on this backend" + ) + + def get_failures(self, request, credentials: dict = None) -> int: + """ + Checks the number of failures associated to the given request and credentials. + + This is a virtual method that needs an implementation in the handler subclass + if the ``settings.AXES_LOCK_OUT_AT_FAILURE`` flag is set to ``True``. + + :raises NotImplementedError: if the handler does not support get_failures implementation. + """ + + raise NotImplementedError( + "get_failures method is not supported on this backend" + ) \ No newline at end of file diff --git a/axes/handlers/cache.py b/axes/handlers/cache.py index 38f4944..04431a4 100644 --- a/axes/handlers/cache.py +++ b/axes/handlers/cache.py @@ -25,27 +25,6 @@ class AxesCacheHandler(AxesHandler): # pylint: disable=too-many-locals self.cache = get_cache() self.cache_timeout = get_cache_timeout() - def reset_attempts(self, *, ip_address: str = None, username: str = None) -> int: - """ - Resets access attempts that match the given IP address or username. - - :raises NotImplementedError: if the handler does not support resetting attempts. - """ - - raise NotImplementedError( - "Reset for access attempts is not supported on this backend" - ) - - def reset_logs(self, *, age_days: int = None) -> int: - """ - Resets access logs that are older than given number of days. - - :raises NotImplementedError: if the handler does not support resetting logs. - """ - - raise NotImplementedError( - "Reset for access logs is not supported on this backend" - ) def get_failures(self, request, credentials: dict = None) -> int: cache_key = get_client_cache_key(request, credentials) @@ -150,27 +129,3 @@ class AxesCacheHandler(AxesHandler): # pylint: disable=too-many-locals ) log.info("AXES: Successful logout by %s.", client_str) - - - def post_save_access_attempt(self, instance, **kwargs): - """ - Handles the ``axes.models.AccessAttempt`` object post save signal. - - :raises NotImplementedError: if the handler does not support post save signal. - """ - - raise NotImplementedError( - "Post save signal callback is not supported on this backend" - ) - - - def post_delete_access_attempt(self, instance, **kwargs): - """ - Handles the ``axes.models.AccessAttempt`` object post delete signal. - - :raises NotImplementedError: if the handler does not support post delete signal. - """ - - raise NotImplementedError( - "Post delete signal callback is not supported on this backend" - ) diff --git a/axes/handlers/database.py b/axes/handlers/database.py index ffe8121..d86c661 100644 --- a/axes/handlers/database.py +++ b/axes/handlers/database.py @@ -235,26 +235,3 @@ class AxesDatabaseHandler(AxesHandler): # pylint: disable=too-many-locals AccessLog.objects.filter( username=username, logout_time__isnull=True ).update(logout_time=request.axes_attempt_time) - - def post_save_access_attempt(self, instance, **kwargs): - """ - Handles the ``axes.models.AccessAttempt`` object post save signal. - - :raises NotImplementedError: if the handler does not support post save signal. - """ - - raise NotImplementedError( - "Post save signal callback is not supported on AxesDatabaseHandler" - ) - - - def post_delete_access_attempt(self, instance, **kwargs): - """ - Handles the ``axes.models.AccessAttempt`` object post delete signal. - - :raises NotImplementedError: if the handler does not support post delete signal. - """ - - raise NotImplementedError( - "Post delete signal callback is not supported on AxesDatabaseHandler" - )