diff --git a/authentik/core/api/applications.py b/authentik/core/api/applications.py index 1b1451d0c8..a2dbabe127 100644 --- a/authentik/core/api/applications.py +++ b/authentik/core/api/applications.py @@ -155,6 +155,13 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet): applications.append(application) return applications + def _filter_applications_with_launch_url(self, pagined_apps: Iterator[Application]) -> list[Application]: + applications = [] + for app in pagined_apps: + if app.get_launch_url(): + applications.append(app) + return applications + @extend_schema( parameters=[ OpenApiParameter( @@ -212,6 +219,11 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet): location=OpenApiParameter.QUERY, type=OpenApiTypes.INT, ), + OpenApiParameter( + name="only_with_launch_url", + location=OpenApiParameter.QUERY, + type=OpenApiTypes.BOOL, + ), ] ) def list(self, request: Request) -> Response: @@ -224,6 +236,8 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet): if superuser_full_list and request.user.is_superuser: return super().list(request) + only_with_launch_url = str(request.query_params.get("only_with_launch_url", "false")).lower() + queryset = self._filter_queryset_for_list(self.get_queryset()) paginator: Pagination = self.paginator paginated_apps = paginator.paginate_queryset(queryset, request) @@ -259,6 +273,10 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet): allowed_applications, timeout=86400, ) + + if only_with_launch_url == "true": + allowed_applications = self._filter_applications_with_launch_url(allowed_applications) + serializer = self.get_serializer(allowed_applications, many=True) return self.get_paginated_response(serializer.data) diff --git a/authentik/core/models.py b/authentik/core/models.py index d3ab0095b3..019572b589 100644 --- a/authentik/core/models.py +++ b/authentik/core/models.py @@ -1,6 +1,7 @@ """authentik core models""" from datetime import datetime +from functools import lru_cache from hashlib import sha256 from typing import Any, Optional, Self from uuid import uuid4 @@ -475,6 +476,10 @@ class Application(SerializerModel, PolicyBindingModel): return self.meta_icon.name return self.meta_icon.url + # maxsize is set as 2 since that is called once to check + # if we should return applications with a launch URL + # and a second time to actually get the launch_url + @lru_cache(maxsize=2) def get_launch_url(self, user: Optional["User"] = None) -> str | None: """Get launch URL if set, otherwise attempt to get launch URL based on provider.""" url = None diff --git a/schema.yml b/schema.yml index 31b0d9c515..c3b7a02bcd 100644 --- a/schema.yml +++ b/schema.yml @@ -2682,6 +2682,10 @@ paths: name: name schema: type: string + - in: query + name: only_with_launch_url + schema: + type: boolean - name: ordering required: false in: query diff --git a/web/src/user/LibraryPage/ak-library.ts b/web/src/user/LibraryPage/ak-library.ts index 5388b25119..5555d9ca97 100644 --- a/web/src/user/LibraryPage/ak-library.ts +++ b/web/src/user/LibraryPage/ak-library.ts @@ -70,6 +70,7 @@ export class LibraryPage extends AKElement { ordering: "name", page, pageSize: 100, + onlyWithLaunchUrl: true, }); const applicationListFetch = await coreApi().coreApplicationsList(applicationListParams(1));