Beispiel #1
0
    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))
Beispiel #2
0
    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))
Beispiel #3
0
    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))
Beispiel #4
0
    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))
Beispiel #5
0
    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))
Beispiel #6
0
    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)
Beispiel #7
0
    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
Beispiel #8
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)
        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,
                )
Beispiel #9
0
    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.')
Beispiel #10
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.')
Beispiel #11
0
    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)
Beispiel #12
0
 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)
Beispiel #13
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)
Beispiel #14
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,
            )
Beispiel #15
0
 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
Beispiel #16
0
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]
    )
Beispiel #17
0
 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)