blueprints: add FindObject tag
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
This commit is contained in:
		
							
								
								
									
										5
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							@ -29,6 +29,7 @@
 | 
				
			|||||||
        "!Enumerate sequence",
 | 
					        "!Enumerate sequence",
 | 
				
			||||||
        "!Env scalar",
 | 
					        "!Env scalar",
 | 
				
			||||||
        "!Find sequence",
 | 
					        "!Find sequence",
 | 
				
			||||||
 | 
					        "!FindObject sequence",
 | 
				
			||||||
        "!Format sequence",
 | 
					        "!Format sequence",
 | 
				
			||||||
        "!If sequence",
 | 
					        "!If sequence",
 | 
				
			||||||
        "!Index scalar",
 | 
					        "!Index scalar",
 | 
				
			||||||
@ -51,7 +52,9 @@
 | 
				
			|||||||
            "ignoreCase": false
 | 
					            "ignoreCase": false
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "go.testFlags": ["-count=1"],
 | 
					    "go.testFlags": [
 | 
				
			||||||
 | 
					        "-count=1"
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
    "github-actions.workflows.pinned.workflows": [
 | 
					    "github-actions.workflows.pinned.workflows": [
 | 
				
			||||||
        ".github/workflows/ci-main.yml"
 | 
					        ".github/workflows/ci-main.yml"
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
				
			|||||||
@ -150,6 +150,7 @@ entries:
 | 
				
			|||||||
              at_index_sequence_default: !AtIndex [!Context sequence, 100, "non existent"]
 | 
					              at_index_sequence_default: !AtIndex [!Context sequence, 100, "non existent"]
 | 
				
			||||||
              at_index_mapping: !AtIndex [!Context mapping, "key2"]
 | 
					              at_index_mapping: !AtIndex [!Context mapping, "key2"]
 | 
				
			||||||
              at_index_mapping_default: !AtIndex [!Context mapping, "invalid", "non existent"]
 | 
					              at_index_mapping_default: !AtIndex [!Context mapping, "invalid", "non existent"]
 | 
				
			||||||
 | 
					              find_object: !AtIndex [!FindObject [authentik_providers_oauth2.scopemapping, [scope_name, openid]], managed]
 | 
				
			||||||
      identifiers:
 | 
					      identifiers:
 | 
				
			||||||
          name: test
 | 
					          name: test
 | 
				
			||||||
      conditions:
 | 
					      conditions:
 | 
				
			||||||
 | 
				
			|||||||
@ -4,6 +4,7 @@ from os import environ
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from django.test import TransactionTestCase
 | 
					from django.test import TransactionTestCase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from authentik.blueprints.tests import apply_blueprint
 | 
				
			||||||
from authentik.blueprints.v1.exporter import FlowExporter
 | 
					from authentik.blueprints.v1.exporter import FlowExporter
 | 
				
			||||||
from authentik.blueprints.v1.importer import Importer, transaction_rollback
 | 
					from authentik.blueprints.v1.importer import Importer, transaction_rollback
 | 
				
			||||||
from authentik.core.models import Group
 | 
					from authentik.core.models import Group
 | 
				
			||||||
@ -126,6 +127,7 @@ class TestBlueprintsV1(TransactionTestCase):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        self.assertEqual(Prompt.objects.filter(field_key="username").count(), count_before)
 | 
					        self.assertEqual(Prompt.objects.filter(field_key="username").count(), count_before)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @apply_blueprint("system/providers-oauth2.yaml")
 | 
				
			||||||
    def test_import_yaml_tags(self):
 | 
					    def test_import_yaml_tags(self):
 | 
				
			||||||
        """Test some yaml tags"""
 | 
					        """Test some yaml tags"""
 | 
				
			||||||
        ExpressionPolicy.objects.filter(name="foo-bar-baz-qux").delete()
 | 
					        ExpressionPolicy.objects.filter(name="foo-bar-baz-qux").delete()
 | 
				
			||||||
@ -136,91 +138,93 @@ class TestBlueprintsV1(TransactionTestCase):
 | 
				
			|||||||
        self.assertTrue(importer.apply())
 | 
					        self.assertTrue(importer.apply())
 | 
				
			||||||
        policy = ExpressionPolicy.objects.filter(name="foo-bar-baz-qux").first()
 | 
					        policy = ExpressionPolicy.objects.filter(name="foo-bar-baz-qux").first()
 | 
				
			||||||
        self.assertTrue(policy)
 | 
					        self.assertTrue(policy)
 | 
				
			||||||
        self.assertTrue(
 | 
					        group = Group.objects.filter(name="test").first()
 | 
				
			||||||
            Group.objects.filter(
 | 
					        self.assertIsNotNone(group)
 | 
				
			||||||
                attributes={
 | 
					        self.assertEqual(
 | 
				
			||||||
                    "policy_pk1": str(policy.pk) + "-suffix",
 | 
					            group.attributes,
 | 
				
			||||||
                    "policy_pk2": str(policy.pk) + "-suffix",
 | 
					            {
 | 
				
			||||||
                    "boolAnd": True,
 | 
					                "policy_pk1": str(policy.pk) + "-suffix",
 | 
				
			||||||
                    "boolNand": False,
 | 
					                "policy_pk2": str(policy.pk) + "-suffix",
 | 
				
			||||||
                    "boolOr": True,
 | 
					                "boolAnd": True,
 | 
				
			||||||
                    "boolNor": False,
 | 
					                "boolNand": False,
 | 
				
			||||||
                    "boolXor": True,
 | 
					                "boolOr": True,
 | 
				
			||||||
                    "boolXnor": False,
 | 
					                "boolNor": False,
 | 
				
			||||||
                    "boolComplex": True,
 | 
					                "boolXor": True,
 | 
				
			||||||
                    "if_true_complex": {
 | 
					                "boolXnor": False,
 | 
				
			||||||
                        "dictionary": {
 | 
					                "boolComplex": True,
 | 
				
			||||||
                            "with": {"keys": "and_values"},
 | 
					                "if_true_complex": {
 | 
				
			||||||
                            "and_nested_custom_tags": "foo-bar",
 | 
					                    "dictionary": {
 | 
				
			||||||
                        }
 | 
					                        "with": {"keys": "and_values"},
 | 
				
			||||||
 | 
					                        "and_nested_custom_tags": "foo-bar",
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "if_false_complex": ["list", "with", "items", "foo-bar"],
 | 
				
			||||||
 | 
					                "if_true_simple": True,
 | 
				
			||||||
 | 
					                "if_short": True,
 | 
				
			||||||
 | 
					                "if_false_simple": 2,
 | 
				
			||||||
 | 
					                "enumerate_mapping_to_mapping": {
 | 
				
			||||||
 | 
					                    "prefix-key1": "other-prefix-value",
 | 
				
			||||||
 | 
					                    "prefix-key2": "other-prefix-2",
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "enumerate_mapping_to_sequence": [
 | 
				
			||||||
 | 
					                    "prefixed-pair-key1-value",
 | 
				
			||||||
 | 
					                    "prefixed-pair-key2-2",
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                "enumerate_sequence_to_sequence": [
 | 
				
			||||||
 | 
					                    "prefixed-items-0-foo",
 | 
				
			||||||
 | 
					                    "prefixed-items-1-bar",
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                "enumerate_sequence_to_mapping": {"index: 0": "foo", "index: 1": "bar"},
 | 
				
			||||||
 | 
					                "nested_complex_enumeration": {
 | 
				
			||||||
 | 
					                    "0": {
 | 
				
			||||||
 | 
					                        "key1": [
 | 
				
			||||||
 | 
					                            ["prefixed-f", "prefixed-o", "prefixed-o"],
 | 
				
			||||||
 | 
					                            {
 | 
				
			||||||
 | 
					                                "outer_value": "foo",
 | 
				
			||||||
 | 
					                                "outer_index": 0,
 | 
				
			||||||
 | 
					                                "middle_value": "value",
 | 
				
			||||||
 | 
					                                "middle_index": "key1",
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                        ],
 | 
				
			||||||
 | 
					                        "key2": [
 | 
				
			||||||
 | 
					                            ["prefixed-f", "prefixed-o", "prefixed-o"],
 | 
				
			||||||
 | 
					                            {
 | 
				
			||||||
 | 
					                                "outer_value": "foo",
 | 
				
			||||||
 | 
					                                "outer_index": 0,
 | 
				
			||||||
 | 
					                                "middle_value": 2,
 | 
				
			||||||
 | 
					                                "middle_index": "key2",
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                        ],
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
                    "if_false_complex": ["list", "with", "items", "foo-bar"],
 | 
					                    "1": {
 | 
				
			||||||
                    "if_true_simple": True,
 | 
					                        "key1": [
 | 
				
			||||||
                    "if_short": True,
 | 
					                            ["prefixed-b", "prefixed-a", "prefixed-r"],
 | 
				
			||||||
                    "if_false_simple": 2,
 | 
					                            {
 | 
				
			||||||
                    "enumerate_mapping_to_mapping": {
 | 
					                                "outer_value": "bar",
 | 
				
			||||||
                        "prefix-key1": "other-prefix-value",
 | 
					                                "outer_index": 1,
 | 
				
			||||||
                        "prefix-key2": "other-prefix-2",
 | 
					                                "middle_value": "value",
 | 
				
			||||||
 | 
					                                "middle_index": "key1",
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                        ],
 | 
				
			||||||
 | 
					                        "key2": [
 | 
				
			||||||
 | 
					                            ["prefixed-b", "prefixed-a", "prefixed-r"],
 | 
				
			||||||
 | 
					                            {
 | 
				
			||||||
 | 
					                                "outer_value": "bar",
 | 
				
			||||||
 | 
					                                "outer_index": 1,
 | 
				
			||||||
 | 
					                                "middle_value": 2,
 | 
				
			||||||
 | 
					                                "middle_index": "key2",
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                        ],
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
                    "enumerate_mapping_to_sequence": [
 | 
					                },
 | 
				
			||||||
                        "prefixed-pair-key1-value",
 | 
					                "nested_context": "context-nested-value",
 | 
				
			||||||
                        "prefixed-pair-key2-2",
 | 
					                "env_null": None,
 | 
				
			||||||
                    ],
 | 
					                "at_index_sequence": "foo",
 | 
				
			||||||
                    "enumerate_sequence_to_sequence": [
 | 
					                "at_index_sequence_default": "non existent",
 | 
				
			||||||
                        "prefixed-items-0-foo",
 | 
					                "at_index_mapping": 2,
 | 
				
			||||||
                        "prefixed-items-1-bar",
 | 
					                "at_index_mapping_default": "non existent",
 | 
				
			||||||
                    ],
 | 
					                "find_object": "goauthentik.io/providers/oauth2/scope-openid",
 | 
				
			||||||
                    "enumerate_sequence_to_mapping": {"index: 0": "foo", "index: 1": "bar"},
 | 
					            },
 | 
				
			||||||
                    "nested_complex_enumeration": {
 | 
					 | 
				
			||||||
                        "0": {
 | 
					 | 
				
			||||||
                            "key1": [
 | 
					 | 
				
			||||||
                                ["prefixed-f", "prefixed-o", "prefixed-o"],
 | 
					 | 
				
			||||||
                                {
 | 
					 | 
				
			||||||
                                    "outer_value": "foo",
 | 
					 | 
				
			||||||
                                    "outer_index": 0,
 | 
					 | 
				
			||||||
                                    "middle_value": "value",
 | 
					 | 
				
			||||||
                                    "middle_index": "key1",
 | 
					 | 
				
			||||||
                                },
 | 
					 | 
				
			||||||
                            ],
 | 
					 | 
				
			||||||
                            "key2": [
 | 
					 | 
				
			||||||
                                ["prefixed-f", "prefixed-o", "prefixed-o"],
 | 
					 | 
				
			||||||
                                {
 | 
					 | 
				
			||||||
                                    "outer_value": "foo",
 | 
					 | 
				
			||||||
                                    "outer_index": 0,
 | 
					 | 
				
			||||||
                                    "middle_value": 2,
 | 
					 | 
				
			||||||
                                    "middle_index": "key2",
 | 
					 | 
				
			||||||
                                },
 | 
					 | 
				
			||||||
                            ],
 | 
					 | 
				
			||||||
                        },
 | 
					 | 
				
			||||||
                        "1": {
 | 
					 | 
				
			||||||
                            "key1": [
 | 
					 | 
				
			||||||
                                ["prefixed-b", "prefixed-a", "prefixed-r"],
 | 
					 | 
				
			||||||
                                {
 | 
					 | 
				
			||||||
                                    "outer_value": "bar",
 | 
					 | 
				
			||||||
                                    "outer_index": 1,
 | 
					 | 
				
			||||||
                                    "middle_value": "value",
 | 
					 | 
				
			||||||
                                    "middle_index": "key1",
 | 
					 | 
				
			||||||
                                },
 | 
					 | 
				
			||||||
                            ],
 | 
					 | 
				
			||||||
                            "key2": [
 | 
					 | 
				
			||||||
                                ["prefixed-b", "prefixed-a", "prefixed-r"],
 | 
					 | 
				
			||||||
                                {
 | 
					 | 
				
			||||||
                                    "outer_value": "bar",
 | 
					 | 
				
			||||||
                                    "outer_index": 1,
 | 
					 | 
				
			||||||
                                    "middle_value": 2,
 | 
					 | 
				
			||||||
                                    "middle_index": "key2",
 | 
					 | 
				
			||||||
                                },
 | 
					 | 
				
			||||||
                            ],
 | 
					 | 
				
			||||||
                        },
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    "nested_context": "context-nested-value",
 | 
					 | 
				
			||||||
                    "env_null": None,
 | 
					 | 
				
			||||||
                    "at_index_sequence": "foo",
 | 
					 | 
				
			||||||
                    "at_index_sequence_default": "non existent",
 | 
					 | 
				
			||||||
                    "at_index_mapping": 2,
 | 
					 | 
				
			||||||
                    "at_index_mapping_default": "non existent",
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            ).exists()
 | 
					 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        self.assertTrue(
 | 
					        self.assertTrue(
 | 
				
			||||||
            OAuthSource.objects.filter(
 | 
					            OAuthSource.objects.filter(
 | 
				
			||||||
 | 
				
			|||||||
@ -311,7 +311,7 @@ class Format(YAMLTag):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Find(YAMLTag):
 | 
					class Find(YAMLTag):
 | 
				
			||||||
    """Find any object"""
 | 
					    """Find any object primary key"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    model_name: str | YAMLTag
 | 
					    model_name: str | YAMLTag
 | 
				
			||||||
    conditions: list[list]
 | 
					    conditions: list[list]
 | 
				
			||||||
@ -326,7 +326,7 @@ class Find(YAMLTag):
 | 
				
			|||||||
                values.append(loader.construct_object(node_values))
 | 
					                values.append(loader.construct_object(node_values))
 | 
				
			||||||
            self.conditions.append(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):
 | 
					        if isinstance(self.model_name, YAMLTag):
 | 
				
			||||||
            model_name = self.model_name.resolve(entry, blueprint)
 | 
					            model_name = self.model_name.resolve(entry, blueprint)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
@ -348,12 +348,29 @@ class Find(YAMLTag):
 | 
				
			|||||||
            else:
 | 
					            else:
 | 
				
			||||||
                query_value = cond[1]
 | 
					                query_value = cond[1]
 | 
				
			||||||
            query &= Q(**{query_key: query_value})
 | 
					            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:
 | 
					        if instance:
 | 
				
			||||||
            return instance.pk
 | 
					            return instance.pk
 | 
				
			||||||
        return None
 | 
					        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):
 | 
					class Condition(YAMLTag):
 | 
				
			||||||
    """Convert all values to a single boolean"""
 | 
					    """Convert all values to a single boolean"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -649,6 +666,7 @@ class BlueprintLoader(SafeLoader):
 | 
				
			|||||||
        super().__init__(*args, **kwargs)
 | 
					        super().__init__(*args, **kwargs)
 | 
				
			||||||
        self.add_constructor("!KeyOf", KeyOf)
 | 
					        self.add_constructor("!KeyOf", KeyOf)
 | 
				
			||||||
        self.add_constructor("!Find", Find)
 | 
					        self.add_constructor("!Find", Find)
 | 
				
			||||||
 | 
					        self.add_constructor("!FindObject", FindObject)
 | 
				
			||||||
        self.add_constructor("!Context", Context)
 | 
					        self.add_constructor("!Context", Context)
 | 
				
			||||||
        self.add_constructor("!Format", Format)
 | 
					        self.add_constructor("!Format", Format)
 | 
				
			||||||
        self.add_constructor("!Condition", Condition)
 | 
					        self.add_constructor("!Condition", Condition)
 | 
				
			||||||
 | 
				
			|||||||
@ -12,6 +12,7 @@ For VS Code, for example, add these entries to your `settings.json`:
 | 
				
			|||||||
        "!Enumerate sequence",
 | 
					        "!Enumerate sequence",
 | 
				
			||||||
        "!Env scalar",
 | 
					        "!Env scalar",
 | 
				
			||||||
        "!Find sequence",
 | 
					        "!Find sequence",
 | 
				
			||||||
 | 
					        "!FindObject sequence",
 | 
				
			||||||
        "!Format sequence",
 | 
					        "!Format sequence",
 | 
				
			||||||
        "!If sequence",
 | 
					        "!If sequence",
 | 
				
			||||||
        "!Index scalar",
 | 
					        "!Index scalar",
 | 
				
			||||||
@ -60,7 +61,22 @@ configure_flow:
 | 
				
			|||||||
    ]
 | 
					    ]
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Looks up any model and resolves to the the matches' primary key.
 | 
					Looks up any model and resolves to 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` <span class="badge badge--version">authentik 2025.2+</span>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Examples:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```yaml
 | 
				
			||||||
 | 
					flow_designation:
 | 
				
			||||||
 | 
					    !AtIndex [
 | 
				
			||||||
 | 
					        !FindObject [authentik_flows.flow, [slug, default-password-change]],
 | 
				
			||||||
 | 
					        designation,
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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.
 | 
					First argument is the model to be queried, remaining arguments are expected to be pairs of key=value pairs to query for.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#### `!Context`
 | 
					#### `!Context`
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user