diff --git a/authentik/core/api/sources.py b/authentik/core/api/sources.py index c7d8205dd2f1..4dff85b45a0c 100644 --- a/authentik/core/api/sources.py +++ b/authentik/core/api/sources.py @@ -5,6 +5,7 @@ from drf_spectacular.utils import OpenApiResponse, extend_schema from rest_framework import mixins from rest_framework.decorators import action +from rest_framework.exceptions import ValidationError from rest_framework.fields import CharField, ReadOnlyField, SerializerMethodField from rest_framework.parsers import MultiPartParser from rest_framework.request import Request @@ -154,6 +155,17 @@ def user_settings(self, request: Request) -> Response: matching_sources.append(source_settings.validated_data) return Response(matching_sources) + def destroy(self, request: Request, *args, **kwargs): + """Prevent deletion of built-in sources""" + instance = self.get_object() + + if instance.slug.startswith("authentik-built-in"): + raise ValidationError( + {"detail": "Built-in sources cannot be deleted"}, code="protected" + ) + + return super().destroy(request, *args, **kwargs) + class UserSourceConnectionSerializer(SourceSerializer): """User source connection""" diff --git a/web/src/admin/sources/SourceListPage.ts b/web/src/admin/sources/SourceListPage.ts index a9af5d2336d0..dae995212ea3 100644 --- a/web/src/admin/sources/SourceListPage.ts +++ b/web/src/admin/sources/SourceListPage.ts @@ -57,10 +57,13 @@ export class SourceListPage extends TablePage { } renderToolbarSelected(): TemplateResult { - const disabled = this.selectedElements.length < 1; + const disabled = + this.selectedElements.length < 1 || + this.selectedElements.some((item) => item.component === ""); + const nonBuiltInSources = this.selectedElements.filter((item) => item.component !== ""); return html` { return new SourcesApi(DEFAULT_CONFIG).sourcesAllUsedByList({ slug: item.slug,