Exemplo n.º 1
0
def get_cache_key(request_or_attempt, credentials: dict = None) -> str:
    """
    Build cache key name from request or AccessAttempt object.

    :param request_or_attempt: HttpRequest or AccessAttempt object
    :param credentials: credentials containing user information
    :return cache_key: Hash key that is usable for Django cache backends
    """

    if isinstance(request_or_attempt, AccessAttempt):
        username = request_or_attempt.username
        ip_address = request_or_attempt.ip_address
        user_agent = request_or_attempt.user_agent
    else:
        username = get_client_username(request_or_attempt, credentials)
        ip_address = get_client_ip(request_or_attempt)
        user_agent = get_client_user_agent(request_or_attempt)

    filter_kwargs = get_filter_kwargs(username, ip_address, user_agent)

    cache_key_components = ''.join(filter_kwargs.values())
    cache_key_digest = md5(cache_key_components.encode()).hexdigest()
    cache_key = 'axes-{}'.format(cache_key_digest)

    return cache_key
Exemplo n.º 2
0
def get_cache_key(request_or_obj):
    """
    Build cache key name from request or AccessAttempt object.
    :param  request_or_obj: Request or AccessAttempt object
    :return cache-key: String, key to be used in cache system
    """
    if isinstance(request_or_obj, AccessAttempt):
        ip = request_or_obj.ip_address
        un = request_or_obj.username
        ua = request_or_obj.user_agent
    else:
        ip = get_client_ip(request_or_obj)
        un = request_or_obj.POST.get(settings.AXES_USERNAME_FORM_FIELD, None)
        ua = request_or_obj.META.get('HTTP_USER_AGENT', '<unknown>')[:255]

    ip = ip.encode('utf-8') if ip else ''.encode('utf-8')
    un = un.encode('utf-8') if un else ''.encode('utf-8')
    ua = ua.encode('utf-8') if ua else ''.encode('utf-8')

    if settings.AXES_ONLY_USER_FAILURES:
        attributes = un
    elif settings.AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP:
        attributes = ip + un
    else:
        attributes = ip

    if settings.AXES_USE_USER_AGENT and not settings.AXES_ONLY_USER_FAILURES:
        attributes += ua

    cache_hash_key = 'axes-{}'.format(md5(attributes).hexdigest())

    return cache_hash_key
Exemplo n.º 3
0
def log_user_logged_in(sender, request, user, **kwargs):  # pylint: disable=unused-argument
    """ When a user logs in, update the access log
    """
    username = user.get_username()
    ip_address = get_client_ip(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]
    log.info('AXES: Successful login by %s.',
             get_client_str(username, ip_address, user_agent, path_info))

    if not settings.AXES_DISABLE_SUCCESS_ACCESS_LOG:
        AccessLog.objects.create(
            user_agent=user_agent,
            ip_address=ip_address,
            username=username,
            http_accept=http_accept,
            path_info=path_info,
            trusted=True,
        )

    if settings.AXES_RESET_ON_SUCCESS:
        count = reset_user_attempts(request)
        log.info('AXES: Deleted %d failed login attempts by %s.', count,
                 get_client_str(username, ip_address, user_agent, path_info))
Exemplo n.º 4
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(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]

        log.info(
            'AXES: Successful login by %s.',
            get_client_str(username, ip_address, user_agent, path_info),
        )

        if not settings.AXES_DISABLE_SUCCESS_ACCESS_LOG:
            AccessLog.objects.create(
                user_agent=user_agent,
                ip_address=ip_address,
                username=username,
                http_accept=http_accept,
                path_info=path_info,
                trusted=True,
            )

        if settings.AXES_RESET_ON_SUCCESS:
            count = reset_user_attempts(request, credentials)
            log.info(
                'AXES: Deleted %d failed login attempts by %s.',
                count,
                get_client_str(username, ip_address, user_agent, path_info),
            )
Exemplo n.º 5
0
def is_already_locked(request):
    ip = get_client_ip(request)

    if (settings.AXES_ONLY_USER_FAILURES
            or settings.AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP
        ) and request.method == 'GET':
        return False

    if settings.AXES_NEVER_LOCKOUT_WHITELIST and ip_in_whitelist(ip):
        return False

    if settings.AXES_ONLY_WHITELIST and not ip_in_whitelist(ip):
        return True

    if ip_in_blacklist(ip):
        return True

    if not is_user_lockable(request):
        return False

    cache_hash_key = get_cache_key(request)
    failures_cached = get_axes_cache().get(cache_hash_key)
    if failures_cached is not None:
        return (failures_cached >= Settings.objects.first().failure_limit
                and settings.AXES_LOCK_OUT_AT_FAILURE)
    else:
        for attempt in get_user_attempts(request):
            if (attempt.failures_since_start >=
                    Settings.objects.first().failure_limit
                    and settings.AXES_LOCK_OUT_AT_FAILURE):
                return True

    return False
