def user_logged_out(self, sender, request, user, **kwargs): # pylint: disable=unused-argument """ When user logs out, update the AccessLog related to the user. """ attempt_time = now() # 1. database query: Clean up expired user attempts from the database clean_expired_user_attempts(attempt_time) username = user.get_username() 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) log.info('AXES: Successful logout by %s.', client_str) if username and not settings.AXES_DISABLE_ACCESS_LOG: # 2. database query: Update existing attempt logs with logout time AccessLog.objects.filter( username=username, logout_time__isnull=True, ).update(logout_time=attempt_time, )
def setUp(self): """ Create a valid user for login. """ self.username = self.VALID_USERNAME self.password = self.VALID_PASSWORD self.email = self.VALID_EMAIL self.ip_address = self.VALID_IP_ADDRESS self.user_agent = self.VALID_USER_AGENT self.path_info = reverse("admin:login") self.user = get_user_model().objects.create_superuser( username=self.username, password=self.password, email=self.email) self.request = HttpRequest() self.request.method = "POST" self.request.META["REMOTE_ADDR"] = self.ip_address self.request.META["HTTP_USER_AGENT"] = self.user_agent self.request.META["PATH_INFO"] = self.path_info self.request.axes_attempt_time = now() self.request.axes_ip_address = get_client_ip_address(self.request) self.request.axes_user_agent = get_client_user_agent(self.request) self.request.axes_path_info = get_client_path_info(self.request) self.request.axes_http_accept = get_client_http_accept(self.request) self.credentials = get_credentials(self.username)
def setUp(self): """ Create a valid user for login. """ self.username = self.VALID_USERNAME self.password = self.VALID_PASSWORD self.email = self.VALID_EMAIL self.ip_address = self.VALID_IP_ADDRESS self.user_agent = self.VALID_USER_AGENT self.path_info = reverse('admin:login') self.user = get_user_model().objects.create_superuser( username=self.username, password=self.password, email=self.email, ) self.request = HttpRequest() self.request.method = 'POST' self.request.META['REMOTE_ADDR'] = self.ip_address self.request.META['HTTP_USER_AGENT'] = self.user_agent self.request.META['PATH_INFO'] = self.path_info self.request.axes_attempt_time = now() self.request.axes_ip_address = get_client_ip_address(self.request) self.request.axes_user_agent = get_client_user_agent(self.request) self.request.axes_path_info = get_client_path_info(self.request) self.request.axes_http_accept = get_client_http_accept(self.request) self.credentials = get_credentials(self.username)
def user_logged_in_logger(sender, request, user, **kwargs): ip_address = get_client_ip_address(request) logger.info("login successful", extra={ 'user': user, 'ip': ip_address, })
def validate_user(self, username, password, client, request, *args, **kwargs): """ Check username and password correspond to a valid and active User Set defaults for necessary request object attributes for Axes compatibility. The ``request`` argument is not a Django ``HttpRequest`` object. """ _request = request if request and not isinstance(request, HttpRequest): request = HttpRequest() request.uri = _request.uri request.method = request.http_method = _request.http_method request.META = request.headers = _request.headers request._params = _request._params request.decoded_body = _request.decoded_body request.axes_ip_address = get_client_ip_address(request) request.axes_user_agent = get_client_user_agent(request) body = QueryDict(str(_request.body), mutable=True) if request.method == "GET": request.GET = body elif request.method == "POST": request.POST = body user = authenticate(request=request, username=username, password=password) if user is not None and user.is_active: request = _request request.user = user return True return False
def user_login_failed_logger(sender, credentials, request, **kwargs): ip_address = get_client_ip_address(request) logger.warning("login failed", extra={ 'user': credentials.get("username", ""), 'ip': ip_address, })
def __call__(self, request: HttpRequest): 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) return self.get_response(request)
def user_logged_out(self, sender, request, user, **kwargs): username = user.get_username() 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) log.info('AXES: Successful logout by %s.', client_str)
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 patch_rate_limit(): from axes.helpers import get_client_ip_address from ratelimit.core import _SIMPLE_KEYS, ip_mask def user_or_ip(request): if request.user.is_authenticated: return str(request.user.pk) return ip_mask(get_client_ip_address(request)) _SIMPLE_KEYS["ip"] = lambda r: ip_mask(get_client_ip_address(r)) _SIMPLE_KEYS["user_or_ip"] = user_or_ip
def locked_out(request, exception=None): if request.POST: form = AxesLockoutCaptchaForm(request.POST) if form.is_valid(): ip = get_client_ip_address(request) reset(ip=ip) return HttpResponseRedirect(reverse_lazy("account_login")) else: form = AxesLockoutCaptchaForm() return render(request, "captcha.html", {"form": form})
def filter_user_attempts(request: HttpRequest, credentials: dict = None) -> QuerySet: """ Return a queryset of AccessAttempts that match the given request and credentials. """ username = get_client_username(request, credentials) ip_address = get_client_ip_address(request) user_agent = get_client_user_agent(request) filter_kwargs = get_client_parameters(username, ip_address, user_agent) return AccessAttempt.objects.filter(**filter_kwargs)
def update_request(self, request: HttpRequest): """ Construct an ``AxesHttpRequest`` from the given ``HttpRequest`` by updating the request with necessary attempt tracking attributes. This method is called by the middleware class ``__call__`` method when iterating over the middleware stack. """ 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)
def update_request(self, request: HttpRequest): """ Construct an ``AxesHttpRequest`` from the given ``HttpRequest`` by updating the request with necessary attempt tracking attributes. This method is called by the middleware class ``__call__`` method when iterating over the middleware stack. """ 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)
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)
def validate_user(self: "AxesOAuth2Validator", username: str, password: str, client: Application, request: Request, *args: Tuple, **kwargs: Dict) -> bool: """Check username and password correspond to a valid and active User. Set defaults for necessary request object attributes for Axes compatibility. Args: username: A string. password: A string. client: Application model instances. request: is not a Django HttpRequest object. args: Tuple of extra argument. kwargs: Dict of extra keyword argument. Returns: bool """ _request = request if request and not isinstance(request, HttpRequest): request = HttpRequest() request.uri = _request.uri request.method = request.http_method = _request.http_method request.META = request.headers = _request.headers request._params = _request._params request.decoded_body = _request.decoded_body request.axes_ip_address = get_client_ip_address(request) request.axes_user_agent = get_client_user_agent(request) body = QueryDict(str(_request.body), mutable=True) if request.method == "GET": request.GET = body elif request.method == "POST": request.POST = body user = authenticate(request=request, username=username, password=password) if user is not None and user.is_active: request = _request request.user = user return True return False
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) 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) 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 user_logged_in(self, sender, request, user, **kwargs): # pylint: disable=unused-argument """ When user logs in, update the AccessLog related to the user. """ attempt_time = now() # 1. database query: Clean up expired user attempts from the database clean_expired_user_attempts(attempt_time) username = user.get_username() credentials = get_credentials(username) 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) log.info('AXES: Successful login by %s.', client_str) if not settings.AXES_DISABLE_SUCCESS_ACCESS_LOG: # 2. database query: Insert new access logs with login time AccessLog.objects.create( username=username, ip_address=ip_address, user_agent=user_agent, http_accept=http_accept, path_info=path_info, attempt_time=attempt_time, trusted=True, ) if settings.AXES_RESET_ON_SUCCESS: # 3. database query: Reset failed attempts for the logging in user count = reset_user_attempts(request, credentials) log.info( 'AXES: Deleted %d failed login attempts by %s from database.', count, client_str)
def reset_request(request: HttpRequest) -> int: """ Reset records that match IP or username, and return the count of removed attempts. This utility method is meant to be used from the CLI or via Python API. """ ip: Optional[str] = get_client_ip_address(request) username = request.GET.get("username", None) ip_or_username = settings.AXES_LOCK_OUT_BY_USER_OR_IP if settings.AXES_ONLY_USER_FAILURES: ip = None elif not (settings.AXES_LOCK_OUT_BY_USER_OR_IP or settings.AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP): username = None if not ip and not username: return 0 # We don't want to reset everything, if there is some wrong request parameter # if settings.AXES_USE_USER_AGENT: # TODO: reset based on user_agent? return reset(ip, username, ip_or_username)
def user_or_ip(request): if request.user.is_authenticated: return str(request.user.pk) return ip_mask(get_client_ip_address(request))
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 form_valid(self, form): ip = get_client_ip_address(self.request) reset(ip=ip) return HttpResponseRedirect(reverse_lazy('login'))