diff --git a/authentik/admin/views/overview.py b/authentik/admin/views/overview.py index 95cab9013..400e47206 100644 --- a/authentik/admin/views/overview.py +++ b/authentik/admin/views/overview.py @@ -9,6 +9,7 @@ from structlog.stdlib import get_logger from authentik.admin.forms.overview import FlowCacheClearForm, PolicyCacheClearForm from authentik.admin.mixins import AdminRequiredMixin +from authentik.core.api.applications import user_app_cache_key LOGGER = get_logger() @@ -26,6 +27,9 @@ class PolicyCacheClearView(AdminRequiredMixin, SuccessMessageMixin, FormView): keys = cache.keys("policy_*") cache.delete_many(keys) LOGGER.debug("Cleared Policy cache", keys=len(keys)) + # Also delete user application cache + keys = user_app_cache_key("*") + cache.delete_many(keys) return super().post(request, *args, **kwargs) diff --git a/authentik/core/api/applications.py b/authentik/core/api/applications.py index c65da9043..621edf7a4 100644 --- a/authentik/core/api/applications.py +++ b/authentik/core/api/applications.py @@ -1,4 +1,5 @@ """Application API Views""" +from django.core.cache import cache from django.db.models import QuerySet from django.http.response import Http404 from guardian.shortcuts import get_objects_for_user @@ -18,6 +19,11 @@ from authentik.events.models import EventAction from authentik.policies.engine import PolicyEngine +def user_app_cache_key(user_pk: str) -> str: + """Cache key where application list for user is saved""" + return f"user_app_cache_{user_pk}" + + class ApplicationSerializer(ModelSerializer): """Application Serializer""" @@ -72,12 +78,15 @@ class ApplicationViewSet(ModelViewSet): """Custom list method that checks Policy based access instead of guardian""" queryset = self._filter_queryset_for_list(self.get_queryset()) self.paginate_queryset(queryset) - allowed_applications = [] - for application in queryset: - engine = PolicyEngine(application, self.request.user, self.request) - engine.build() - if engine.passing: - allowed_applications.append(application) + allowed_applications = cache.get(user_app_cache_key(self.request.user.pk)) + if not allowed_applications: + allowed_applications = [] + for application in queryset: + engine = PolicyEngine(application, self.request.user, self.request) + engine.build() + if engine.passing: + allowed_applications.append(application) + cache.set(user_app_cache_key(self.request.user.pk), allowed_applications) serializer = self.get_serializer(allowed_applications, many=True) return self.get_paginated_response(serializer.data) diff --git a/authentik/outposts/signals.py b/authentik/outposts/signals.py index b4f1a6151..fe7024427 100644 --- a/authentik/outposts/signals.py +++ b/authentik/outposts/signals.py @@ -20,7 +20,6 @@ UPDATE_TRIGGERING_MODELS = ( @receiver(post_save) -# pylint: disable=unused-argument def post_save_update(sender, instance: Model, **_): """If an Outpost is saved, Ensure that token is created/updated diff --git a/authentik/policies/signals.py b/authentik/policies/signals.py index 33045766e..29f23d8ba 100644 --- a/authentik/policies/signals.py +++ b/authentik/policies/signals.py @@ -4,6 +4,8 @@ from django.db.models.signals import post_save from django.dispatch import receiver from structlog.stdlib import get_logger +from authentik.core.api.applications import user_app_cache_key + LOGGER = get_logger() @@ -23,3 +25,6 @@ def invalidate_policy_cache(sender, instance, **_): total += len(keys) cache.delete_many(keys) LOGGER.debug("Invalidating policy cache", policy=instance, keys=total) + # Also delete user application cache + keys = user_app_cache_key("*") + cache.delete_many(keys)