def test_get_cache_key(self, get_ip_mock):
        """ Test the cache key format"""
        # Getting cache key from request
        ip_address = '127.0.0.1'
        cache_hash_key = 'axes-{}'.format(
            hashlib.md5(ip_address.encode()).hexdigest())

        request_factory = RequestFactory()
        request = request_factory.post('/admin/login/',
                                       data={
                                           'username': self.VALID_USERNAME,
                                           'password': '******'
                                       })

        self.assertEqual(cache_hash_key, get_cache_key(request))

        # Getting cache key from AccessAttempt Object
        attempt = AccessAttempt(
            user_agent='<unknown>',
            ip_address=ip_address,
            username=self.VALID_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_cache_key(attempt))
    def test_get_cache_key_credentials(self, _):
        """
        Test the cache key format.
        """

        # Getting cache key from request
        ip_address = '127.0.0.1'
        cache_hash_key = 'axes-{}'.format(
            hashlib.md5(ip_address.encode()).hexdigest()
        )

        request_factory = RequestFactory()
        request = request_factory.post('/admin/login/',
                                       data={
                                           'username': self.VALID_USERNAME,
                                           'password': '******'
                                       })

        # Difference between the upper test: new call signature with credentials
        credentials = {'username': self.VALID_USERNAME}

        self.assertEqual(cache_hash_key, get_cache_key(request, credentials))

        # Getting cache key from AccessAttempt Object
        attempt = AccessAttempt(
            user_agent='<unknown>',
            ip_address=ip_address,
            username=self.VALID_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_cache_key(attempt))
示例#3
0
    def post_delete_access_attempt(self, instance, **kwargs):  # pylint: disable=unused-argument
        """
        Update cache after deleting AccessAttempts.
        """

        cache_hash_key = get_cache_key(instance)
        get_axes_cache().delete(cache_hash_key)
示例#4
0
    def post_delete_access_attempt(self, instance, **kwargs):  # pylint: disable=unused-argument
        """
        Update cache after deleting AccessAttempts.
        """

        cache_hash_key = get_cache_key(instance)
        get_axes_cache().delete(cache_hash_key)
示例#5
0
    def post_save_access_attempt(self, instance, **kwargs):  # pylint: disable=unused-argument
        """
        Update cache after saving AccessAttempts.
        """

        cache_hash_key = get_cache_key(instance)

        if not get_axes_cache().get(cache_hash_key):
            cache_timeout = get_cache_timeout()
            get_axes_cache().set(cache_hash_key, instance.failures_since_start, cache_timeout)
示例#6
0
    def post_save_access_attempt(self, instance, **kwargs):  # pylint: disable=unused-argument
        """
        Update cache after saving AccessAttempts.
        """

        cache_key = get_cache_key(instance)

        if not get_axes_cache().get(cache_key):
            get_axes_cache().set(
                cache_key,
                instance.failures_since_start,
                get_cache_timeout(),
            )
示例#7
0
 def get_context_data(self, **kwargs):
     cache_hash_key = get_cache_key(self.request)
     attempt = get_axes_cache().get(cache_hash_key)
     if not attempt:
         attempt = 0
     username = self.request.POST.get('auth-username', None)
     time = settings.AXES_COOLOFF_TIME
     attempts_left = (settings.AXES_FAILURE_LIMIT - attempt)
     kwargs= dict(kwargs, attempt=attempt, username=username, time=time, attempts_left=attempts_left)
     
     context = super().get_context_data(**kwargs)
     self.set_partner_site_info()
     context[self.redirect_field_name] = self.request.POST.get(
         self.redirect_field_name,
         self.request.GET.get(self.redirect_field_name, '')
     )
     return context
示例#8
0
def reset(ip=None, username=None):
    """Reset records that match ip or username, and
    return the count of removed attempts.
    """
    count = 0

    attempts = AccessAttempt.objects.all()
    if ip:
        attempts = attempts.filter(ip_address=ip)
    if username:
        attempts = attempts.filter(username=username)

    if attempts:
        count = attempts.count()
        for attempt in attempts:
            cache_hash_key = get_cache_key(attempt)
            if cache.get(cache_hash_key):
                cache.delete(cache_hash_key)

        attempts.delete()
    return count
示例#9
0
def reset(ip=None, username=None):
    """Reset records that match ip or username, and
    return the count of removed attempts.
    """
    count = 0

    attempts = AccessAttempt.objects.all()
    if ip:
        attempts = attempts.filter(ip_address=ip)
    if username:
        attempts = attempts.filter(username=username)

    if attempts:
        count = attempts.count()
        # import should be here to avoid circular dependency with get_ip
        from axes.attempts import get_cache_key
        for attempt in attempts:
            cache_hash_key = get_cache_key(attempt)
            if cache.get(cache_hash_key):
                cache.delete(cache_hash_key)

        attempts.delete()
    return count
