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 version: 1
context: context:
foo: bar foo: bar
policy_property: name
policy_property_value: foo-bar-baz-qux
entries: entries:
- attrs: - attrs:
expression: return True expression: return True
identifiers: identifiers:
name: !Format [foo-%s-%s, !Context foo, !Context bar] name: !Format [foo-%s-%s-%s, !Context foo, !Context bar, qux]
id: default-source-enrollment-if-username id: policy
model: authentik_policies_expression.expressionpolicy 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.tests import load_yaml_fixture
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.flows.models import Flow, FlowDesignation, FlowStageBinding from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.lib.generators import generate_id from authentik.lib.generators import generate_id
from authentik.policies.expression.models import ExpressionPolicy from authentik.policies.expression.models import ExpressionPolicy
@ -74,11 +75,21 @@ class TestBlueprintsV1(TransactionTestCase):
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-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"}) importer = Importer(load_yaml_fixture("fixtures/tags.yaml"), {"bar": "baz"})
self.assertTrue(importer.validate()[0]) self.assertTrue(importer.validate()[0])
self.assertTrue(importer.apply()) 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): def test_export_validate_import_policies(self):
"""Test export and validate it""" """Test export and validate it"""

View File

@ -188,11 +188,18 @@ class Format(YAMLTag):
self.format_string = node.value[0].value self.format_string = node.value[0].value
self.args = [] self.args = []
for raw_node in node.value[1:]: 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: 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: try:
return self.format_string % tuple(self.args) return self.format_string % tuple(args)
except TypeError as exc: except TypeError as exc:
raise EntryInvalidError(exc) raise EntryInvalidError(exc)
@ -219,7 +226,15 @@ class Find(YAMLTag):
def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any: def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any:
query = Q() query = Q()
for cond in self.conditions: 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() instance = self.model_class.objects.filter(query).first()
if instance: if instance:
return instance.pk 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` #### `!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. 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. First argument is the model to be queried, remaining arguments are expected to be pairs of key=value pairs to query for.