diff --git a/authentik/blueprints/management/commands/apply_blueprint.py b/authentik/blueprints/management/commands/apply_blueprint.py index 7b9081aae..4aea0159d 100644 --- a/authentik/blueprints/management/commands/apply_blueprint.py +++ b/authentik/blueprints/management/commands/apply_blueprint.py @@ -19,10 +19,8 @@ class Command(BaseCommand): for blueprint_path in options.get("blueprints", []): content = BlueprintInstance(path=blueprint_path).retrieve() importer = Importer(content) - valid, logs = importer.validate() + valid, _ = importer.validate() if not valid: - for log in logs: - getattr(LOGGER, log.pop("log_level"))(**log) self.stderr.write("blueprint invalid") sys_exit(1) importer.apply() diff --git a/authentik/blueprints/v1/importer.py b/authentik/blueprints/v1/importer.py index 1983d87c1..ed900bc38 100644 --- a/authentik/blueprints/v1/importer.py +++ b/authentik/blueprints/v1/importer.py @@ -40,6 +40,10 @@ from authentik.lib.models import SerializerModel from authentik.outposts.models import OutpostServiceConnection from authentik.policies.models import Policy, PolicyBindingModel +# Context set when the serializer is created in a blueprint context +# Update website/developer-docs/blueprints/v1/models.md when used +SERIALIZER_CONTEXT_BLUEPRINT = "blueprint_entry" + def is_model_allowed(model: type[Model]) -> bool: """Check if model is allowed""" @@ -158,7 +162,12 @@ class Importer: raise EntryInvalidError(f"Model {model} not allowed") if issubclass(model, BaseMetaModel): serializer_class: type[Serializer] = model.serializer() - serializer = serializer_class(data=entry.get_attrs(self.__import)) + serializer = serializer_class( + data=entry.get_attrs(self.__import), + context={ + SERIALIZER_CONTEXT_BLUEPRINT: entry, + }, + ) try: serializer.is_valid(raise_exception=True) except ValidationError as exc: @@ -217,7 +226,12 @@ class Importer: always_merger.merge(full_data, updated_identifiers) serializer_kwargs["data"] = full_data - serializer: Serializer = model().serializer(**serializer_kwargs) + serializer: Serializer = model().serializer( + context={ + SERIALIZER_CONTEXT_BLUEPRINT: entry, + }, + **serializer_kwargs, + ) try: serializer.is_valid(raise_exception=True) except ValidationError as exc: diff --git a/authentik/core/api/tokens.py b/authentik/core/api/tokens.py index 8e5c32d3e..b00011baf 100644 --- a/authentik/core/api/tokens.py +++ b/authentik/core/api/tokens.py @@ -16,6 +16,7 @@ from rest_framework.viewsets import ModelViewSet from authentik.api.authorization import OwnerSuperuserPermissions from authentik.api.decorators import permission_required from authentik.blueprints.api import ManagedSerializer +from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT from authentik.core.api.used_by import UsedByMixin from authentik.core.api.users import UserSerializer from authentik.core.api.utils import PassiveSerializer @@ -29,6 +30,11 @@ class TokenSerializer(ManagedSerializer, ModelSerializer): user_obj = UserSerializer(required=False, source="user", read_only=True) + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + if SERIALIZER_CONTEXT_BLUEPRINT in self.context: + self.fields["key"] = CharField() + def validate(self, attrs: dict[Any, str]) -> dict[Any, str]: """Ensure only API or App password tokens are created.""" request: Request = self.context.get("request") diff --git a/website/developer-docs/api/api.md b/website/developer-docs/api/api.md index 62d50000b..e6b7b6dc3 100644 --- a/website/developer-docs/api/api.md +++ b/website/developer-docs/api/api.md @@ -18,7 +18,7 @@ When authenticating with a flow, you'll get an authenticated Session cookie, tha ### API Token -Superusers can create tokens to authenticate as any user with a static key, which can optionally be expiring and auto-rotate. +Users can create tokens to authenticate as any user with a static key, which can optionally be expiring and auto-rotate. ### JWT Token diff --git a/website/developer-docs/blueprints/v1/models.md b/website/developer-docs/blueprints/v1/models.md new file mode 100644 index 000000000..2303aaf31 --- /dev/null +++ b/website/developer-docs/blueprints/v1/models.md @@ -0,0 +1,27 @@ +# Models + +Some models behave differently and allow for access to different API fields when created via blueprint. + +### `authentik_core.token` + +:::info +Requires authentik 2023.4 +::: + +Via the standard API, a token's key cannot be changed, it can only be rotated. This is to ensure a high entropy in it's key, and to prevent insecure data from being used. However, when provisioning tokens via a blueprint, it may be required to set a token to an existing value. + +With blueprints, the field `key` can be set, to set the token's key to any value. + +For example: + +```yaml +# [...] +- model: authentik_core.token + state: present + identifiers: + identifier: my-token + attrs: + key: this-should-be-a-long-value + user: !KeyOf my-user + intent: api +``` diff --git a/website/sidebarsDev.js b/website/sidebarsDev.js index 41ff6d4d3..cbbc255e1 100644 --- a/website/sidebarsDev.js +++ b/website/sidebarsDev.js @@ -16,6 +16,7 @@ module.exports = { "blueprints/v1/structure", "blueprints/v1/tags", "blueprints/v1/example", + "blueprints/v1/models", "blueprints/v1/meta", ], },