示例#10
0
    def user_login_failed(self, sender, credentials, request, **kwargs):  # pylint: disable=unused-argument
        """
        When user login fails, save AccessAttempt record in database and lock user out if necessary.

        :raises AxesSignalPermissionDenied: if user should is locked out
        """

        if request is None:
            log.warning(
                'AxesHandler.user_login_failed does not function without a request.'
            )
            return

        username = get_client_username(request, credentials)
        ip_address = get_client_ip(request)
        user_agent = get_client_user_agent(request)
        path_info = request.META.get('PATH_INFO', '<unknown>')[:255]
        http_accept = request.META.get('HTTP_ACCEPT', '<unknown>')[:1025]
        client_str = get_client_str(username, ip_address, user_agent,
                                    path_info)

        if settings.AXES_NEVER_LOCKOUT_WHITELIST and ip_in_whitelist(
                ip_address):
            log.info('Login failed from whitelisted IP %s.', ip_address)
            return

        attempts = get_user_attempts(request, credentials)
        cache_key = get_cache_key(request, credentials)
        num_failures_cached = get_axes_cache().get(cache_key)

        if num_failures_cached:
            failures = num_failures_cached
        elif attempts:
            failures = attempts.aggregate(
                Max('failures_since_start'), )['failures_since_start__max']
        else:
            failures = 0

        # add a failed attempt for this user
        failures += 1
        get_axes_cache().set(
            cache_key,
            failures,
            get_cache_timeout(),
        )

        if attempts:
            # Update existing attempt information but do not touch the username, ip_address, or user_agent fields,
            # because attackers can request the site with multiple different usernames, addresses, or programs.
            for attempt in attempts:
                template = '{}\n---------\n{}'

                attempt.get_data = template.format(
                    attempt.get_data,
                    query2str(request.GET),
                )
                attempt.post_data = template.format(attempt.post_data,
                                                    query2str(request.POST))
                attempt.http_accept = http_accept
                attempt.path_info = path_info
                attempt.failures_since_start = failures
                attempt.attempt_time = now()
                attempt.save()

                log.info(
                    'AXES: Repeated login failure by %s. Count = %d of %d',
                    client_str,
                    failures,
                    settings.AXES_FAILURE_LIMIT,
                )
        else:
            # Record failed attempt. Whether or not the IP address or user agent is
            # used in counting failures is handled elsewhere, so we just record # everything here.
            AccessAttempt.objects.create(
                username=username,
                ip_address=ip_address,
                user_agent=user_agent,
                get_data=query2str(request.GET),
                post_data=query2str(request.POST),
                http_accept=http_accept,
                path_info=path_info,
                failures_since_start=failures,
            )

            log.info(
                'AXES: New login failure by %s. Creating access record.',
                client_str,
            )

        if is_already_locked(request, credentials):
            log.warning(
                'AXES: Locked 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.')
示例#11
0
def log_user_login_failed(sender, credentials, request, **kwargs):  # pylint: disable=unused-argument
    """ Create an AccessAttempt record if the login wasn't successful
    """
    if request is None:
        log.warning('Attempt to authenticate with a custom backend failed.')
        return

    ip_address = get_client_ip(request)
    username = get_client_username(request)
    user_agent = request.META.get('HTTP_USER_AGENT', '<unknown>')[:255]
    path_info = request.META.get('PATH_INFO', '<unknown>')[:255]
    http_accept = request.META.get('HTTP_ACCEPT', '<unknown>')[:1025]

    if settings.AXES_NEVER_LOCKOUT_WHITELIST and ip_in_whitelist(ip_address):
        return

    failures = 0
    attempts = get_user_attempts(request)
    cache_hash_key = get_cache_key(request)
    cache_timeout = get_cache_timeout()

    failures_cached = get_axes_cache().get(cache_hash_key)
    if failures_cached is not None:
        failures = failures_cached
    else:
        for attempt in attempts:
            failures = max(failures, attempt.failures_since_start)

    # add a failed attempt for this user
    failures += 1
    get_axes_cache().set(cache_hash_key, failures, cache_timeout)

    # has already attempted, update the info
    if attempts:
        for attempt in attempts:
            attempt.get_data = '%s\n---------\n%s' % (
                attempt.get_data,
                query2str(request.GET),
            )
            attempt.post_data = '%s\n---------\n%s' % (attempt.post_data,
                                                       query2str(request.POST))
            attempt.http_accept = http_accept
            attempt.path_info = path_info
            attempt.failures_since_start = failures
            attempt.attempt_time = timezone.now()
            attempt.save()

            log.info(
                'AXES: Repeated login failure by %s. Count = %d of %d',
                get_client_str(username, ip_address, user_agent, path_info),
                failures, settings.AXES_FAILURE_LIMIT)
    else:
        # Record failed attempt. Whether or not the IP address or user agent is
        # used in counting failures is handled elsewhere, so we just record
        # everything here.
        AccessAttempt.objects.create(
            user_agent=user_agent,
            ip_address=ip_address,
            username=username,
            get_data=query2str(request.GET),
            post_data=query2str(request.POST),
            http_accept=http_accept,
            path_info=path_info,
            failures_since_start=failures,
        )

        log.info('AXES: New login failure by %s. Creating access record.',
                 get_client_str(username, ip_address, user_agent, path_info))

    # no matter what, we want to lock them out if they're past the number of
    # attempts allowed, unless the user is set to notlockable
    if (failures >= settings.AXES_FAILURE_LIMIT
            and settings.AXES_LOCK_OUT_AT_FAILURE
            and is_user_lockable(request)):
        log.warning(
            'AXES: locked out %s after repeated login attempts.',
            get_client_str(username, ip_address, user_agent, path_info))

        # send signal when someone is locked out.
        user_locked_out.send('axes',
                             request=request,
                             username=username,
                             ip_address=ip_address)
