Пример #1
0
def is_already_locked(request):
    ip = get_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 = cache.get(cache_hash_key)
    if failures_cached is not None:
        return (failures_cached >= settings.AXES_FAILURE_LIMIT
                and settings.AXES_LOCK_OUT_AT_FAILURE)
    else:
        for attempt in get_user_attempts(request):
            if (attempt.failures_since_start >= settings.AXES_FAILURE_LIMIT
                    and settings.AXES_LOCK_OUT_AT_FAILURE):
                return True

    return False
Пример #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_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:
        attributes += ua

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

    return cache_hash_key
Пример #3
0
    def test_custom_header_parsing(self):
        self.ip = '2001:db8:cafe::17'

        valid_headers = [
            ' 2001:db8:cafe::17 , 2001:db8:cafe::18',
        ]

        for header in valid_headers:
            self.request.META[settings.AXES_REVERSE_PROXY_HEADER] = header
            self.assertEqual(self.ip, get_ip(self.request))
Пример #4
0
    def test_custom_header_parsing(self):
        self.ip = '2001:db8:cafe::17'

        valid_headers = [
            ' 2001:db8:cafe::17 , 2001:db8:cafe::18',
        ]

        for header in valid_headers:
            self.request.META[settings.AXES_REVERSE_PROXY_HEADER] = header
            self.assertEqual(self.ip, get_ip(self.request))
Пример #5
0
    def test_iis_ipv4_port_stripping(self):
        self.ip = '192.168.1.1'

        valid_headers = [
            '192.168.1.1:6112',
            '192.168.1.1:6033, 192.168.1.2:9001',
        ]

        for header in valid_headers:
            self.request.META['HTTP_X_FORWARDED_FOR'] = header
            self.assertEqual(self.ip, get_ip(self.request))
Пример #6
0
    def test_iis_ipv4_port_stripping(self):
        self.ip = '192.168.1.1'

        valid_headers = [
            '192.168.1.1:6112',
            '192.168.1.1:6033, 192.168.1.2:9001',
        ]

        for header in valid_headers:
            self.request.META['HTTP_X_FORWARDED_FOR'] = header
            self.assertEqual(self.ip, get_ip(self.request))
Пример #7
0
    def test_header_ordering(self):
        self.ip = '2.2.2.2'

        valid_headers = [
            '4.4.4.4, 3.3.3.3, 2.2.2.2, 1.1.1.1',
            '         3.3.3.3, 2.2.2.2, 1.1.1.1',
            '                  2.2.2.2, 1.1.1.1',
        ]

        for header in valid_headers:
            self.request.META[settings.AXES_REVERSE_PROXY_HEADER] = header
            self.assertEqual(self.ip, get_ip(self.request))
Пример #8
0
    def test_valid_ipv6_parsing(self):
        self.ip = '2001:db8:cafe::17'

        valid_headers = [
            '2001:db8:cafe::17',
            '2001:db8:cafe::17 , 2001:db8:cafe::18',
            '2001:db8:cafe::17,  2001:db8:cafe::18, 192.168.1.1',
        ]

        for header in valid_headers:
            self.request.META['HTTP_X_FORWARDED_FOR'] = header
            self.assertEqual(self.ip, get_ip(self.request))
Пример #9
0
    def test_header_ordering(self):
        self.ip = '2.2.2.2'

        valid_headers = [
            '4.4.4.4, 3.3.3.3, 2.2.2.2, 1.1.1.1',
            '         3.3.3.3, 2.2.2.2, 1.1.1.1',
            '                  2.2.2.2, 1.1.1.1',
        ]

        for header in valid_headers:
            self.request.META[settings.AXES_REVERSE_PROXY_HEADER] = header
            self.assertEqual(self.ip, get_ip(self.request))
Пример #10
0
    def test_valid_ipv6_parsing(self):
        self.ip = '2001:db8:cafe::17'

        valid_headers = [
            '2001:db8:cafe::17',
            '2001:db8:cafe::17 , 2001:db8:cafe::18',
            '2001:db8:cafe::17,  2001:db8:cafe::18, 192.168.1.1',
        ]

        for header in valid_headers:
            self.request.META['HTTP_X_FORWARDED_FOR'] = header
            self.assertEqual(self.ip, get_ip(self.request))
