From d55af8e966b52b5bc9cef61a9fbe1744957b4a16 Mon Sep 17 00:00:00 2001 From: Camilo Nova Date: Fri, 17 Nov 2017 18:12:30 -0500 Subject: [PATCH] Improve tests and fix global lockout. Fixes #261 --- axes/attempts.py | 6 +++ axes/tests/test_access_attempt_config.py | 56 ++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/axes/attempts.py b/axes/attempts.py index 57c5fe3..3ecb291 100644 --- a/axes/attempts.py +++ b/axes/attempts.py @@ -177,6 +177,12 @@ def is_user_lockable(request): 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 diff --git a/axes/tests/test_access_attempt_config.py b/axes/tests/test_access_attempt_config.py index 99e57b2..fe1414f 100644 --- a/axes/tests/test_access_attempt_config.py +++ b/axes/tests/test_access_attempt_config.py @@ -51,15 +51,21 @@ class AccessAttemptConfigTest(TestCase): ) return response - def _lockout_user1_from_ip1(self): + def _lockout_user_from_ip(self, username, ip_addr): for i in range(1, settings.AXES_FAILURE_LIMIT + 1): response = self._login( - username=self.USER_1, + username=username, password=self.WRONG_PASSWORD, - ip_addr=self.IP_1 + ip_addr=ip_addr, ) return response + def _lockout_user1_from_ip1(self): + return self._lockout_user_from_ip( + username=self.USER_1, + ip_addr=self.IP_1, + ) + def setUp(self): """Create two valid users for authentication. """ @@ -178,6 +184,17 @@ class AccessAttemptConfigTest(TestCase): ) self.assertEqual(response.status_code, self.ALLOWED) + @override_settings(AXES_ONLY_USER_FAILURES=True) + def test_lockout_by_user_with_empty_username_allows_other_users_without_cache( + self, cache_get_mock=None, cache_set_mock=None + ): + # User with empty username is locked out from IP 1. + self._lockout_user_from_ip(username='', ip_addr=self.IP_1) + + # Still possible to access the login page + response = self.client.get(reverse('admin:login'), REMOTE_ADDR=self.IP_1) + self.assertContains(response, self.LOGIN_FORM_KEY, status_code=200) + # Test for true and false positives when blocking by user and IP together. # Cache disabled. When LOCK_OUT_BY_COMBINATION_USER_AND_IP = True @override_settings(AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=True) @@ -232,6 +249,17 @@ class AccessAttemptConfigTest(TestCase): ) self.assertEqual(response.status_code, self.ALLOWED) + @override_settings(AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=True) + def test_lockout_by_user_and_ip_with_empty_username_allows_other_users_without_cache( + self, cache_get_mock=None, cache_set_mock=None + ): + # User with empty username is locked out from IP 1. + self._lockout_user_from_ip(username='', ip_addr=self.IP_1) + + # Still possible to access the login page + response = self.client.get(reverse('admin:login'), REMOTE_ADDR=self.IP_1) + self.assertContains(response, self.LOGIN_FORM_KEY, status_code=200) + # Test for true and false positives when blocking by IP *OR* user (default) # With cache enabled. Default criteria. def test_lockout_by_ip_blocks_when_same_user_same_ip_using_cache(self): @@ -282,6 +310,17 @@ class AccessAttemptConfigTest(TestCase): ) self.assertEqual(response.status_code, self.ALLOWED) + @override_settings(AXES_ONLY_USER_FAILURES=True) + def test_lockout_by_user_with_empty_username_allows_other_users_using_cache( + self, cache_get_mock=None, cache_set_mock=None + ): + # User with empty username is locked out from IP 1. + self._lockout_user_from_ip(username='', ip_addr=self.IP_1) + + # Still possible to access the login page + response = self.client.get(reverse('admin:login'), REMOTE_ADDR=self.IP_1) + self.assertContains(response, self.LOGIN_FORM_KEY, status_code=200) + # Test for true and false positives when blocking by user only. # With cache enabled. When AXES_ONLY_USER_FAILURES = True @override_settings(AXES_ONLY_USER_FAILURES=True) @@ -389,3 +428,14 @@ class AccessAttemptConfigTest(TestCase): ip_addr=self.IP_2 ) self.assertEqual(response.status_code, self.ALLOWED) + + @override_settings(AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=True) + def test_lockout_by_user_and_ip_with_empty_username_allows_other_users_using_cache( + self, cache_get_mock=None, cache_set_mock=None + ): + # User with empty username is locked out from IP 1. + self._lockout_user_from_ip(username='', ip_addr=self.IP_1) + + # Still possible to access the login page + response = self.client.get(reverse('admin:login'), REMOTE_ADDR=self.IP_1) + self.assertContains(response, self.LOGIN_FORM_KEY, status_code=200)