diff --git a/authentik/core/api/tokens.py b/authentik/core/api/tokens.py index 4bf748252..c923e97f2 100644 --- a/authentik/core/api/tokens.py +++ b/authentik/core/api/tokens.py @@ -2,7 +2,7 @@ from typing import Any from django_filters.rest_framework import DjangoFilterBackend -from drf_spectacular.utils import OpenApiResponse, extend_schema +from drf_spectacular.utils import OpenApiResponse, extend_schema, inline_serializer from guardian.shortcuts import assign_perm, get_anonymous_user from rest_framework.decorators import action from rest_framework.exceptions import ValidationError @@ -20,6 +20,7 @@ from authentik.core.api.users import UserSerializer from authentik.core.api.utils import PassiveSerializer from authentik.core.models import USER_ATTRIBUTE_TOKEN_EXPIRING, Token, TokenIntents from authentik.events.models import Event, EventAction +from authentik.events.utils import model_to_dict from authentik.managed.api import ManagedSerializer @@ -110,10 +111,39 @@ class TokenViewSet(UsedByMixin, ModelViewSet): 404: OpenApiResponse(description="Token not found or expired"), } ) - @action(detail=True, pagination_class=None, filter_backends=[]) + @action(detail=True, pagination_class=None, filter_backends=[], methods=["GET"]) # pylint: disable=unused-argument def view_key(self, request: Request, identifier: str) -> Response: """Return token key and log access""" token: Token = self.get_object() Event.new(EventAction.SECRET_VIEW, secret=token).from_http(request) # noqa # nosec return Response(TokenViewSerializer({"key": token.key}).data) + + @permission_required("authentik_core.set_token_key") + @extend_schema( + request=inline_serializer( + "TokenSetKey", + { + "key": CharField(), + }, + ), + responses={ + 204: OpenApiResponse(description="Successfully changed key"), + 400: OpenApiResponse(description="Missing key"), + 404: OpenApiResponse(description="Token not found or expired"), + }, + ) + @action(detail=True, pagination_class=None, filter_backends=[], methods=["POST"]) + # pylint: disable=unused-argument + def set_key(self, request: Request, identifier: str) -> Response: + """Return token key and log access""" + token: Token = self.get_object() + key = request.POST.get("key") + if not key: + return Response(status=400) + token.key = key + token.save() + Event.new(EventAction.MODEL_UPDATED, model=model_to_dict(token)).from_http( + request + ) # noqa # nosec + return Response(status=204) diff --git a/schema.yml b/schema.yml index aa283ef2f..7393502d4 100644 --- a/schema.yml +++ b/schema.yml @@ -2787,6 +2787,35 @@ paths: $ref: '#/components/schemas/ValidationError' '403': $ref: '#/components/schemas/GenericError' + /core/tokens/{identifier}/set_key/: + post: + operationId: core_tokens_set_key_create + description: Return token key and log access + parameters: + - in: path + name: identifier + schema: + type: string + required: true + tags: + - core + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TokenSetKeyRequest' + required: true + security: + - authentik: [] + responses: + '204': + description: Successfully changed key + '400': + description: Missing key + '404': + description: Token not found or expired + '403': + $ref: '#/components/schemas/GenericError' /core/tokens/{identifier}/used_by/: get: operationId: core_tokens_used_by_list @@ -30807,6 +30836,14 @@ components: type: boolean required: - identifier + TokenSetKeyRequest: + type: object + properties: + key: + type: string + minLength: 1 + required: + - key TokenView: type: object description: Show token's current key