Exemple #1
0
    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, )
Exemple #2
0
    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)
Exemple #3
0
    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,
    })
Exemple #5
0
 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)
Exemple #8
0
    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)
Exemple #9
0
    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.')
Exemple #10
0
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
Exemple #11
0
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})
Exemple #12
0
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)
Exemple #13
0
    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)
Exemple #14
0
    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)
Exemple #15
0
    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
Exemple #17
0
    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)
Exemple #18
0
    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)
Exemple #19
0
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)
Exemple #20
0
 def user_or_ip(request):
     if request.user.is_authenticated:
         return str(request.user.pk)
     return ip_mask(get_client_ip_address(request))
Exemple #21
0
    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.')
Exemple #22
0
 def form_valid(self, form):
     ip = get_client_ip_address(self.request)
     reset(ip=ip)
     return HttpResponseRedirect(reverse_lazy('login'))