From bccfb0b48c49ef2ed48af2a14f8d5aae146b6c44 Mon Sep 17 00:00:00 2001 From: "Jens L." Date: Thu, 23 Jan 2025 12:26:48 +0100 Subject: [PATCH] sources: allow uuid or slug to be used for retrieving a source (2024.12 fix) (#12772) sources: allow uuid or slug to be used for retrieving a source Signed-off-by: Jens Langhammer --- authentik/lib/api.py | 37 +++++++++++++++++++ authentik/sources/kerberos/api/source.py | 4 +- authentik/sources/ldap/api.py | 4 +- authentik/sources/oauth/api/source.py | 4 +- authentik/sources/plex/api/source.py | 4 +- authentik/sources/saml/api/source.py | 4 +- authentik/sources/scim/api/sources.py | 4 +- .../admin/providers/oauth2/OAuth2Sources.ts | 2 +- 8 files changed, 56 insertions(+), 7 deletions(-) create mode 100644 authentik/lib/api.py diff --git a/authentik/lib/api.py b/authentik/lib/api.py new file mode 100644 index 0000000000..6c8170216b --- /dev/null +++ b/authentik/lib/api.py @@ -0,0 +1,37 @@ +from collections.abc import Callable, Sequence +from typing import Self +from uuid import UUID + +from django.db.models import Model, Q, QuerySet, UUIDField +from django.shortcuts import get_object_or_404 + + +class MultipleFieldLookupMixin: + """Helper mixin class to add support for multiple lookup_fields. + `lookup_fields` needs to be set which specifies the actual fields to query, `lookup_field` + is only used to generate the URL.""" + + lookup_field: str + lookup_fields: str | Sequence[str] + + get_queryset: Callable[[Self], QuerySet] + filter_queryset: Callable[[Self, QuerySet], QuerySet] + + def get_object(self): + queryset: QuerySet = self.get_queryset() + queryset = self.filter_queryset(queryset) + if isinstance(self.lookup_fields, str): + self.lookup_fields = [self.lookup_fields] + query = Q() + model: Model = queryset.model + for field in self.lookup_fields: + field_inst = model._meta.get_field(field) + # Sanity check, if the field we're filtering again, only apply the filter if + # our value looks like a UUID + if isinstance(field_inst, UUIDField): + try: + UUID(self.kwargs[self.lookup_field]) + except ValueError: + continue + query |= Q(**{field: self.kwargs[self.lookup_field]}) + return get_object_or_404(queryset, query) diff --git a/authentik/sources/kerberos/api/source.py b/authentik/sources/kerberos/api/source.py index cffc0a8940..0ce35dd8ad 100644 --- a/authentik/sources/kerberos/api/source.py +++ b/authentik/sources/kerberos/api/source.py @@ -13,6 +13,7 @@ from authentik.core.api.sources import SourceSerializer from authentik.core.api.used_by import UsedByMixin from authentik.core.api.utils import PassiveSerializer from authentik.events.api.tasks import SystemTaskSerializer +from authentik.lib.api import MultipleFieldLookupMixin from authentik.sources.kerberos.models import KerberosSource from authentik.sources.kerberos.tasks import CACHE_KEY_STATUS @@ -59,12 +60,13 @@ class KerberosSyncStatusSerializer(PassiveSerializer): tasks = SystemTaskSerializer(many=True, read_only=True) -class KerberosSourceViewSet(UsedByMixin, ModelViewSet): +class KerberosSourceViewSet(MultipleFieldLookupMixin, UsedByMixin, ModelViewSet): """Kerberos Source Viewset""" queryset = KerberosSource.objects.all() serializer_class = KerberosSourceSerializer lookup_field = "slug" + lookup_fields = ["slug", "pbm_uuid"] filterset_fields = [ "name", "slug", diff --git a/authentik/sources/ldap/api.py b/authentik/sources/ldap/api.py index 89ef164cd7..5846ea0728 100644 --- a/authentik/sources/ldap/api.py +++ b/authentik/sources/ldap/api.py @@ -18,6 +18,7 @@ from authentik.core.api.property_mappings import PropertyMappingFilterSet, Prope from authentik.core.api.sources import SourceSerializer from authentik.core.api.used_by import UsedByMixin from authentik.crypto.models import CertificateKeyPair +from authentik.lib.api import MultipleFieldLookupMixin from authentik.lib.sync.outgoing.api import SyncStatusSerializer from authentik.sources.ldap.models import LDAPSource, LDAPSourcePropertyMapping from authentik.sources.ldap.tasks import CACHE_KEY_STATUS, SYNC_CLASSES @@ -103,12 +104,13 @@ class LDAPSourceSerializer(SourceSerializer): extra_kwargs = {"bind_password": {"write_only": True}} -class LDAPSourceViewSet(UsedByMixin, ModelViewSet): +class LDAPSourceViewSet(MultipleFieldLookupMixin, UsedByMixin, ModelViewSet): """LDAP Source Viewset""" queryset = LDAPSource.objects.all() serializer_class = LDAPSourceSerializer lookup_field = "slug" + lookup_fields = ["slug", "pbm_uuid"] filterset_fields = [ "name", "slug", diff --git a/authentik/sources/oauth/api/source.py b/authentik/sources/oauth/api/source.py index ebba67d2f2..ce0c7f7bda 100644 --- a/authentik/sources/oauth/api/source.py +++ b/authentik/sources/oauth/api/source.py @@ -16,6 +16,7 @@ from rest_framework.viewsets import ModelViewSet from authentik.core.api.sources import SourceSerializer from authentik.core.api.used_by import UsedByMixin from authentik.core.api.utils import PassiveSerializer +from authentik.lib.api import MultipleFieldLookupMixin from authentik.lib.utils.http import get_http_session from authentik.sources.oauth.models import OAuthSource from authentik.sources.oauth.types.registry import SourceType, registry @@ -170,12 +171,13 @@ class OAuthSourceFilter(FilterSet): ] -class OAuthSourceViewSet(UsedByMixin, ModelViewSet): +class OAuthSourceViewSet(MultipleFieldLookupMixin, UsedByMixin, ModelViewSet): """Source Viewset""" queryset = OAuthSource.objects.all() serializer_class = OAuthSourceSerializer lookup_field = "slug" + lookup_fields = ["slug", "pbm_uuid"] filterset_class = OAuthSourceFilter search_fields = ["name", "slug"] ordering = ["name"] diff --git a/authentik/sources/plex/api/source.py b/authentik/sources/plex/api/source.py index f662fe9f4b..9310b3505e 100644 --- a/authentik/sources/plex/api/source.py +++ b/authentik/sources/plex/api/source.py @@ -18,6 +18,7 @@ from authentik.core.api.used_by import UsedByMixin from authentik.core.api.utils import PassiveSerializer from authentik.flows.challenge import RedirectChallenge from authentik.flows.views.executor import to_stage_response +from authentik.lib.api import MultipleFieldLookupMixin from authentik.rbac.decorators import permission_required from authentik.sources.plex.models import PlexSource, UserPlexSourceConnection from authentik.sources.plex.plex import PlexAuth, PlexSourceFlowManager @@ -45,12 +46,13 @@ class PlexTokenRedeemSerializer(PassiveSerializer): plex_token = CharField() -class PlexSourceViewSet(UsedByMixin, ModelViewSet): +class PlexSourceViewSet(MultipleFieldLookupMixin, UsedByMixin, ModelViewSet): """Plex source Viewset""" queryset = PlexSource.objects.all() serializer_class = PlexSourceSerializer lookup_field = "slug" + lookup_fields = ["slug", "pbm_uuid"] filterset_fields = [ "name", "slug", diff --git a/authentik/sources/saml/api/source.py b/authentik/sources/saml/api/source.py index 5cf4dc7ea6..e40bd0276c 100644 --- a/authentik/sources/saml/api/source.py +++ b/authentik/sources/saml/api/source.py @@ -9,6 +9,7 @@ from rest_framework.viewsets import ModelViewSet from authentik.core.api.sources import SourceSerializer from authentik.core.api.used_by import UsedByMixin +from authentik.lib.api import MultipleFieldLookupMixin from authentik.providers.saml.api.providers import SAMLMetadataSerializer from authentik.sources.saml.models import SAMLSource from authentik.sources.saml.processors.metadata import MetadataProcessor @@ -37,12 +38,13 @@ class SAMLSourceSerializer(SourceSerializer): ] -class SAMLSourceViewSet(UsedByMixin, ModelViewSet): +class SAMLSourceViewSet(MultipleFieldLookupMixin, UsedByMixin, ModelViewSet): """SAMLSource Viewset""" queryset = SAMLSource.objects.all() serializer_class = SAMLSourceSerializer lookup_field = "slug" + lookup_fields = ["slug", "pbm_uuid"] filterset_fields = [ "name", "slug", diff --git a/authentik/sources/scim/api/sources.py b/authentik/sources/scim/api/sources.py index 729e89c7a2..5d0499520e 100644 --- a/authentik/sources/scim/api/sources.py +++ b/authentik/sources/scim/api/sources.py @@ -7,6 +7,7 @@ from rest_framework.viewsets import ModelViewSet from authentik.core.api.sources import SourceSerializer from authentik.core.api.tokens import TokenSerializer from authentik.core.api.used_by import UsedByMixin +from authentik.lib.api import MultipleFieldLookupMixin from authentik.sources.scim.models import SCIMSource @@ -47,12 +48,13 @@ class SCIMSourceSerializer(SourceSerializer): ] -class SCIMSourceViewSet(UsedByMixin, ModelViewSet): +class SCIMSourceViewSet(MultipleFieldLookupMixin, UsedByMixin, ModelViewSet): """SCIMSource Viewset""" queryset = SCIMSource.objects.all() serializer_class = SCIMSourceSerializer lookup_field = "slug" + lookup_fields = ["slug", "pbm_uuid"] filterset_fields = ["name", "slug"] search_fields = ["name", "slug", "token__identifier", "token__user__username"] ordering = ["name"] diff --git a/web/src/admin/providers/oauth2/OAuth2Sources.ts b/web/src/admin/providers/oauth2/OAuth2Sources.ts index f8dcb910b5..69743223ec 100644 --- a/web/src/admin/providers/oauth2/OAuth2Sources.ts +++ b/web/src/admin/providers/oauth2/OAuth2Sources.ts @@ -4,7 +4,7 @@ import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types"; import { OAuthSource, SourcesApi } from "@goauthentik/api"; const sourceToSelect = (source: OAuthSource) => [ - source.slug, + source.pk, `${source.name} (${source.slug})`, source.name, source,