diff --git a/authentik/core/api/applications.py b/authentik/core/api/applications.py index eab41f471..dcbb41017 100644 --- a/authentik/core/api/applications.py +++ b/authentik/core/api/applications.py @@ -1,13 +1,16 @@ """Application API Views""" +from typing import Optional + from django.core.cache import cache from django.db.models import QuerySet from django.http.response import HttpResponseBadRequest from django.shortcuts import get_object_or_404 +from django.utils.functional import SimpleLazyObject from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema from guardian.shortcuts import get_objects_for_user from rest_framework.decorators import action -from rest_framework.fields import ReadOnlyField +from rest_framework.fields import ReadOnlyField, SerializerMethodField from rest_framework.parsers import MultiPartParser from rest_framework.request import Request from rest_framework.response import Response @@ -39,11 +42,22 @@ def user_app_cache_key(user_pk: str) -> str: class ApplicationSerializer(ModelSerializer): """Application Serializer""" - launch_url = ReadOnlyField(source="get_launch_url") + launch_url = SerializerMethodField() provider_obj = ProviderSerializer(source="get_provider", required=False) meta_icon = ReadOnlyField(source="get_meta_icon") + def get_launch_url(self, app: Application) -> Optional[str]: + """Allow formatting of launch URL""" + url = app.get_launch_url() + if not url: + return url + user = self.context["request"].user + if isinstance(user, SimpleLazyObject): + user._setup() + user = user._wrapped + return url % user.__dict__ + class Meta: model = Application diff --git a/authentik/core/tests/test_applications_api.py b/authentik/core/tests/test_applications_api.py index bec6632ff..34cb8855c 100644 --- a/authentik/core/tests/test_applications_api.py +++ b/authentik/core/tests/test_applications_api.py @@ -13,7 +13,9 @@ class TestApplicationsAPI(APITestCase): def setUp(self) -> None: self.user = create_test_admin_user() - self.allowed = Application.objects.create(name="allowed", slug="allowed") + self.allowed = Application.objects.create( + name="allowed", slug="allowed", meta_launch_url="https://goauthentik.io/%(username)s" + ) self.denied = Application.objects.create(name="denied", slug="denied") PolicyBinding.objects.create( target=self.denied, @@ -64,8 +66,8 @@ class TestApplicationsAPI(APITestCase): "slug": "allowed", "provider": None, "provider_obj": None, - "launch_url": None, - "meta_launch_url": "", + "launch_url": f"https://goauthentik.io/{self.user.username}", + "meta_launch_url": "https://goauthentik.io/%(username)s", "meta_icon": None, "meta_description": "", "meta_publisher": "", @@ -100,8 +102,8 @@ class TestApplicationsAPI(APITestCase): "slug": "allowed", "provider": None, "provider_obj": None, - "launch_url": None, - "meta_launch_url": "", + "launch_url": f"https://goauthentik.io/{self.user.username}", + "meta_launch_url": "https://goauthentik.io/%(username)s", "meta_icon": None, "meta_description": "", "meta_publisher": "", diff --git a/website/docs/core/applications.md b/website/docs/core/applications.md index 21acbd8ac..4d4e1ddb9 100644 --- a/website/docs/core/applications.md +++ b/website/docs/core/applications.md @@ -24,6 +24,9 @@ The following aspects can be configured: - *Name*: This is the name shown for the application card - *Launch URL*: The URL that is opened when a user clicks on the application. When left empty, authentik tries to guess it based on the provider + + Starting with authentik 2022.2, you can use placeholders in the launch url to build them dynamically based on logged in user. For example, you can set the Launch URL to `https://goauthentik.io/%(username)s`, which will be replaced with the currently logged in user's username. + - *Icon (URL)*: Optionally configure an Icon for the application - *Publisher*: Text shown below the application - *Description*: Subtext shown on the application card below the publisher