def handle_setting_changed(sender, setting, value, enter, **kwargs): # pylint: disable=unused-argument """ Reinitialize handler implementation if a relevant setting changes in e.g. application reconfiguration or during testing. """ if setting == 'AXES_HANDLER': AxesProxyHandler.get_implementation(force=True)
def authenticate(self, request: AxesHttpRequest, username: str = None, password: str = None, **kwargs: dict): """ Checks user lockout status and raises an exception if user is not allowed to log in. This method interrupts the login flow and inserts error message directly to the ``response_context`` attribute that is supplied as a keyword argument. :keyword response_context: kwarg that will be have its ``error`` attribute updated with context. :raises AxesBackendRequestParameterRequired: if request parameter is not passed. :raises AxesBackendPermissionDenied: if user is already locked out. """ if request is None: raise AxesBackendRequestParameterRequired('AxesBackend requires a request as an argument to authenticate') credentials = get_credentials(username=username, password=password, **kwargs) if AxesProxyHandler.is_allowed(request, credentials): return # Locked out, don't try to authenticate, just update response_context and return. # Its a bit weird to pass a context and expect a response value but its nice to get a "why" back. error_msg = get_lockout_message() response_context = kwargs.get('response_context', {}) response_context['error'] = error_msg # Raise an error that stops the authentication flows at django.contrib.auth.authenticate. # This error stops bubbling up at the authenticate call which catches backend PermissionDenied errors. # After this error is caught by authenticate it emits a signal indicating user login failed, # which is processed by axes.signals.log_user_login_failed which logs the attempt and raises # a second exception which bubbles up the middleware stack and produces a HTTP 403 Forbidden reply # in the axes.middleware.AxesMiddleware.process_exception middleware exception handler. raise AxesBackendPermissionDenied('AxesBackend detected that the given user is locked out')
def handle_user_login_failed(*args, **kwargs): AxesProxyHandler.user_login_failed(*args, **kwargs)
def test_is_allowed_no_lock_out(self): self.assertTrue(AxesProxyHandler.is_allowed(self.request))
def test_is_allowed_with_whitelisted_ip_address(self): self.assertTrue(AxesProxyHandler.is_allowed(self.request))
def test_handler_reset_attempts(self): self.create_attempt() self.assertEqual(1, AxesProxyHandler.reset_attempts()) self.assertFalse(AccessAttempt.objects.count())
def test_handler_reset_attempts_ip(self): self.assertEqual(2, AxesProxyHandler.reset_attempts(ip_address=self.IP_1)) self.assertEqual(AccessAttempt.objects.count(), 3) self.assertEqual( AccessAttempt.objects.filter(ip_address=self.IP_1).count(), 0)
def test_user_logged_out(self, handler): self.assertFalse(handler.user_logged_out.called) AxesProxyHandler.user_logged_out(self.sender, self.request, self.user) self.assertTrue(handler.user_logged_out.called)
def test_is_admin_site_no_admin_site(self): request = MagicMock() request.path = "/admin/" self.assertTrue(AxesProxyHandler().is_admin_site(self.request))
def test_base_handler_reset_logs_raises(self): with self.assertRaises(NotImplementedError): AxesProxyHandler.reset_logs()
def test_user_whitelisted(self, is_whitelisted): self.assertFalse(AxesProxyHandler().is_locked(self.request, self.credentials)) self.assertEqual(1, is_whitelisted.call_count)
def inner(request: AxesHttpRequest, *args, **kwargs): if AxesProxyHandler.is_allowed(request): return func(request, *args, **kwargs) return get_lockout_response(request)
def inner(self, *args, **kwargs): if AxesProxyHandler.is_allowed(self.request): return func(self, *args, **kwargs) return get_lockout_response(self.request)
def handle_post_delete_access_attempt(*args, **kwargs): AxesProxyHandler.post_delete_access_attempt(*args, **kwargs)
def handle_user_logged_out(*args, **kwargs): AxesProxyHandler.user_logged_out(*args, **kwargs)
def test_base_handler_raises_on_undefined_is_allowed_to_authenticate(self): with self.assertRaises(NotImplementedError): AxesProxyHandler.is_allowed(self.request, {})
def test_user_login_failed(self, handler): self.assertFalse(handler.user_login_failed.called) AxesProxyHandler.user_login_failed(self.sender, self.credentials, self.request) self.assertTrue(handler.user_login_failed.called)
def test_handler_reset_attempts_ip_or_username(self): with self.assertRaises(NotImplementedError): AxesProxyHandler.reset_attempts()
def test_post_delete_access_attempt(self, handler): self.assertFalse(handler.post_delete_access_attempt.called) AxesProxyHandler.post_delete_access_attempt(self.instance) self.assertTrue(handler.post_delete_access_attempt.called)
def test_user_login_failed_with_none_username(self, cache_set): credentials = {"username": None, "password": "******"} sender = MagicMock() AxesProxyHandler.user_login_failed(sender, credentials, self.request) self.assertTrue(cache_set.called)
def test_is_allowed_with_blacklisted_ip_address(self): self.assertFalse(AxesProxyHandler.is_allowed(self.request))
def check_empty_request(self, log, handler): AxesProxyHandler.user_login_failed(sender=None, credentials={}, request=None) log.error.assert_called_with( f"AXES: {handler}.user_login_failed does not function without a request." )
def test_handler_reset_logs(self): self.create_log() self.assertEqual(1, AxesProxyHandler.reset_logs()) self.assertFalse(AccessLog.objects.count())
def test_handler_reset_attempts_username(self): self.assertEqual(2, AxesProxyHandler.reset_attempts(username=self.USERNAME_1)) self.assertEqual(AccessAttempt.objects.count(), 3) self.assertEqual( AccessAttempt.objects.filter(ip_address=self.USERNAME_1).count(), 0 )
def test_is_allowed_with_whitelisted_method(self): self.request.method = "GET" self.assertTrue(AxesProxyHandler.is_allowed(self.request))
def test_user_login_failed_with_none_username(self): credentials = {"username": None, "password": "******"} sender = MagicMock() AxesProxyHandler.user_login_failed(sender, credentials, self.request) attempt = AccessAttempt.objects.all() self.assertEqual(1, AccessAttempt.objects.filter(username__isnull=True).count())
def test_only_admin_site(self): request = MagicMock() request.path = "/test/" self.assertTrue(AxesProxyHandler.is_allowed(self.request))
def test_handler_reset_logs(self): self.assertEqual(0, AxesProxyHandler.reset_logs())
def test_handler_is_allowed(self): self.assertEqual(True, AxesProxyHandler.is_allowed(self.request, {}))
def test_handler_get_failures(self): self.assertEqual(0, AxesProxyHandler.get_failures(self.request, {}))
def handle(self, *args, **options): count = AxesProxyHandler.reset_logs(age_days=options['age']) if count: self.stdout.write(f'{count} logs removed.') else: self.stdout.write('No logs found.')