blueprints: Support nested custom tags in !Find and !Format tags (#4127)
				
					
				
			* Added support for nested tags to !Find and !Format * Added tests * Fix variable names * Added docs * Fixed small mistake in tests * Fixed variable names * Broke example into multiple lines
This commit is contained in:
		
							
								
								
									
										13
									
								
								authentik/blueprints/tests/fixtures/tags.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								authentik/blueprints/tests/fixtures/tags.yaml
									
									
									
									
										vendored
									
									
								
							@ -1,10 +1,19 @@
 | 
			
		||||
version: 1
 | 
			
		||||
context:
 | 
			
		||||
    foo: bar
 | 
			
		||||
    policy_property: name
 | 
			
		||||
    policy_property_value: foo-bar-baz-qux
 | 
			
		||||
entries:
 | 
			
		||||
- attrs:
 | 
			
		||||
    expression: return True
 | 
			
		||||
  identifiers:
 | 
			
		||||
    name: !Format [foo-%s-%s, !Context foo, !Context bar]
 | 
			
		||||
  id: default-source-enrollment-if-username
 | 
			
		||||
    name: !Format [foo-%s-%s-%s, !Context foo, !Context bar, qux]
 | 
			
		||||
  id: policy
 | 
			
		||||
  model: authentik_policies_expression.expressionpolicy
 | 
			
		||||
- attrs:
 | 
			
		||||
    attributes:
 | 
			
		||||
      policy_pk1: !Format ["%s-%s", !Find [authentik_policies_expression.expressionpolicy, [!Context policy_property, !Context policy_property_value], [expression, return True]], suffix]
 | 
			
		||||
      policy_pk2: !Format ["%s-%s", !KeyOf policy, suffix]
 | 
			
		||||
  identifiers:
 | 
			
		||||
    name: test
 | 
			
		||||
  model: authentik_core.group
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,7 @@ from django.test import TransactionTestCase
 | 
			
		||||
from authentik.blueprints.tests import load_yaml_fixture
 | 
			
		||||
from authentik.blueprints.v1.exporter import FlowExporter
 | 
			
		||||
from authentik.blueprints.v1.importer import Importer, transaction_rollback
 | 
			
		||||
from authentik.core.models import Group
 | 
			
		||||
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
 | 
			
		||||
from authentik.lib.generators import generate_id
 | 
			
		||||
from authentik.policies.expression.models import ExpressionPolicy
 | 
			
		||||
@ -74,11 +75,21 @@ class TestBlueprintsV1(TransactionTestCase):
 | 
			
		||||
 | 
			
		||||
    def test_import_yaml_tags(self):
 | 
			
		||||
        """Test some yaml tags"""
 | 
			
		||||
        ExpressionPolicy.objects.filter(name="foo-foo-bar").delete()
 | 
			
		||||
        ExpressionPolicy.objects.filter(name="foo-bar-baz-qux").delete()
 | 
			
		||||
        Group.objects.filter(name="test").delete()
 | 
			
		||||
        importer = Importer(load_yaml_fixture("fixtures/tags.yaml"), {"bar": "baz"})
 | 
			
		||||
        self.assertTrue(importer.validate()[0])
 | 
			
		||||
        self.assertTrue(importer.apply())
 | 
			
		||||
        self.assertTrue(ExpressionPolicy.objects.filter(name="foo-foo-bar"))
 | 
			
		||||
        policy = ExpressionPolicy.objects.filter(name="foo-bar-baz-qux").first()
 | 
			
		||||
        self.assertTrue(policy)
 | 
			
		||||
        self.assertTrue(
 | 
			
		||||
            Group.objects.filter(
 | 
			
		||||
                attributes__contains={
 | 
			
		||||
                    "policy_pk1": str(policy.pk) + "-suffix",
 | 
			
		||||
                    "policy_pk2": str(policy.pk) + "-suffix",
 | 
			
		||||
                }
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_export_validate_import_policies(self):
 | 
			
		||||
        """Test export and validate it"""
 | 
			
		||||
 | 
			
		||||
@ -188,11 +188,18 @@ class Format(YAMLTag):
 | 
			
		||||
        self.format_string = node.value[0].value
 | 
			
		||||
        self.args = []
 | 
			
		||||
        for raw_node in node.value[1:]:
 | 
			
		||||
            self.args.append(raw_node.value)
 | 
			
		||||
            self.args.append(loader.construct_object(raw_node))
 | 
			
		||||
 | 
			
		||||
    def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any:
 | 
			
		||||
        args = []
 | 
			
		||||
        for arg in self.args:
 | 
			
		||||
            if isinstance(arg, YAMLTag):
 | 
			
		||||
                args.append(arg.resolve(entry, blueprint))
 | 
			
		||||
            else:
 | 
			
		||||
                args.append(arg)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            return self.format_string % tuple(self.args)
 | 
			
		||||
            return self.format_string % tuple(args)
 | 
			
		||||
        except TypeError as exc:
 | 
			
		||||
            raise EntryInvalidError(exc)
 | 
			
		||||
 | 
			
		||||
@ -219,7 +226,15 @@ class Find(YAMLTag):
 | 
			
		||||
    def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any:
 | 
			
		||||
        query = Q()
 | 
			
		||||
        for cond in self.conditions:
 | 
			
		||||
            query &= Q(**{cond[0]: cond[1]})
 | 
			
		||||
            if isinstance(cond[0], YAMLTag):
 | 
			
		||||
                query_key = cond[0].resolve(entry, blueprint)
 | 
			
		||||
            else:
 | 
			
		||||
                query_key = cond[0]
 | 
			
		||||
            if isinstance(cond[1], YAMLTag):
 | 
			
		||||
                query_value = cond[1].resolve(entry, blueprint)
 | 
			
		||||
            else:
 | 
			
		||||
                query_value = cond[1]
 | 
			
		||||
            query &= Q(**{query_key: query_value})
 | 
			
		||||
        instance = self.model_class.objects.filter(query).first()
 | 
			
		||||
        if instance:
 | 
			
		||||
            return instance.pk
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,19 @@ If no matching entry can be found, an error is raised and the blueprint is inval
 | 
			
		||||
 | 
			
		||||
#### `!Find`
 | 
			
		||||
 | 
			
		||||
Example: `configure_flow: !Find [authentik_flows.flow, [slug, default-password-change]]`
 | 
			
		||||
Examples:
 | 
			
		||||
 | 
			
		||||
`configure_flow: !Find [authentik_flows.flow, [slug, default-password-change]]`
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
configure_flow: !Find [
 | 
			
		||||
  authentik_flows.flow,
 | 
			
		||||
  [
 | 
			
		||||
    !Context property_name,
 | 
			
		||||
    !Context property_value
 | 
			
		||||
  ]
 | 
			
		||||
]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user