def lockout_response(request): if gs("LOCKOUT_TEMPLATE"): context = {"cooloff_time": gs("COOLOFF_TIME"), "failure_limit": gs("FAILURE_LIMIT")} return render_to_response(gs("LOCKOUT_TEMPLATE"), context, context_instance=RequestContext(request)) LOCKOUT_URL = get_lockout_url() if LOCKOUT_URL: return HttpResponseRedirect(LOCKOUT_URL) if gs("COOLOFF_TIME"): return HttpResponse("Account locked: too many login attempts. " "Please try again later.") else: return HttpResponse("Account locked: too many login attempts. " "Contact an admin to unlock your account.")
def get_ip(request): if not gs("BEHIND_REVERSE_PROXY"): ip = request.META.get("REMOTE_ADDR", "") else: logging.debug( "Axes is configured to be behind reverse proxy...looking for header value %s", gs("REVERSE_PROXY_HEADER") ) ip = request.META.get(gs("REVERSE_PROXY_HEADER"), "") if ip == "": raise Warning( "Axes is configured for operation behind a reverse proxy but could not find " "an HTTP header value {0}. Check your proxy server settings " "to make sure this header value is being passed.".format(gs("REVERSE_PROXY_HEADER")) ) return ip
def is_already_locked(request): ip = get_ip(request) if gs("ONLY_WHITELIST"): if not ip_in_whitelist(ip): return True if ip_in_blacklist(ip): return True attempts = get_user_attempts(request) user_lockable = is_user_lockable(request) for attempt in attempts: if attempt.failures_since_start >= gs("FAILURE_LIMIT") and gs("LOCK_OUT_AT_FAILURE") and user_lockable: return True return False
def get_user_attempts(request): """ Returns access attempt record if it exists. Otherwise return None. """ ip = get_ip(request) username = request.POST.get("username", None) if gs("USE_USER_AGENT"): ua = request.META.get("HTTP_USER_AGENT", "<unknown>") 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 len(attempts) == 0: params = {"ip_address": ip, "trusted": False} if gs("USE_USER_AGENT"): params["user_agent"] = ua attempts = AccessAttempt.objects.filter(**params) if username and not ip_in_whitelist(ip): del params["ip_address"] params["username"] = username attempts |= AccessAttempt.objects.filter(**params) attempts = list(attempts) if gs("COOLOFF_TIME"): for attempt in attempts: COOLOFF_TIME = gs("COOLOFF_TIME") if isinstance(COOLOFF_TIME, int) or isinstance(COOLOFF_TIME, float): COOLOFF_TIME = timedelta(hours=COOLOFF_TIME) if attempt.attempt_time + COOLOFF_TIME < datetime.now() and attempt.trusted is False: attempt.delete() attempts.pop(attempts.index(attempt)) return attempts
def decorated_login(request, *args, **kwargs): # share some useful information if func.__name__ != "decorated_login" and gs("VERBOSE"): log.debug("AXES: Calling decorated function: %s" % func.__name__) if args: log.debug("args: %s" % args) if kwargs: log.debug("kwargs: %s" % 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', 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) # 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 login_unsuccessful = response and not response.has_header("location") and response.status_code != 302 log_access_request(request, login_unsuccessful) if check_request(request, login_unsuccessful): return response return lockout_response(request) return response
def ip_in_blacklist(ip): if gs("IP_BLACKLIST") is not None: return ip in gs("IP_BLACKLIST") else: return False
def ip_in_whitelist(ip): if gs("IP_WHITELIST") is not None: return ip in gs("IP_WHITELIST") else: return False
def check_request(request, login_unsuccessful): ip_address = get_ip(request) username = request.POST.get("username", None) failures = 0 attempts = get_user_attempts(request) for attempt in attempts: failures = max(failures, attempt.failures_since_start) if login_unsuccessful: # add a failed attempt for this user failures += 1 # Create an AccessAttempt record if the login wasn't successful # 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.items())) attempt.post_data = "%s\n---------\n%s" % (attempt.post_data, query2str(request.POST.items())) attempt.http_accept = request.META.get("HTTP_ACCEPT", "<unknown>") attempt.path_info = request.META.get("PATH_INFO", "<unknown>") attempt.failures_since_start = failures attempt.attempt_time = datetime.now() attempt.save() log.info( "AXES: Repeated login failure by %s. Updating access " "record. Count = %s" % (attempt.ip_address, failures) ) else: create_new_failure_records(request, failures) else: # user logged in -- forget the failed attempts failures = 0 trusted_record_exists = False for attempt in attempts: if not attempt.trusted: attempt.delete() else: trusted_record_exists = True attempt.failures_since_start = 0 attempt.save() if trusted_record_exists is False: create_new_trusted_record(request) user_lockable = is_user_lockable(request) # 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 >= gs("FAILURE_LIMIT") and gs("LOCK_OUT_AT_FAILURE") and user_lockable: # We log them out in case they actually managed to enter the correct # password logout(request) log.warn("AXES: locked out %s after repeated login attempts." % (ip_address,)) # send signal when someone is locked out. user_locked_out.send("axes", request=request, username=username, ip_address=ip_address) # if a trusted login has violated lockout, revoke trust for attempt in [a for a in attempts if a.trusted]: attempt.delete() create_new_failure_records(request, failures) return False return True
def ip_in_whitelist(ip): if gs("IP_WHITELIST") is not None: return ip in gs("IP_WHITELIST") else: return False def ip_in_blacklist(ip): if gs("IP_BLACKLIST") is not None: return ip in gs("IP_BLACKLIST") else: return False log = logging.getLogger(gs("LOGGER")) if gs("VERBOSE"): log.debug("AXES: BEGIN LOG") log.debug("Using django-axes " + axes.get_version()) def is_user_lockable(request): """ Check if the user has a profile with nolockout If so, then return the value to see if this user is special and doesn't get their account locked out """ username = request.POST.get("username", None) try: user = User.objects.get(username=username) except User.DoesNotExist: # not a valid user return True