blueprints: allow setting of token key in blueprint context (#4995)
closes #4717 Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
		@ -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()
 | 
			
		||||
 | 
			
		||||
@ -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:
 | 
			
		||||
 | 
			
		||||
@ -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")
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										27
									
								
								website/developer-docs/blueprints/v1/models.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								website/developer-docs/blueprints/v1/models.md
									
									
									
									
									
										Normal file
									
								
							@ -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
 | 
			
		||||
```
 | 
			
		||||
@ -16,6 +16,7 @@ module.exports = {
 | 
			
		||||
                "blueprints/v1/structure",
 | 
			
		||||
                "blueprints/v1/tags",
 | 
			
		||||
                "blueprints/v1/example",
 | 
			
		||||
                "blueprints/v1/models",
 | 
			
		||||
                "blueprints/v1/meta",
 | 
			
		||||
            ],
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user