示例#12
0
def delete_cache_after_delete(instance, **kwargs):  # pylint: disable=unused-argument
    cache_hash_key = get_cache_key(instance)
    get_axes_cache().delete(cache_hash_key)
示例#13
0
def update_cache_after_save(instance, **kwargs):  # pylint: disable=unused-argument
    cache_hash_key = get_cache_key(instance)
    if not get_axes_cache().get(cache_hash_key):
        cache_timeout = get_cache_timeout()
        get_axes_cache().set(cache_hash_key, instance.failures_since_start,
                             cache_timeout)
示例#14
0
    def user_login_failed(self, sender, credentials, request, **kwargs):  # pylint: disable=unused-argument
        """
        When user login fails, save AccessAttempt record in database and lock user out if necessary.

        :raises AxesSignalPermissionDenied: if user should is locked out
        """

        if request is None:
            log.warning('AxesHandler.user_login_failed does not function without a request.')
            return

        ip_address = get_client_ip(request)
        username = get_client_username(request, credentials)
        user_agent = request.META.get('HTTP_USER_AGENT', '<unknown>')[:255]
        path_info = request.META.get('PATH_INFO', '<unknown>')[:255]
        http_accept = request.META.get('HTTP_ACCEPT', '<unknown>')[:1025]

        if settings.AXES_NEVER_LOCKOUT_WHITELIST and ip_in_whitelist(ip_address):
            log.info('Login failed from whitelisted IP %s.', ip_address)
            return

        failures = 0
        attempts = get_user_attempts(request, credentials)
        cache_hash_key = get_cache_key(request, credentials)
        cache_timeout = get_cache_timeout()

        failures_cached = get_axes_cache().get(cache_hash_key)
        if failures_cached is not None:
            failures = failures_cached
        else:
            for attempt in attempts:
                failures = max(failures, attempt.failures_since_start)

        # add a failed attempt for this user
        failures += 1
        get_axes_cache().set(cache_hash_key, failures, cache_timeout)

        # has already attempted, update the info
        if attempts:
            for attempt in attempts:
                attempt.get_data = '%s\n---------\n%s' % (
                    attempt.get_data,
                    query2str(request.GET),
                )
                attempt.post_data = '%s\n---------\n%s' % (
                    attempt.post_data,
                    query2str(request.POST)
                )
                attempt.http_accept = http_accept
                attempt.path_info = path_info
                attempt.failures_since_start = failures
                attempt.attempt_time = now()
                attempt.save()

                log.info(
                    'AXES: Repeated login failure by %s. Count = %d of %d',
                    get_client_str(username, ip_address, user_agent, path_info),
                    failures,
                    settings.AXES_FAILURE_LIMIT,
                )
        else:
            # Record failed attempt. Whether or not the IP address or user agent is
            # used in counting failures is handled elsewhere, so we just record
            # everything here.
            AccessAttempt.objects.create(
                user_agent=user_agent,
                ip_address=ip_address,
                username=username,
                get_data=query2str(request.GET),
                post_data=query2str(request.POST),
                http_accept=http_accept,
                path_info=path_info,
                failures_since_start=failures,
            )

            log.info(
                'AXES: New login failure by %s. Creating access record.',
                get_client_str(username, ip_address, user_agent, path_info),
            )

        # no matter what, we want to lock them out if they're past the number of
        # attempts allowed, unless the user is set to notlockable
        if (
            failures >= settings.AXES_FAILURE_LIMIT and
            settings.AXES_LOCK_OUT_AT_FAILURE and
            is_user_lockable(request, credentials)
        ):
            log.warning(
                'AXES: Locked out %s after repeated login failures.',
                get_client_str(username, ip_address, user_agent, path_info),
            )

            # send signal when someone is locked out.
            user_locked_out.send(
                'axes',
                request=request,
                username=username,
                ip_address=ip_address,
            )

            raise AxesSignalPermissionDenied('Locked out due to repeated login failures.')
示例#15
0
def delete_cache_after_delete(instance, **kwargs):
    cache_hash_key = get_cache_key(instance)
    cache.delete(cache_hash_key)
示例#16
0
def update_cache_after_save(instance, **kwargs):
    cache_hash_key = get_cache_key(instance)
    if not cache.get(cache_hash_key):
        cache_timeout = get_cache_timeout()
        cache.set(cache_hash_key, instance.failures_since_start, cache_timeout)