We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
API: Application Pagination only works when user is superuser and { superuser_full_list: true } is passed to the API
**Describe the bug**

This works:

`/api/v3/core/applications/?ordering=name&page=7&page_size=20&search=&superuser_full_list=true`

This does not:

`/api/v3/core/applications/?ordering=name&page=7&page_size=20&search=`

If you pass a page request and do not set `superuser_full_list` _or_ you are not an admin user, you will always only get the first page of results from the Application API for authentik.

**Expected behavior**

The `page=` argument should be respected by the API under all circumstances.

**Version and Deployment (please complete the following information):**

-   authentik version: post-2024.2, commit 2196bde820c4f59a128483670960971df073b076
-   Deployment: authentik [full development environment](https://docs.goauthentik.io/developer-docs/setup/full-dev-environment)

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `authentik/core/api/applications.py`
Content:
```
1 """Application API Views"""
2 
3 from collections.abc import Iterator
4 from copy import copy
5 from datetime import timedelta
6 
7 from django.core.cache import cache
8 from django.db.models import QuerySet
9 from django.db.models.functions import ExtractHour
10 from django.shortcuts import get_object_or_404
11 from drf_spectacular.types import OpenApiTypes
12 from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
13 from guardian.shortcuts import get_objects_for_user
14 from rest_framework.decorators import action
15 from rest_framework.exceptions import ValidationError
16 from rest_framework.fields import CharField, ReadOnlyField, SerializerMethodField
17 from rest_framework.parsers import MultiPartParser
18 from rest_framework.request import Request
19 from rest_framework.response import Response
20 from rest_framework.serializers import ModelSerializer
21 from rest_framework.viewsets import ModelViewSet
22 from structlog.stdlib import get_logger
23 
24 from authentik.admin.api.metrics import CoordinateSerializer
25 from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT
26 from authentik.core.api.providers import ProviderSerializer
27 from authentik.core.api.used_by import UsedByMixin
28 from authentik.core.models import Application, User
29 from authentik.events.logs import LogEventSerializer, capture_logs
30 from authentik.events.models import EventAction
31 from authentik.lib.utils.file import (
32     FilePathSerializer,
33     FileUploadSerializer,
34     set_file,
35     set_file_url,
36 )
37 from authentik.policies.api.exec import PolicyTestResultSerializer
38 from authentik.policies.engine import PolicyEngine
39 from authentik.policies.types import CACHE_PREFIX, PolicyResult
40 from authentik.rbac.decorators import permission_required
41 from authentik.rbac.filters import ObjectFilter
42 
43 LOGGER = get_logger()
44 
45 
46 def user_app_cache_key(user_pk: str) -> str:
47     """Cache key where application list for user is saved"""
48     return f"{CACHE_PREFIX}/app_access/{user_pk}"
49 
50 
51 class ApplicationSerializer(ModelSerializer):
52     """Application Serializer"""
53 
54     launch_url = SerializerMethodField()
55     provider_obj = ProviderSerializer(source="get_provider", required=False, read_only=True)
56     backchannel_providers_obj = ProviderSerializer(
57         source="backchannel_providers", required=False, read_only=True, many=True
58     )
59 
60     meta_icon = ReadOnlyField(source="get_meta_icon")
61 
62     def get_launch_url(self, app: Application) -> str | None:
63         """Allow formatting of launch URL"""
64         user = None
65         if "request" in self.context:
66             user = self.context["request"].user
67         return app.get_launch_url(user)
68 
69     def __init__(self, *args, **kwargs) -> None:
70         super().__init__(*args, **kwargs)
71         if SERIALIZER_CONTEXT_BLUEPRINT in self.context:
72             self.fields["icon"] = CharField(source="meta_icon", required=False)
73 
74     class Meta:
75         model = Application
76         fields = [
77             "pk",
78             "name",
79             "slug",
80             "provider",
81             "provider_obj",
82             "backchannel_providers",
83             "backchannel_providers_obj",
84             "launch_url",
85             "open_in_new_tab",
86             "meta_launch_url",
87             "meta_icon",
88             "meta_description",
89             "meta_publisher",
90             "policy_engine_mode",
91             "group",
92         ]
93         extra_kwargs = {
94             "meta_icon": {"read_only": True},
95             "backchannel_providers": {"required": False},
96         }
97 
98 
99 class ApplicationViewSet(UsedByMixin, ModelViewSet):
100     """Application Viewset"""
101 
102     queryset = Application.objects.all().prefetch_related("provider")
103     serializer_class = ApplicationSerializer
104     search_fields = [
105         "name",
106         "slug",
107         "meta_launch_url",
108         "meta_description",
109         "meta_publisher",
110         "group",
111     ]
112     filterset_fields = [
113         "name",
114         "slug",
115         "meta_launch_url",
116         "meta_description",
117         "meta_publisher",
118         "group",
119     ]
120     lookup_field = "slug"
121     ordering = ["name"]
122 
123     def _filter_queryset_for_list(self, queryset: QuerySet) -> QuerySet:
124         """Custom filter_queryset method which ignores guardian, but still supports sorting"""
125         for backend in list(self.filter_backends):
126             if backend == ObjectFilter:
127                 continue
128             queryset = backend().filter_queryset(self.request, queryset, self)
129         return queryset
130 
131     def _get_allowed_applications(
132         self, pagined_apps: Iterator[Application], user: User | None = None
133     ) -> list[Application]:
134         applications = []
135         request = self.request._request
136         if user:
137             request = copy(request)
138             request.user = user
139         for application in pagined_apps:
140             engine = PolicyEngine(application, request.user, request)
141             engine.build()
142             if engine.passing:
143                 applications.append(application)
144         return applications
145 
146     @extend_schema(
147         parameters=[
148             OpenApiParameter(
149                 name="for_user",
150                 location=OpenApiParameter.QUERY,
151                 type=OpenApiTypes.INT,
152             )
153         ],
154         responses={
155             200: PolicyTestResultSerializer(),
156         },
157     )
158     @action(detail=True, methods=["GET"])
159     def check_access(self, request: Request, slug: str) -> Response:
160         """Check access to a single application by slug"""
161         # Don't use self.get_object as that checks for view_application permission
162         # which the user might not have, even if they have access
163         application = get_object_or_404(Application, slug=slug)
164         # If the current user is superuser, they can set `for_user`
165         for_user = request.user
166         if request.user.is_superuser and "for_user" in request.query_params:
167             try:
168                 for_user = User.objects.filter(pk=request.query_params.get("for_user")).first()
169             except ValueError:
170                 raise ValidationError({"for_user": "for_user must be numerical"}) from None
171             if not for_user:
172                 raise ValidationError({"for_user": "User not found"})
173         engine = PolicyEngine(application, for_user, request)
174         engine.use_cache = False
175         with capture_logs() as logs:
176             engine.build()
177             result = engine.result
178         response = PolicyTestResultSerializer(PolicyResult(False))
179         if result.passing:
180             response = PolicyTestResultSerializer(PolicyResult(True))
181         if request.user.is_superuser:
182             log_messages = []
183             for log in logs:
184                 if log.attributes.get("process", "") == "PolicyProcess":
185                     continue
186                 log_messages.append(LogEventSerializer(log).data)
187             result.log_messages = log_messages
188             response = PolicyTestResultSerializer(result)
189         return Response(response.data)
190 
191     @extend_schema(
192         parameters=[
193             OpenApiParameter(
194                 name="superuser_full_list",
195                 location=OpenApiParameter.QUERY,
196                 type=OpenApiTypes.BOOL,
197             ),
198             OpenApiParameter(
199                 name="for_user",
200                 location=OpenApiParameter.QUERY,
201                 type=OpenApiTypes.INT,
202             ),
203         ]
204     )
205     def list(self, request: Request) -> Response:
206         """Custom list method that checks Policy based access instead of guardian"""
207         should_cache = request.query_params.get("search", "") == ""
208 
209         superuser_full_list = (
210             str(request.query_params.get("superuser_full_list", "false")).lower() == "true"
211         )
212         if superuser_full_list and request.user.is_superuser:
213             return super().list(request)
214 
215         queryset = self._filter_queryset_for_list(self.get_queryset())
216         paginated_apps = self.paginate_queryset(queryset)
217 
218         if "for_user" in request.query_params:
219             try:
220                 for_user: int = int(request.query_params.get("for_user", 0))
221                 for_user = (
222                     get_objects_for_user(request.user, "authentik_core.view_user_applications")
223                     .filter(pk=for_user)
224                     .first()
225                 )
226                 if not for_user:
227                     raise ValidationError({"for_user": "User not found"})
228             except ValueError as exc:
229                 raise ValidationError from exc
230             allowed_applications = self._get_allowed_applications(paginated_apps, user=for_user)
231             serializer = self.get_serializer(allowed_applications, many=True)
232             return self.get_paginated_response(serializer.data)
233 
234         allowed_applications = []
235         if not should_cache:
236             allowed_applications = self._get_allowed_applications(paginated_apps)
237         if should_cache:
238             allowed_applications = cache.get(user_app_cache_key(self.request.user.pk))
239             if not allowed_applications:
240                 LOGGER.debug("Caching allowed application list")
241                 allowed_applications = self._get_allowed_applications(paginated_apps)
242                 cache.set(
243                     user_app_cache_key(self.request.user.pk),
244                     allowed_applications,
245                     timeout=86400,
246                 )
247         serializer = self.get_serializer(allowed_applications, many=True)
248         return self.get_paginated_response(serializer.data)
249 
250     @permission_required("authentik_core.change_application")
251     @extend_schema(
252         request={
253             "multipart/form-data": FileUploadSerializer,
254         },
255         responses={
256             200: OpenApiResponse(description="Success"),
257             400: OpenApiResponse(description="Bad request"),
258         },
259     )
260     @action(
261         detail=True,
262         pagination_class=None,
263         filter_backends=[],
264         methods=["POST"],
265         parser_classes=(MultiPartParser,),
266     )
267     def set_icon(self, request: Request, slug: str):
268         """Set application icon"""
269         app: Application = self.get_object()
270         return set_file(request, app, "meta_icon")
271 
272     @permission_required("authentik_core.change_application")
273     @extend_schema(
274         request=FilePathSerializer,
275         responses={
276             200: OpenApiResponse(description="Success"),
277             400: OpenApiResponse(description="Bad request"),
278         },
279     )
280     @action(
281         detail=True,
282         pagination_class=None,
283         filter_backends=[],
284         methods=["POST"],
285     )
286     def set_icon_url(self, request: Request, slug: str):
287         """Set application icon (as URL)"""
288         app: Application = self.get_object()
289         return set_file_url(request, app, "meta_icon")
290 
291     @permission_required("authentik_core.view_application", ["authentik_events.view_event"])
292     @extend_schema(responses={200: CoordinateSerializer(many=True)})
293     @action(detail=True, pagination_class=None, filter_backends=[])
294     def metrics(self, request: Request, slug: str):
295         """Metrics for application logins"""
296         app = self.get_object()
297         return Response(
298             get_objects_for_user(request.user, "authentik_events.view_event").filter(
299                 action=EventAction.AUTHORIZE_APPLICATION,
300                 context__authorized_application__pk=app.pk.hex,
301             )
302             # 3 data points per day, so 8 hour spans
303             .get_events_per(timedelta(days=7), ExtractHour, 7 * 3)
304         )
305
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/authentik/core/api/applications.py b/authentik/core/api/applications.py
--- a/authentik/core/api/applications.py
+++ b/authentik/core/api/applications.py
@@ -22,6 +22,7 @@
 from structlog.stdlib import get_logger
 
 from authentik.admin.api.metrics import CoordinateSerializer
+from authentik.api.pagination import Pagination
 from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT
 from authentik.core.api.providers import ProviderSerializer
 from authentik.core.api.used_by import UsedByMixin
@@ -43,9 +44,12 @@
 LOGGER = get_logger()
 
 
-def user_app_cache_key(user_pk: str) -> str:
+def user_app_cache_key(user_pk: str, page_number: int | None = None) -> str:
     """Cache key where application list for user is saved"""
-    return f"{CACHE_PREFIX}/app_access/{user_pk}"
+    key = f"{CACHE_PREFIX}/app_access/{user_pk}"
+    if page_number:
+        key += f"/{page_number}"
+    return key
 
 
 class ApplicationSerializer(ModelSerializer):
@@ -213,7 +217,8 @@
             return super().list(request)
 
         queryset = self._filter_queryset_for_list(self.get_queryset())
-        paginated_apps = self.paginate_queryset(queryset)
+        paginator: Pagination = self.paginator
+        paginated_apps = paginator.paginate_queryset(queryset, request)
 
         if "for_user" in request.query_params:
             try:
@@ -235,12 +240,14 @@
         if not should_cache:
             allowed_applications = self._get_allowed_applications(paginated_apps)
         if should_cache:
-            allowed_applications = cache.get(user_app_cache_key(self.request.user.pk))
+            allowed_applications = cache.get(
+                user_app_cache_key(self.request.user.pk, paginator.page.number)
+            )
             if not allowed_applications:
-                LOGGER.debug("Caching allowed application list")
+                LOGGER.debug("Caching allowed application list", page=paginator.page.number)
                 allowed_applications = self._get_allowed_applications(paginated_apps)
                 cache.set(
-                    user_app_cache_key(self.request.user.pk),
+                    user_app_cache_key(self.request.user.pk, paginator.page.number),
                     allowed_applications,
                     timeout=86400,
                 )

{"golden_diff": "diff --git a/authentik/core/api/applications.py b/authentik/core/api/applications.py\n--- a/authentik/core/api/applications.py\n+++ b/authentik/core/api/applications.py\n@@ -22,6 +22,7 @@\n from structlog.stdlib import get_logger\n \n from authentik.admin.api.metrics import CoordinateSerializer\n+from authentik.api.pagination import Pagination\n from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT\n from authentik.core.api.providers import ProviderSerializer\n from authentik.core.api.used_by import UsedByMixin\n@@ -43,9 +44,12 @@\n LOGGER = get_logger()\n \n \n-def user_app_cache_key(user_pk: str) -> str:\n+def user_app_cache_key(user_pk: str, page_number: int | None = None) -> str:\n     \"\"\"Cache key where application list for user is saved\"\"\"\n-    return f\"{CACHE_PREFIX}/app_access/{user_pk}\"\n+    key = f\"{CACHE_PREFIX}/app_access/{user_pk}\"\n+    if page_number:\n+        key += f\"/{page_number}\"\n+    return key\n \n \n class ApplicationSerializer(ModelSerializer):\n@@ -213,7 +217,8 @@\n             return super().list(request)\n \n         queryset = self._filter_queryset_for_list(self.get_queryset())\n-        paginated_apps = self.paginate_queryset(queryset)\n+        paginator: Pagination = self.paginator\n+        paginated_apps = paginator.paginate_queryset(queryset, request)\n \n         if \"for_user\" in request.query_params:\n             try:\n@@ -235,12 +240,14 @@\n         if not should_cache:\n             allowed_applications = self._get_allowed_applications(paginated_apps)\n         if should_cache:\n-            allowed_applications = cache.get(user_app_cache_key(self.request.user.pk))\n+            allowed_applications = cache.get(\n+                user_app_cache_key(self.request.user.pk, paginator.page.number)\n+            )\n             if not allowed_applications:\n-                LOGGER.debug(\"Caching allowed application list\")\n+                LOGGER.debug(\"Caching allowed application list\", page=paginator.page.number)\n                 allowed_applications = self._get_allowed_applications(paginated_apps)\n                 cache.set(\n-                    user_app_cache_key(self.request.user.pk),\n+                    user_app_cache_key(self.request.user.pk, paginator.page.number),\n                     allowed_applications,\n                     timeout=86400,\n                 )\n", "issue": "API: Application Pagination only works when user is superuser and { superuser_full_list: true } is passed to the API\n**Describe the bug**\r\n\r\nThis works:\r\n\r\n`/api/v3/core/applications/?ordering=name&page=7&page_size=20&search=&superuser_full_list=true`\r\n\r\nThis does not:\r\n\r\n`/api/v3/core/applications/?ordering=name&page=7&page_size=20&search=`\r\n\r\nIf you pass a page request and do not set `superuser_full_list` _or_ you are not an admin user, you will always only get the first page of results from the Application API for authentik.\r\n\r\n**Expected behavior**\r\n\r\nThe `page=` argument should be respected by the API under all circumstances.\r\n\r\n**Version and Deployment (please complete the following information):**\r\n\r\n-   authentik version: post-2024.2, commit 2196bde820c4f59a128483670960971df073b076\r\n-   Deployment: authentik [full development environment](https://docs.goauthentik.io/developer-docs/setup/full-dev-environment)\n", "before_files": [{"content": "\"\"\"Application API Views\"\"\"\n\nfrom collections.abc import Iterator\nfrom copy import copy\nfrom datetime import timedelta\n\nfrom django.core.cache import cache\nfrom django.db.models import QuerySet\nfrom django.db.models.functions import ExtractHour\nfrom django.shortcuts import get_object_or_404\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema\nfrom guardian.shortcuts import get_objects_for_user\nfrom rest_framework.decorators import action\nfrom rest_framework.exceptions import ValidationError\nfrom rest_framework.fields import CharField, ReadOnlyField, SerializerMethodField\nfrom rest_framework.parsers import MultiPartParser\nfrom rest_framework.request import Request\nfrom rest_framework.response import Response\nfrom rest_framework.serializers import ModelSerializer\nfrom rest_framework.viewsets import ModelViewSet\nfrom structlog.stdlib import get_logger\n\nfrom authentik.admin.api.metrics import CoordinateSerializer\nfrom authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT\nfrom authentik.core.api.providers import ProviderSerializer\nfrom authentik.core.api.used_by import UsedByMixin\nfrom authentik.core.models import Application, User\nfrom authentik.events.logs import LogEventSerializer, capture_logs\nfrom authentik.events.models import EventAction\nfrom authentik.lib.utils.file import (\n    FilePathSerializer,\n    FileUploadSerializer,\n    set_file,\n    set_file_url,\n)\nfrom authentik.policies.api.exec import PolicyTestResultSerializer\nfrom authentik.policies.engine import PolicyEngine\nfrom authentik.policies.types import CACHE_PREFIX, PolicyResult\nfrom authentik.rbac.decorators import permission_required\nfrom authentik.rbac.filters import ObjectFilter\n\nLOGGER = get_logger()\n\n\ndef user_app_cache_key(user_pk: str) -> str:\n    \"\"\"Cache key where application list for user is saved\"\"\"\n    return f\"{CACHE_PREFIX}/app_access/{user_pk}\"\n\n\nclass ApplicationSerializer(ModelSerializer):\n    \"\"\"Application Serializer\"\"\"\n\n    launch_url = SerializerMethodField()\n    provider_obj = ProviderSerializer(source=\"get_provider\", required=False, read_only=True)\n    backchannel_providers_obj = ProviderSerializer(\n        source=\"backchannel_providers\", required=False, read_only=True, many=True\n    )\n\n    meta_icon = ReadOnlyField(source=\"get_meta_icon\")\n\n    def get_launch_url(self, app: Application) -> str | None:\n        \"\"\"Allow formatting of launch URL\"\"\"\n        user = None\n        if \"request\" in self.context:\n            user = self.context[\"request\"].user\n        return app.get_launch_url(user)\n\n    def __init__(self, *args, **kwargs) -> None:\n        super().__init__(*args, **kwargs)\n        if SERIALIZER_CONTEXT_BLUEPRINT in self.context:\n            self.fields[\"icon\"] = CharField(source=\"meta_icon\", required=False)\n\n    class Meta:\n        model = Application\n        fields = [\n            \"pk\",\n            \"name\",\n            \"slug\",\n            \"provider\",\n            \"provider_obj\",\n            \"backchannel_providers\",\n            \"backchannel_providers_obj\",\n            \"launch_url\",\n            \"open_in_new_tab\",\n            \"meta_launch_url\",\n            \"meta_icon\",\n            \"meta_description\",\n            \"meta_publisher\",\n            \"policy_engine_mode\",\n            \"group\",\n        ]\n        extra_kwargs = {\n            \"meta_icon\": {\"read_only\": True},\n            \"backchannel_providers\": {\"required\": False},\n        }\n\n\nclass ApplicationViewSet(UsedByMixin, ModelViewSet):\n    \"\"\"Application Viewset\"\"\"\n\n    queryset = Application.objects.all().prefetch_related(\"provider\")\n    serializer_class = ApplicationSerializer\n    search_fields = [\n        \"name\",\n        \"slug\",\n        \"meta_launch_url\",\n        \"meta_description\",\n        \"meta_publisher\",\n        \"group\",\n    ]\n    filterset_fields = [\n        \"name\",\n        \"slug\",\n        \"meta_launch_url\",\n        \"meta_description\",\n        \"meta_publisher\",\n        \"group\",\n    ]\n    lookup_field = \"slug\"\n    ordering = [\"name\"]\n\n    def _filter_queryset_for_list(self, queryset: QuerySet) -> QuerySet:\n        \"\"\"Custom filter_queryset method which ignores guardian, but still supports sorting\"\"\"\n        for backend in list(self.filter_backends):\n            if backend == ObjectFilter:\n                continue\n            queryset = backend().filter_queryset(self.request, queryset, self)\n        return queryset\n\n    def _get_allowed_applications(\n        self, pagined_apps: Iterator[Application], user: User | None = None\n    ) -> list[Application]:\n        applications = []\n        request = self.request._request\n        if user:\n            request = copy(request)\n            request.user = user\n        for application in pagined_apps:\n            engine = PolicyEngine(application, request.user, request)\n            engine.build()\n            if engine.passing:\n                applications.append(application)\n        return applications\n\n    @extend_schema(\n        parameters=[\n            OpenApiParameter(\n                name=\"for_user\",\n                location=OpenApiParameter.QUERY,\n                type=OpenApiTypes.INT,\n            )\n        ],\n        responses={\n            200: PolicyTestResultSerializer(),\n        },\n    )\n    @action(detail=True, methods=[\"GET\"])\n    def check_access(self, request: Request, slug: str) -> Response:\n        \"\"\"Check access to a single application by slug\"\"\"\n        # Don't use self.get_object as that checks for view_application permission\n        # which the user might not have, even if they have access\n        application = get_object_or_404(Application, slug=slug)\n        # If the current user is superuser, they can set `for_user`\n        for_user = request.user\n        if request.user.is_superuser and \"for_user\" in request.query_params:\n            try:\n                for_user = User.objects.filter(pk=request.query_params.get(\"for_user\")).first()\n            except ValueError:\n                raise ValidationError({\"for_user\": \"for_user must be numerical\"}) from None\n            if not for_user:\n                raise ValidationError({\"for_user\": \"User not found\"})\n        engine = PolicyEngine(application, for_user, request)\n        engine.use_cache = False\n        with capture_logs() as logs:\n            engine.build()\n            result = engine.result\n        response = PolicyTestResultSerializer(PolicyResult(False))\n        if result.passing:\n            response = PolicyTestResultSerializer(PolicyResult(True))\n        if request.user.is_superuser:\n            log_messages = []\n            for log in logs:\n                if log.attributes.get(\"process\", \"\") == \"PolicyProcess\":\n                    continue\n                log_messages.append(LogEventSerializer(log).data)\n            result.log_messages = log_messages\n            response = PolicyTestResultSerializer(result)\n        return Response(response.data)\n\n    @extend_schema(\n        parameters=[\n            OpenApiParameter(\n                name=\"superuser_full_list\",\n                location=OpenApiParameter.QUERY,\n                type=OpenApiTypes.BOOL,\n            ),\n            OpenApiParameter(\n                name=\"for_user\",\n                location=OpenApiParameter.QUERY,\n                type=OpenApiTypes.INT,\n            ),\n        ]\n    )\n    def list(self, request: Request) -> Response:\n        \"\"\"Custom list method that checks Policy based access instead of guardian\"\"\"\n        should_cache = request.query_params.get(\"search\", \"\") == \"\"\n\n        superuser_full_list = (\n            str(request.query_params.get(\"superuser_full_list\", \"false\")).lower() == \"true\"\n        )\n        if superuser_full_list and request.user.is_superuser:\n            return super().list(request)\n\n        queryset = self._filter_queryset_for_list(self.get_queryset())\n        paginated_apps = self.paginate_queryset(queryset)\n\n        if \"for_user\" in request.query_params:\n            try:\n                for_user: int = int(request.query_params.get(\"for_user\", 0))\n                for_user = (\n                    get_objects_for_user(request.user, \"authentik_core.view_user_applications\")\n                    .filter(pk=for_user)\n                    .first()\n                )\n                if not for_user:\n                    raise ValidationError({\"for_user\": \"User not found\"})\n            except ValueError as exc:\n                raise ValidationError from exc\n            allowed_applications = self._get_allowed_applications(paginated_apps, user=for_user)\n            serializer = self.get_serializer(allowed_applications, many=True)\n            return self.get_paginated_response(serializer.data)\n\n        allowed_applications = []\n        if not should_cache:\n            allowed_applications = self._get_allowed_applications(paginated_apps)\n        if should_cache:\n            allowed_applications = cache.get(user_app_cache_key(self.request.user.pk))\n            if not allowed_applications:\n                LOGGER.debug(\"Caching allowed application list\")\n                allowed_applications = self._get_allowed_applications(paginated_apps)\n                cache.set(\n                    user_app_cache_key(self.request.user.pk),\n                    allowed_applications,\n                    timeout=86400,\n                )\n        serializer = self.get_serializer(allowed_applications, many=True)\n        return self.get_paginated_response(serializer.data)\n\n    @permission_required(\"authentik_core.change_application\")\n    @extend_schema(\n        request={\n            \"multipart/form-data\": FileUploadSerializer,\n        },\n        responses={\n            200: OpenApiResponse(description=\"Success\"),\n            400: OpenApiResponse(description=\"Bad request\"),\n        },\n    )\n    @action(\n        detail=True,\n        pagination_class=None,\n        filter_backends=[],\n        methods=[\"POST\"],\n        parser_classes=(MultiPartParser,),\n    )\n    def set_icon(self, request: Request, slug: str):\n        \"\"\"Set application icon\"\"\"\n        app: Application = self.get_object()\n        return set_file(request, app, \"meta_icon\")\n\n    @permission_required(\"authentik_core.change_application\")\n    @extend_schema(\n        request=FilePathSerializer,\n        responses={\n            200: OpenApiResponse(description=\"Success\"),\n            400: OpenApiResponse(description=\"Bad request\"),\n        },\n    )\n    @action(\n        detail=True,\n        pagination_class=None,\n        filter_backends=[],\n        methods=[\"POST\"],\n    )\n    def set_icon_url(self, request: Request, slug: str):\n        \"\"\"Set application icon (as URL)\"\"\"\n        app: Application = self.get_object()\n        return set_file_url(request, app, \"meta_icon\")\n\n    @permission_required(\"authentik_core.view_application\", [\"authentik_events.view_event\"])\n    @extend_schema(responses={200: CoordinateSerializer(many=True)})\n    @action(detail=True, pagination_class=None, filter_backends=[])\n    def metrics(self, request: Request, slug: str):\n        \"\"\"Metrics for application logins\"\"\"\n        app = self.get_object()\n        return Response(\n            get_objects_for_user(request.user, \"authentik_events.view_event\").filter(\n                action=EventAction.AUTHORIZE_APPLICATION,\n                context__authorized_application__pk=app.pk.hex,\n            )\n            # 3 data points per day, so 8 hour spans\n            .get_events_per(timedelta(days=7), ExtractHour, 7 * 3)\n        )\n", "path": "authentik/core/api/applications.py"}], "after_files": [{"content": "\"\"\"Application API Views\"\"\"\n\nfrom collections.abc import Iterator\nfrom copy import copy\nfrom datetime import timedelta\n\nfrom django.core.cache import cache\nfrom django.db.models import QuerySet\nfrom django.db.models.functions import ExtractHour\nfrom django.shortcuts import get_object_or_404\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema\nfrom guardian.shortcuts import get_objects_for_user\nfrom rest_framework.decorators import action\nfrom rest_framework.exceptions import ValidationError\nfrom rest_framework.fields import CharField, ReadOnlyField, SerializerMethodField\nfrom rest_framework.parsers import MultiPartParser\nfrom rest_framework.request import Request\nfrom rest_framework.response import Response\nfrom rest_framework.serializers import ModelSerializer\nfrom rest_framework.viewsets import ModelViewSet\nfrom structlog.stdlib import get_logger\n\nfrom authentik.admin.api.metrics import CoordinateSerializer\nfrom authentik.api.pagination import Pagination\nfrom authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT\nfrom authentik.core.api.providers import ProviderSerializer\nfrom authentik.core.api.used_by import UsedByMixin\nfrom authentik.core.models import Application, User\nfrom authentik.events.logs import LogEventSerializer, capture_logs\nfrom authentik.events.models import EventAction\nfrom authentik.lib.utils.file import (\n    FilePathSerializer,\n    FileUploadSerializer,\n    set_file,\n    set_file_url,\n)\nfrom authentik.policies.api.exec import PolicyTestResultSerializer\nfrom authentik.policies.engine import PolicyEngine\nfrom authentik.policies.types import CACHE_PREFIX, PolicyResult\nfrom authentik.rbac.decorators import permission_required\nfrom authentik.rbac.filters import ObjectFilter\n\nLOGGER = get_logger()\n\n\ndef user_app_cache_key(user_pk: str, page_number: int | None = None) -> str:\n    \"\"\"Cache key where application list for user is saved\"\"\"\n    key = f\"{CACHE_PREFIX}/app_access/{user_pk}\"\n    if page_number:\n        key += f\"/{page_number}\"\n    return key\n\n\nclass ApplicationSerializer(ModelSerializer):\n    \"\"\"Application Serializer\"\"\"\n\n    launch_url = SerializerMethodField()\n    provider_obj = ProviderSerializer(source=\"get_provider\", required=False, read_only=True)\n    backchannel_providers_obj = ProviderSerializer(\n        source=\"backchannel_providers\", required=False, read_only=True, many=True\n    )\n\n    meta_icon = ReadOnlyField(source=\"get_meta_icon\")\n\n    def get_launch_url(self, app: Application) -> str | None:\n        \"\"\"Allow formatting of launch URL\"\"\"\n        user = None\n        if \"request\" in self.context:\n            user = self.context[\"request\"].user\n        return app.get_launch_url(user)\n\n    def __init__(self, *args, **kwargs) -> None:\n        super().__init__(*args, **kwargs)\n        if SERIALIZER_CONTEXT_BLUEPRINT in self.context:\n            self.fields[\"icon\"] = CharField(source=\"meta_icon\", required=False)\n\n    class Meta:\n        model = Application\n        fields = [\n            \"pk\",\n            \"name\",\n            \"slug\",\n            \"provider\",\n            \"provider_obj\",\n            \"backchannel_providers\",\n            \"backchannel_providers_obj\",\n            \"launch_url\",\n            \"open_in_new_tab\",\n            \"meta_launch_url\",\n            \"meta_icon\",\n            \"meta_description\",\n            \"meta_publisher\",\n            \"policy_engine_mode\",\n            \"group\",\n        ]\n        extra_kwargs = {\n            \"meta_icon\": {\"read_only\": True},\n            \"backchannel_providers\": {\"required\": False},\n        }\n\n\nclass ApplicationViewSet(UsedByMixin, ModelViewSet):\n    \"\"\"Application Viewset\"\"\"\n\n    queryset = Application.objects.all().prefetch_related(\"provider\")\n    serializer_class = ApplicationSerializer\n    search_fields = [\n        \"name\",\n        \"slug\",\n        \"meta_launch_url\",\n        \"meta_description\",\n        \"meta_publisher\",\n        \"group\",\n    ]\n    filterset_fields = [\n        \"name\",\n        \"slug\",\n        \"meta_launch_url\",\n        \"meta_description\",\n        \"meta_publisher\",\n        \"group\",\n    ]\n    lookup_field = \"slug\"\n    ordering = [\"name\"]\n\n    def _filter_queryset_for_list(self, queryset: QuerySet) -> QuerySet:\n        \"\"\"Custom filter_queryset method which ignores guardian, but still supports sorting\"\"\"\n        for backend in list(self.filter_backends):\n            if backend == ObjectFilter:\n                continue\n            queryset = backend().filter_queryset(self.request, queryset, self)\n        return queryset\n\n    def _get_allowed_applications(\n        self, pagined_apps: Iterator[Application], user: User | None = None\n    ) -> list[Application]:\n        applications = []\n        request = self.request._request\n        if user:\n            request = copy(request)\n            request.user = user\n        for application in pagined_apps:\n            engine = PolicyEngine(application, request.user, request)\n            engine.build()\n            if engine.passing:\n                applications.append(application)\n        return applications\n\n    @extend_schema(\n        parameters=[\n            OpenApiParameter(\n                name=\"for_user\",\n                location=OpenApiParameter.QUERY,\n                type=OpenApiTypes.INT,\n            )\n        ],\n        responses={\n            200: PolicyTestResultSerializer(),\n        },\n    )\n    @action(detail=True, methods=[\"GET\"])\n    def check_access(self, request: Request, slug: str) -> Response:\n        \"\"\"Check access to a single application by slug\"\"\"\n        # Don't use self.get_object as that checks for view_application permission\n        # which the user might not have, even if they have access\n        application = get_object_or_404(Application, slug=slug)\n        # If the current user is superuser, they can set `for_user`\n        for_user = request.user\n        if request.user.is_superuser and \"for_user\" in request.query_params:\n            try:\n                for_user = User.objects.filter(pk=request.query_params.get(\"for_user\")).first()\n            except ValueError:\n                raise ValidationError({\"for_user\": \"for_user must be numerical\"}) from None\n            if not for_user:\n                raise ValidationError({\"for_user\": \"User not found\"})\n        engine = PolicyEngine(application, for_user, request)\n        engine.use_cache = False\n        with capture_logs() as logs:\n            engine.build()\n            result = engine.result\n        response = PolicyTestResultSerializer(PolicyResult(False))\n        if result.passing:\n            response = PolicyTestResultSerializer(PolicyResult(True))\n        if request.user.is_superuser:\n            log_messages = []\n            for log in logs:\n                if log.attributes.get(\"process\", \"\") == \"PolicyProcess\":\n                    continue\n                log_messages.append(LogEventSerializer(log).data)\n            result.log_messages = log_messages\n            response = PolicyTestResultSerializer(result)\n        return Response(response.data)\n\n    @extend_schema(\n        parameters=[\n            OpenApiParameter(\n                name=\"superuser_full_list\",\n                location=OpenApiParameter.QUERY,\n                type=OpenApiTypes.BOOL,\n            ),\n            OpenApiParameter(\n                name=\"for_user\",\n                location=OpenApiParameter.QUERY,\n                type=OpenApiTypes.INT,\n            ),\n        ]\n    )\n    def list(self, request: Request) -> Response:\n        \"\"\"Custom list method that checks Policy based access instead of guardian\"\"\"\n        should_cache = request.query_params.get(\"search\", \"\") == \"\"\n\n        superuser_full_list = (\n            str(request.query_params.get(\"superuser_full_list\", \"false\")).lower() == \"true\"\n        )\n        if superuser_full_list and request.user.is_superuser:\n            return super().list(request)\n\n        queryset = self._filter_queryset_for_list(self.get_queryset())\n        paginator: Pagination = self.paginator\n        paginated_apps = paginator.paginate_queryset(queryset, request)\n\n        if \"for_user\" in request.query_params:\n            try:\n                for_user: int = int(request.query_params.get(\"for_user\", 0))\n                for_user = (\n                    get_objects_for_user(request.user, \"authentik_core.view_user_applications\")\n                    .filter(pk=for_user)\n                    .first()\n                )\n                if not for_user:\n                    raise ValidationError({\"for_user\": \"User not found\"})\n            except ValueError as exc:\n                raise ValidationError from exc\n            allowed_applications = self._get_allowed_applications(paginated_apps, user=for_user)\n            serializer = self.get_serializer(allowed_applications, many=True)\n            return self.get_paginated_response(serializer.data)\n\n        allowed_applications = []\n        if not should_cache:\n            allowed_applications = self._get_allowed_applications(paginated_apps)\n        if should_cache:\n            allowed_applications = cache.get(\n                user_app_cache_key(self.request.user.pk, paginator.page.number)\n            )\n            if not allowed_applications:\n                LOGGER.debug(\"Caching allowed application list\", page=paginator.page.number)\n                allowed_applications = self._get_allowed_applications(paginated_apps)\n                cache.set(\n                    user_app_cache_key(self.request.user.pk, paginator.page.number),\n                    allowed_applications,\n                    timeout=86400,\n                )\n        serializer = self.get_serializer(allowed_applications, many=True)\n        return self.get_paginated_response(serializer.data)\n\n    @permission_required(\"authentik_core.change_application\")\n    @extend_schema(\n        request={\n            \"multipart/form-data\": FileUploadSerializer,\n        },\n        responses={\n            200: OpenApiResponse(description=\"Success\"),\n            400: OpenApiResponse(description=\"Bad request\"),\n        },\n    )\n    @action(\n        detail=True,\n        pagination_class=None,\n        filter_backends=[],\n        methods=[\"POST\"],\n        parser_classes=(MultiPartParser,),\n    )\n    def set_icon(self, request: Request, slug: str):\n        \"\"\"Set application icon\"\"\"\n        app: Application = self.get_object()\n        return set_file(request, app, \"meta_icon\")\n\n    @permission_required(\"authentik_core.change_application\")\n    @extend_schema(\n        request=FilePathSerializer,\n        responses={\n            200: OpenApiResponse(description=\"Success\"),\n            400: OpenApiResponse(description=\"Bad request\"),\n        },\n    )\n    @action(\n        detail=True,\n        pagination_class=None,\n        filter_backends=[],\n        methods=[\"POST\"],\n    )\n    def set_icon_url(self, request: Request, slug: str):\n        \"\"\"Set application icon (as URL)\"\"\"\n        app: Application = self.get_object()\n        return set_file_url(request, app, \"meta_icon\")\n\n    @permission_required(\"authentik_core.view_application\", [\"authentik_events.view_event\"])\n    @extend_schema(responses={200: CoordinateSerializer(many=True)})\n    @action(detail=True, pagination_class=None, filter_backends=[])\n    def metrics(self, request: Request, slug: str):\n        \"\"\"Metrics for application logins\"\"\"\n        app = self.get_object()\n        return Response(\n            get_objects_for_user(request.user, \"authentik_events.view_event\").filter(\n                action=EventAction.AUTHORIZE_APPLICATION,\n                context__authorized_application__pk=app.pk.hex,\n            )\n            # 3 data points per day, so 8 hour spans\n            .get_events_per(timedelta(days=7), ExtractHour, 7 * 3)\n        )\n", "path": "authentik/core/api/applications.py"}]}