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:
sdimovv 2022-12-01 15:10:26 +00:00 committed by GitHub
parent 3251bdc220
commit 1f7d52c5ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 55 additions and 8 deletions

View File

@ -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

View File

@ -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"""

View File

@ -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

View File

@ -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.