mirror of
https://github.com/jazzband/django-axes.git
synced 2026-05-21 22:01:53 +00:00
Test blocking configs, without the cache enabled.
Added 12 tests that verify lockouts for default, AXES_ONLY_USER_FAILURES, and LOCK_OUT_BY_COMBINATION_USER_AND_IP settings, under four conditions each: same/different user, and same/different IP address. Truth Table: ¦ ¦ ¦ ¦ ¦ ¦ ¦User IP Action ¦ ¦ ¦ ¦ ¦ ¦|-------------------------------- IP Only | Same Same Block (Default) | Same Different Allow ¦ ¦ ¦ ¦ ¦ ¦| Different Same Block ¦ ¦ ¦ ¦ ¦ ¦| Different Different Allow ¦ ¦ ¦ ¦ ¦ ¦|-------------------------------- User Only | Same Same Block ¦ ¦ ¦ ¦ ¦ ¦| Same Different Block ¦ ¦ ¦ ¦ ¦ ¦| Different Same Allow ¦ ¦ ¦ ¦ ¦ ¦| Different Different Allow ¦ ¦ ¦ ¦ ¦ ¦|-------------------------------- User and IP | Same Same Block ¦ ¦ ¦ ¦ ¦ ¦| Same Different Allow ¦ ¦ ¦ ¦ ¦ ¦| Different Same Allow ¦ ¦ ¦ ¦ ¦ ¦| Different Different Allow
This commit is contained in:
parent
9de8b356a6
commit
fb205cc95c
2 changed files with 282 additions and 0 deletions
281
axes/tests.py
281
axes/tests.py
|
|
@ -443,6 +443,287 @@ class AccessAttemptTest(TestCase):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class AccessAttemptConfigTest(TestCase):
|
||||
""" This set of tests checks for lockouts under different configurations
|
||||
and circumstances to prevent false positives and false negatives.
|
||||
Always block attempted logins for the same user from the same IP.
|
||||
Always allow attempted logins for a different user from a different IP.
|
||||
"""
|
||||
|
||||
IP_1 = '10.1.1.1'
|
||||
IP_2 = '10.2.2.2'
|
||||
USER_1 = 'valid-user-1'
|
||||
USER_2 = 'valid-user-2'
|
||||
VALID_PASSWORD = 'valid-password'
|
||||
WRONG_PASSWORD = 'wrong-password'
|
||||
LOCKED_MESSAGE = 'Account locked: too many login attempts.'
|
||||
LOGIN_FORM_KEY = '<input type="submit" value="Log in" />'
|
||||
ALLOWED = 302
|
||||
BLOCKED = 403
|
||||
|
||||
def _login(self, username, password, ip_addr='127.0.0.1',
|
||||
is_json=False, **kwargs):
|
||||
"""Login a user and get the response.
|
||||
IP address can be configured to test IP blocking functionality.
|
||||
"""
|
||||
try:
|
||||
admin_login = reverse('admin:login')
|
||||
except NoReverseMatch:
|
||||
admin_login = reverse('admin:index')
|
||||
|
||||
headers = {
|
||||
'user_agent': 'test-browser'
|
||||
}
|
||||
post_data = {
|
||||
'username': username,
|
||||
'password': password,
|
||||
'this_is_the_login_form': 1,
|
||||
}
|
||||
post_data.update(kwargs)
|
||||
|
||||
if is_json:
|
||||
headers.update({
|
||||
'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest',
|
||||
'content_type': 'application/json',
|
||||
})
|
||||
post_data = json.dumps(post_data)
|
||||
|
||||
response = self.client.post(
|
||||
admin_login, post_data, REMOTE_ADDR=ip_addr, **headers
|
||||
)
|
||||
return response
|
||||
|
||||
def _lockout_user1_from_ip1(self):
|
||||
for i in range(1, FAILURE_LIMIT+1):
|
||||
response = self._login(
|
||||
username=self.USER_1,
|
||||
password=self.WRONG_PASSWORD,
|
||||
ip_addr=self.IP_1
|
||||
)
|
||||
return response
|
||||
|
||||
def setUp(self):
|
||||
"""Create two valid users for authentication.
|
||||
"""
|
||||
|
||||
self.user = User.objects.create_superuser(
|
||||
username=self.USER_1,
|
||||
email='test_1@example.com',
|
||||
password=self.VALID_PASSWORD,
|
||||
)
|
||||
self.user = User.objects.create_superuser(
|
||||
username=self.USER_2,
|
||||
email='test_2@example.com',
|
||||
password=self.VALID_PASSWORD,
|
||||
)
|
||||
|
||||
# Test for true and false positives when blocking by IP *OR* user (default).
|
||||
# Cache disabled. Default settings.
|
||||
@patch('axes.decorators.cache.set', return_value=None)
|
||||
@patch('axes.decorators.cache.get', return_value=None)
|
||||
def test_lockout_by_ip_blocks_when_same_user_same_ip_without_cache(
|
||||
self, cache_get_mock=None, cache_set_mock=None
|
||||
):
|
||||
# User 1 is locked out from IP 1.
|
||||
self._lockout_user1_from_ip1()
|
||||
|
||||
# User 1 is still blocked from IP 1.
|
||||
response = self._login(
|
||||
self.USER_1,
|
||||
self.VALID_PASSWORD,
|
||||
ip_addr=self.IP_1
|
||||
)
|
||||
self.assertEqual(response.status_code, self.BLOCKED)
|
||||
|
||||
@patch('axes.decorators.cache.set', return_value=None)
|
||||
@patch('axes.decorators.cache.get', return_value=None)
|
||||
def test_lockout_by_ip_allows_when_same_user_diff_ip_without_cache(
|
||||
self, cache_get_mock=None, cache_set_mock=None
|
||||
):
|
||||
# User 1 is locked out from IP 1.
|
||||
self._lockout_user1_from_ip1()
|
||||
|
||||
# User 1 can still login from IP 2.
|
||||
response = self._login(
|
||||
self.USER_1,
|
||||
self.VALID_PASSWORD,
|
||||
ip_addr=self.IP_2
|
||||
)
|
||||
self.assertEqual(response.status_code, self.ALLOWED)
|
||||
|
||||
@patch('axes.decorators.cache.set', return_value=None)
|
||||
@patch('axes.decorators.cache.get', return_value=None)
|
||||
def test_lockout_by_ip_blocks_when_diff_user_same_ip_without_cache(
|
||||
self, cache_get_mock=None, cache_set_mock=None
|
||||
):
|
||||
# User 1 is locked out from IP 1.
|
||||
self._lockout_user1_from_ip1()
|
||||
|
||||
# User 2 is also locked out from IP 1.
|
||||
response = self._login(
|
||||
self.USER_2,
|
||||
self.VALID_PASSWORD,
|
||||
ip_addr=self.IP_1
|
||||
)
|
||||
self.assertEqual(response.status_code, self.BLOCKED)
|
||||
|
||||
@patch('axes.decorators.cache.set', return_value=None)
|
||||
@patch('axes.decorators.cache.get', return_value=None)
|
||||
def test_lockout_by_ip_allows_when_diff_user_diff_ip_without_cache(
|
||||
self, cache_get_mock=None, cache_set_mock=None
|
||||
):
|
||||
# User 1 is locked out from IP 1.
|
||||
self._lockout_user1_from_ip1()
|
||||
|
||||
# User 2 can still login from IP 2.
|
||||
response = self._login(
|
||||
self.USER_2,
|
||||
self.VALID_PASSWORD,
|
||||
ip_addr=self.IP_2
|
||||
)
|
||||
self.assertEqual(response.status_code, self.ALLOWED)
|
||||
|
||||
# Test for true and false positives when blocking by user only.
|
||||
# Cache disabled. When AXES_ONLY_USER_FAILURES = True
|
||||
@patch('axes.decorators.AXES_ONLY_USER_FAILURES', True)
|
||||
@patch('axes.decorators.cache.set', return_value=None)
|
||||
@patch('axes.decorators.cache.get', return_value=None)
|
||||
def test_lockout_by_user_blocks_when_same_user_same_ip_without_cache(
|
||||
self, cache_get_mock=None, cache_set_mock=None
|
||||
):
|
||||
# User 1 is locked out from IP 1.
|
||||
self._lockout_user1_from_ip1()
|
||||
|
||||
# User 1 is still blocked from IP 1.
|
||||
response = self._login(
|
||||
self.USER_1,
|
||||
self.VALID_PASSWORD,
|
||||
ip_addr=self.IP_1
|
||||
)
|
||||
self.assertEqual(response.status_code, self.BLOCKED)
|
||||
|
||||
@patch('axes.decorators.AXES_ONLY_USER_FAILURES', True)
|
||||
@patch('axes.decorators.cache.set', return_value=None)
|
||||
@patch('axes.decorators.cache.get', return_value=None)
|
||||
def test_lockout_by_user_blocks_when_same_user_diff_ip_without_cache(
|
||||
self, cache_get_mock=None, cache_set_mock=None
|
||||
):
|
||||
# User 1 is locked out from IP 1.
|
||||
self._lockout_user1_from_ip1()
|
||||
|
||||
# User 1 is also locked out from IP 2.
|
||||
response = self._login(
|
||||
self.USER_1,
|
||||
self.VALID_PASSWORD,
|
||||
ip_addr=self.IP_2
|
||||
)
|
||||
self.assertEqual(response.status_code, self.BLOCKED)
|
||||
|
||||
@patch('axes.decorators.AXES_ONLY_USER_FAILURES', True)
|
||||
@patch('axes.decorators.cache.set', return_value=None)
|
||||
@patch('axes.decorators.cache.get', return_value=None)
|
||||
def test_lockout_by_user_allows_when_diff_user_same_ip_without_cache(
|
||||
self, cache_get_mock=None, cache_set_mock=None
|
||||
):
|
||||
# User 1 is locked out from IP 1.
|
||||
self._lockout_user1_from_ip1()
|
||||
|
||||
# User 2 can still login from IP 1.
|
||||
response = self._login(
|
||||
self.USER_2,
|
||||
self.VALID_PASSWORD,
|
||||
ip_addr=self.IP_1
|
||||
)
|
||||
self.assertEqual(response.status_code, self.ALLOWED)
|
||||
|
||||
@patch('axes.decorators.AXES_ONLY_USER_FAILURES', True)
|
||||
@patch('axes.decorators.cache.set', return_value=None)
|
||||
@patch('axes.decorators.cache.get', return_value=None)
|
||||
def test_lockout_by_user_allows_when_diff_user_diff_ip_without_cache(
|
||||
self, cache_get_mock=None, cache_set_mock=None
|
||||
):
|
||||
# User 1 is locked out from IP 1.
|
||||
self._lockout_user1_from_ip1()
|
||||
|
||||
# User 2 can still login from IP 2.
|
||||
response = self._login(
|
||||
self.USER_2,
|
||||
self.VALID_PASSWORD,
|
||||
ip_addr=self.IP_2
|
||||
)
|
||||
self.assertEqual(response.status_code, self.ALLOWED)
|
||||
|
||||
# Test for true and false positives when blocking by user and IP together.
|
||||
# Cache disabled. When LOCK_OUT_BY_COMBINATION_USER_AND_IP = True
|
||||
@patch('axes.decorators.LOCK_OUT_BY_COMBINATION_USER_AND_IP', True)
|
||||
@patch('axes.decorators.cache.set', return_value=None)
|
||||
@patch('axes.decorators.cache.get', return_value=None)
|
||||
def test_lockout_by_user_and_ip_blocks_when_same_user_same_ip_without_cache(
|
||||
self, cache_get_mock=None, cache_set_mock=None
|
||||
):
|
||||
# User 1 is locked out from IP 1.
|
||||
self._lockout_user1_from_ip1()
|
||||
|
||||
# User 1 is still blocked from IP 1.
|
||||
response = self._login(
|
||||
self.USER_1,
|
||||
self.VALID_PASSWORD,
|
||||
ip_addr=self.IP_1
|
||||
)
|
||||
self.assertEqual(response.status_code, self.BLOCKED)
|
||||
|
||||
@patch('axes.decorators.LOCK_OUT_BY_COMBINATION_USER_AND_IP', True)
|
||||
@patch('axes.decorators.cache.set', return_value=None)
|
||||
@patch('axes.decorators.cache.get', return_value=None)
|
||||
def test_lockout_by_user_and_ip_allows_when_same_user_diff_ip_without_cache(
|
||||
self, cache_get_mock=None, cache_set_mock=None
|
||||
):
|
||||
# User 1 is locked out from IP 1.
|
||||
self._lockout_user1_from_ip1()
|
||||
|
||||
# User 1 can still login from IP 2.
|
||||
response = self._login(
|
||||
self.USER_1,
|
||||
self.VALID_PASSWORD,
|
||||
ip_addr=self.IP_2
|
||||
)
|
||||
self.assertEqual(response.status_code, self.ALLOWED)
|
||||
|
||||
@patch('axes.decorators.LOCK_OUT_BY_COMBINATION_USER_AND_IP', True)
|
||||
@patch('axes.decorators.cache.set', return_value=None)
|
||||
@patch('axes.decorators.cache.get', return_value=None)
|
||||
def test_lockout_by_user_and_ip_allows_when_diff_user_same_ip_without_cache(
|
||||
self, cache_get_mock=None, cache_set_mock=None
|
||||
):
|
||||
# User 1 is locked out from IP 1.
|
||||
self._lockout_user1_from_ip1()
|
||||
|
||||
# User 2 can still login from IP 1.
|
||||
response = self._login(
|
||||
self.USER_2,
|
||||
self.VALID_PASSWORD,
|
||||
ip_addr=self.IP_1
|
||||
)
|
||||
self.assertEqual(response.status_code, self.ALLOWED)
|
||||
|
||||
@patch('axes.decorators.LOCK_OUT_BY_COMBINATION_USER_AND_IP', True)
|
||||
@patch('axes.decorators.cache.set', return_value=None)
|
||||
@patch('axes.decorators.cache.get', return_value=None)
|
||||
def test_lockout_by_user_and_ip_allows_when_diff_user_diff_ip_without_cache(
|
||||
self, cache_get_mock=None, cache_set_mock=None
|
||||
):
|
||||
# User 1 is locked out from IP 1.
|
||||
self._lockout_user1_from_ip1()
|
||||
|
||||
# User 2 can still login from IP 2.
|
||||
response = self._login(
|
||||
self.USER_2,
|
||||
self.VALID_PASSWORD,
|
||||
ip_addr=self.IP_2
|
||||
)
|
||||
self.assertEqual(response.status_code, self.ALLOWED)
|
||||
|
||||
|
||||
class UtilsTest(TestCase):
|
||||
def test_iso8601(self):
|
||||
"""Tests iso8601 correctly translates datetime.timdelta to ISO 8601
|
||||
|
|
|
|||
|
|
@ -20,5 +20,6 @@ def run_tests(settings_module, *modules):
|
|||
if __name__ == '__main__':
|
||||
run_tests('axes.test_settings', [
|
||||
'axes.tests.AccessAttemptTest',
|
||||
'axes.tests.AccessAttemptConfigTest',
|
||||
'axes.tests.UtilsTest',
|
||||
])
|
||||
|
|
|
|||
Loading…
Reference in a new issue