Ejemplo n.º 1
0
    def user_logged_out(self, sender, request: AxesHttpRequest, user, **kwargs):  # pylint: disable=unused-argument
        """
        When user logs out, update the AccessLog related to the user.
        """

        if not hasattr(request, 'axes_attempt_time'):
            log.error('AXES: AxesDatabaseHandler.user_logged_out needs a valid AxesHttpRequest object.')
            return

        # 1. database query: Clean up expired user attempts from the database
        clean_expired_user_attempts(request.axes_attempt_time)

        username = user.get_username() if user else None
        client_str = get_client_str(username, request.axes_ip_address, request.axes_user_agent, request.axes_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=request.axes_attempt_time,
            )
Ejemplo n.º 2
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: AxesDatabaseHandler.user_logged_in needs a valid AxesHttpRequest object.')
            return

        # 1. database query: Clean up expired user attempts from the database
        clean_expired_user_attempts(request.axes_attempt_time)

        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 not settings.AXES_DISABLE_SUCCESS_ACCESS_LOG:
            # 2. database query: Insert new access logs with login time
            AccessLog.objects.create(
                username=username,
                ip_address=request.axes_ip_address,
                user_agent=request.axes_user_agent,
                http_accept=request.axes_http_accept,
                path_info=request.axes_path_info,
                attempt_time=request.axes_attempt_time,
            )

        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.º 3
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.
        """

        # 1. database query: Clean up expired user attempts from the database
        clean_expired_user_attempts(request.axes_attempt_time)

        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 not settings.AXES_DISABLE_SUCCESS_ACCESS_LOG:
            # 2. database query: Insert new access logs with login time
            AccessLog.objects.create(
                username=username,
                ip_address=request.axes_ip_address,
                user_agent=request.axes_user_agent,
                http_accept=request.axes_http_accept,
                path_info=request.axes_path_info,
                attempt_time=request.axes_attempt_time,
            )

        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.º 4
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.º 5
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,
                )
Ejemplo n.º 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)
Ejemplo n.º 7
0
 def check_whitelist(self, log):
     with override_settings(
         AXES_NEVER_LOCKOUT_WHITELIST=True,
         AXES_IP_WHITELIST=[self.ip_address],
     ):
         AxesProxyHandler.user_login_failed(sender=None, request=self.request, credentials=self.credentials)
         client_str = get_client_str(self.username, self.ip_address, self.user_agent, self.path_info)
         log.info.assert_called_with('AXES: Login failed from whitelisted client %s.', client_str)
Ejemplo n.º 8
0
    def user_logged_out(self, sender, request: AxesHttpRequest, user, **kwargs):
        if not hasattr(request, 'axes_attempt_time'):
            log.error('AXES: AxesCacheHandler.user_logged_out needs a valid AxesHttpRequest object.')
            return

        username = user.get_username() if user else None
        client_str = get_client_str(username, request.axes_ip_address, request.axes_user_agent, request.axes_path_info)

        log.info('AXES: Successful logout by %s.', client_str)
Ejemplo n.º 9
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.º 10
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.')
Ejemplo n.º 11
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.º 12
0
    def test_verbose_ip_only_client_details_tuple(self):
        username = '******'
        ip_address = '127.0.0.1'
        user_agent = 'Googlebot/2.1 (+http://www.googlebot.com/bot.html)'
        path_info = ('admin', 'login')

        expected = self.get_expected_client_str(username, ip_address, user_agent, path_info[0])
        actual = get_client_str(username, ip_address, user_agent, path_info)

        self.assertEqual(expected, actual)
Ejemplo n.º 13
0
    def test_verbose_user_ip_combo_client_details(self):
        username = '******'
        ip_address = '127.0.0.1'
        user_agent = 'Googlebot/2.1 (+http://www.googlebot.com/bot.html)'
        path_info = '/admin/'

        expected = self.get_expected_client_str(username, ip_address, user_agent, path_info)
        actual = get_client_str(username, ip_address, user_agent, path_info)

        self.assertEqual(expected, actual)
Ejemplo n.º 14
0
    def user_logged_out(self, sender, request, user, **kwargs):
        username = user.get_username() if user else None
        client_str = get_client_str(
            username,
            request.axes_ip_address,
            request.axes_user_agent,
            request.axes_path_info,
        )

        log.info("AXES: Successful logout by %s.", client_str)
Ejemplo n.º 15
0
    def test_non_verbose_ip_only_client_details(self):
        username = '******'
        ip_address = '127.0.0.1'
        user_agent = 'Googlebot/2.1 (+http://www.googlebot.com/bot.html)'
        path_info = '/admin/'

        expected = '{ip_address: "127.0.0.1", path_info: "/admin/"}'
        actual = get_client_str(username, ip_address, user_agent, path_info)

        self.assertEqual(expected, actual)
Ejemplo n.º 16
0
    def test_non_verbose_user_agent_client_details(self):
        username = '******'
        ip_address = '127.0.0.1'
        user_agent = 'Googlebot/2.1 (+http://www.googlebot.com/bot.html)'
        path_info = '/admin/'

        expected = '{ip_address: "127.0.0.1", user_agent: "Googlebot/2.1 (+http://www.googlebot.com/bot.html)", path_info: "/admin/"}'
        actual = get_client_str(username, ip_address, user_agent, path_info)

        self.assertEqual(expected, actual)
Ejemplo n.º 17
0
    def test_non_verbose_user_agent_client_details(self):
        username = "******"
        ip_address = "127.0.0.1"
        user_agent = "Googlebot/2.1 (+http://www.googlebot.com/bot.html)"
        path_info = "/admin/"

        expected = '{ip_address: "127.0.0.1", user_agent: "Googlebot/2.1 (+http://www.googlebot.com/bot.html)", path_info: "/admin/"}'
        actual = get_client_str(username, ip_address, user_agent, path_info)

        self.assertEqual(expected, actual)
Ejemplo n.º 18
0
    def test_verbose_ip_only_client_details_tuple(self):
        username = '******'
        ip_address = '127.0.0.1'
        user_agent = 'Googlebot/2.1 (+http://www.googlebot.com/bot.html)'
        path_info = ('admin', 'login')

        expected = self.get_expected_client_str(username, ip_address,
                                                user_agent, path_info[0])
        actual = get_client_str(username, ip_address, user_agent, path_info)

        self.assertEqual(expected, actual)
Ejemplo n.º 19
0
    def test_verbose_ip_only_client_details_tuple(self):
        username = "******"
        ip_address = "127.0.0.1"
        user_agent = "Googlebot/2.1 (+http://www.googlebot.com/bot.html)"
        path_info = ("admin", "login")

        expected = self.get_expected_client_str(username, ip_address,
                                                user_agent, path_info[0])
        actual = get_client_str(username, ip_address, user_agent, path_info)

        self.assertEqual(expected, actual)
Ejemplo n.º 20
0
    def test_imbalanced_quotes(self):
        username = "******"
        ip_address = "127.0.0.1"
        user_agent = "Googlebot/2.1 (+http://www.googlebot.com/bot.html)"
        path_info = "/admin/"

        expected = self.get_expected_client_str(username, ip_address,
                                                user_agent, path_info)
        actual = get_client_str(username, ip_address, user_agent, path_info)

        self.assertEqual(expected, actual)
Ejemplo n.º 21
0
    def test_verbose_user_ip_combo_client_details(self):
        username = '******'
        ip_address = '127.0.0.1'
        user_agent = 'Googlebot/2.1 (+http://www.googlebot.com/bot.html)'
        path_info = '/admin/'

        expected = self.get_expected_client_str(username, ip_address,
                                                user_agent, path_info)
        actual = get_client_str(username, ip_address, user_agent, path_info)

        self.assertEqual(expected, actual)
Ejemplo n.º 22
0
    def user_logged_out(self, sender, request: AxesHttpRequest, user,
                        **kwargs):
        if not hasattr(request, 'axes_attempt_time'):
            log.error(
                'AXES: AxesCacheHandler.user_logged_out needs a valid AxesHttpRequest object.'
            )
            return

        username = user.get_username() if user else None
        client_str = get_client_str(username, request.axes_ip_address,
                                    request.axes_user_agent,
                                    request.axes_path_info)

        log.info('AXES: Successful logout by %s.', client_str)
Ejemplo n.º 23
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)
Ejemplo n.º 24
0
 def test_get_client_str_callable(self):
     self.assertEqual(
         get_client_str("username", "ip_address", "user_agent",
                        "path_info"), "client string")
Ejemplo n.º 25
0
    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.')
Ejemplo n.º 26
0
    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,
            )
Ejemplo n.º 27
0
    def user_logged_out(self, sender, request: AxesHttpRequest, user, **kwargs):
        username = user.get_username()
        client_str = get_client_str(username, request.axes_ip_address, request.axes_user_agent, request.axes_path_info)

        log.info('AXES: Successful logout by %s.', client_str)
Ejemplo n.º 28
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,
            )
Ejemplo n.º 29
0
    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,
            )
Ejemplo n.º 30
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.')