From 81ae02e6238956c76095dc978a295e671da3c580 Mon Sep 17 00:00:00 2001 From: "Jens L." Date: Tue, 10 Dec 2024 13:07:50 +0100 Subject: [PATCH] enterprise: allow deletion/modification of users when in read-only mode (#12289) * enterprise: allow deletion/modification of users when in read-only mode Signed-off-by: Jens Langhammer * actually 10.5+ Signed-off-by: Jens Langhammer * Apply suggestions from code review Co-authored-by: Tana M Berry Signed-off-by: Jens L. --------- Signed-off-by: Jens Langhammer Signed-off-by: Jens L. Co-authored-by: Tana M Berry --- authentik/enterprise/middleware.py | 4 ++ authentik/enterprise/tests/test_read_only.py | 46 ++++++++++++++++++++ website/docs/enterprise/manage-enterprise.md | 10 ++++- 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/authentik/enterprise/middleware.py b/authentik/enterprise/middleware.py index 681194eece..d17573b40c 100644 --- a/authentik/enterprise/middleware.py +++ b/authentik/enterprise/middleware.py @@ -6,6 +6,7 @@ from django.http import HttpRequest, HttpResponse, JsonResponse from django.urls import resolve from structlog.stdlib import BoundLogger, get_logger +from authentik.core.api.users import UserViewSet from authentik.enterprise.api import LicenseViewSet from authentik.enterprise.license import LicenseKey from authentik.enterprise.models import LicenseUsageStatus @@ -59,6 +60,9 @@ class EnterpriseMiddleware: # Flow executor is mounted as an API path but explicitly allowed if request.resolver_match._func_path == class_to_path(FlowExecutorView): return True + # Always allow making changes to users, even in case the license has ben exceeded + if request.resolver_match._func_path == class_to_path(UserViewSet): + return True # Only apply these restrictions to the API if "authentik_api" not in request.resolver_match.app_names: return True diff --git a/authentik/enterprise/tests/test_read_only.py b/authentik/enterprise/tests/test_read_only.py index f6af83c989..df6da957c9 100644 --- a/authentik/enterprise/tests/test_read_only.py +++ b/authentik/enterprise/tests/test_read_only.py @@ -215,3 +215,49 @@ class TestReadOnly(FlowTestCase): {"detail": "Request denied due to expired/invalid license.", "code": "denied_license"}, ) self.assertEqual(response.status_code, 400) + + @patch( + "authentik.enterprise.license.LicenseKey.validate", + MagicMock( + return_value=LicenseKey( + aud="", + exp=expiry_valid, + name=generate_id(), + internal_users=100, + external_users=100, + ) + ), + ) + @patch( + "authentik.enterprise.license.LicenseKey.get_internal_user_count", + MagicMock(return_value=1000), + ) + @patch( + "authentik.enterprise.license.LicenseKey.get_external_user_count", + MagicMock(return_value=1000), + ) + @patch( + "authentik.enterprise.license.LicenseKey.record_usage", + MagicMock(), + ) + def test_manage_users(self): + """Test that managing users is still possible""" + License.objects.create(key=generate_id()) + usage = LicenseUsage.objects.create( + internal_user_count=100, + external_user_count=100, + status=LicenseUsageStatus.VALID, + ) + usage.record_date = now() - timedelta(weeks=THRESHOLD_READ_ONLY_WEEKS + 1) + usage.save(update_fields=["record_date"]) + + admin = create_test_admin_user() + self.client.force_login(admin) + + # Reading is always allowed + response = self.client.get(reverse("authentik_api:user-list")) + self.assertEqual(response.status_code, 200) + + # Writing should also be allowed + response = self.client.patch(reverse("authentik_api:user-detail", kwargs={"pk": admin.pk})) + self.assertEqual(response.status_code, 200) diff --git a/website/docs/enterprise/manage-enterprise.md b/website/docs/enterprise/manage-enterprise.md index a873195dca..53d2af5ec4 100644 --- a/website/docs/enterprise/manage-enterprise.md +++ b/website/docs/enterprise/manage-enterprise.md @@ -101,7 +101,15 @@ The following events occur when a license expires or the internal/external user - After another 2 weeks, users get a warning banner -- After another 2 weeks, the authentik Enterprise instance becomes “read-only” +- After another 2 weeks, the authentik Enterprise instance becomes "read-only" + + When an authentik instance is in read-only mode, the following actions are still possible: + + - Users can authenticate and authorize applications + - Licenses can be modified + - Users can be modified/deleted authentik 2024.10.5+ + + After the violation is corrected (either the user count returns to be within the limits of the license or the license is renewed), authentik will return to the standard read-write mode and the notification will disappear. ### About users and licenses