Ejemplo n.º 1
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)
Ejemplo n.º 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)
Ejemplo n.º 3
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, )
Ejemplo n.º 4
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
Ejemplo n.º 5
0
    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)
Ejemplo n.º 6
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)
Ejemplo n.º 7
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.')
Ejemplo n.º 8
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)
Ejemplo n.º 9
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)
Ejemplo n.º 10
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)
Ejemplo n.º 11
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
Ejemplo n.º 13
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)
Ejemplo n.º 14
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)
Ejemplo n.º 15
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.')