Skip to content

Commit a3f8611

Browse files
dominic-rDominic RBeryJu
authoredMar 14, 2025··
sources: prevent deletion of built-in source (goauthentik#12914)
* web: sources: disable "delete" button for built-in source * poetry doesn't like that I use python 3.13 / implement check on backend too * fix ruff i think Signed-off-by: Dominic R <[email protected]> * nvm Signed-off-by: Dominic R <[email protected]> * reformat * check by managed attribute Signed-off-by: Jens Langhammer <[email protected]> * like this? --------- Signed-off-by: Dominic R <[email protected]> Signed-off-by: Jens Langhammer <[email protected]> Co-authored-by: Dominic R <[email protected]> Co-authored-by: Jens Langhammer <[email protected]>
1 parent 75eb025 commit a3f8611

File tree

4 files changed

+20
-3
lines changed

4 files changed

+20
-3
lines changed
 

‎authentik/core/api/sources.py

+12
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from drf_spectacular.utils import OpenApiResponse, extend_schema
66
from rest_framework import mixins
77
from rest_framework.decorators import action
8+
from rest_framework.exceptions import ValidationError
89
from rest_framework.fields import CharField, ReadOnlyField, SerializerMethodField
910
from rest_framework.parsers import MultiPartParser
1011
from rest_framework.request import Request
@@ -154,6 +155,17 @@ def user_settings(self, request: Request) -> Response:
154155
matching_sources.append(source_settings.validated_data)
155156
return Response(matching_sources)
156157

158+
def destroy(self, request: Request, *args, **kwargs):
159+
"""Prevent deletion of built-in sources"""
160+
instance: Source = self.get_object()
161+
162+
if instance.managed == Source.MANAGED_INBUILT:
163+
raise ValidationError(
164+
{"detail": "Built-in sources cannot be deleted"}, code="protected"
165+
)
166+
167+
return super().destroy(request, *args, **kwargs)
168+
157169

158170
class UserSourceConnectionSerializer(SourceSerializer):
159171
"""User source connection"""

‎authentik/core/apps.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,5 @@ def source_inbuilt(self):
3232
"name": "authentik Built-in",
3333
"slug": "authentik-built-in",
3434
},
35-
managed="goauthentik.io/sources/inbuilt",
35+
managed=Source.MANAGED_INBUILT,
3636
)

‎authentik/core/models.py

+2
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,8 @@ class SourceGroupMatchingModes(models.TextChoices):
678678
class Source(ManagedModel, SerializerModel, PolicyBindingModel):
679679
"""Base Authentication source, i.e. an OAuth Provider, SAML Remote or LDAP Server"""
680680

681+
MANAGED_INBUILT = "goauthentik.io/sources/inbuilt"
682+
681683
name = models.TextField(help_text=_("Source's display Name."))
682684
slug = models.SlugField(help_text=_("Internal source name, used in URLs."), unique=True)
683685

‎web/src/admin/sources/SourceListPage.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,13 @@ export class SourceListPage extends TablePage<Source> {
5757
}
5858

5959
renderToolbarSelected(): TemplateResult {
60-
const disabled = this.selectedElements.length < 1;
60+
const disabled =
61+
this.selectedElements.length < 1 ||
62+
this.selectedElements.some((item) => item.component === "");
63+
const nonBuiltInSources = this.selectedElements.filter((item) => item.component !== "");
6164
return html`<ak-forms-delete-bulk
6265
objectLabel=${msg("Source(s)")}
63-
.objects=${this.selectedElements}
66+
.objects=${nonBuiltInSources}
6467
.usedBy=${(item: Source) => {
6568
return new SourcesApi(DEFAULT_CONFIG).sourcesAllUsedByList({
6669
slug: item.slug,

0 commit comments

Comments
 (0)
Please sign in to comment.