diff --git a/tests/base.py b/tests/base.py index 52f32b5..be5cd56 100644 --- a/tests/base.py +++ b/tests/base.py @@ -19,7 +19,7 @@ from axes.helpers import ( get_credentials, get_failure_limit, ) -from axes.models import AccessAttempt, AccessLog +from axes.models import AccessAttempt, AccessLog, AccessFailureLog from axes.utils import reset @@ -79,6 +79,7 @@ class AxesTestCase(TestCase): self.request.axes_path_info = get_client_path_info(self.request) self.request.axes_http_accept = get_client_http_accept(self.request) self.request.axes_failures_since_start = None + self.request.axes_locked_out = False self.credentials = get_credentials(self.username) @@ -103,6 +104,9 @@ class AxesTestCase(TestCase): def create_log(self, **kwargs): return AccessLog.objects.create(**self.get_kwargs_with_defaults(**kwargs)) + def create_failure_log(self, **kwargs): + return AccessFailureLog.objects.create(**self.get_kwargs_with_defaults(**kwargs)) + def reset(self, ip=None, username=None): return reset(ip, username) diff --git a/tests/test_admin.py b/tests/test_admin.py index be9c35c..d739095 100644 --- a/tests/test_admin.py +++ b/tests/test_admin.py @@ -5,7 +5,7 @@ from django.contrib import admin from django.test import override_settings import axes.admin -from axes.models import AccessAttempt, AccessLog +from axes.models import AccessAttempt, AccessLog, AccessFailureLog from tests.base import AxesTestCase @@ -15,14 +15,18 @@ class AxesEnableAdminFlag(AxesTestCase): admin.site.unregister(AccessAttempt) with suppress(admin.sites.NotRegistered): admin.site.unregister(AccessLog) + with suppress(admin.sites.NotRegistered): + admin.site.unregister(AccessFailureLog) @override_settings(AXES_ENABLE_ADMIN=False) def test_disable_admin(self): reload(axes.admin) self.assertFalse(admin.site.is_registered(AccessAttempt)) self.assertFalse(admin.site.is_registered(AccessLog)) + self.assertFalse(admin.site.is_registered(AccessFailureLog)) def test_enable_admin_by_default(self): reload(axes.admin) self.assertTrue(admin.site.is_registered(AccessAttempt)) self.assertTrue(admin.site.is_registered(AccessLog)) + self.assertTrue(admin.site.is_registered(AccessFailureLog)) diff --git a/tests/test_failures.py b/tests/test_failures.py new file mode 100644 index 0000000..88c5edc --- /dev/null +++ b/tests/test_failures.py @@ -0,0 +1,16 @@ +from axes.models import AccessFailureLog +from tests.base import AxesTestCase +from axes.helpers import get_failure_limit +from django.test import override_settings + +@override_settings(AXES_ENABLE_ACCESS_FAILURE_LOG=True) +class FailureLogTestCase(AxesTestCase): + def test_failure_log(self): + self.login(is_valid_username=True, is_valid_password=False) + self.assertEqual(AccessFailureLog.objects.count(), 1) + self.assertTrue(AccessFailureLog.objects.filter(username=self.VALID_USERNAME).exists()) + self.assertTrue(AccessFailureLog.objects.filter(ip_address=self.ip_address).exists()) + + def test_failure_locked_out(self): + self.check_lockout() + self.assertEqual(AccessFailureLog.objects.filter(locked_out=True).count(), 1) diff --git a/tests/test_handlers.py b/tests/test_handlers.py index b7cc0c1..5ae36b0 100644 --- a/tests/test_handlers.py +++ b/tests/test_handlers.py @@ -12,7 +12,7 @@ from django.utils.timezone import timedelta from axes.conf import settings from axes.handlers.proxy import AxesProxyHandler from axes.helpers import get_client_str -from axes.models import AccessAttempt, AccessLog +from axes.models import AccessAttempt, AccessLog, AccessFailureLog from tests.base import AxesTestCase @@ -212,6 +212,7 @@ class ResetAttemptsTestCase(AxesHandlerBaseTestCase): AXES_HANDLER="axes.handlers.database.AxesDatabaseHandler", AXES_COOLOFF_TIME=timedelta(seconds=2), AXES_RESET_ON_SUCCESS=True, + AXES_ENABLE_ACCESS_FAILURE_LOG=True, ) @mark.xfail( python_implementation() == "PyPy", @@ -240,6 +241,28 @@ class AxesDatabaseHandlerTestCase(AxesHandlerBaseTestCase): self.assertEqual(1, AxesProxyHandler.reset_logs(age_days=42)) self.assertEqual(AccessLog.objects.count(), 1) + def test_handler_reset_failure_logs(self): + self.create_failure_log() + self.assertEqual(1, AxesProxyHandler.reset_failure_logs()) + self.assertFalse(AccessFailureLog.objects.count()) + + def test_handler_reset_failure_logs_older_than_42_days(self): + self.create_failure_log() + + then = timezone.now() - timezone.timedelta(days=90) + with patch("django.utils.timezone.now", return_value=then): + self.create_failure_log() + + self.assertEqual(AccessFailureLog.objects.count(), 2) + self.assertEqual(1, AxesProxyHandler.reset_failure_logs(age_days=42)) + self.assertEqual(AccessFailureLog.objects.count(), 1) + + def test_handler_remove_out_of_limit_failure_logs(self): + _more = 10 + for i in range(settings.AXES_ACCESS_FAILURE_LOG_PER_USER_LIMIT + _more): + self.create_failure_log() + self.assertEqual(_more, AxesProxyHandler.remove_out_of_limit_failure_logs(username=self.username)) + @override_settings(AXES_RESET_ON_SUCCESS=True) def test_handler(self): self.check_handler() diff --git a/tests/test_models.py b/tests/test_models.py index 95aade1..6ba0f5b 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -4,7 +4,7 @@ from django.db.migrations.autodetector import MigrationAutodetector from django.db.migrations.executor import MigrationExecutor from django.db.migrations.state import ProjectState -from axes.models import AccessAttempt, AccessLog +from axes.models import AccessAttempt, AccessLog, AccessFailureLog from tests.base import AxesTestCase @@ -16,6 +16,7 @@ class ModelsTestCase(AxesTestCase): failures_since_start=self.failures_since_start ) self.access_log = AccessLog() + self.access_failure_log = AccessFailureLog() def test_access_attempt_str(self): self.assertIn("Access", str(self.access_attempt)) @@ -23,6 +24,9 @@ class ModelsTestCase(AxesTestCase): def test_access_log_str(self): self.assertIn("Access", str(self.access_log)) + def test_access_failure_log_str(self): + self.assertIn("Failed", str(self.access_failure_log)) + class MigrationsTestCase(AxesTestCase): def test_missing_migrations(self):