def test_get_cache_key_credentials(self): """ Test the cache key format. """ # Getting cache key from request ip_address = self.ip_address cache_hash_key = 'axes-{}'.format(md5(ip_address.encode()).hexdigest()) request_factory = RequestFactory() request = request_factory.post('/admin/login/', data={ 'username': self.username, 'password': '******' }) # Difference between the upper test: new call signature with credentials credentials = {'username': self.username} self.assertEqual(cache_hash_key, get_client_cache_key(request, credentials)) # Getting cache key from AccessAttempt Object attempt = AccessAttempt( user_agent='<unknown>', ip_address=ip_address, username=self.username, get_data='', post_data='', http_accept=request.META.get('HTTP_ACCEPT', '<unknown>'), path_info=request.META.get('PATH_INFO', '<unknown>'), failures_since_start=0, ) self.assertEqual(cache_hash_key, get_client_cache_key(attempt))
def test_get_cache_key(self): """ Test the cache key format. """ cache_hash_digest = md5(self.ip_address.encode()).hexdigest() # Getting cache key from request cache_hash_key = f'axes-{cache_hash_digest}' request_factory = RequestFactory() request = request_factory.post( '/admin/login/', data={ 'username': self.username, 'password': '******', }, ) self.assertEqual(cache_hash_key, get_client_cache_key(request)) # Getting cache key from AccessAttempt Object attempt = AccessAttempt( user_agent='<unknown>', ip_address=self.ip_address, username=self.username, get_data='', post_data='', http_accept=request.META.get('HTTP_ACCEPT', '<unknown>'), path_info=request.META.get('PATH_INFO', '<unknown>'), failures_since_start=0, ) self.assertEqual(cache_hash_key, get_client_cache_key(attempt))
def test_get_cache_key(self): """ Test the cache key format. """ cache_hash_digest = md5(self.ip_address.encode()).hexdigest() cache_hash_key = f'axes-{cache_hash_digest}' # Getting cache key from request request_factory = RequestFactory() request = request_factory.post( '/admin/login/', data={ 'username': self.username, 'password': '******', }, ) self.assertEqual(cache_hash_key, get_client_cache_key(request)) # Getting cache key from AccessAttempt Object attempt = AccessAttempt( user_agent='<unknown>', ip_address=self.ip_address, username=self.username, get_data='', post_data='', http_accept=request.META.get('HTTP_ACCEPT', '<unknown>'), path_info=request.META.get('PATH_INFO', '<unknown>'), failures_since_start=0, ) self.assertEqual(cache_hash_key, get_client_cache_key(attempt))
def test_get_cache_key_empty_ip_address(self): """ Simulate an empty IP address in the request. """ empty_ip_address = "" cache_hash_digest = md5(empty_ip_address.encode()).hexdigest() cache_hash_key = f"axes-{cache_hash_digest}" # Getting cache key from request request_factory = RequestFactory() request = request_factory.post( "/admin/login/", data={"username": self.username, "password": "******"}, REMOTE_ADDR=empty_ip_address, ) self.assertEqual(cache_hash_key, get_client_cache_key(request)) # Getting cache key from AccessAttempt Object attempt = AccessAttempt( user_agent="<unknown>", ip_address=empty_ip_address, username=self.username, get_data="", post_data="", http_accept=request.META.get("HTTP_ACCEPT", "<unknown>"), path_info=request.META.get("PATH_INFO", "<unknown>"), failures_since_start=0, ) self.assertEqual(cache_hash_key, get_client_cache_key(attempt))
def test_get_cache_key_credentials(self): """ Test the cache key format. """ ip_address = self.ip_address cache_hash_digest = md5(ip_address.encode()).hexdigest() cache_hash_key = f"axes-{cache_hash_digest}" # Getting cache key from request request_factory = RequestFactory() request = request_factory.post("/admin/login/", data={ "username": self.username, "password": "******" }) # Difference between the upper test: new call signature with credentials credentials = {"username": self.username} self.assertEqual([cache_hash_key], get_client_cache_key(request, credentials)) # Getting cache key from AccessAttempt Object attempt = AccessAttempt( user_agent="<unknown>", ip_address=ip_address, username=self.username, get_data="", post_data="", http_accept=request.META.get("HTTP_ACCEPT", "<unknown>"), path_info=request.META.get("PATH_INFO", "<unknown>"), failures_since_start=0, ) self.assertEqual([cache_hash_key], get_client_cache_key(attempt))
def user_logged_in(self, sender, request: AxesHttpRequest, user, **kwargs): # pylint: disable=unused-argument """ When user logs in, update the AccessLog related to the user. """ if not hasattr(request, 'axes_attempt_time'): log.error( 'AXES: AxesCacheHandler.user_logged_in needs a valid AxesHttpRequest object.' ) return username = user.get_username() credentials = get_credentials(username) client_str = get_client_str(username, request.axes_ip_address, request.axes_user_agent, request.axes_path_info) log.info('AXES: Successful login by %s.', client_str) if settings.AXES_RESET_ON_SUCCESS: cache_key = get_client_cache_key(request, credentials) failures_since_start = self.cache.get(cache_key, default=0) self.cache.delete(cache_key) log.info( 'AXES: Deleted %d failed login attempts by %s from cache.', failures_since_start, client_str)
def reset_attempts( self, *, ip_address: str = None, username: str = None, ip_or_username: bool = False, ) -> int: cache_keys: list = [] count = 0 if ip_address is None and username is None: raise NotImplementedError("Cannot clear all entries from cache") if ip_or_username: raise NotImplementedError( "Due to the cache key ip_or_username=True is not supported") cache_keys.extend( get_client_cache_key( AccessAttempt(username=username, ip_address=ip_address))) for cache_key in cache_keys: deleted = self.cache.delete(cache_key) count += int(deleted) if deleted is not None else 1 log.info("AXES: Reset %d access attempts from database.", count) return count
def user_logged_in(self, sender, request, user, **kwargs): # pylint: disable=unused-argument """ When user logs in, update the AccessLog related to the user. """ username = user.get_username() credentials = get_credentials(username) client_str = get_client_str( username, request.axes_ip_address, request.axes_user_agent, request.axes_path_info, request, ) log.info("AXES: Successful login by %s.", client_str) if settings.AXES_RESET_ON_SUCCESS: cache_keys = get_client_cache_key(request, credentials) for cache_key in cache_keys: failures_since_start = self.cache.get(cache_key, default=0) self.cache.delete(cache_key) log.info( "AXES: Deleted %d failed login attempts by %s from cache.", failures_since_start, client_str, )
def user_login_failed( self, sender, credentials: dict, request: AxesHttpRequest = None, **kwargs ): # pylint: disable=too-many-locals """ When user login fails, save attempt record in cache and lock user out if necessary. :raises AxesSignalPermissionDenied: if user should be locked out. """ if request is None: log.error('AXES: AxesCacheHandler.user_login_failed does not function without a request.') return if not hasattr(request, 'axes_attempt_time'): log.error('AXES: AxesCacheHandler.user_login_failed needs a valid AxesHttpRequest object.') return username = get_client_username(request, credentials) client_str = get_client_str(username, request.axes_ip_address, request.axes_user_agent, request.axes_path_info) if self.is_whitelisted(request, credentials): log.info('AXES: Login failed from whitelisted client %s.', client_str) return failures_since_start = 1 + self.get_failures(request, credentials) if failures_since_start > 1: log.warning( 'AXES: Repeated login failure by %s. Count = %d of %d. Updating existing record in the cache.', client_str, failures_since_start, settings.AXES_FAILURE_LIMIT, ) else: log.warning( 'AXES: New login failure by %s. Creating new record in the cache.', client_str, ) cache_key = get_client_cache_key(request, credentials) self.cache.set(cache_key, failures_since_start, self.cache_timeout) if failures_since_start >= settings.AXES_FAILURE_LIMIT: log.warning('AXES: Locking out %s after repeated login failures.', client_str) user_locked_out.send( 'axes', request=request, username=username, ip_address=request.axes_ip_address, ) raise AxesSignalPermissionDenied('Locked out due to repeated login failures.')
def user_login_failed(self, sender, credentials, request=None, **kwargs): # pylint: disable=too-many-locals """ When user login fails, save attempt record in cache and lock user out if necessary. :raises AxesSignalPermissionDenied: if user should be locked out. """ if request is None: log.error( 'AXES: AxesCacheHandler.user_login_failed does not function without a request.' ) return username = get_client_username(request, credentials) ip_address = get_client_ip_address(request) user_agent = get_client_user_agent(request) path_info = get_client_path_info(request) client_str = get_client_str(username, ip_address, user_agent, path_info) if self.is_whitelisted(request, credentials): log.info('AXES: Login failed from whitelisted client %s.', client_str) return failures_since_start = 1 + self.get_failures(request, credentials) if failures_since_start > 1: log.warning( 'AXES: Repeated login failure by %s. Count = %d of %d. Updating existing record in the cache.', client_str, failures_since_start, settings.AXES_FAILURE_LIMIT, ) else: log.warning( 'AXES: New login failure by %s. Creating new record in the cache.', client_str, ) cache_key = get_client_cache_key(request, credentials) self.cache.set(cache_key, failures_since_start, self.cache_timeout) if failures_since_start >= settings.AXES_FAILURE_LIMIT: log.warning('AXES: Locking out %s after repeated login failures.', client_str) user_locked_out.send( 'axes', request=request, username=username, ip_address=ip_address, ) raise AxesSignalPermissionDenied( 'Locked out due to repeated login failures.')
def user_logged_in(self, sender, request: AxesHttpRequest, user, **kwargs): # pylint: disable=unused-argument """ When user logs in, update the AccessLog related to the user. """ if not hasattr(request, 'axes_attempt_time'): log.error('AXES: AxesCacheHandler.user_logged_in needs a valid AxesHttpRequest object.') return username = user.get_username() credentials = get_credentials(username) client_str = get_client_str(username, request.axes_ip_address, request.axes_user_agent, request.axes_path_info) log.info('AXES: Successful login by %s.', client_str) if settings.AXES_RESET_ON_SUCCESS: cache_key = get_client_cache_key(request, credentials) failures_since_start = self.cache.get(cache_key, default=0) self.cache.delete(cache_key) log.info('AXES: Deleted %d failed login attempts by %s from cache.', failures_since_start, client_str)
def get_failures(self, request: AxesHttpRequest, credentials: dict = None) -> int: cache_key = get_client_cache_key(request, credentials) return self.cache.get(cache_key, default=0)
def get_failures(self, request, credentials=None, attempt_time=None) -> int: cache_key = get_client_cache_key(request, credentials) return self.cache.get(cache_key, default=0)
def user_login_failed(self, sender, credentials: dict, request=None, **kwargs): # pylint: disable=too-many-locals """ When user login fails, save attempt record in cache and lock user out if necessary. :raises AxesSignalPermissionDenied: if user should be locked out. """ if request is None: log.error( "AXES: AxesCacheHandler.user_login_failed does not function without a request." ) return username = get_client_username(request, credentials) if settings.AXES_ONLY_USER_FAILURES and username is None: log.warning( "AXES: Username is None and AXES_ONLY_USER_FAILURES is enabled, new record will NOT be created." ) return client_str = get_client_str( username, request.axes_ip_address, request.axes_user_agent, request.axes_path_info, request, ) if self.is_whitelisted(request, credentials): log.info("AXES: Login failed from whitelisted client %s.", client_str) return failures_since_start = 1 + self.get_failures(request, credentials) request.axes_failures_since_start = failures_since_start if failures_since_start > 1: log.warning( "AXES: Repeated login failure by %s. Count = %d of %d. Updating existing record in the cache.", client_str, failures_since_start, get_failure_limit(request, credentials), ) else: log.warning( "AXES: New login failure by %s. Creating new record in the cache.", client_str, ) cache_keys = get_client_cache_key(request, credentials) for cache_key in cache_keys: failures = self.cache.get(cache_key, default=0) self.cache.set(cache_key, failures + 1, self.cache_timeout) if (settings.AXES_LOCK_OUT_AT_FAILURE and failures_since_start >= get_failure_limit( request, credentials)): log.warning("AXES: Locking out %s after repeated login failures.", client_str) request.axes_locked_out = True user_locked_out.send( "axes", request=request, username=username, ip_address=request.axes_ip_address, )
def get_failures(self, request, credentials: dict = None) -> int: cache_keys = get_client_cache_key(request, credentials) failure_count = max( self.cache.get(cache_key, default=0) for cache_key in cache_keys) return failure_count
def email_admins_on_user_locked_out(request, username, ip_address, **kwargs): """Email admins on user locked out.""" tasks.email_admins_on_user_locked_out.apply_async( [get_client_cache_key(request, get_credentials(username)), ip_address] )
def get_failures(self, request: AxesHttpRequest, credentials: dict = None) -> int: cache_key = get_client_cache_key(request, credentials) return self.cache.get(cache_key, default=0)