2019-02-12 21:22:52 +00:00
|
|
|
from unittest.mock import patch
|
|
|
|
|
|
2019-02-22 23:22:11 +00:00
|
|
|
from django.test import override_settings
|
2019-02-12 21:22:52 +00:00
|
|
|
|
2024-03-04 18:27:20 +00:00
|
|
|
from axes import __version__
|
2019-02-12 21:22:52 +00:00
|
|
|
from axes.apps import AppConfig
|
2019-02-22 23:22:11 +00:00
|
|
|
from axes.models import AccessAttempt, AccessLog
|
2021-01-06 21:42:25 +00:00
|
|
|
from tests.base import AxesTestCase
|
2019-02-12 21:22:52 +00:00
|
|
|
|
2022-05-09 14:23:37 +00:00
|
|
|
_BEGIN = "AXES: BEGIN version %s, %s"
|
2024-03-04 18:27:20 +00:00
|
|
|
_VERSION = __version__
|
2022-05-09 14:23:37 +00:00
|
|
|
|
2019-02-12 21:22:52 +00:00
|
|
|
|
2020-09-26 14:50:13 +00:00
|
|
|
@patch("axes.apps.AppConfig.initialized", False)
|
2019-09-28 16:27:50 +00:00
|
|
|
@patch("axes.apps.log")
|
2019-02-22 23:22:11 +00:00
|
|
|
class AppsTestCase(AxesTestCase):
|
2019-02-12 21:22:52 +00:00
|
|
|
def test_axes_config_log_re_entrant(self, log):
|
|
|
|
|
"""
|
2019-02-16 16:33:07 +00:00
|
|
|
Test that initialize call count does not increase on repeat calls.
|
2019-02-12 21:22:52 +00:00
|
|
|
"""
|
|
|
|
|
|
2019-02-16 16:33:07 +00:00
|
|
|
AppConfig.initialize()
|
2019-02-12 21:22:52 +00:00
|
|
|
calls = log.info.call_count
|
|
|
|
|
|
2019-02-16 16:33:07 +00:00
|
|
|
AppConfig.initialize()
|
2019-02-12 21:22:52 +00:00
|
|
|
self.assertTrue(
|
|
|
|
|
calls == log.info.call_count and calls > 0,
|
2019-09-28 16:27:50 +00:00
|
|
|
"AxesConfig.initialize needs to be re-entrant",
|
2019-02-12 21:22:52 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@override_settings(AXES_VERBOSE=False)
|
|
|
|
|
def test_axes_config_log_not_verbose(self, log):
|
2019-02-16 16:33:07 +00:00
|
|
|
AppConfig.initialize()
|
2019-02-12 21:22:52 +00:00
|
|
|
self.assertFalse(log.info.called)
|
|
|
|
|
|
2023-05-04 09:51:16 +00:00
|
|
|
@override_settings(AXES_LOCKOUT_PARAMETERS=["username"])
|
2019-02-12 21:22:52 +00:00
|
|
|
def test_axes_config_log_user_only(self, log):
|
2019-02-16 16:33:07 +00:00
|
|
|
AppConfig.initialize()
|
2023-05-04 09:51:16 +00:00
|
|
|
log.info.assert_called_with(_BEGIN, _VERSION, "blocking by username")
|
2019-02-12 21:22:52 +00:00
|
|
|
|
|
|
|
|
def test_axes_config_log_ip_only(self, log):
|
2019-02-16 16:33:07 +00:00
|
|
|
AppConfig.initialize()
|
2023-05-04 09:51:16 +00:00
|
|
|
log.info.assert_called_with(_BEGIN, _VERSION, "blocking by ip_address")
|
2019-02-12 21:22:52 +00:00
|
|
|
|
2023-05-10 17:06:55 +00:00
|
|
|
@override_settings(AXES_LOCKOUT_PARAMETERS=[["username", "ip_address"]])
|
2019-02-12 21:22:52 +00:00
|
|
|
def test_axes_config_log_user_ip(self, log):
|
2019-02-16 16:33:07 +00:00
|
|
|
AppConfig.initialize()
|
2023-05-04 09:51:16 +00:00
|
|
|
log.info.assert_called_with(
|
|
|
|
|
_BEGIN, _VERSION, "blocking by combination of username and ip_address"
|
|
|
|
|
)
|
2019-02-22 23:22:11 +00:00
|
|
|
|
2023-05-04 09:51:16 +00:00
|
|
|
@override_settings(AXES_LOCKOUT_PARAMETERS=["username", "ip_address"])
|
2020-07-07 08:46:22 +00:00
|
|
|
def test_axes_config_log_user_or_ip(self, log):
|
|
|
|
|
AppConfig.initialize()
|
2023-05-04 09:51:16 +00:00
|
|
|
log.info.assert_called_with(_BEGIN, _VERSION, "blocking by username or ip_address")
|
2020-07-07 08:46:22 +00:00
|
|
|
|
2019-02-22 23:22:11 +00:00
|
|
|
|
|
|
|
|
class AccessLogTestCase(AxesTestCase):
|
|
|
|
|
def test_access_log_on_logout(self):
|
|
|
|
|
"""
|
2024-04-30 14:22:50 +00:00
|
|
|
Test a valid logout and make sure the logout_time is updated only for that.
|
2019-02-22 23:22:11 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
self.login(is_valid_username=True, is_valid_password=True)
|
2024-04-30 14:22:50 +00:00
|
|
|
latest_log = AccessLog.objects.latest("id")
|
|
|
|
|
self.assertIsNone(latest_log.logout_time)
|
|
|
|
|
other_log = self.create_log(session_hash='not-the-session')
|
|
|
|
|
self.assertIsNone(other_log.logout_time)
|
2019-02-22 23:22:11 +00:00
|
|
|
|
2024-04-30 14:22:50 +00:00
|
|
|
response = self.logout()
|
2019-09-28 16:27:50 +00:00
|
|
|
self.assertContains(response, "Logged out")
|
2024-04-30 14:22:50 +00:00
|
|
|
other_log.refresh_from_db()
|
|
|
|
|
self.assertIsNone(other_log.logout_time)
|
|
|
|
|
latest_log.refresh_from_db()
|
|
|
|
|
self.assertIsNotNone(latest_log.logout_time)
|
2019-02-22 23:22:11 +00:00
|
|
|
|
2023-02-17 13:42:49 +00:00
|
|
|
@override_settings(DATA_UPLOAD_MAX_NUMBER_FIELDS=1500)
|
2019-02-22 23:22:11 +00:00
|
|
|
def test_log_data_truncated(self):
|
|
|
|
|
"""
|
|
|
|
|
Test that get_query_str properly truncates data to the max_length (default 1024).
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# An impossibly large post dict
|
2024-09-21 08:32:34 +00:00
|
|
|
extra_data = {"too-large-field": "x" * 2 ** 16}
|
2019-02-22 23:22:11 +00:00
|
|
|
self.login(**extra_data)
|
2019-09-28 16:27:50 +00:00
|
|
|
self.assertEqual(len(AccessAttempt.objects.latest("id").post_data), 1024)
|
2019-02-22 23:22:11 +00:00
|
|
|
|
2019-05-25 17:23:18 +00:00
|
|
|
@override_settings(AXES_DISABLE_ACCESS_LOG=True)
|
2019-02-22 23:22:11 +00:00
|
|
|
def test_valid_logout_without_success_log(self):
|
|
|
|
|
AccessLog.objects.all().delete()
|
|
|
|
|
|
|
|
|
|
response = self.login(is_valid_username=True, is_valid_password=True)
|
2024-04-30 14:22:50 +00:00
|
|
|
response = self.logout()
|
2019-02-22 23:22:11 +00:00
|
|
|
|
|
|
|
|
self.assertEqual(AccessLog.objects.all().count(), 0)
|
2019-09-28 16:27:50 +00:00
|
|
|
self.assertContains(response, "Logged out", html=True)
|
2019-02-22 23:22:11 +00:00
|
|
|
|
2019-05-25 17:23:18 +00:00
|
|
|
@override_settings(AXES_DISABLE_ACCESS_LOG=True)
|
2019-02-22 23:22:11 +00:00
|
|
|
def test_valid_login_without_success_log(self):
|
|
|
|
|
"""
|
|
|
|
|
Test that a valid login does not generate an AccessLog when DISABLE_SUCCESS_ACCESS_LOG is True.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
AccessLog.objects.all().delete()
|
|
|
|
|
|
|
|
|
|
response = self.login(is_valid_username=True, is_valid_password=True)
|
|
|
|
|
|
|
|
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
|
self.assertEqual(AccessLog.objects.all().count(), 0)
|
|
|
|
|
|
|
|
|
|
@override_settings(AXES_DISABLE_ACCESS_LOG=True)
|
|
|
|
|
def test_valid_logout_without_log(self):
|
|
|
|
|
AccessLog.objects.all().delete()
|
|
|
|
|
|
|
|
|
|
response = self.login(is_valid_username=True, is_valid_password=True)
|
2024-04-30 14:22:50 +00:00
|
|
|
response = self.logout()
|
2019-02-22 23:22:11 +00:00
|
|
|
|
2019-05-25 17:23:18 +00:00
|
|
|
self.assertEqual(AccessLog.objects.count(), 0)
|
2019-09-28 16:27:50 +00:00
|
|
|
self.assertContains(response, "Logged out", html=True)
|
2019-02-22 23:22:11 +00:00
|
|
|
|
|
|
|
|
@override_settings(AXES_DISABLE_ACCESS_LOG=True)
|
|
|
|
|
def test_non_valid_login_without_log(self):
|
|
|
|
|
"""
|
|
|
|
|
Test that a non-valid login does generate an AccessLog when DISABLE_ACCESS_LOG is True.
|
|
|
|
|
"""
|
|
|
|
|
AccessLog.objects.all().delete()
|
|
|
|
|
|
|
|
|
|
response = self.login(is_valid_username=True, is_valid_password=False)
|
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
|
|
|
|
|
self.assertEqual(AccessLog.objects.all().count(), 0)
|