From bafe8a5104b0395f9d9ec721a1b958f912b623ec Mon Sep 17 00:00:00 2001 From: Marc 'risson' Schmitt Date: Thu, 24 Jul 2025 15:30:47 +0200 Subject: [PATCH] blueprints: add FindObject tag (#12415) --- .vscode/settings.json | 9 +++++-- authentik/blueprints/tests/fixtures/tags.yaml | 1 + authentik/blueprints/tests/test_v1.py | 3 +++ authentik/blueprints/v1/common.py | 24 ++++++++++++++++--- website/docs/customize/blueprints/v1/tags.mdx | 19 +++++++++++++++ 5 files changed, 51 insertions(+), 5 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 8cf2775688..def214de5f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,6 +10,7 @@ "!File scalar", "!File sequence", "!Find sequence", + "!FindObject sequence", "!Format sequence", "!If sequence", "!Index scalar", @@ -33,6 +34,10 @@ "ignoreCase": false } ], - "go.testFlags": ["-count=1"], - "github-actions.workflows.pinned.workflows": [".github/workflows/ci-main.yml"] + "go.testFlags": [ + "-count=1" + ], + "github-actions.workflows.pinned.workflows": [ + ".github/workflows/ci-main.yml" + ] } diff --git a/authentik/blueprints/tests/fixtures/tags.yaml b/authentik/blueprints/tests/fixtures/tags.yaml index ce6cacfe1e..b0e53727f0 100644 --- a/authentik/blueprints/tests/fixtures/tags.yaml +++ b/authentik/blueprints/tests/fixtures/tags.yaml @@ -154,6 +154,7 @@ entries: at_index_sequence_default: !AtIndex [!Context sequence, 100, "non existent"] at_index_mapping: !AtIndex [!Context mapping, "key2"] at_index_mapping_default: !AtIndex [!Context mapping, "invalid", "non existent"] + find_object: !AtIndex [!FindObject [authentik_providers_oauth2.scopemapping, [scope_name, openid]], managed] identifiers: name: test conditions: diff --git a/authentik/blueprints/tests/test_v1.py b/authentik/blueprints/tests/test_v1.py index f2bc12b51a..04cabe36cc 100644 --- a/authentik/blueprints/tests/test_v1.py +++ b/authentik/blueprints/tests/test_v1.py @@ -5,6 +5,7 @@ from tempfile import mkstemp from django.test import TransactionTestCase +from authentik.blueprints.tests import apply_blueprint from authentik.blueprints.v1.exporter import FlowExporter from authentik.blueprints.v1.importer import Importer, transaction_rollback from authentik.core.models import Group @@ -127,6 +128,7 @@ class TestBlueprintsV1(TransactionTestCase): self.assertEqual(Prompt.objects.filter(field_key="username").count(), count_before) + @apply_blueprint("system/providers-oauth2.yaml") def test_import_yaml_tags(self): """Test some yaml tags""" ExpressionPolicy.objects.filter(name="foo-bar-baz-qux").delete() @@ -237,6 +239,7 @@ class TestBlueprintsV1(TransactionTestCase): "at_index_sequence_default": "non existent", "at_index_mapping": 2, "at_index_mapping_default": "non existent", + "find_object": "goauthentik.io/providers/oauth2/scope-openid", }, ) self.assertTrue( diff --git a/authentik/blueprints/v1/common.py b/authentik/blueprints/v1/common.py index d98927927e..4c975f7cd3 100644 --- a/authentik/blueprints/v1/common.py +++ b/authentik/blueprints/v1/common.py @@ -367,7 +367,7 @@ class Format(YAMLTag): class Find(YAMLTag): - """Find any object""" + """Find any object primary key""" model_name: str | YAMLTag conditions: list[list] @@ -382,7 +382,7 @@ class Find(YAMLTag): values.append(loader.construct_object(node_values)) self.conditions.append(values) - def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any: + def _get_instance(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any: if isinstance(self.model_name, YAMLTag): model_name = self.model_name.resolve(entry, blueprint) else: @@ -404,12 +404,29 @@ class Find(YAMLTag): else: query_value = cond[1] query &= Q(**{query_key: query_value}) - instance = model_class.objects.filter(query).first() + return model_class.objects.filter(query).first() + + def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any: + instance = self._get_instance(entry, blueprint) if instance: return instance.pk return None +class FindObject(Find): + """Find any object""" + + def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any: + instance = self._get_instance(entry, blueprint) + if not instance: + return None + if not isinstance(instance, SerializerModel): + raise EntryInvalidError.from_entry( + f"Model {self.model_name} is not resolvable through FindObject", entry + ) + return instance.serializer(instance=instance).data + + class Condition(YAMLTag): """Convert all values to a single boolean""" @@ -705,6 +722,7 @@ class BlueprintLoader(SafeLoader): super().__init__(*args, **kwargs) self.add_constructor("!KeyOf", KeyOf) self.add_constructor("!Find", Find) + self.add_constructor("!FindObject", FindObject) self.add_constructor("!Context", Context) self.add_constructor("!Format", Format) self.add_constructor("!Condition", Condition) diff --git a/website/docs/customize/blueprints/v1/tags.mdx b/website/docs/customize/blueprints/v1/tags.mdx index 521ab030b9..0b320b9989 100644 --- a/website/docs/customize/blueprints/v1/tags.mdx +++ b/website/docs/customize/blueprints/v1/tags.mdx @@ -79,6 +79,25 @@ configure_flow: Looks up any model and resolves to the the matches' primary key. First argument is the model to be queried, remaining arguments are expected to be pairs of key=value pairs to query for. +#### `!FindObject` authentik 2025.8+ + +Examples: + +{/* prettier-ignore-start */} + +```yaml +flow_designation: + !AtIndex [ + !FindObject [authentik_flows.flow, [slug, default-password-change]], + designation, + ] +``` + +{/* prettier-ignore-end */} + +Looks up any model and resolves to the matches' serialized data. +First argument is the model to be queried, remaining arguments are expected to be pairs of key=value pairs to query for. + #### `!Context` Example: