authentik.stages.prompt.api

Prompt Stage API Views

  1"""Prompt Stage API Views"""
  2
  3from drf_spectacular.utils import extend_schema
  4from rest_framework.decorators import action
  5from rest_framework.request import Request
  6from rest_framework.response import Response
  7from rest_framework.serializers import CharField
  8from rest_framework.validators import UniqueValidator
  9from rest_framework.viewsets import ModelViewSet
 10
 11from authentik.core.api.used_by import UsedByMixin
 12from authentik.core.api.utils import ModelSerializer
 13from authentik.core.expression.exceptions import PropertyMappingExpressionException
 14from authentik.flows.api.stages import StageSerializer
 15from authentik.flows.challenge import HttpChallengeResponse
 16from authentik.flows.planner import FlowPlan
 17from authentik.flows.views.executor import FlowExecutorView
 18from authentik.lib.generators import generate_id
 19from authentik.lib.utils.errors import exception_to_string
 20from authentik.stages.prompt.models import Prompt, PromptStage
 21from authentik.stages.prompt.stage import PromptChallenge, PromptStageView
 22
 23
 24class PromptStageSerializer(StageSerializer):
 25    """PromptStage Serializer"""
 26
 27    name = CharField(validators=[UniqueValidator(queryset=PromptStage.objects.all())])
 28
 29    class Meta:
 30        model = PromptStage
 31        fields = StageSerializer.Meta.fields + [
 32            "fields",
 33            "validation_policies",
 34        ]
 35
 36
 37class PromptStageViewSet(UsedByMixin, ModelViewSet):
 38    """PromptStage Viewset"""
 39
 40    queryset = PromptStage.objects.prefetch_related(
 41        "flow_set",
 42        "fields",
 43        "validation_policies",
 44    ).all()
 45    serializer_class = PromptStageSerializer
 46    filterset_fields = "__all__"
 47    ordering = ["name"]
 48    search_fields = ["name"]
 49
 50
 51class PromptSerializer(ModelSerializer):
 52    """Prompt Serializer"""
 53
 54    prompt_stages_obj = PromptStageSerializer(
 55        source="promptstage_set", many=True, required=False, read_only=True
 56    )
 57
 58    class Meta:
 59        model = Prompt
 60        fields = [
 61            "pk",
 62            "name",
 63            "field_key",
 64            "label",
 65            "type",
 66            "required",
 67            "placeholder",
 68            "initial_value",
 69            "order",
 70            "prompt_stages_obj",
 71            "sub_text",
 72            "placeholder_expression",
 73            "initial_value_expression",
 74        ]
 75
 76
 77class PromptViewSet(UsedByMixin, ModelViewSet):
 78    """Prompt Viewset"""
 79
 80    queryset = Prompt.objects.all().prefetch_related(
 81        "promptstage_set",
 82        "promptstage_set__flow_set",
 83        "promptstage_set__fields",
 84        "promptstage_set__validation_policies",
 85    )
 86    serializer_class = PromptSerializer
 87    ordering = ["field_key"]
 88    filterset_fields = ["field_key", "name", "label", "type", "placeholder"]
 89    search_fields = ["field_key", "name", "label", "type", "placeholder"]
 90
 91    @extend_schema(
 92        request=PromptSerializer,
 93        responses={
 94            200: PromptChallenge,
 95        },
 96    )
 97    @action(detail=False, methods=["POST"])
 98    def preview(self, request: Request) -> Response:
 99        """Preview a prompt as a challenge, just like a flow would receive"""
100        # Remove a couple things from the request, the serializer will fail on these
101        # when previewing an existing prompt
102        # and since we don't plan to save from this, set a random name and remove the stage
103        request.data["name"] = generate_id()
104        request.data.pop("promptstage_set", None)
105        # Validate data, same as a normal edit/create request
106        prompt = PromptSerializer(data=request.data)
107        prompt.is_valid(raise_exception=True)
108        # Convert serializer to prompt instance
109        prompt_model = Prompt(**prompt.validated_data)
110        # Convert to field challenge
111        try:
112            fields = PromptStageView(
113                FlowExecutorView(
114                    plan=FlowPlan(""),
115                    request=request._request,
116                ),
117                request=request._request,
118            ).get_prompt_challenge_fields([prompt_model], {}, dry_run=True)
119        except PropertyMappingExpressionException as exc:
120            return Response(
121                {
122                    "non_field_errors": [
123                        exception_to_string(exc.exc),
124                    ]
125                },
126                status=400,
127            )
128        challenge = PromptChallenge(
129            data={
130                "fields": fields,
131            },
132        )
133        challenge.is_valid()
134        return HttpChallengeResponse(challenge)
class PromptStageSerializer(authentik.flows.api.stages.StageSerializer):
25class PromptStageSerializer(StageSerializer):
26    """PromptStage Serializer"""
27
28    name = CharField(validators=[UniqueValidator(queryset=PromptStage.objects.all())])
29
30    class Meta:
31        model = PromptStage
32        fields = StageSerializer.Meta.fields + [
33            "fields",
34            "validation_policies",
35        ]

PromptStage Serializer

name
class PromptStageSerializer.Meta:
30    class Meta:
31        model = PromptStage
32        fields = StageSerializer.Meta.fields + [
33            "fields",
34            "validation_policies",
35        ]
fields = ['pk', 'name', 'component', 'verbose_name', 'verbose_name_plural', 'meta_model_name', 'flow_set', 'fields', 'validation_policies']
class PromptStageViewSet(authentik.core.api.used_by.UsedByMixin, rest_framework.viewsets.ModelViewSet):
38class PromptStageViewSet(UsedByMixin, ModelViewSet):
39    """PromptStage Viewset"""
40
41    queryset = PromptStage.objects.prefetch_related(
42        "flow_set",
43        "fields",
44        "validation_policies",
45    ).all()
46    serializer_class = PromptStageSerializer
47    filterset_fields = "__all__"
48    ordering = ["name"]
49    search_fields = ["name"]

PromptStage Viewset

queryset = <InheritanceQuerySet []>
serializer_class = <class 'PromptStageSerializer'>
filterset_fields = '__all__'
ordering = ['name']
search_fields = ['name']
name = None
description = None
suffix = None
detail = None
basename = None
class PromptSerializer(authentik.core.api.utils.ModelSerializer):
52class PromptSerializer(ModelSerializer):
53    """Prompt Serializer"""
54
55    prompt_stages_obj = PromptStageSerializer(
56        source="promptstage_set", many=True, required=False, read_only=True
57    )
58
59    class Meta:
60        model = Prompt
61        fields = [
62            "pk",
63            "name",
64            "field_key",
65            "label",
66            "type",
67            "required",
68            "placeholder",
69            "initial_value",
70            "order",
71            "prompt_stages_obj",
72            "sub_text",
73            "placeholder_expression",
74            "initial_value_expression",
75        ]

Prompt Serializer

prompt_stages_obj
class PromptSerializer.Meta:
59    class Meta:
60        model = Prompt
61        fields = [
62            "pk",
63            "name",
64            "field_key",
65            "label",
66            "type",
67            "required",
68            "placeholder",
69            "initial_value",
70            "order",
71            "prompt_stages_obj",
72            "sub_text",
73            "placeholder_expression",
74            "initial_value_expression",
75        ]
fields = ['pk', 'name', 'field_key', 'label', 'type', 'required', 'placeholder', 'initial_value', 'order', 'prompt_stages_obj', 'sub_text', 'placeholder_expression', 'initial_value_expression']
class PromptViewSet(authentik.core.api.used_by.UsedByMixin, rest_framework.viewsets.ModelViewSet):
 78class PromptViewSet(UsedByMixin, ModelViewSet):
 79    """Prompt Viewset"""
 80
 81    queryset = Prompt.objects.all().prefetch_related(
 82        "promptstage_set",
 83        "promptstage_set__flow_set",
 84        "promptstage_set__fields",
 85        "promptstage_set__validation_policies",
 86    )
 87    serializer_class = PromptSerializer
 88    ordering = ["field_key"]
 89    filterset_fields = ["field_key", "name", "label", "type", "placeholder"]
 90    search_fields = ["field_key", "name", "label", "type", "placeholder"]
 91
 92    @extend_schema(
 93        request=PromptSerializer,
 94        responses={
 95            200: PromptChallenge,
 96        },
 97    )
 98    @action(detail=False, methods=["POST"])
 99    def preview(self, request: Request) -> Response:
100        """Preview a prompt as a challenge, just like a flow would receive"""
101        # Remove a couple things from the request, the serializer will fail on these
102        # when previewing an existing prompt
103        # and since we don't plan to save from this, set a random name and remove the stage
104        request.data["name"] = generate_id()
105        request.data.pop("promptstage_set", None)
106        # Validate data, same as a normal edit/create request
107        prompt = PromptSerializer(data=request.data)
108        prompt.is_valid(raise_exception=True)
109        # Convert serializer to prompt instance
110        prompt_model = Prompt(**prompt.validated_data)
111        # Convert to field challenge
112        try:
113            fields = PromptStageView(
114                FlowExecutorView(
115                    plan=FlowPlan(""),
116                    request=request._request,
117                ),
118                request=request._request,
119            ).get_prompt_challenge_fields([prompt_model], {}, dry_run=True)
120        except PropertyMappingExpressionException as exc:
121            return Response(
122                {
123                    "non_field_errors": [
124                        exception_to_string(exc.exc),
125                    ]
126                },
127                status=400,
128            )
129        challenge = PromptChallenge(
130            data={
131                "fields": fields,
132            },
133        )
134        challenge.is_valid()
135        return HttpChallengeResponse(challenge)

Prompt Viewset

queryset = <QuerySet []>
serializer_class = <class 'PromptSerializer'>
ordering = ['field_key']
filterset_fields = ['field_key', 'name', 'label', 'type', 'placeholder']
search_fields = ['field_key', 'name', 'label', 'type', 'placeholder']
@extend_schema(request=PromptSerializer, responses={200: PromptChallenge})
@action(detail=False, methods=['POST'])
def preview( self, request: rest_framework.request.Request) -> rest_framework.response.Response:
 92    @extend_schema(
 93        request=PromptSerializer,
 94        responses={
 95            200: PromptChallenge,
 96        },
 97    )
 98    @action(detail=False, methods=["POST"])
 99    def preview(self, request: Request) -> Response:
100        """Preview a prompt as a challenge, just like a flow would receive"""
101        # Remove a couple things from the request, the serializer will fail on these
102        # when previewing an existing prompt
103        # and since we don't plan to save from this, set a random name and remove the stage
104        request.data["name"] = generate_id()
105        request.data.pop("promptstage_set", None)
106        # Validate data, same as a normal edit/create request
107        prompt = PromptSerializer(data=request.data)
108        prompt.is_valid(raise_exception=True)
109        # Convert serializer to prompt instance
110        prompt_model = Prompt(**prompt.validated_data)
111        # Convert to field challenge
112        try:
113            fields = PromptStageView(
114                FlowExecutorView(
115                    plan=FlowPlan(""),
116                    request=request._request,
117                ),
118                request=request._request,
119            ).get_prompt_challenge_fields([prompt_model], {}, dry_run=True)
120        except PropertyMappingExpressionException as exc:
121            return Response(
122                {
123                    "non_field_errors": [
124                        exception_to_string(exc.exc),
125                    ]
126                },
127                status=400,
128            )
129        challenge = PromptChallenge(
130            data={
131                "fields": fields,
132            },
133        )
134        challenge.is_valid()
135        return HttpChallengeResponse(challenge)

Preview a prompt as a challenge, just like a flow would receive

name = None
description = None
suffix = None
detail = None
basename = None