def user_login_failed(self, sender, credentials: dict, request=None, **kwargs): super().user_login_failed(sender, credentials, request, **kwargs) username = credentials.get("username") user_locked = self._is_user_locked( username=credentials.get("username")) if user_locked: # Let's not create a HealthcareFailedAccessAttempt if the user has # been blocked by an admin request.axes_locked_out = True user_locked_out.send( "axes", request=request, username=username, ip_address=request.axes_ip_address, ) else: HealthcareFailedAccessAttempt.objects.create(username=username) attempts = self.get_healthcareuser_failures(request, credentials) if attempts >= settings.AXES_SLOW_FAILURE_LIMIT: request.axes_locked_out = True user_locked_out.send( "axes", request=request, username=username, ip_address=request.axes_ip_address, )
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 check_request(request, login_unsuccessful): ip_address = get_ip(request) username = request.POST.get('username', None) failures = 0 attempts = get_user_attempts(request) for attempt in attempts: failures = max(failures, attempt.failures_since_start) if login_unsuccessful: # add a failed attempt for this user failures += 1 # Create an AccessAttempt record if the login wasn't successful # has already attempted, update the info if len(attempts): for attempt in attempts: attempt.get_data = '%s\n---------\n%s' % ( attempt.get_data, query2str(request.GET.items()), ) attempt.post_data = '%s\n---------\n%s' % ( attempt.post_data, query2str(request.POST.items()) ) attempt.http_accept = request.META.get('HTTP_ACCEPT', '<unknown>') attempt.path_info = request.META.get('PATH_INFO', '<unknown>') attempt.failures_since_start = failures attempt.attempt_time = datetime.now() attempt.save() log.info('AXES: Repeated login failure by %s. Updating access ' 'record. Count = %s' % (attempt.ip_address, failures)) else: create_new_failure_records(request, failures) else: # user logged in -- forget the failed attempts failures = 0 trusted_record_exists = False for attempt in attempts: if not attempt.trusted: attempt.delete() else: trusted_record_exists = True attempt.failures_since_start = 0 attempt.save() if trusted_record_exists is False: create_new_trusted_record(request) user_lockable = is_user_lockable(request) # no matter what, we want to lock them out if they're past the number of # attempts allowed, unless the user is set to notlockable if failures > FAILURE_LIMIT and LOCK_OUT_AT_FAILURE and user_lockable: # We log them out in case they actually managed to enter the correct # password logout(request) log.warn('AXES: locked out %s after repeated login attempts.' % (ip_address,)) # send signal when someone is locked out. user_locked_out.send("axes", request=request, username=username, ip_address=ip_address) # if a trusted login has violated lockout, revoke trust for attempt in [a for a in attempts if a.trusted]: attempt.delete() create_new_failure_records(request, failures) return False return True
def user_login_failed(self, sender, credentials: dict, request=None, **kwargs): # pylint: disable=too-many-locals """ When user login fails, save AccessAttempt record in database and lock user out if necessary. :raises AxesSignalPermissionDenied: if user should be locked out. """ if request is None: log.error( "AXES: AxesDatabaseHandler.user_login_failed does not function without a request." ) return # 1. database query: Clean up expired user attempts from the database before logging new attempts clean_expired_user_attempts(request.axes_attempt_time) username = get_client_username(request, credentials) client_str = get_client_str( username, request.axes_ip_address, request.axes_user_agent, request.axes_path_info, ) # This replaces null byte chars that crash saving failures, meaning an attacker doesn't get locked out. get_data = get_query_str(request.GET).replace("\0", "0x00") post_data = get_query_str(request.POST).replace("\0", "0x00") if self.is_whitelisted(request, credentials): log.info("AXES: Login failed from whitelisted client %s.", client_str) return # 2. database query: Calculate the current maximum failure number from the existing attempts failures_since_start = 1 + self.get_failures(request, credentials) # 3. database query: Insert or update access records with the new failure data try: attempt = AccessAttempt.objects.get( username=username, ip_address=request.axes_ip_address, user_agent=request.axes_user_agent, ) # Update failed attempt information but do not touch the username, IP address, or user agent fields, # because attackers can request the site with multiple different configurations # in order to bypass the defense mechanisms that are used by the site. log.warning( "AXES: Repeated login failure by %s. Count = %d of %d. Updating existing record in the database.", client_str, attempt.failures_since_start, get_failure_limit(request, credentials), ) separator = "\n---------\n" attempt.get_data = Concat("get_data", Value(separator + get_data)) attempt.post_data = Concat("post_data", Value(separator + post_data)) attempt.http_accept = request.axes_http_accept attempt.path_info = request.axes_path_info attempt.failures_since_start += 1 attempt.attempt_time = request.axes_attempt_time attempt.save() except AccessAttempt.DoesNotExist: # Record failed attempt with all the relevant information. # Filtering based on username, IP address and user agent handled elsewhere, # and this handler just records the available information for further use. if not (settings.AXES_ONLY_USER_FAILURES and username is None): log.warning( "AXES: New login failure by %s. Creating new record in the database.", client_str, ) AccessAttempt.objects.create( username=username, ip_address=request.axes_ip_address, user_agent=request.axes_user_agent, get_data=get_data, post_data=post_data, http_accept=request.axes_http_accept, path_info=request.axes_path_info, failures_since_start=1, attempt_time=request.axes_attempt_time, ) else: log.warning( "AXES: Username is None and AXES_ONLY_USER_FAILURES is enable, New record won't be created." ) 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 check_request(request, login_unsuccessful): ip_address = get_ip(request) username = request.POST.get('username', None) failures = 0 attempts = get_user_attempts(request) for attempt in attempts: failures = max(failures, attempt.failures_since_start) if login_unsuccessful: # add a failed attempt for this user failures += 1 # Create an AccessAttempt record if the login wasn't successful # has already attempted, update the info if len(attempts): for attempt in attempts: attempt.get_data = '%s\n---------\n%s' % ( attempt.get_data, query2str(request.GET.items()), ) attempt.post_data = '%s\n---------\n%s' % ( attempt.post_data, query2str(request.POST.items())) attempt.http_accept = request.META.get('HTTP_ACCEPT', '<unknown>') attempt.path_info = request.META.get('PATH_INFO', '<unknown>') attempt.failures_since_start = failures attempt.attempt_time = datetime.now() attempt.save() log.info('AXES: Repeated login failure by %s. Updating access ' 'record. Count = %s' % (attempt.ip_address, failures)) else: create_new_failure_records(request, failures) else: # user logged in -- forget the failed attempts failures = 0 trusted_record_exists = False for attempt in attempts: if not attempt.trusted: attempt.delete() else: trusted_record_exists = True attempt.failures_since_start = 0 attempt.save() if trusted_record_exists is False: create_new_trusted_record(request) user_lockable = is_user_lockable(request) # no matter what, we want to lock them out if they're past the number of # attempts allowed, unless the user is set to notlockable if failures > FAILURE_LIMIT and LOCK_OUT_AT_FAILURE and user_lockable: # We log them out in case they actually managed to enter the correct # password logout(request) log.warn('AXES: locked out %s after repeated login attempts.' % (ip_address, )) # send signal when someone is locked out. user_locked_out.send("axes", request=request, username=username, ip_address=ip_address) # if a trusted login has violated lockout, revoke trust for attempt in [a for a in attempts if a.trusted]: attempt.delete() create_new_failure_records(request, failures) return False return True
def user_login_failed(self, sender, credentials, request, **kwargs): # pylint: disable=unused-argument """ When user login fails, save AccessAttempt record in database and lock user out if necessary. :raises AxesSignalPermissionDenied: if user should is locked out """ if request is None: log.warning( 'AxesHandler.user_login_failed does not function without a request.' ) return username = get_client_username(request, credentials) ip_address = get_client_ip(request) user_agent = get_client_user_agent(request) path_info = request.META.get('PATH_INFO', '<unknown>')[:255] http_accept = request.META.get('HTTP_ACCEPT', '<unknown>')[:1025] client_str = get_client_str(username, ip_address, user_agent, path_info) if settings.AXES_NEVER_LOCKOUT_WHITELIST and ip_in_whitelist( ip_address): log.info('Login failed from whitelisted IP %s.', ip_address) return attempts = get_user_attempts(request, credentials) cache_key = get_cache_key(request, credentials) num_failures_cached = get_axes_cache().get(cache_key) if num_failures_cached: failures = num_failures_cached elif attempts: failures = attempts.aggregate( Max('failures_since_start'), )['failures_since_start__max'] else: failures = 0 # add a failed attempt for this user failures += 1 get_axes_cache().set( cache_key, failures, get_cache_timeout(), ) if attempts: # Update existing attempt information but do not touch the username, ip_address, or user_agent fields, # because attackers can request the site with multiple different usernames, addresses, or programs. for attempt in attempts: template = '{}\n---------\n{}' attempt.get_data = template.format( attempt.get_data, query2str(request.GET), ) attempt.post_data = template.format(attempt.post_data, query2str(request.POST)) attempt.http_accept = http_accept attempt.path_info = path_info attempt.failures_since_start = failures attempt.attempt_time = now() attempt.save() log.info( 'AXES: Repeated login failure by %s. Count = %d of %d', client_str, failures, settings.AXES_FAILURE_LIMIT, ) else: # Record failed attempt. Whether or not the IP address or user agent is # used in counting failures is handled elsewhere, so we just record # everything here. AccessAttempt.objects.create( username=username, ip_address=ip_address, user_agent=user_agent, get_data=query2str(request.GET), post_data=query2str(request.POST), http_accept=http_accept, path_info=path_info, failures_since_start=failures, ) log.info( 'AXES: New login failure by %s. Creating access record.', client_str, ) if is_already_locked(request, credentials): log.warning( 'AXES: Locked 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_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 check_request(request, login_unsuccessful): ip_address = get_ip(request) username = request.POST.get(USERNAME_FORM_FIELD, None) failures = 0 attempts = get_user_attempts(request) cache_hash_key = get_cache_key(request) cache_timeout = get_cache_timeout() failures_cached = cache.get(cache_hash_key) if failures_cached is not None: failures = failures_cached else: for attempt in attempts: failures = max(failures, attempt.failures_since_start) if login_unsuccessful: # add a failed attempt for this user failures += 1 cache.set(cache_hash_key, failures, cache_timeout) # Create an AccessAttempt record if the login wasn't successful # has already attempted, update the info if len(attempts): for attempt in attempts: attempt.get_data = '%s\n---------\n%s' % ( attempt.get_data, query2str(request.GET), ) attempt.post_data = '%s\n---------\n%s' % ( attempt.post_data, query2str(request.POST) ) attempt.http_accept = \ request.META.get('HTTP_ACCEPT', '<unknown>')[:1025] attempt.path_info = \ request.META.get('PATH_INFO', '<unknown>')[:255] attempt.failures_since_start = failures attempt.attempt_time = datetime.now() attempt.save() log.info( 'AXES: Repeated login failure by %s. Updating access ' 'record. Count = %s' % (attempt.ip_address, failures) ) else: create_new_failure_records(request, failures) else: # user logged in -- forget the failed attempts failures = 0 trusted_record_exists = False for attempt in attempts: if not attempt.trusted: attempt.delete() failures_cached = cache.get(cache_hash_key) if failures_cached is not None: cache.set(cache_hash_key, failures_cached - 1, cache_timeout) else: trusted_record_exists = True attempt.failures_since_start = 0 attempt.save() cache.set(cache_hash_key, 0, cache_timeout) if trusted_record_exists is False: create_new_trusted_record(request) if NEVER_LOCKOUT_WHITELIST and ip_in_whitelist(ip_address): return True user_lockable = is_user_lockable(request) # no matter what, we want to lock them out if they're past the number of # attempts allowed, unless the user is set to notlockable if failures >= FAILURE_LIMIT and LOCK_OUT_AT_FAILURE and user_lockable: # We log them out in case they actually managed to enter the correct # password if hasattr(request, 'user') and request.user.is_authenticated(): logout(request) log.warn( 'AXES: locked out %s after repeated login attempts.' % ip_address ) # send signal when someone is locked out. user_locked_out.send( 'axes', request=request, username=username, ip_address=ip_address ) # if a trusted login has violated lockout, revoke trust for attempt in [a for a in attempts if a.trusted]: attempt.delete() create_new_failure_records(request, failures) return False return True
def user_login_failed(self, sender, credentials, request=None, **kwargs): # pylint: disable=too-many-locals """ When user login fails, save AccessAttempt record in database and lock user out if necessary. :raises AxesSignalPermissionDenied: if user should be locked out. """ attempt_time = now() # 1. database query: Clean up expired user attempts from the database before logging new attempts clean_expired_user_attempts(attempt_time) if request is None: log.error( 'AXES: AxesDatabaseHandler.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) http_accept = get_client_http_accept(request) client_str = get_client_str(username, ip_address, user_agent, path_info) get_data = get_query_str(request.GET) post_data = get_query_str(request.POST) if self.is_whitelisted(request, credentials): log.info('AXES: Login failed from whitelisted client %s.', client_str) return # 2. database query: Calculate the current maximum failure number from the existing attempts failures_since_start = 1 + self.get_failures(request, credentials, attempt_time) # 3. database query: Insert or update access records with the new failure data if failures_since_start > 1: # Update failed attempt information but do not touch the username, IP address, or user agent fields, # because attackers can request the site with multiple different configurations # in order to bypass the defense mechanisms that are used by the site. log.warning( 'AXES: Repeated login failure by %s. Count = %d of %d. Updating existing record in the database.', client_str, failures_since_start, settings.AXES_FAILURE_LIMIT, ) separator = '\n---------\n' attempts = get_user_attempts(request, credentials, attempt_time) attempts.update( get_data=Concat('get_data', Value(separator + get_data)), post_data=Concat('post_data', Value(separator + post_data)), http_accept=http_accept, path_info=path_info, failures_since_start=failures_since_start, attempt_time=attempt_time, ) else: # Record failed attempt with all the relevant information. # Filtering based on username, IP address and user agent handled elsewhere, # and this handler just records the available information for further use. log.warning( 'AXES: New login failure by %s. Creating new record in the database.', client_str, ) AccessAttempt.objects.create( username=username, ip_address=ip_address, user_agent=user_agent, get_data=get_data, post_data=post_data, http_accept=http_accept, path_info=path_info, failures_since_start=failures_since_start, attempt_time=attempt_time, ) 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_login_failed(self, sender, credentials: dict, request=None, **kwargs): # pylint: disable=too-many-locals """ When user login fails, save AccessAttempt record in database, mark request with lockout attribute and emit lockout signal. """ log.info( "AXES: User login failed, running database handler for failure.") if request is None: log.error( "AXES: AxesDatabaseHandler.user_login_failed does not function without a request." ) return # 1. database query: Clean up expired user attempts from the database before logging new attempts clean_expired_user_attempts(request.axes_attempt_time) username = get_client_username(request, credentials) client_str = get_client_str( username, request.axes_ip_address, request.axes_user_agent, request.axes_path_info, request, ) # This replaces null byte chars that crash saving failures. get_data = get_query_str(request.GET).replace("\0", "0x00") post_data = get_query_str(request.POST).replace("\0", "0x00") if self.is_whitelisted(request, credentials): log.info("AXES: Login failed from whitelisted client %s.", client_str) return # 2. database query: Get or create access record with the new failure data 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." ) else: with transaction.atomic(): ( attempt, created, ) = AccessAttempt.objects.select_for_update().get_or_create( username=username, ip_address=request.axes_ip_address, user_agent=request.axes_user_agent, defaults={ "get_data": get_data, "post_data": post_data, "http_accept": request.axes_http_accept, "path_info": request.axes_path_info, "failures_since_start": 1, "attempt_time": request.axes_attempt_time, }, ) # Record failed attempt with all the relevant information. # Filtering based on username, IP address and user agent handled elsewhere, # and this handler just records the available information for further use. if created: log.warning( "AXES: New login failure by %s. Created new record in the database.", client_str, ) # 3. database query if there were previous attempts in the database # Update failed attempt information but do not touch the username, IP address, or user agent fields, # because attackers can request the site with multiple different configurations # in order to bypass the defense mechanisms that are used by the site. else: separator = "\n---------\n" attempt.get_data = Concat("get_data", Value(separator + get_data)) attempt.post_data = Concat("post_data", Value(separator + post_data)) attempt.http_accept = request.axes_http_accept attempt.path_info = request.axes_path_info attempt.failures_since_start = F( "failures_since_start") + 1 attempt.attempt_time = request.axes_attempt_time attempt.save() log.warning( "AXES: Repeated login failure by %s. Updated existing record in the database.", client_str, ) # 3. or 4. database query: Calculate the current maximum failure number from the existing attempts failures_since_start = self.get_failures(request, credentials) request.axes_failures_since_start = failures_since_start 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 user_login_failed(self, sender, credentials, request, **kwargs): # pylint: disable=unused-argument """ When user login fails, save AccessAttempt record in database and lock user out if necessary. :raises AxesSignalPermissionDenied: if user should is locked out """ if request is None: log.warning('AxesHandler.user_login_failed does not function without a request.') return ip_address = get_client_ip(request) username = get_client_username(request, credentials) user_agent = request.META.get('HTTP_USER_AGENT', '<unknown>')[:255] path_info = request.META.get('PATH_INFO', '<unknown>')[:255] http_accept = request.META.get('HTTP_ACCEPT', '<unknown>')[:1025] if settings.AXES_NEVER_LOCKOUT_WHITELIST and ip_in_whitelist(ip_address): log.info('Login failed from whitelisted IP %s.', ip_address) return failures = 0 attempts = get_user_attempts(request, credentials) cache_hash_key = get_cache_key(request, credentials) cache_timeout = get_cache_timeout() failures_cached = get_axes_cache().get(cache_hash_key) if failures_cached is not None: failures = failures_cached else: for attempt in attempts: failures = max(failures, attempt.failures_since_start) # add a failed attempt for this user failures += 1 get_axes_cache().set(cache_hash_key, failures, cache_timeout) # has already attempted, update the info if attempts: for attempt in attempts: attempt.get_data = '%s\n---------\n%s' % ( attempt.get_data, query2str(request.GET), ) attempt.post_data = '%s\n---------\n%s' % ( attempt.post_data, query2str(request.POST) ) attempt.http_accept = http_accept attempt.path_info = path_info attempt.failures_since_start = failures attempt.attempt_time = now() attempt.save() log.info( 'AXES: Repeated login failure by %s. Count = %d of %d', get_client_str(username, ip_address, user_agent, path_info), failures, settings.AXES_FAILURE_LIMIT, ) else: # Record failed attempt. Whether or not the IP address or user agent is # used in counting failures is handled elsewhere, so we just record # everything here. AccessAttempt.objects.create( user_agent=user_agent, ip_address=ip_address, username=username, get_data=query2str(request.GET), post_data=query2str(request.POST), http_accept=http_accept, path_info=path_info, failures_since_start=failures, ) log.info( 'AXES: New login failure by %s. Creating access record.', get_client_str(username, ip_address, user_agent, path_info), ) # no matter what, we want to lock them out if they're past the number of # attempts allowed, unless the user is set to notlockable if ( failures >= settings.AXES_FAILURE_LIMIT and settings.AXES_LOCK_OUT_AT_FAILURE and is_user_lockable(request, credentials) ): log.warning( 'AXES: Locked out %s after repeated login failures.', get_client_str(username, ip_address, user_agent, path_info), ) # send signal when someone is locked out. user_locked_out.send( 'axes', request=request, username=username, ip_address=ip_address, ) raise AxesSignalPermissionDenied('Locked out due to repeated login failures.')
def check_request(request, login_unsuccessful): ip_address = get_ip(request) username = request.POST.get(USERNAME_FORM_FIELD, None) user_agent = request.META.get('HTTP_USER_AGENT', '<unknown>')[:255] path_info = request.META.get('PATH_INFO', '<unknown>')[:255] failures = 0 attempts = get_user_attempts(request) cache_hash_key = get_cache_key(request) cache_timeout = get_cache_timeout() failures_cached = cache.get(cache_hash_key) if failures_cached is not None: failures = failures_cached else: for attempt in attempts: failures = max(failures, attempt.failures_since_start) if login_unsuccessful: # add a failed attempt for this user failures += 1 cache.set(cache_hash_key, failures, cache_timeout) # Create an AccessAttempt record if the login wasn't successful # has already attempted, update the info if len(attempts): for attempt in attempts: attempt.get_data = '%s\n---------\n%s' % ( attempt.get_data, query2str(request.GET), ) attempt.post_data = '%s\n---------\n%s' % ( attempt.post_data, query2str(request.POST) ) attempt.http_accept = \ request.META.get('HTTP_ACCEPT', '<unknown>')[:1025] attempt.path_info = path_info,path_info attempt.failures_since_start = failures attempt.attempt_time = datetime.now() attempt.save() log_repeated_attempt(username, ip_address, user_agent, path_info, failures) else: create_new_failure_records(request, failures) else: # user logged in -- forget the failed attempts failures = 0 trusted_record_exists = False for attempt in attempts: if not attempt.trusted: attempt.delete() failures_cached = cache.get(cache_hash_key) if failures_cached is not None: cache.set(cache_hash_key, failures_cached - 1, cache_timeout) else: trusted_record_exists = True attempt.failures_since_start = 0 attempt.save() cache.set(cache_hash_key, 0, cache_timeout) if trusted_record_exists is False: create_new_trusted_record(request) if NEVER_LOCKOUT_WHITELIST and ip_in_whitelist(ip_address): return True user_lockable = is_user_lockable(request) # no matter what, we want to lock them out if they're past the number of # attempts allowed, unless the user is set to notlockable if failures >= FAILURE_LIMIT and LOCK_OUT_AT_FAILURE and user_lockable: # We log them out in case they actually managed to enter the correct # password if hasattr(request, 'user') and request.user.is_authenticated(): logout(request) username = request.POST.get(USERNAME_FORM_FIELD, None) log_lockout(username, ip_address, user_agent, path_info) # send signal when someone is locked out. user_locked_out.send( 'axes', request=request, username=username, ip_address=ip_address ) # if a trusted login has violated lockout, revoke trust for attempt in [a for a in attempts if a.trusted]: attempt.delete() create_new_failure_records(request, failures) return False return True
def user_login_failed( self, sender, credentials: dict, request: AxesHttpRequest = None, **kwargs ): # pylint: disable=too-many-locals """ When user login fails, save AccessAttempt record in database and lock user out if necessary. :raises AxesSignalPermissionDenied: if user should be locked out. """ if request is None: log.error('AXES: AxesDatabaseHandler.user_login_failed does not function without a request.') return if not hasattr(request, 'axes_attempt_time'): log.error('AXES: AxesDatabaseHandler.user_login_failed needs a valid AxesHttpRequest object.') return # 1. database query: Clean up expired user attempts from the database before logging new attempts clean_expired_user_attempts(request.axes_attempt_time) username = get_client_username(request, credentials) client_str = get_client_str(username, request.axes_ip_address, request.axes_user_agent, request.axes_path_info) get_data = get_query_str(request.GET) post_data = get_query_str(request.POST) if self.is_whitelisted(request, credentials): log.info('AXES: Login failed from whitelisted client %s.', client_str) return # 2. database query: Calculate the current maximum failure number from the existing attempts failures_since_start = 1 + self.get_failures(request, credentials) # 3. database query: Insert or update access records with the new failure data if failures_since_start > 1: # Update failed attempt information but do not touch the username, IP address, or user agent fields, # because attackers can request the site with multiple different configurations # in order to bypass the defense mechanisms that are used by the site. log.warning( 'AXES: Repeated login failure by %s. Count = %d of %d. Updating existing record in the database.', client_str, failures_since_start, settings.AXES_FAILURE_LIMIT, ) separator = '\n---------\n' attempts = get_user_attempts(request, credentials) attempts.update( get_data=Concat('get_data', Value(separator + get_data)), post_data=Concat('post_data', Value(separator + post_data)), http_accept=request.axes_http_accept, path_info=request.axes_path_info, failures_since_start=failures_since_start, attempt_time=request.axes_attempt_time, ) else: # Record failed attempt with all the relevant information. # Filtering based on username, IP address and user agent handled elsewhere, # and this handler just records the available information for further use. log.warning( 'AXES: New login failure by %s. Creating new record in the database.', client_str, ) AccessAttempt.objects.create( username=username, ip_address=request.axes_ip_address, user_agent=request.axes_user_agent, get_data=get_data, post_data=post_data, http_accept=request.axes_http_accept, path_info=request.axes_path_info, failures_since_start=failures_since_start, attempt_time=request.axes_attempt_time, ) 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.')