diff --git a/defender/config.py b/defender/config.py index b63ef65..309e787 100644 --- a/defender/config.py +++ b/defender/config.py @@ -1,6 +1,13 @@ from django.conf import settings from django.utils.translation import ugettext_lazy +class LoginAttemptStatus(object): + + values = ['LOGIN_SUCCEED','LOGIN_FAILED_SHOW_WARNING','LOGIN_FAILED_LOCK_USER', 'LOGIN_FAILED_PASS_USER'] + + class __metaclass__(type): + def __getattr__(self, name): + return self.values.index(name) def get_setting(variable, default=None): """ get the 'variable' from settings if not there use the diff --git a/defender/decorators.py b/defender/decorators.py index 5bdf5cd..3900b82 100644 --- a/defender/decorators.py +++ b/defender/decorators.py @@ -1,5 +1,7 @@ from . import utils - +from . import config +from django.shortcuts import render_to_response +from django.template import RequestContext def watch_login(func): """ @@ -36,10 +38,18 @@ def watch_login(func): # it inline for now. utils.add_login_attempt_to_db(request, not login_unsuccessful) - if utils.check_request(request, login_unsuccessful): + login_attempt_status = utils.check_request(request, login_unsuccessful) + + if login_attempt_status in [config.LoginAttemptStatus.LOGIN_SUCCEED, config.LoginAttemptStatus.LOGIN_FAILED_PASS_USER]: return response - return utils.lockout_response(request) + elif login_attempt_status == config.LoginAttemptStatus.LOGIN_FAILED_LOCK_USER: + return utils.lockout_response(request) + + elif login_attempt_status == config.LoginAttemptStatus.LOGIN_FAILED_SHOW_WARNING: + return render_to_response('auth/login.html', {"error_list": ["Invalid email and/or password. " + "WARNING: Your account will lock after 2 more unsuccessful login attempts."]}, + context_instance=RequestContext(request)) return response diff --git a/defender/utils.py b/defender/utils.py index fb21264..12be2e6 100644 --- a/defender/utils.py +++ b/defender/utils.py @@ -160,20 +160,22 @@ def record_failed_attempt(ip, username): """ record the failed login attempt, if over limit return False, if not over limit return True """ # increment the failed count, and get current number - ip_count = increment_key(get_ip_attempt_cache_key(ip)) user_count = increment_key(get_username_attempt_cache_key(username)) - ip_block = False + status = config.LoginAttemptStatus.LOGIN_FAILED_PASS_USER user_block = False - # if either are over the limit, add to block - if ip_count > config.FAILURE_LIMIT: - block_ip(ip) - ip_block = True - if user_count > config.FAILURE_LIMIT: + + if config.WARNING_LIMIT: + if user_count == config.WARNING_LIMIT: + return config.LoginAttemptStatus.LOGIN_FAILED_SHOW_WARNING + + if user_count >= config.FAILURE_LIMIT: block_username(username) user_block = True - # if any blocks return False, no blocks return True - return not (ip_block or user_block) + + if user_block: + return config.LoginAttemptStatus.LOGIN_FAILED_LOCK_USER + return status def unblock_ip(ip, pipe=None): @@ -266,7 +268,7 @@ def check_request(request, login_unsuccessful): if not login_unsuccessful: # user logged in -- forget the failed attempts reset_failed_attempts(ip=ip_address, username=username) - return True + return config.LoginAttemptStatus.LOGIN_SUCCEED else: # add a failed attempt for this user return record_failed_attempt(ip_address, username)