From dcbf106daaf174733513b965e696f5314b637b5a Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Sat, 6 Aug 2022 20:54:00 +0200 Subject: [PATCH] blueprints: add !Context to lookup things from instance context Signed-off-by: Jens Langhammer --- .../management/commands/schema_template.json | 5 ++++ authentik/blueprints/v1/common.py | 25 +++++++++++++++++++ authentik/blueprints/v1/importer.py | 10 ++++++-- authentik/blueprints/v1/tasks.py | 2 +- blueprints/schema.json | 5 ++++ .../developer-docs/blueprints/v1/structure.md | 9 +++++++ 6 files changed, 53 insertions(+), 3 deletions(-) diff --git a/authentik/blueprints/management/commands/schema_template.json b/authentik/blueprints/management/commands/schema_template.json index 8ead5ef3e..7e3f740be 100644 --- a/authentik/blueprints/management/commands/schema_template.json +++ b/authentik/blueprints/management/commands/schema_template.json @@ -30,6 +30,11 @@ } } }, + "context": { + "$id": "#/properties/context", + "type": "object", + "additionalProperties": true + }, "entries": { "type": "array", "items": { diff --git a/authentik/blueprints/v1/common.py b/authentik/blueprints/v1/common.py index c508a6e8b..3d618d60b 100644 --- a/authentik/blueprints/v1/common.py +++ b/authentik/blueprints/v1/common.py @@ -100,6 +100,7 @@ class Blueprint: entries: list[BlueprintEntry] = field(default_factory=list) metadata: Optional[BlueprintMetadata] = field(default=None) + context: Optional[dict] = field(default_factory=dict) class YAMLTag: @@ -136,6 +137,29 @@ class KeyOf(YAMLTag): ) +class Context(YAMLTag): + """Lookup key from instance context""" + + key: str + default: Optional[Any] + + # pylint: disable=unused-argument + def __init__(self, loader: "BlueprintLoader", node: ScalarNode | SequenceNode) -> None: + super().__init__() + self.default = None + if isinstance(node, ScalarNode): + self.key = node.value + if isinstance(node, SequenceNode): + self.key = node.value[0].value + self.default = node.value[1].value + + def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any: + value = self.default + if self.key in blueprint.context: + value = blueprint.context[self.key] + return value + + class Find(YAMLTag): """Find any object""" @@ -189,6 +213,7 @@ class BlueprintLoader(SafeLoader): super().__init__(*args, **kwargs) self.add_constructor("!KeyOf", KeyOf) self.add_constructor("!Find", Find) + self.add_constructor("!Context", Context) class EntryInvalidError(SentryIgnoredException): diff --git a/authentik/blueprints/v1/importer.py b/authentik/blueprints/v1/importer.py index 87fc8aeda..2a844d22e 100644 --- a/authentik/blueprints/v1/importer.py +++ b/authentik/blueprints/v1/importer.py @@ -1,10 +1,11 @@ """Blueprint importer""" from contextlib import contextmanager from copy import deepcopy -from typing import Any +from typing import Any, Optional from dacite import from_dict from dacite.exceptions import DaciteError +from deepmerge import always_merger from django.apps import apps from django.db import transaction from django.db.models import Model @@ -75,7 +76,7 @@ class Importer: logger: BoundLogger - def __init__(self, yaml_input: str): + def __init__(self, yaml_input: str, context: Optional[dict] = None): self.__pk_map: dict[Any, Model] = {} self.logger = get_logger() import_dict = load(yaml_input, BlueprintLoader) @@ -83,6 +84,11 @@ class Importer: self.__import = from_dict(Blueprint, import_dict) except DaciteError as exc: raise EntryInvalidError from exc + context = {} + always_merger.merge(context, self.__import.context) + if context: + always_merger.merge(context, context) + self.__import.context = context @property def blueprint(self) -> Blueprint: diff --git a/authentik/blueprints/v1/tasks.py b/authentik/blueprints/v1/tasks.py index 5325da58d..5fcc2aadf 100644 --- a/authentik/blueprints/v1/tasks.py +++ b/authentik/blueprints/v1/tasks.py @@ -121,7 +121,7 @@ def apply_blueprint(self: MonitoredTask, instance_pk: str): full_path = Path(CONFIG.y("blueprints_dir")).joinpath(Path(instance.path)) file_hash = sha512(full_path.read_bytes()).hexdigest() with open(full_path, "r", encoding="utf-8") as blueprint_file: - importer = Importer(blueprint_file.read()) + importer = Importer(blueprint_file.read(), instance.context) valid, logs = importer.validate() if not valid: instance.status = BlueprintInstanceStatus.ERROR diff --git a/blueprints/schema.json b/blueprints/schema.json index 3facde094..41a86f14c 100644 --- a/blueprints/schema.json +++ b/blueprints/schema.json @@ -30,6 +30,11 @@ } } }, + "context": { + "$id": "#/properties/context", + "type": "object", + "additionalProperties": true + }, "entries": { "type": "array", "items": { diff --git a/website/developer-docs/blueprints/v1/structure.md b/website/developer-docs/blueprints/v1/structure.md index 16ebc25da..cd8394ec9 100644 --- a/website/developer-docs/blueprints/v1/structure.md +++ b/website/developer-docs/blueprints/v1/structure.md @@ -21,6 +21,12 @@ Example: `configure_flow: !Find [authentik_flows.flow, [slug, default-password-c 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. +#### `!Context` + +Example: `configure_flow: !Context foo` + +Find values from the context. Can optionally be called with a default like `!Context [foo, default-value]`. + ## Structure ```yaml @@ -32,6 +38,9 @@ metadata: labels: foo: bar name: example-blueprint +# Optional default context, instance context is merged over this. +context: + foo: bar # List of entries (required) entries: - # Model in app.model notation, possibilities are listed in the schema (required)