Exemplo n.º 6
0
def get_cache_key(request_or_attempt, credentials: dict = None) -> str:
    """
    Build cache key name from request or AccessAttempt object.

    :param request_or_attempt: HttpRequest or AccessAttempt object
    :param credentials: credentials containing user information
    :return cache_key: Hash key that is usable for Django cache backends
    """

    if isinstance(request_or_attempt, AccessAttempt):
        username = request_or_attempt.username
        ip_address = request_or_attempt.ip_address
        user_agent = request_or_attempt.user_agent
    else:
        username = get_client_username(request_or_attempt, credentials)
        ip_address = get_client_ip(request_or_attempt)
        user_agent = get_client_user_agent(request_or_attempt)

    filter_kwargs = get_filter_kwargs(username, ip_address, user_agent)

    cache_key_components = ''.join(filter_kwargs.values())
    cache_key_digest = md5(cache_key_components.encode()).hexdigest()
    cache_key = 'axes-{}'.format(cache_key_digest)

    return cache_key
Exemplo n.º 7
0
def user_login_failed_logger(sender, credentials, request, **kwargs):
    ip_address = get_client_ip(request)

    logger.warning("login failed", extra={
        'user': credentials.get("username", ""),
        'ip': ip_address,
    })
Exemplo n.º 8
0
def user_logged_in_logger(sender, request, user, **kwargs):
    ip_address = get_client_ip(request)

    logger.info("login successful", extra={
        'user': user,
        'ip': ip_address,
    })
Exemplo n.º 9
0
def query_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(request)
    user_agent = get_client_user_agent(request)

    filter_kwargs = get_filter_kwargs(username, ip_address, user_agent)

    return AccessAttempt.objects.filter(**filter_kwargs)
Exemplo n.º 10
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(request)
    user_agent = get_client_user_agent(request)

    filter_kwargs = get_filter_kwargs(username, ip_address, user_agent)

    return AccessAttempt.objects.filter(**filter_kwargs)
Exemplo n.º 11
0
def is_ip_blacklisted(request: HttpRequest) -> bool:
    """
    Check if the given request refers to a blacklisted IP.
    """

    ip = get_client_ip(request)

    if settings.AXES_NEVER_LOCKOUT_WHITELIST and ip_in_whitelist(ip):
        return False

    if settings.AXES_ONLY_WHITELIST and not ip_in_whitelist(ip):
        return True

    if ip_in_blacklist(ip):
        return True

    return False
Exemplo n.º 12
0
def is_ip_blacklisted(request: HttpRequest) -> bool:
    """
    Check if the given request refers to a blacklisted IP.
    """

    ip = get_client_ip(request)

    if settings.AXES_NEVER_LOCKOUT_WHITELIST and ip_in_whitelist(ip):
        return False

    if settings.AXES_ONLY_WHITELIST and not ip_in_whitelist(ip):
        return True

    if ip_in_blacklist(ip):
        return True

    return False
Exemplo n.º 13
0
def _query_user_attempts(request):
    """Returns access attempt record if it exists.
    Otherwise return None.
    """
    ip = get_client_ip(request)
    username = get_client_username(request)

    if settings.AXES_ONLY_USER_FAILURES:
        attempts = AccessAttempt.objects.filter(username=username)
    elif settings.AXES_USE_USER_AGENT:
        ua = request.META.get('HTTP_USER_AGENT', '<unknown>')[:255]
        attempts = AccessAttempt.objects.filter(user_agent=ua,
                                                ip_address=ip,
                                                username=username,
                                                trusted=True)
    else:
        attempts = AccessAttempt.objects.filter(ip_address=ip,
                                                username=username,
                                                trusted=True)

    if not attempts:
        params = {'trusted': False}

        if settings.AXES_ONLY_USER_FAILURES:
            params['username'] = username
        elif settings.AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP:
            params['username'] = username
            params['ip_address'] = ip
        else:
            params['ip_address'] = ip

        if settings.AXES_USE_USER_AGENT and not settings.AXES_ONLY_USER_FAILURES:
            params['user_agent'] = ua

        attempts = AccessAttempt.objects.filter(**params)

    return attempts
Exemplo n.º 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

        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.')
Exemplo n.º 15
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)
Exemplo n.º 16
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.')