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 <jens@goauthentik.io> * actually 10.5+ Signed-off-by: Jens Langhammer <jens@goauthentik.io> * Apply suggestions from code review Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com> Signed-off-by: Jens L. <jens@beryju.org> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io> Signed-off-by: Jens L. <jens@beryju.org> Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
This commit is contained in:
@ -6,6 +6,7 @@ from django.http import HttpRequest, HttpResponse, JsonResponse
|
|||||||
from django.urls import resolve
|
from django.urls import resolve
|
||||||
from structlog.stdlib import BoundLogger, get_logger
|
from structlog.stdlib import BoundLogger, get_logger
|
||||||
|
|
||||||
|
from authentik.core.api.users import UserViewSet
|
||||||
from authentik.enterprise.api import LicenseViewSet
|
from authentik.enterprise.api import LicenseViewSet
|
||||||
from authentik.enterprise.license import LicenseKey
|
from authentik.enterprise.license import LicenseKey
|
||||||
from authentik.enterprise.models import LicenseUsageStatus
|
from authentik.enterprise.models import LicenseUsageStatus
|
||||||
@ -59,6 +60,9 @@ class EnterpriseMiddleware:
|
|||||||
# Flow executor is mounted as an API path but explicitly allowed
|
# Flow executor is mounted as an API path but explicitly allowed
|
||||||
if request.resolver_match._func_path == class_to_path(FlowExecutorView):
|
if request.resolver_match._func_path == class_to_path(FlowExecutorView):
|
||||||
return True
|
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
|
# Only apply these restrictions to the API
|
||||||
if "authentik_api" not in request.resolver_match.app_names:
|
if "authentik_api" not in request.resolver_match.app_names:
|
||||||
return True
|
return True
|
||||||
|
|||||||
@ -215,3 +215,49 @@ class TestReadOnly(FlowTestCase):
|
|||||||
{"detail": "Request denied due to expired/invalid license.", "code": "denied_license"},
|
{"detail": "Request denied due to expired/invalid license.", "code": "denied_license"},
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, 400)
|
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)
|
||||||
|
|||||||
@ -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, 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 <span class="badge badge--version">authentik 2024.10.5+</span>
|
||||||
|
|
||||||
|
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
|
### About users and licenses
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user