Compare commits

...

1 Commits

Author SHA1 Message Date
906c63c16f blueprints: add FindObject tag
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2024-12-19 15:55:34 +01:00
5 changed files with 130 additions and 88 deletions

View File

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

View File

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

View File

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

View File

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

View File

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