diff --git a/defender/tests.py b/defender/tests.py index 43eb4fb..bb62c26 100644 --- a/defender/tests.py +++ b/defender/tests.py @@ -31,6 +31,7 @@ ADMIN_LOGIN_URL = reverse('admin:login') DJANGO_VERSION = StrictVersion(get_version()) VALID_USERNAME = VALID_PASSWORD = 'valid' +UPPER_USERNAME = 'VALID' class AccessAttemptTest(DefenderTestCase): @@ -210,6 +211,18 @@ class AccessAttemptTest(DefenderTestCase): response = self._login(username=VALID_USERNAME, remote_addr='8.8.8.8') self.assertContains(response, self.LOCKED_MESSAGE) + def test_blocked_username_uppercase_saved_lower(self): + """ + Test that a uppercase username is saved in lowercase + within the cache. + """ + for i in range(0, config.FAILURE_LIMIT + 2): + ip = '74.125.239.{0}.'.format(i) + self._login(username=UPPER_USERNAME, remote_addr=ip) + + self.assertNotIn(UPPER_USERNAME, utils.get_blocked_usernames()) + self.assertIn(UPPER_USERNAME.lower(), utils.get_blocked_usernames()) + def test_cooling_off(self): """ Tests if the cooling time allows a user to login """ diff --git a/defender/utils.py b/defender/utils.py index 9da01af..36e6676 100644 --- a/defender/utils.py +++ b/defender/utils.py @@ -48,6 +48,16 @@ def get_ip(request): return ip_address +def lower_username(username): + """ + Single entry point to force the username to lowercase, all the functions + that need to deal with username should call this. + """ + if username: + return username.lower() + return None + + def get_ip_attempt_cache_key(ip_address): """ get the cache key by ip """ return "{0}:failed:ip:{1}".format(config.CACHE_PREFIX, ip_address) @@ -55,7 +65,8 @@ def get_ip_attempt_cache_key(ip_address): def get_username_attempt_cache_key(username): """ get the cache key by username """ - return "{0}:failed:username:{1}".format(config.CACHE_PREFIX, username) + return "{0}:failed:username:{1}".format(config.CACHE_PREFIX, + lower_username(username)) def get_ip_blocked_cache_key(ip_address): @@ -65,7 +76,8 @@ def get_ip_blocked_cache_key(ip_address): def get_username_blocked_cache_key(username): """ get the cache key by username """ - return "{0}:blocked:username:{1}".format(config.CACHE_PREFIX, username) + return "{0}:blocked:username:{1}".format(config.CACHE_PREFIX, + lower_username(username)) def strip_keys(key_list): @@ -128,7 +140,7 @@ def get_user_attempts(request, get_username=get_username_from_request): """ ip_address = get_ip(request) - username = get_username(request) + username = lower_username(get_username(request)) # get by IP ip_count = REDIS_SERVER.get(get_ip_attempt_cache_key(ip_address))