Пример #11
0
def log_user_logged_in(sender, request, user, **kwargs):
    """ When a user logs in, update the access log
    """
    username = user.get_username()
    ip_address = get_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 {0}.'.format(
        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,
        )
Пример #12
0
def _query_user_attempts(request):
    """Returns access attempt record if it exists.
    Otherwise return None.
    """
    ip = get_ip(request)
    username = request.POST.get(settings.AXES_USERNAME_FORM_FIELD, None)

    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:
            params['user_agent'] = ua

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

    return attempts
Пример #13
0
 def test_invalid_headers_no_ip(self):
     self.request.META[settings.AXES_REVERSE_PROXY_HEADER] = ''
     with self.assertRaises(Warning):
         get_ip(self.request)
Пример #14
0
 def test_invalid_headers_no_ip(self):
     self.request.META[settings.AXES_REVERSE_PROXY_HEADER] = ''
     with self.assertRaises(Warning):
         get_ip(self.request)
Пример #15
0
def log_user_login_failed(sender, credentials, request, **kwargs):
    """ Create an AccessAttempt record if the login wasn't successful
    """
    username_field = get_user_model().USERNAME_FIELD
    if request is None or username_field not in credentials:
        log.error('Attempt to authenticate with a custom backend failed.')
        return

    ip_address = get_ip(request)
    username = credentials[username_field]
    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 = 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
    cache.set(cache_hash_key, failures, cache_timeout)

    # has already attempted, update the info
    if len(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()

            fail_msg = 'AXES: Repeated login failure by {0}.'.format(
                get_client_str(username, ip_address, user_agent, path_info))
            count_msg = 'Count = {0} of {1}'.format(
                failures, settings.AXES_FAILURE_LIMIT)
            log.info('{0} {1}'.format(fail_msg, count_msg))
    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 {0}. Creating access record.'.format(
                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 {0} after repeated login attempts.'.format(
                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)
Пример #16
0
    def decorated_login(request, *args, **kwargs):
        # share some useful information
        if func.__name__ != 'decorated_login' and VERBOSE:
            log_decorated_call(func, args, kwargs)

        # TODO: create a class to hold the attempts records and perform checks
        # with its methods? or just store attempts=get_user_attempts here and
        # pass it to the functions
        # also no need to keep accessing these:
        # ip = request.META.get('REMOTE_ADDR', '')
        # ua = request.META.get('HTTP_USER_AGENT', '<unknown>')
        # username = request.POST.get(USERNAME_FORM_FIELD, None)

        # if the request is currently under lockout, do not proceed to the
        # login function, go directly to lockout url, do not pass go, do not
        # collect messages about this login attempt
        if is_already_locked(request):
            return lockout_response(request, populate_login_form=True)

        # call the login function
        response = func(request, *args, **kwargs)

        if func.__name__ == 'decorated_login':
            # if we're dealing with this function itself, don't bother checking
            # for invalid login attempts.  I suppose there's a bunch of
            # recursion going on here that used to cause one failed login
            # attempt to generate 10+ failed access attempt records (with 3
            # failed attempts each supposedly)
            return response

        if request.method == 'POST':
            # see if the login was successful

            if request.is_ajax():
                login_unsuccessful = is_ajax_login_failed(response)
            else:
                login_unsuccessful = is_login_failed(response)

            # create a log of a login attempt
            create_access_log(request, login_unsuccessful)

            user_agent = request.META.get('HTTP_USER_AGENT', '<unknown>')[:255]
            http_accept = request.META.get('HTTP_ACCEPT', '<unknown>')[:1025]
            path_info = request.META.get('PATH_INFO', '<unknown>')[:255]
            if not DISABLE_ACCESS_LOG:
                username = request.POST.get(USERNAME_FORM_FIELD, None)
                ip_address = get_ip(request)

                if login_unsuccessful or not 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=not login_unsuccessful,
                    )
                if not login_unsuccessful and not DISABLE_SUCCESS_ACCESS_LOG:
                    log_successful_attempt(username, ip_address,
                                           user_agent, path_info)

            if check_request(request, login_unsuccessful):
                return response

            return lockout_response(request, populate_login_form=True)

        return response