authentik.stages.prompt.tests

Prompt tests

  1"""Prompt tests"""
  2
  3from unittest.mock import MagicMock, patch
  4
  5from django.test import RequestFactory
  6from django.urls import reverse
  7from rest_framework.exceptions import ErrorDetail, ValidationError
  8
  9from authentik.core.tests.utils import create_test_admin_user, create_test_flow
 10from authentik.flows.markers import StageMarker
 11from authentik.flows.models import FlowStageBinding
 12from authentik.flows.planner import FlowPlan
 13from authentik.flows.tests import FlowTestCase
 14from authentik.flows.views.executor import SESSION_KEY_PLAN, FlowExecutorView
 15from authentik.lib.generators import generate_id
 16from authentik.policies.expression.models import ExpressionPolicy
 17from authentik.stages.prompt.models import FieldTypes, InlineFileField, Prompt, PromptStage
 18from authentik.stages.prompt.stage import (
 19    PLAN_CONTEXT_PROMPT,
 20    PromptChallengeResponse,
 21    PromptStageView,
 22)
 23
 24
 25class TestPromptStage(FlowTestCase):
 26    """Prompt tests"""
 27
 28    def setUp(self):
 29        super().setUp()
 30        self.user = create_test_admin_user()
 31        self.factory = RequestFactory()
 32        self.flow = create_test_flow()
 33        username_prompt = Prompt.objects.create(
 34            name=generate_id(),
 35            field_key="username_prompt",
 36            label="USERNAME_LABEL",
 37            type=FieldTypes.USERNAME,
 38            required=True,
 39            placeholder="USERNAME_PLACEHOLDER",
 40            initial_value="akuser",
 41        )
 42        text_prompt = Prompt.objects.create(
 43            name=generate_id(),
 44            field_key="text_prompt",
 45            label="TEXT_LABEL",
 46            type=FieldTypes.TEXT,
 47            required=True,
 48            placeholder="TEXT_PLACEHOLDER",
 49            initial_value="some text",
 50        )
 51        text_area_prompt = Prompt.objects.create(
 52            name=generate_id(),
 53            field_key="text_area_prompt",
 54            label="TEXT_AREA_LABEL",
 55            type=FieldTypes.TEXT_AREA,
 56            required=True,
 57            placeholder="TEXT_AREA_PLACEHOLDER",
 58            initial_value="some text",
 59        )
 60        email_prompt = Prompt.objects.create(
 61            name=generate_id(),
 62            field_key="email_prompt",
 63            label="EMAIL_LABEL",
 64            type=FieldTypes.EMAIL,
 65            required=True,
 66            placeholder="EMAIL_PLACEHOLDER",
 67            initial_value="email@example.com",
 68        )
 69        password_prompt = Prompt.objects.create(
 70            name=generate_id(),
 71            field_key="password_prompt",
 72            label="PASSWORD_LABEL",
 73            type=FieldTypes.PASSWORD,
 74            required=True,
 75            placeholder="PASSWORD_PLACEHOLDER",
 76            initial_value="supersecurepassword",
 77        )
 78        password2_prompt = Prompt.objects.create(
 79            name=generate_id(),
 80            field_key="password2_prompt",
 81            label="PASSWORD_LABEL",
 82            type=FieldTypes.PASSWORD,
 83            required=True,
 84            placeholder="PASSWORD_PLACEHOLDER",
 85            initial_value="supersecurepassword",
 86        )
 87        number_prompt = Prompt.objects.create(
 88            name=generate_id(),
 89            field_key="number_prompt",
 90            label="NUMBER_LABEL",
 91            type=FieldTypes.NUMBER,
 92            required=True,
 93            placeholder="NUMBER_PLACEHOLDER",
 94            initial_value="42",
 95        )
 96        hidden_prompt = Prompt.objects.create(
 97            name=generate_id(),
 98            field_key="hidden_prompt",
 99            type=FieldTypes.HIDDEN,
100            required=True,
101            placeholder="HIDDEN_PLACEHOLDER",
102            initial_value="something idk",
103        )
104        static_prompt = Prompt.objects.create(
105            name=generate_id(),
106            field_key="static_prompt",
107            type=FieldTypes.STATIC,
108            required=True,
109            placeholder="static",
110            initial_value="something idk",
111        )
112        radio_button_group = Prompt.objects.create(
113            name=generate_id(),
114            field_key="radio_button_group",
115            type=FieldTypes.RADIO_BUTTON_GROUP,
116            required=True,
117            placeholder="test",
118            initial_value="test",
119        )
120        dropdown = Prompt.objects.create(
121            name=generate_id(),
122            field_key="dropdown",
123            type=FieldTypes.DROPDOWN,
124            required=True,
125        )
126        self.stage = PromptStage.objects.create(name="prompt-stage")
127        self.stage.fields.set(
128            [
129                username_prompt,
130                text_prompt,
131                email_prompt,
132                password_prompt,
133                password2_prompt,
134                number_prompt,
135                hidden_prompt,
136                static_prompt,
137                radio_button_group,
138                dropdown,
139            ]
140        )
141
142        self.prompt_data = {
143            username_prompt.field_key: "test-username",
144            text_prompt.field_key: "test-input",
145            text_area_prompt.field_key: "test-area-input",
146            email_prompt.field_key: "test@test.test",
147            password_prompt.field_key: "test",
148            password2_prompt.field_key: "test",
149            number_prompt.field_key: 3,
150            hidden_prompt.field_key: hidden_prompt.initial_value,
151            static_prompt.field_key: static_prompt.initial_value,
152            radio_button_group.field_key: radio_button_group.initial_value,
153            dropdown.field_key: "",
154        }
155
156        self.binding = FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2)
157
158        self.request = RequestFactory().get("/")
159        self.request.user = create_test_admin_user()
160        self.flow_executor = FlowExecutorView(request=self.request)
161        self.stage_view = PromptStageView(self.flow_executor, request=self.request)
162
163    def test_inline_file_field(self):
164        """test InlineFileField"""
165        with self.assertRaises(ValidationError):
166            InlineFileField().to_internal_value("foo")
167        with self.assertRaises(ValidationError):
168            InlineFileField().to_internal_value("data:foo/bar;foo,qwer")
169        self.assertEqual(
170            InlineFileField().to_internal_value("data:mine/type;base64,Zm9v"),
171            "data:mine/type;base64,Zm9v",
172        )
173        self.assertEqual(
174            InlineFileField().to_internal_value("data:mine/type;base64,Zm9vqwer"),
175            "data:mine/type;base64,Zm9vqwer",
176        )
177
178    def test_render(self):
179        """Test render of form, check if all prompts are rendered correctly"""
180        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
181        session = self.client.session
182        session[SESSION_KEY_PLAN] = plan
183        session.save()
184
185        response = self.client.get(
186            reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
187        )
188        self.assertEqual(response.status_code, 200)
189        for prompt in self.stage.fields.all():
190            self.assertIn(prompt.field_key, response.content.decode())
191            self.assertIn(prompt.label, response.content.decode())
192            self.assertIn(prompt.placeholder, response.content.decode())
193
194    def test_valid_challenge_with_policy(self):
195        """Test challenge_response validation"""
196        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
197        expr = (
198            "return request.context['prompt_data']['password_prompt'] "
199            "== request.context['prompt_data']['password2_prompt']"
200        )
201        expr_policy = ExpressionPolicy.objects.create(name="validate-form", expression=expr)
202        self.stage.validation_policies.set([expr_policy])
203        self.stage.save()
204        challenge_response = PromptChallengeResponse(
205            None,
206            stage_instance=self.stage,
207            plan=plan,
208            data=self.prompt_data,
209            stage=self.stage_view,
210        )
211        self.assertEqual(challenge_response.is_valid(), True)
212
213    def test_invalid_challenge(self):
214        """Test challenge_response validation"""
215        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
216        expr = "False"
217        expr_policy = ExpressionPolicy.objects.create(name=generate_id(), expression=expr)
218        self.stage.validation_policies.set([expr_policy])
219        self.stage.save()
220        challenge_response = PromptChallengeResponse(
221            None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
222        )
223        self.assertEqual(challenge_response.is_valid(), False)
224
225    def test_invalid_challenge_multiple(self):
226        """Test challenge_response validation (multiple policies)"""
227        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
228        expr_policy1 = ExpressionPolicy.objects.create(name=generate_id(), expression="False")
229        expr_policy2 = ExpressionPolicy.objects.create(name=generate_id(), expression="False")
230        self.stage.validation_policies.set([expr_policy1, expr_policy2])
231        self.stage.save()
232        challenge_response = PromptChallengeResponse(
233            None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
234        )
235        self.assertEqual(challenge_response.is_valid(), False)
236
237    def test_valid_challenge_request(self):
238        """Test a request with valid challenge_response data"""
239        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
240        session = self.client.session
241        session[SESSION_KEY_PLAN] = plan
242        session.save()
243
244        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
245        expr = (
246            "return request.context['prompt_data']['password_prompt'] "
247            "== request.context['prompt_data']['password2_prompt']"
248        )
249        expr_policy = ExpressionPolicy.objects.create(name=generate_id(), expression=expr)
250        self.stage.validation_policies.set([expr_policy])
251        self.stage.save()
252        challenge_response = PromptChallengeResponse(
253            None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
254        )
255        self.assertEqual(challenge_response.is_valid(), True)
256
257        with patch("authentik.flows.views.executor.FlowExecutorView.cancel", MagicMock()):
258            response = self.client.post(
259                reverse(
260                    "authentik_api:flow-executor",
261                    kwargs={"flow_slug": self.flow.slug},
262                ),
263                challenge_response.validated_data,
264            )
265        self.assertEqual(response.status_code, 200)
266        self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
267
268        # Check that valid data has been saved
269        session = self.client.session
270        plan: FlowPlan = session[SESSION_KEY_PLAN]
271        data = plan.context[PLAN_CONTEXT_PROMPT]
272        for prompt in self.stage.fields.all():
273            prompt: Prompt
274            self.assertEqual(data[prompt.field_key], self.prompt_data[prompt.field_key])
275
276    def test_invalid_password(self):
277        """Test challenge_response validation"""
278        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
279        self.prompt_data["password2_prompt"] = "qwerqwerqr"
280        challenge_response = PromptChallengeResponse(
281            None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
282        )
283        self.assertEqual(challenge_response.is_valid(), False)
284        self.assertEqual(
285            challenge_response.errors,
286            {"non_field_errors": [ErrorDetail(string="Passwords don't match.", code="invalid")]},
287        )
288
289    def test_invalid_username(self):
290        """Test challenge_response validation"""
291        user = create_test_admin_user()
292        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
293        self.prompt_data["username_prompt"] = user.username
294        challenge_response = PromptChallengeResponse(
295            None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
296        )
297        self.assertEqual(challenge_response.is_valid(), False)
298        self.assertEqual(
299            challenge_response.errors,
300            {"username_prompt": [ErrorDetail(string="Username is already taken.", code="invalid")]},
301        )
302
303    def test_invalid_choice_field(self):
304        """Test invalid choice field value"""
305        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
306        self.prompt_data["radio_button_group"] = "some invalid choice"
307        self.prompt_data["dropdown"] = "another invalid choice"
308        challenge_response = PromptChallengeResponse(
309            None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
310        )
311        self.assertEqual(challenge_response.is_valid(), False)
312        self.assertEqual(
313            challenge_response.errors,
314            {
315                "radio_button_group": [
316                    ErrorDetail(
317                        string=f"\"{self.prompt_data['radio_button_group']}\" "
318                        "is not a valid choice.",
319                        code="invalid_choice",
320                    )
321                ],
322                "dropdown": [
323                    ErrorDetail(
324                        string=f"\"{self.prompt_data['dropdown']}\" is not a valid choice.",
325                        code="invalid_choice",
326                    )
327                ],
328            },
329        )
330
331    def test_static_hidden_overwrite(self):
332        """Test that static and hidden fields ignore any value sent to them"""
333        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
334        plan.context[PLAN_CONTEXT_PROMPT] = {"hidden_prompt": "hidden"}
335        self.prompt_data["hidden_prompt"] = "foo"
336        self.prompt_data["static_prompt"] = "foo"
337        challenge_response = PromptChallengeResponse(
338            None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
339        )
340        self.assertEqual(challenge_response.is_valid(), True)
341        self.assertNotEqual(challenge_response.validated_data["hidden_prompt"], "foo")
342        self.assertEqual(challenge_response.validated_data["hidden_prompt"], "hidden")
343        self.assertNotEqual(challenge_response.validated_data["static_prompt"], "foo")
344
345    def test_prompt_placeholder(self):
346        """Test placeholder and expression"""
347        context = {
348            "foo": generate_id(),
349        }
350        prompt: Prompt = Prompt(
351            field_key="text_prompt_expression",
352            label="TEXT_LABEL",
353            type=FieldTypes.TEXT,
354            placeholder="return prompt_context['foo']",
355            placeholder_expression=True,
356        )
357        self.assertEqual(
358            prompt.get_placeholder(context, self.user, self.factory.get("/")), context["foo"]
359        )
360
361    def test_prompt_placeholder_does_not_take_value_from_context(self):
362        """Test placeholder does not automatically take value from context"""
363        context = {
364            "foo": generate_id(),
365        }
366        prompt: Prompt = Prompt(
367            field_key="text_prompt_expression",
368            label="TEXT_LABEL",
369            type=FieldTypes.TEXT,
370            placeholder="return prompt_context['foo']",
371            placeholder_expression=True,
372        )
373        context["text_prompt_expression"] = generate_id()
374
375        self.assertEqual(
376            prompt.get_placeholder(context, self.user, self.factory.get("/")), context["foo"]
377        )
378
379    def test_prompt_initial_value(self):
380        """Test initial_value and expression"""
381        context = {
382            "foo": generate_id(),
383        }
384        prompt: Prompt = Prompt(
385            field_key="text_prompt_expression",
386            label="TEXT_LABEL",
387            type=FieldTypes.TEXT,
388            initial_value="return prompt_context['foo']",
389            initial_value_expression=True,
390        )
391        self.assertEqual(
392            prompt.get_initial_value(context, self.user, self.factory.get("/")), context["foo"]
393        )
394        context["text_prompt_expression"] = generate_id()
395        self.assertEqual(
396            prompt.get_initial_value(context, self.user, self.factory.get("/")),
397            context["text_prompt_expression"],
398        )
399        self.assertNotEqual(
400            prompt.get_initial_value(context, self.user, self.factory.get("/")), context["foo"]
401        )
402
403    def test_choice_prompts_placeholder_and_initial_value_no_choices(self):
404        """Test placeholder and initial value of choice fields with 0 choices"""
405        context = {}
406
407        prompt: Prompt = Prompt(
408            field_key="fixed_choice_prompt_expression",
409            label="LABEL",
410            type=FieldTypes.RADIO_BUTTON_GROUP,
411            placeholder="return []",
412            placeholder_expression=True,
413            initial_value="Invalid choice",
414            initial_value_expression=False,
415        )
416        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
417        self.assertEqual(prompt.get_initial_value(context, self.user, self.factory.get("/")), "")
418        self.assertEqual(prompt.get_choices(context, self.user, self.factory.get("/")), tuple())
419
420    def test_choice_prompts_placeholder_and_initial_value_single_choice(self):
421        """Test placeholder and initial value of choice fields with 1 choice"""
422        context = {"foo": generate_id()}
423
424        prompt: Prompt = Prompt(
425            field_key="fixed_choice_prompt_expression",
426            label="LABEL",
427            type=FieldTypes.DROPDOWN,
428            placeholder=context["foo"],
429            placeholder_expression=False,
430            initial_value=context["foo"],
431            initial_value_expression=False,
432        )
433        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
434        self.assertEqual(
435            prompt.get_initial_value(context, self.user, self.factory.get("/")), context["foo"]
436        )
437        self.assertEqual(
438            prompt.get_choices(context, self.user, self.factory.get("/")), (context["foo"],)
439        )
440
441        prompt: Prompt = Prompt(
442            field_key="fixed_choice_prompt_expression",
443            label="LABEL",
444            type=FieldTypes.DROPDOWN,
445            placeholder="return [prompt_context['foo']]",
446            placeholder_expression=True,
447            initial_value="return prompt_context['foo']",
448            initial_value_expression=True,
449        )
450        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
451        self.assertEqual(
452            prompt.get_initial_value(context, self.user, self.factory.get("/")), context["foo"]
453        )
454        self.assertEqual(
455            prompt.get_choices(context, self.user, self.factory.get("/")), (context["foo"],)
456        )
457
458    def test_choice_prompts_placeholder_and_initial_value_single_choice_dict(self):
459        """Test placeholder and initial value of dict choice fields with 1 choice"""
460        context = {"foo": {"label": "foo", "value": generate_id()}}
461
462        prompt: Prompt = Prompt(
463            field_key="fixed_choice_prompt_expression",
464            label="LABEL",
465            type=FieldTypes.DROPDOWN,
466            placeholder=context["foo"],
467            placeholder_expression=False,
468            initial_value=context["foo"]["value"],
469            initial_value_expression=False,
470        )
471        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
472        self.assertEqual(
473            prompt.get_initial_value(context, self.user, self.factory.get("/")),
474            context["foo"]["value"],
475        )
476        self.assertEqual(
477            prompt.get_choices(context, self.user, self.factory.get("/")), (context["foo"],)
478        )
479
480        prompt: Prompt = Prompt(
481            field_key="fixed_choice_prompt_expression",
482            label="LABEL",
483            type=FieldTypes.DROPDOWN,
484            placeholder="return [prompt_context['foo']]",
485            placeholder_expression=True,
486            initial_value="return prompt_context['foo']['value']",
487            initial_value_expression=True,
488        )
489        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
490        self.assertEqual(
491            prompt.get_initial_value(context, self.user, self.factory.get("/")),
492            context["foo"]["value"],
493        )
494        self.assertEqual(
495            prompt.get_choices(context, self.user, self.factory.get("/")), (context["foo"],)
496        )
497
498    def test_choice_prompts_placeholder_and_initial_value_multiple_choices(self):
499        """Test placeholder and initial value of choice fields with multiple choices"""
500        context = {}
501
502        prompt: Prompt = Prompt(
503            field_key="fixed_choice_prompt_expression",
504            label="LABEL",
505            type=FieldTypes.RADIO_BUTTON_GROUP,
506            placeholder="return ['test', True, 42]",
507            placeholder_expression=True,
508        )
509        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
510        self.assertEqual(
511            prompt.get_initial_value(context, self.user, self.factory.get("/")), "test"
512        )
513        self.assertEqual(
514            prompt.get_choices(context, self.user, self.factory.get("/")), ("test", True, 42)
515        )
516
517        prompt: Prompt = Prompt(
518            field_key="fixed_choice_prompt_expression",
519            label="LABEL",
520            type=FieldTypes.RADIO_BUTTON_GROUP,
521            placeholder="return ['test', True, 42]",
522            placeholder_expression=True,
523            initial_value="return True",
524            initial_value_expression=True,
525        )
526        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
527        self.assertEqual(prompt.get_initial_value(context, self.user, self.factory.get("/")), True)
528        self.assertEqual(
529            prompt.get_choices(context, self.user, self.factory.get("/")), ("test", True, 42)
530        )
531
532    def test_choice_prompts_placeholder_and_initial_value_multiple_choices_dict(self):
533        """Test placeholder and initial value of dict choice fields with multiple dict choices"""
534        context = {}
535
536        prompt: Prompt = Prompt(
537            field_key="fixed_choice_prompt_expression",
538            label="LABEL",
539            type=FieldTypes.RADIO_BUTTON_GROUP,
540            placeholder="return ["
541            "{'label': 'Option 1', 'value': 'value1'},"
542            "{'label': 'Option 2', 'value': 'value2'}"
543            "]",
544            placeholder_expression=True,
545        )
546        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
547        self.assertEqual(
548            prompt.get_initial_value(context, self.user, self.factory.get("/")).get("value"),
549            "value1",
550        )
551        self.assertEqual(
552            tuple(
553                (choice["label"], choice["value"])
554                for choice in prompt.get_choices(context, self.user, self.factory.get("/"))
555            ),
556            (("Option 1", "value1"), ("Option 2", "value2")),
557        )
558
559        prompt: Prompt = Prompt(
560            field_key="fixed_choice_prompt_expression",
561            label="LABEL",
562            type=FieldTypes.RADIO_BUTTON_GROUP,
563            placeholder="return ["
564            "{'label': 'Option 1', 'value': 'value1'},"
565            "{'label': 'Option 2', 'value': 'value2'}"
566            "]",
567            placeholder_expression=True,
568            initial_value="return 'value2'",
569            initial_value_expression=True,
570        )
571        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
572        self.assertEqual(
573            prompt.get_initial_value(context, self.user, self.factory.get("/")), "value2"
574        )
575        self.assertEqual(
576            tuple(
577                (choice["label"], choice["value"])
578                for choice in prompt.get_choices(context, self.user, self.factory.get("/"))
579            ),
580            (("Option 1", "value1"), ("Option 2", "value2")),
581        )
582
583    def test_choice_prompts_placeholder_and_initial_value_from_context(self):
584        """Test placeholder and initial value of choice fields with values from context"""
585        rand_value = generate_id()
586        context = {
587            "fixed_choice_prompt_expression": rand_value,
588            "fixed_choice_prompt_expression__choices": ["test", 42, rand_value],
589        }
590
591        prompt: Prompt = Prompt(
592            field_key="fixed_choice_prompt_expression",
593            label="LABEL",
594            type=FieldTypes.RADIO_BUTTON_GROUP,
595        )
596        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
597        self.assertEqual(
598            prompt.get_initial_value(context, self.user, self.factory.get("/")), rand_value
599        )
600        self.assertEqual(
601            prompt.get_choices(context, self.user, self.factory.get("/")), ("test", 42, rand_value)
602        )
603
604    def test_initial_value_not_valid_choice(self):
605        """Test initial_value not a valid choice"""
606        context = {}
607        prompt: Prompt = Prompt(
608            field_key="choice_prompt",
609            label="TEXT_LABEL",
610            type=FieldTypes.DROPDOWN,
611            placeholder="choice",
612            initial_value="another_choice",
613        )
614        self.assertEqual(
615            prompt.get_choices(context, self.user, self.factory.get("/")),
616            ("choice",),
617        )
618        self.assertEqual(
619            prompt.get_initial_value(context, self.user, self.factory.get("/")),
620            "choice",
621        )
622
623    def test_choices_are_none_for_non_choice_fields(self):
624        """Test choices are None for non choice fields"""
625        context = {}
626        prompt: Prompt = Prompt(
627            field_key="text_prompt_expression",
628            label="TEXT_LABEL",
629            type=FieldTypes.TEXT,
630            placeholder="choice",
631        )
632        self.assertEqual(
633            prompt.get_choices(context, self.user, self.factory.get("/")),
634            None,
635        )
636
637    def test_prompt_placeholder_error(self):
638        """Test placeholder and expression"""
639        context = {}
640        prompt: Prompt = Prompt(
641            field_key="text_prompt_expression",
642            label="TEXT_LABEL",
643            type=FieldTypes.TEXT,
644            placeholder="something invalid dunno",
645            placeholder_expression=True,
646        )
647        self.assertEqual(
648            prompt.get_placeholder(context, self.user, self.factory.get("/")),
649            "something invalid dunno",
650        )
651
652    def test_prompt_placeholder_disabled(self):
653        """Test placeholder and expression"""
654        context = {}
655        prompt: Prompt = Prompt(
656            field_key="text_prompt_expression",
657            label="TEXT_LABEL",
658            type=FieldTypes.TEXT,
659            placeholder="return prompt_context['foo']",
660            placeholder_expression=False,
661        )
662        self.assertEqual(
663            prompt.get_placeholder(context, self.user, self.factory.get("/")), prompt.placeholder
664        )
665
666    def test_invalid_save(self):
667        """Ensure field can't be saved with invalid type"""
668        prompt: Prompt = Prompt(
669            field_key="text_prompt_expression",
670            label="TEXT_LABEL",
671            type="foo",
672            placeholder="foo",
673            placeholder_expression=False,
674            sub_text="test",
675            order=123,
676        )
677        with self.assertRaises(ValueError):
678            prompt.save()
679
680    def test_api_preview(self):
681        """Test API preview"""
682        self.client.force_login(self.user)
683        response = self.client.post(
684            reverse("authentik_api:prompt-preview"),
685            data={
686                "field_key": "text_prompt_expression",
687                "label": "TEXT_LABEL",
688                "type": FieldTypes.TEXT,
689                "placeholder": 'return "Hello world"',
690                "placeholder_expression": True,
691                "initial_value": 'return "Hello Hello world"',
692                "initial_value_expression": True,
693                "sub_text": "test",
694                "order": 123,
695            },
696        )
697        self.assertEqual(response.status_code, 200)
698        self.assertJSONEqual(
699            response.content.decode(),
700            {
701                "component": "ak-stage-prompt",
702                "fields": [
703                    {
704                        "field_key": "text_prompt_expression",
705                        "label": "TEXT_LABEL",
706                        "type": "text",
707                        "required": True,
708                        "placeholder": "Hello world",
709                        "initial_value": "Hello Hello world",
710                        "order": 123,
711                        "sub_text": "test",
712                        "choices": None,
713                    }
714                ],
715            },
716        )
717
718    def test_api_preview_invalid_expression(self):
719        """Test API preview"""
720        self.client.force_login(self.user)
721        response = self.client.post(
722            reverse("authentik_api:prompt-preview"),
723            data={
724                "field_key": "text_prompt_expression",
725                "label": "TEXT_LABEL",
726                "type": FieldTypes.TEXT,
727                "placeholder": "return [",
728                "placeholder_expression": True,
729                "sub_text": "test",
730                "order": 123,
731            },
732        )
733        self.assertEqual(response.status_code, 400)
734        self.assertIn("non_field_errors", response.content.decode())
735
736
737def field_type_tester_factory(field_type: FieldTypes, required: bool):
738    """Test field for field_type"""
739
740    def tester(self: TestPromptStage):
741        prompt: Prompt = Prompt(
742            field_key="text_prompt_expression",
743            label="TEXT_LABEL",
744            type=field_type,
745            placeholder="foo",
746            placeholder_expression=False,
747            sub_text="test",
748            order=123,
749            required=required,
750        )
751        self.assertIsNotNone(prompt.field("foo"))
752
753    return tester
754
755
756for _required in (True, False):
757    for _type in FieldTypes:
758        test_name = f"test_field_type_{_type}"
759        if _required:
760            test_name += "_required"
761        setattr(TestPromptStage, test_name, field_type_tester_factory(_type, _required))
class TestPromptStage(authentik.flows.tests.FlowTestCase):
 26class TestPromptStage(FlowTestCase):
 27    """Prompt tests"""
 28
 29    def setUp(self):
 30        super().setUp()
 31        self.user = create_test_admin_user()
 32        self.factory = RequestFactory()
 33        self.flow = create_test_flow()
 34        username_prompt = Prompt.objects.create(
 35            name=generate_id(),
 36            field_key="username_prompt",
 37            label="USERNAME_LABEL",
 38            type=FieldTypes.USERNAME,
 39            required=True,
 40            placeholder="USERNAME_PLACEHOLDER",
 41            initial_value="akuser",
 42        )
 43        text_prompt = Prompt.objects.create(
 44            name=generate_id(),
 45            field_key="text_prompt",
 46            label="TEXT_LABEL",
 47            type=FieldTypes.TEXT,
 48            required=True,
 49            placeholder="TEXT_PLACEHOLDER",
 50            initial_value="some text",
 51        )
 52        text_area_prompt = Prompt.objects.create(
 53            name=generate_id(),
 54            field_key="text_area_prompt",
 55            label="TEXT_AREA_LABEL",
 56            type=FieldTypes.TEXT_AREA,
 57            required=True,
 58            placeholder="TEXT_AREA_PLACEHOLDER",
 59            initial_value="some text",
 60        )
 61        email_prompt = Prompt.objects.create(
 62            name=generate_id(),
 63            field_key="email_prompt",
 64            label="EMAIL_LABEL",
 65            type=FieldTypes.EMAIL,
 66            required=True,
 67            placeholder="EMAIL_PLACEHOLDER",
 68            initial_value="email@example.com",
 69        )
 70        password_prompt = Prompt.objects.create(
 71            name=generate_id(),
 72            field_key="password_prompt",
 73            label="PASSWORD_LABEL",
 74            type=FieldTypes.PASSWORD,
 75            required=True,
 76            placeholder="PASSWORD_PLACEHOLDER",
 77            initial_value="supersecurepassword",
 78        )
 79        password2_prompt = Prompt.objects.create(
 80            name=generate_id(),
 81            field_key="password2_prompt",
 82            label="PASSWORD_LABEL",
 83            type=FieldTypes.PASSWORD,
 84            required=True,
 85            placeholder="PASSWORD_PLACEHOLDER",
 86            initial_value="supersecurepassword",
 87        )
 88        number_prompt = Prompt.objects.create(
 89            name=generate_id(),
 90            field_key="number_prompt",
 91            label="NUMBER_LABEL",
 92            type=FieldTypes.NUMBER,
 93            required=True,
 94            placeholder="NUMBER_PLACEHOLDER",
 95            initial_value="42",
 96        )
 97        hidden_prompt = Prompt.objects.create(
 98            name=generate_id(),
 99            field_key="hidden_prompt",
100            type=FieldTypes.HIDDEN,
101            required=True,
102            placeholder="HIDDEN_PLACEHOLDER",
103            initial_value="something idk",
104        )
105        static_prompt = Prompt.objects.create(
106            name=generate_id(),
107            field_key="static_prompt",
108            type=FieldTypes.STATIC,
109            required=True,
110            placeholder="static",
111            initial_value="something idk",
112        )
113        radio_button_group = Prompt.objects.create(
114            name=generate_id(),
115            field_key="radio_button_group",
116            type=FieldTypes.RADIO_BUTTON_GROUP,
117            required=True,
118            placeholder="test",
119            initial_value="test",
120        )
121        dropdown = Prompt.objects.create(
122            name=generate_id(),
123            field_key="dropdown",
124            type=FieldTypes.DROPDOWN,
125            required=True,
126        )
127        self.stage = PromptStage.objects.create(name="prompt-stage")
128        self.stage.fields.set(
129            [
130                username_prompt,
131                text_prompt,
132                email_prompt,
133                password_prompt,
134                password2_prompt,
135                number_prompt,
136                hidden_prompt,
137                static_prompt,
138                radio_button_group,
139                dropdown,
140            ]
141        )
142
143        self.prompt_data = {
144            username_prompt.field_key: "test-username",
145            text_prompt.field_key: "test-input",
146            text_area_prompt.field_key: "test-area-input",
147            email_prompt.field_key: "test@test.test",
148            password_prompt.field_key: "test",
149            password2_prompt.field_key: "test",
150            number_prompt.field_key: 3,
151            hidden_prompt.field_key: hidden_prompt.initial_value,
152            static_prompt.field_key: static_prompt.initial_value,
153            radio_button_group.field_key: radio_button_group.initial_value,
154            dropdown.field_key: "",
155        }
156
157        self.binding = FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2)
158
159        self.request = RequestFactory().get("/")
160        self.request.user = create_test_admin_user()
161        self.flow_executor = FlowExecutorView(request=self.request)
162        self.stage_view = PromptStageView(self.flow_executor, request=self.request)
163
164    def test_inline_file_field(self):
165        """test InlineFileField"""
166        with self.assertRaises(ValidationError):
167            InlineFileField().to_internal_value("foo")
168        with self.assertRaises(ValidationError):
169            InlineFileField().to_internal_value("data:foo/bar;foo,qwer")
170        self.assertEqual(
171            InlineFileField().to_internal_value("data:mine/type;base64,Zm9v"),
172            "data:mine/type;base64,Zm9v",
173        )
174        self.assertEqual(
175            InlineFileField().to_internal_value("data:mine/type;base64,Zm9vqwer"),
176            "data:mine/type;base64,Zm9vqwer",
177        )
178
179    def test_render(self):
180        """Test render of form, check if all prompts are rendered correctly"""
181        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
182        session = self.client.session
183        session[SESSION_KEY_PLAN] = plan
184        session.save()
185
186        response = self.client.get(
187            reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
188        )
189        self.assertEqual(response.status_code, 200)
190        for prompt in self.stage.fields.all():
191            self.assertIn(prompt.field_key, response.content.decode())
192            self.assertIn(prompt.label, response.content.decode())
193            self.assertIn(prompt.placeholder, response.content.decode())
194
195    def test_valid_challenge_with_policy(self):
196        """Test challenge_response validation"""
197        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
198        expr = (
199            "return request.context['prompt_data']['password_prompt'] "
200            "== request.context['prompt_data']['password2_prompt']"
201        )
202        expr_policy = ExpressionPolicy.objects.create(name="validate-form", expression=expr)
203        self.stage.validation_policies.set([expr_policy])
204        self.stage.save()
205        challenge_response = PromptChallengeResponse(
206            None,
207            stage_instance=self.stage,
208            plan=plan,
209            data=self.prompt_data,
210            stage=self.stage_view,
211        )
212        self.assertEqual(challenge_response.is_valid(), True)
213
214    def test_invalid_challenge(self):
215        """Test challenge_response validation"""
216        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
217        expr = "False"
218        expr_policy = ExpressionPolicy.objects.create(name=generate_id(), expression=expr)
219        self.stage.validation_policies.set([expr_policy])
220        self.stage.save()
221        challenge_response = PromptChallengeResponse(
222            None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
223        )
224        self.assertEqual(challenge_response.is_valid(), False)
225
226    def test_invalid_challenge_multiple(self):
227        """Test challenge_response validation (multiple policies)"""
228        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
229        expr_policy1 = ExpressionPolicy.objects.create(name=generate_id(), expression="False")
230        expr_policy2 = ExpressionPolicy.objects.create(name=generate_id(), expression="False")
231        self.stage.validation_policies.set([expr_policy1, expr_policy2])
232        self.stage.save()
233        challenge_response = PromptChallengeResponse(
234            None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
235        )
236        self.assertEqual(challenge_response.is_valid(), False)
237
238    def test_valid_challenge_request(self):
239        """Test a request with valid challenge_response data"""
240        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
241        session = self.client.session
242        session[SESSION_KEY_PLAN] = plan
243        session.save()
244
245        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
246        expr = (
247            "return request.context['prompt_data']['password_prompt'] "
248            "== request.context['prompt_data']['password2_prompt']"
249        )
250        expr_policy = ExpressionPolicy.objects.create(name=generate_id(), expression=expr)
251        self.stage.validation_policies.set([expr_policy])
252        self.stage.save()
253        challenge_response = PromptChallengeResponse(
254            None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
255        )
256        self.assertEqual(challenge_response.is_valid(), True)
257
258        with patch("authentik.flows.views.executor.FlowExecutorView.cancel", MagicMock()):
259            response = self.client.post(
260                reverse(
261                    "authentik_api:flow-executor",
262                    kwargs={"flow_slug": self.flow.slug},
263                ),
264                challenge_response.validated_data,
265            )
266        self.assertEqual(response.status_code, 200)
267        self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
268
269        # Check that valid data has been saved
270        session = self.client.session
271        plan: FlowPlan = session[SESSION_KEY_PLAN]
272        data = plan.context[PLAN_CONTEXT_PROMPT]
273        for prompt in self.stage.fields.all():
274            prompt: Prompt
275            self.assertEqual(data[prompt.field_key], self.prompt_data[prompt.field_key])
276
277    def test_invalid_password(self):
278        """Test challenge_response validation"""
279        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
280        self.prompt_data["password2_prompt"] = "qwerqwerqr"
281        challenge_response = PromptChallengeResponse(
282            None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
283        )
284        self.assertEqual(challenge_response.is_valid(), False)
285        self.assertEqual(
286            challenge_response.errors,
287            {"non_field_errors": [ErrorDetail(string="Passwords don't match.", code="invalid")]},
288        )
289
290    def test_invalid_username(self):
291        """Test challenge_response validation"""
292        user = create_test_admin_user()
293        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
294        self.prompt_data["username_prompt"] = user.username
295        challenge_response = PromptChallengeResponse(
296            None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
297        )
298        self.assertEqual(challenge_response.is_valid(), False)
299        self.assertEqual(
300            challenge_response.errors,
301            {"username_prompt": [ErrorDetail(string="Username is already taken.", code="invalid")]},
302        )
303
304    def test_invalid_choice_field(self):
305        """Test invalid choice field value"""
306        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
307        self.prompt_data["radio_button_group"] = "some invalid choice"
308        self.prompt_data["dropdown"] = "another invalid choice"
309        challenge_response = PromptChallengeResponse(
310            None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
311        )
312        self.assertEqual(challenge_response.is_valid(), False)
313        self.assertEqual(
314            challenge_response.errors,
315            {
316                "radio_button_group": [
317                    ErrorDetail(
318                        string=f"\"{self.prompt_data['radio_button_group']}\" "
319                        "is not a valid choice.",
320                        code="invalid_choice",
321                    )
322                ],
323                "dropdown": [
324                    ErrorDetail(
325                        string=f"\"{self.prompt_data['dropdown']}\" is not a valid choice.",
326                        code="invalid_choice",
327                    )
328                ],
329            },
330        )
331
332    def test_static_hidden_overwrite(self):
333        """Test that static and hidden fields ignore any value sent to them"""
334        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
335        plan.context[PLAN_CONTEXT_PROMPT] = {"hidden_prompt": "hidden"}
336        self.prompt_data["hidden_prompt"] = "foo"
337        self.prompt_data["static_prompt"] = "foo"
338        challenge_response = PromptChallengeResponse(
339            None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
340        )
341        self.assertEqual(challenge_response.is_valid(), True)
342        self.assertNotEqual(challenge_response.validated_data["hidden_prompt"], "foo")
343        self.assertEqual(challenge_response.validated_data["hidden_prompt"], "hidden")
344        self.assertNotEqual(challenge_response.validated_data["static_prompt"], "foo")
345
346    def test_prompt_placeholder(self):
347        """Test placeholder and expression"""
348        context = {
349            "foo": generate_id(),
350        }
351        prompt: Prompt = Prompt(
352            field_key="text_prompt_expression",
353            label="TEXT_LABEL",
354            type=FieldTypes.TEXT,
355            placeholder="return prompt_context['foo']",
356            placeholder_expression=True,
357        )
358        self.assertEqual(
359            prompt.get_placeholder(context, self.user, self.factory.get("/")), context["foo"]
360        )
361
362    def test_prompt_placeholder_does_not_take_value_from_context(self):
363        """Test placeholder does not automatically take value from context"""
364        context = {
365            "foo": generate_id(),
366        }
367        prompt: Prompt = Prompt(
368            field_key="text_prompt_expression",
369            label="TEXT_LABEL",
370            type=FieldTypes.TEXT,
371            placeholder="return prompt_context['foo']",
372            placeholder_expression=True,
373        )
374        context["text_prompt_expression"] = generate_id()
375
376        self.assertEqual(
377            prompt.get_placeholder(context, self.user, self.factory.get("/")), context["foo"]
378        )
379
380    def test_prompt_initial_value(self):
381        """Test initial_value and expression"""
382        context = {
383            "foo": generate_id(),
384        }
385        prompt: Prompt = Prompt(
386            field_key="text_prompt_expression",
387            label="TEXT_LABEL",
388            type=FieldTypes.TEXT,
389            initial_value="return prompt_context['foo']",
390            initial_value_expression=True,
391        )
392        self.assertEqual(
393            prompt.get_initial_value(context, self.user, self.factory.get("/")), context["foo"]
394        )
395        context["text_prompt_expression"] = generate_id()
396        self.assertEqual(
397            prompt.get_initial_value(context, self.user, self.factory.get("/")),
398            context["text_prompt_expression"],
399        )
400        self.assertNotEqual(
401            prompt.get_initial_value(context, self.user, self.factory.get("/")), context["foo"]
402        )
403
404    def test_choice_prompts_placeholder_and_initial_value_no_choices(self):
405        """Test placeholder and initial value of choice fields with 0 choices"""
406        context = {}
407
408        prompt: Prompt = Prompt(
409            field_key="fixed_choice_prompt_expression",
410            label="LABEL",
411            type=FieldTypes.RADIO_BUTTON_GROUP,
412            placeholder="return []",
413            placeholder_expression=True,
414            initial_value="Invalid choice",
415            initial_value_expression=False,
416        )
417        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
418        self.assertEqual(prompt.get_initial_value(context, self.user, self.factory.get("/")), "")
419        self.assertEqual(prompt.get_choices(context, self.user, self.factory.get("/")), tuple())
420
421    def test_choice_prompts_placeholder_and_initial_value_single_choice(self):
422        """Test placeholder and initial value of choice fields with 1 choice"""
423        context = {"foo": generate_id()}
424
425        prompt: Prompt = Prompt(
426            field_key="fixed_choice_prompt_expression",
427            label="LABEL",
428            type=FieldTypes.DROPDOWN,
429            placeholder=context["foo"],
430            placeholder_expression=False,
431            initial_value=context["foo"],
432            initial_value_expression=False,
433        )
434        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
435        self.assertEqual(
436            prompt.get_initial_value(context, self.user, self.factory.get("/")), context["foo"]
437        )
438        self.assertEqual(
439            prompt.get_choices(context, self.user, self.factory.get("/")), (context["foo"],)
440        )
441
442        prompt: Prompt = Prompt(
443            field_key="fixed_choice_prompt_expression",
444            label="LABEL",
445            type=FieldTypes.DROPDOWN,
446            placeholder="return [prompt_context['foo']]",
447            placeholder_expression=True,
448            initial_value="return prompt_context['foo']",
449            initial_value_expression=True,
450        )
451        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
452        self.assertEqual(
453            prompt.get_initial_value(context, self.user, self.factory.get("/")), context["foo"]
454        )
455        self.assertEqual(
456            prompt.get_choices(context, self.user, self.factory.get("/")), (context["foo"],)
457        )
458
459    def test_choice_prompts_placeholder_and_initial_value_single_choice_dict(self):
460        """Test placeholder and initial value of dict choice fields with 1 choice"""
461        context = {"foo": {"label": "foo", "value": generate_id()}}
462
463        prompt: Prompt = Prompt(
464            field_key="fixed_choice_prompt_expression",
465            label="LABEL",
466            type=FieldTypes.DROPDOWN,
467            placeholder=context["foo"],
468            placeholder_expression=False,
469            initial_value=context["foo"]["value"],
470            initial_value_expression=False,
471        )
472        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
473        self.assertEqual(
474            prompt.get_initial_value(context, self.user, self.factory.get("/")),
475            context["foo"]["value"],
476        )
477        self.assertEqual(
478            prompt.get_choices(context, self.user, self.factory.get("/")), (context["foo"],)
479        )
480
481        prompt: Prompt = Prompt(
482            field_key="fixed_choice_prompt_expression",
483            label="LABEL",
484            type=FieldTypes.DROPDOWN,
485            placeholder="return [prompt_context['foo']]",
486            placeholder_expression=True,
487            initial_value="return prompt_context['foo']['value']",
488            initial_value_expression=True,
489        )
490        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
491        self.assertEqual(
492            prompt.get_initial_value(context, self.user, self.factory.get("/")),
493            context["foo"]["value"],
494        )
495        self.assertEqual(
496            prompt.get_choices(context, self.user, self.factory.get("/")), (context["foo"],)
497        )
498
499    def test_choice_prompts_placeholder_and_initial_value_multiple_choices(self):
500        """Test placeholder and initial value of choice fields with multiple choices"""
501        context = {}
502
503        prompt: Prompt = Prompt(
504            field_key="fixed_choice_prompt_expression",
505            label="LABEL",
506            type=FieldTypes.RADIO_BUTTON_GROUP,
507            placeholder="return ['test', True, 42]",
508            placeholder_expression=True,
509        )
510        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
511        self.assertEqual(
512            prompt.get_initial_value(context, self.user, self.factory.get("/")), "test"
513        )
514        self.assertEqual(
515            prompt.get_choices(context, self.user, self.factory.get("/")), ("test", True, 42)
516        )
517
518        prompt: Prompt = Prompt(
519            field_key="fixed_choice_prompt_expression",
520            label="LABEL",
521            type=FieldTypes.RADIO_BUTTON_GROUP,
522            placeholder="return ['test', True, 42]",
523            placeholder_expression=True,
524            initial_value="return True",
525            initial_value_expression=True,
526        )
527        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
528        self.assertEqual(prompt.get_initial_value(context, self.user, self.factory.get("/")), True)
529        self.assertEqual(
530            prompt.get_choices(context, self.user, self.factory.get("/")), ("test", True, 42)
531        )
532
533    def test_choice_prompts_placeholder_and_initial_value_multiple_choices_dict(self):
534        """Test placeholder and initial value of dict choice fields with multiple dict choices"""
535        context = {}
536
537        prompt: Prompt = Prompt(
538            field_key="fixed_choice_prompt_expression",
539            label="LABEL",
540            type=FieldTypes.RADIO_BUTTON_GROUP,
541            placeholder="return ["
542            "{'label': 'Option 1', 'value': 'value1'},"
543            "{'label': 'Option 2', 'value': 'value2'}"
544            "]",
545            placeholder_expression=True,
546        )
547        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
548        self.assertEqual(
549            prompt.get_initial_value(context, self.user, self.factory.get("/")).get("value"),
550            "value1",
551        )
552        self.assertEqual(
553            tuple(
554                (choice["label"], choice["value"])
555                for choice in prompt.get_choices(context, self.user, self.factory.get("/"))
556            ),
557            (("Option 1", "value1"), ("Option 2", "value2")),
558        )
559
560        prompt: Prompt = Prompt(
561            field_key="fixed_choice_prompt_expression",
562            label="LABEL",
563            type=FieldTypes.RADIO_BUTTON_GROUP,
564            placeholder="return ["
565            "{'label': 'Option 1', 'value': 'value1'},"
566            "{'label': 'Option 2', 'value': 'value2'}"
567            "]",
568            placeholder_expression=True,
569            initial_value="return 'value2'",
570            initial_value_expression=True,
571        )
572        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
573        self.assertEqual(
574            prompt.get_initial_value(context, self.user, self.factory.get("/")), "value2"
575        )
576        self.assertEqual(
577            tuple(
578                (choice["label"], choice["value"])
579                for choice in prompt.get_choices(context, self.user, self.factory.get("/"))
580            ),
581            (("Option 1", "value1"), ("Option 2", "value2")),
582        )
583
584    def test_choice_prompts_placeholder_and_initial_value_from_context(self):
585        """Test placeholder and initial value of choice fields with values from context"""
586        rand_value = generate_id()
587        context = {
588            "fixed_choice_prompt_expression": rand_value,
589            "fixed_choice_prompt_expression__choices": ["test", 42, rand_value],
590        }
591
592        prompt: Prompt = Prompt(
593            field_key="fixed_choice_prompt_expression",
594            label="LABEL",
595            type=FieldTypes.RADIO_BUTTON_GROUP,
596        )
597        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
598        self.assertEqual(
599            prompt.get_initial_value(context, self.user, self.factory.get("/")), rand_value
600        )
601        self.assertEqual(
602            prompt.get_choices(context, self.user, self.factory.get("/")), ("test", 42, rand_value)
603        )
604
605    def test_initial_value_not_valid_choice(self):
606        """Test initial_value not a valid choice"""
607        context = {}
608        prompt: Prompt = Prompt(
609            field_key="choice_prompt",
610            label="TEXT_LABEL",
611            type=FieldTypes.DROPDOWN,
612            placeholder="choice",
613            initial_value="another_choice",
614        )
615        self.assertEqual(
616            prompt.get_choices(context, self.user, self.factory.get("/")),
617            ("choice",),
618        )
619        self.assertEqual(
620            prompt.get_initial_value(context, self.user, self.factory.get("/")),
621            "choice",
622        )
623
624    def test_choices_are_none_for_non_choice_fields(self):
625        """Test choices are None for non choice fields"""
626        context = {}
627        prompt: Prompt = Prompt(
628            field_key="text_prompt_expression",
629            label="TEXT_LABEL",
630            type=FieldTypes.TEXT,
631            placeholder="choice",
632        )
633        self.assertEqual(
634            prompt.get_choices(context, self.user, self.factory.get("/")),
635            None,
636        )
637
638    def test_prompt_placeholder_error(self):
639        """Test placeholder and expression"""
640        context = {}
641        prompt: Prompt = Prompt(
642            field_key="text_prompt_expression",
643            label="TEXT_LABEL",
644            type=FieldTypes.TEXT,
645            placeholder="something invalid dunno",
646            placeholder_expression=True,
647        )
648        self.assertEqual(
649            prompt.get_placeholder(context, self.user, self.factory.get("/")),
650            "something invalid dunno",
651        )
652
653    def test_prompt_placeholder_disabled(self):
654        """Test placeholder and expression"""
655        context = {}
656        prompt: Prompt = Prompt(
657            field_key="text_prompt_expression",
658            label="TEXT_LABEL",
659            type=FieldTypes.TEXT,
660            placeholder="return prompt_context['foo']",
661            placeholder_expression=False,
662        )
663        self.assertEqual(
664            prompt.get_placeholder(context, self.user, self.factory.get("/")), prompt.placeholder
665        )
666
667    def test_invalid_save(self):
668        """Ensure field can't be saved with invalid type"""
669        prompt: Prompt = Prompt(
670            field_key="text_prompt_expression",
671            label="TEXT_LABEL",
672            type="foo",
673            placeholder="foo",
674            placeholder_expression=False,
675            sub_text="test",
676            order=123,
677        )
678        with self.assertRaises(ValueError):
679            prompt.save()
680
681    def test_api_preview(self):
682        """Test API preview"""
683        self.client.force_login(self.user)
684        response = self.client.post(
685            reverse("authentik_api:prompt-preview"),
686            data={
687                "field_key": "text_prompt_expression",
688                "label": "TEXT_LABEL",
689                "type": FieldTypes.TEXT,
690                "placeholder": 'return "Hello world"',
691                "placeholder_expression": True,
692                "initial_value": 'return "Hello Hello world"',
693                "initial_value_expression": True,
694                "sub_text": "test",
695                "order": 123,
696            },
697        )
698        self.assertEqual(response.status_code, 200)
699        self.assertJSONEqual(
700            response.content.decode(),
701            {
702                "component": "ak-stage-prompt",
703                "fields": [
704                    {
705                        "field_key": "text_prompt_expression",
706                        "label": "TEXT_LABEL",
707                        "type": "text",
708                        "required": True,
709                        "placeholder": "Hello world",
710                        "initial_value": "Hello Hello world",
711                        "order": 123,
712                        "sub_text": "test",
713                        "choices": None,
714                    }
715                ],
716            },
717        )
718
719    def test_api_preview_invalid_expression(self):
720        """Test API preview"""
721        self.client.force_login(self.user)
722        response = self.client.post(
723            reverse("authentik_api:prompt-preview"),
724            data={
725                "field_key": "text_prompt_expression",
726                "label": "TEXT_LABEL",
727                "type": FieldTypes.TEXT,
728                "placeholder": "return [",
729                "placeholder_expression": True,
730                "sub_text": "test",
731                "order": 123,
732            },
733        )
734        self.assertEqual(response.status_code, 400)
735        self.assertIn("non_field_errors", response.content.decode())

Prompt tests

def setUp(self):
 29    def setUp(self):
 30        super().setUp()
 31        self.user = create_test_admin_user()
 32        self.factory = RequestFactory()
 33        self.flow = create_test_flow()
 34        username_prompt = Prompt.objects.create(
 35            name=generate_id(),
 36            field_key="username_prompt",
 37            label="USERNAME_LABEL",
 38            type=FieldTypes.USERNAME,
 39            required=True,
 40            placeholder="USERNAME_PLACEHOLDER",
 41            initial_value="akuser",
 42        )
 43        text_prompt = Prompt.objects.create(
 44            name=generate_id(),
 45            field_key="text_prompt",
 46            label="TEXT_LABEL",
 47            type=FieldTypes.TEXT,
 48            required=True,
 49            placeholder="TEXT_PLACEHOLDER",
 50            initial_value="some text",
 51        )
 52        text_area_prompt = Prompt.objects.create(
 53            name=generate_id(),
 54            field_key="text_area_prompt",
 55            label="TEXT_AREA_LABEL",
 56            type=FieldTypes.TEXT_AREA,
 57            required=True,
 58            placeholder="TEXT_AREA_PLACEHOLDER",
 59            initial_value="some text",
 60        )
 61        email_prompt = Prompt.objects.create(
 62            name=generate_id(),
 63            field_key="email_prompt",
 64            label="EMAIL_LABEL",
 65            type=FieldTypes.EMAIL,
 66            required=True,
 67            placeholder="EMAIL_PLACEHOLDER",
 68            initial_value="email@example.com",
 69        )
 70        password_prompt = Prompt.objects.create(
 71            name=generate_id(),
 72            field_key="password_prompt",
 73            label="PASSWORD_LABEL",
 74            type=FieldTypes.PASSWORD,
 75            required=True,
 76            placeholder="PASSWORD_PLACEHOLDER",
 77            initial_value="supersecurepassword",
 78        )
 79        password2_prompt = Prompt.objects.create(
 80            name=generate_id(),
 81            field_key="password2_prompt",
 82            label="PASSWORD_LABEL",
 83            type=FieldTypes.PASSWORD,
 84            required=True,
 85            placeholder="PASSWORD_PLACEHOLDER",
 86            initial_value="supersecurepassword",
 87        )
 88        number_prompt = Prompt.objects.create(
 89            name=generate_id(),
 90            field_key="number_prompt",
 91            label="NUMBER_LABEL",
 92            type=FieldTypes.NUMBER,
 93            required=True,
 94            placeholder="NUMBER_PLACEHOLDER",
 95            initial_value="42",
 96        )
 97        hidden_prompt = Prompt.objects.create(
 98            name=generate_id(),
 99            field_key="hidden_prompt",
100            type=FieldTypes.HIDDEN,
101            required=True,
102            placeholder="HIDDEN_PLACEHOLDER",
103            initial_value="something idk",
104        )
105        static_prompt = Prompt.objects.create(
106            name=generate_id(),
107            field_key="static_prompt",
108            type=FieldTypes.STATIC,
109            required=True,
110            placeholder="static",
111            initial_value="something idk",
112        )
113        radio_button_group = Prompt.objects.create(
114            name=generate_id(),
115            field_key="radio_button_group",
116            type=FieldTypes.RADIO_BUTTON_GROUP,
117            required=True,
118            placeholder="test",
119            initial_value="test",
120        )
121        dropdown = Prompt.objects.create(
122            name=generate_id(),
123            field_key="dropdown",
124            type=FieldTypes.DROPDOWN,
125            required=True,
126        )
127        self.stage = PromptStage.objects.create(name="prompt-stage")
128        self.stage.fields.set(
129            [
130                username_prompt,
131                text_prompt,
132                email_prompt,
133                password_prompt,
134                password2_prompt,
135                number_prompt,
136                hidden_prompt,
137                static_prompt,
138                radio_button_group,
139                dropdown,
140            ]
141        )
142
143        self.prompt_data = {
144            username_prompt.field_key: "test-username",
145            text_prompt.field_key: "test-input",
146            text_area_prompt.field_key: "test-area-input",
147            email_prompt.field_key: "test@test.test",
148            password_prompt.field_key: "test",
149            password2_prompt.field_key: "test",
150            number_prompt.field_key: 3,
151            hidden_prompt.field_key: hidden_prompt.initial_value,
152            static_prompt.field_key: static_prompt.initial_value,
153            radio_button_group.field_key: radio_button_group.initial_value,
154            dropdown.field_key: "",
155        }
156
157        self.binding = FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2)
158
159        self.request = RequestFactory().get("/")
160        self.request.user = create_test_admin_user()
161        self.flow_executor = FlowExecutorView(request=self.request)
162        self.stage_view = PromptStageView(self.flow_executor, request=self.request)

Hook method for setting up the test fixture before exercising it.

def test_inline_file_field(self):
164    def test_inline_file_field(self):
165        """test InlineFileField"""
166        with self.assertRaises(ValidationError):
167            InlineFileField().to_internal_value("foo")
168        with self.assertRaises(ValidationError):
169            InlineFileField().to_internal_value("data:foo/bar;foo,qwer")
170        self.assertEqual(
171            InlineFileField().to_internal_value("data:mine/type;base64,Zm9v"),
172            "data:mine/type;base64,Zm9v",
173        )
174        self.assertEqual(
175            InlineFileField().to_internal_value("data:mine/type;base64,Zm9vqwer"),
176            "data:mine/type;base64,Zm9vqwer",
177        )

test InlineFileField

def test_render(self):
179    def test_render(self):
180        """Test render of form, check if all prompts are rendered correctly"""
181        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
182        session = self.client.session
183        session[SESSION_KEY_PLAN] = plan
184        session.save()
185
186        response = self.client.get(
187            reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
188        )
189        self.assertEqual(response.status_code, 200)
190        for prompt in self.stage.fields.all():
191            self.assertIn(prompt.field_key, response.content.decode())
192            self.assertIn(prompt.label, response.content.decode())
193            self.assertIn(prompt.placeholder, response.content.decode())

Test render of form, check if all prompts are rendered correctly

def test_valid_challenge_with_policy(self):
195    def test_valid_challenge_with_policy(self):
196        """Test challenge_response validation"""
197        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
198        expr = (
199            "return request.context['prompt_data']['password_prompt'] "
200            "== request.context['prompt_data']['password2_prompt']"
201        )
202        expr_policy = ExpressionPolicy.objects.create(name="validate-form", expression=expr)
203        self.stage.validation_policies.set([expr_policy])
204        self.stage.save()
205        challenge_response = PromptChallengeResponse(
206            None,
207            stage_instance=self.stage,
208            plan=plan,
209            data=self.prompt_data,
210            stage=self.stage_view,
211        )
212        self.assertEqual(challenge_response.is_valid(), True)

Test challenge_response validation

def test_invalid_challenge(self):
214    def test_invalid_challenge(self):
215        """Test challenge_response validation"""
216        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
217        expr = "False"
218        expr_policy = ExpressionPolicy.objects.create(name=generate_id(), expression=expr)
219        self.stage.validation_policies.set([expr_policy])
220        self.stage.save()
221        challenge_response = PromptChallengeResponse(
222            None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
223        )
224        self.assertEqual(challenge_response.is_valid(), False)

Test challenge_response validation

def test_invalid_challenge_multiple(self):
226    def test_invalid_challenge_multiple(self):
227        """Test challenge_response validation (multiple policies)"""
228        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
229        expr_policy1 = ExpressionPolicy.objects.create(name=generate_id(), expression="False")
230        expr_policy2 = ExpressionPolicy.objects.create(name=generate_id(), expression="False")
231        self.stage.validation_policies.set([expr_policy1, expr_policy2])
232        self.stage.save()
233        challenge_response = PromptChallengeResponse(
234            None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
235        )
236        self.assertEqual(challenge_response.is_valid(), False)

Test challenge_response validation (multiple policies)

def test_valid_challenge_request(self):
238    def test_valid_challenge_request(self):
239        """Test a request with valid challenge_response data"""
240        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
241        session = self.client.session
242        session[SESSION_KEY_PLAN] = plan
243        session.save()
244
245        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
246        expr = (
247            "return request.context['prompt_data']['password_prompt'] "
248            "== request.context['prompt_data']['password2_prompt']"
249        )
250        expr_policy = ExpressionPolicy.objects.create(name=generate_id(), expression=expr)
251        self.stage.validation_policies.set([expr_policy])
252        self.stage.save()
253        challenge_response = PromptChallengeResponse(
254            None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
255        )
256        self.assertEqual(challenge_response.is_valid(), True)
257
258        with patch("authentik.flows.views.executor.FlowExecutorView.cancel", MagicMock()):
259            response = self.client.post(
260                reverse(
261                    "authentik_api:flow-executor",
262                    kwargs={"flow_slug": self.flow.slug},
263                ),
264                challenge_response.validated_data,
265            )
266        self.assertEqual(response.status_code, 200)
267        self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
268
269        # Check that valid data has been saved
270        session = self.client.session
271        plan: FlowPlan = session[SESSION_KEY_PLAN]
272        data = plan.context[PLAN_CONTEXT_PROMPT]
273        for prompt in self.stage.fields.all():
274            prompt: Prompt
275            self.assertEqual(data[prompt.field_key], self.prompt_data[prompt.field_key])

Test a request with valid challenge_response data

def test_invalid_password(self):
277    def test_invalid_password(self):
278        """Test challenge_response validation"""
279        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
280        self.prompt_data["password2_prompt"] = "qwerqwerqr"
281        challenge_response = PromptChallengeResponse(
282            None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
283        )
284        self.assertEqual(challenge_response.is_valid(), False)
285        self.assertEqual(
286            challenge_response.errors,
287            {"non_field_errors": [ErrorDetail(string="Passwords don't match.", code="invalid")]},
288        )

Test challenge_response validation

def test_invalid_username(self):
290    def test_invalid_username(self):
291        """Test challenge_response validation"""
292        user = create_test_admin_user()
293        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
294        self.prompt_data["username_prompt"] = user.username
295        challenge_response = PromptChallengeResponse(
296            None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
297        )
298        self.assertEqual(challenge_response.is_valid(), False)
299        self.assertEqual(
300            challenge_response.errors,
301            {"username_prompt": [ErrorDetail(string="Username is already taken.", code="invalid")]},
302        )

Test challenge_response validation

def test_invalid_choice_field(self):
304    def test_invalid_choice_field(self):
305        """Test invalid choice field value"""
306        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
307        self.prompt_data["radio_button_group"] = "some invalid choice"
308        self.prompt_data["dropdown"] = "another invalid choice"
309        challenge_response = PromptChallengeResponse(
310            None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
311        )
312        self.assertEqual(challenge_response.is_valid(), False)
313        self.assertEqual(
314            challenge_response.errors,
315            {
316                "radio_button_group": [
317                    ErrorDetail(
318                        string=f"\"{self.prompt_data['radio_button_group']}\" "
319                        "is not a valid choice.",
320                        code="invalid_choice",
321                    )
322                ],
323                "dropdown": [
324                    ErrorDetail(
325                        string=f"\"{self.prompt_data['dropdown']}\" is not a valid choice.",
326                        code="invalid_choice",
327                    )
328                ],
329            },
330        )

Test invalid choice field value

def test_static_hidden_overwrite(self):
332    def test_static_hidden_overwrite(self):
333        """Test that static and hidden fields ignore any value sent to them"""
334        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
335        plan.context[PLAN_CONTEXT_PROMPT] = {"hidden_prompt": "hidden"}
336        self.prompt_data["hidden_prompt"] = "foo"
337        self.prompt_data["static_prompt"] = "foo"
338        challenge_response = PromptChallengeResponse(
339            None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
340        )
341        self.assertEqual(challenge_response.is_valid(), True)
342        self.assertNotEqual(challenge_response.validated_data["hidden_prompt"], "foo")
343        self.assertEqual(challenge_response.validated_data["hidden_prompt"], "hidden")
344        self.assertNotEqual(challenge_response.validated_data["static_prompt"], "foo")

Test that static and hidden fields ignore any value sent to them

def test_prompt_placeholder(self):
346    def test_prompt_placeholder(self):
347        """Test placeholder and expression"""
348        context = {
349            "foo": generate_id(),
350        }
351        prompt: Prompt = Prompt(
352            field_key="text_prompt_expression",
353            label="TEXT_LABEL",
354            type=FieldTypes.TEXT,
355            placeholder="return prompt_context['foo']",
356            placeholder_expression=True,
357        )
358        self.assertEqual(
359            prompt.get_placeholder(context, self.user, self.factory.get("/")), context["foo"]
360        )

Test placeholder and expression

def test_prompt_placeholder_does_not_take_value_from_context(self):
362    def test_prompt_placeholder_does_not_take_value_from_context(self):
363        """Test placeholder does not automatically take value from context"""
364        context = {
365            "foo": generate_id(),
366        }
367        prompt: Prompt = Prompt(
368            field_key="text_prompt_expression",
369            label="TEXT_LABEL",
370            type=FieldTypes.TEXT,
371            placeholder="return prompt_context['foo']",
372            placeholder_expression=True,
373        )
374        context["text_prompt_expression"] = generate_id()
375
376        self.assertEqual(
377            prompt.get_placeholder(context, self.user, self.factory.get("/")), context["foo"]
378        )

Test placeholder does not automatically take value from context

def test_prompt_initial_value(self):
380    def test_prompt_initial_value(self):
381        """Test initial_value and expression"""
382        context = {
383            "foo": generate_id(),
384        }
385        prompt: Prompt = Prompt(
386            field_key="text_prompt_expression",
387            label="TEXT_LABEL",
388            type=FieldTypes.TEXT,
389            initial_value="return prompt_context['foo']",
390            initial_value_expression=True,
391        )
392        self.assertEqual(
393            prompt.get_initial_value(context, self.user, self.factory.get("/")), context["foo"]
394        )
395        context["text_prompt_expression"] = generate_id()
396        self.assertEqual(
397            prompt.get_initial_value(context, self.user, self.factory.get("/")),
398            context["text_prompt_expression"],
399        )
400        self.assertNotEqual(
401            prompt.get_initial_value(context, self.user, self.factory.get("/")), context["foo"]
402        )

Test initial_value and expression

def test_choice_prompts_placeholder_and_initial_value_no_choices(self):
404    def test_choice_prompts_placeholder_and_initial_value_no_choices(self):
405        """Test placeholder and initial value of choice fields with 0 choices"""
406        context = {}
407
408        prompt: Prompt = Prompt(
409            field_key="fixed_choice_prompt_expression",
410            label="LABEL",
411            type=FieldTypes.RADIO_BUTTON_GROUP,
412            placeholder="return []",
413            placeholder_expression=True,
414            initial_value="Invalid choice",
415            initial_value_expression=False,
416        )
417        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
418        self.assertEqual(prompt.get_initial_value(context, self.user, self.factory.get("/")), "")
419        self.assertEqual(prompt.get_choices(context, self.user, self.factory.get("/")), tuple())

Test placeholder and initial value of choice fields with 0 choices

def test_choice_prompts_placeholder_and_initial_value_single_choice(self):
421    def test_choice_prompts_placeholder_and_initial_value_single_choice(self):
422        """Test placeholder and initial value of choice fields with 1 choice"""
423        context = {"foo": generate_id()}
424
425        prompt: Prompt = Prompt(
426            field_key="fixed_choice_prompt_expression",
427            label="LABEL",
428            type=FieldTypes.DROPDOWN,
429            placeholder=context["foo"],
430            placeholder_expression=False,
431            initial_value=context["foo"],
432            initial_value_expression=False,
433        )
434        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
435        self.assertEqual(
436            prompt.get_initial_value(context, self.user, self.factory.get("/")), context["foo"]
437        )
438        self.assertEqual(
439            prompt.get_choices(context, self.user, self.factory.get("/")), (context["foo"],)
440        )
441
442        prompt: Prompt = Prompt(
443            field_key="fixed_choice_prompt_expression",
444            label="LABEL",
445            type=FieldTypes.DROPDOWN,
446            placeholder="return [prompt_context['foo']]",
447            placeholder_expression=True,
448            initial_value="return prompt_context['foo']",
449            initial_value_expression=True,
450        )
451        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
452        self.assertEqual(
453            prompt.get_initial_value(context, self.user, self.factory.get("/")), context["foo"]
454        )
455        self.assertEqual(
456            prompt.get_choices(context, self.user, self.factory.get("/")), (context["foo"],)
457        )

Test placeholder and initial value of choice fields with 1 choice

def test_choice_prompts_placeholder_and_initial_value_single_choice_dict(self):
459    def test_choice_prompts_placeholder_and_initial_value_single_choice_dict(self):
460        """Test placeholder and initial value of dict choice fields with 1 choice"""
461        context = {"foo": {"label": "foo", "value": generate_id()}}
462
463        prompt: Prompt = Prompt(
464            field_key="fixed_choice_prompt_expression",
465            label="LABEL",
466            type=FieldTypes.DROPDOWN,
467            placeholder=context["foo"],
468            placeholder_expression=False,
469            initial_value=context["foo"]["value"],
470            initial_value_expression=False,
471        )
472        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
473        self.assertEqual(
474            prompt.get_initial_value(context, self.user, self.factory.get("/")),
475            context["foo"]["value"],
476        )
477        self.assertEqual(
478            prompt.get_choices(context, self.user, self.factory.get("/")), (context["foo"],)
479        )
480
481        prompt: Prompt = Prompt(
482            field_key="fixed_choice_prompt_expression",
483            label="LABEL",
484            type=FieldTypes.DROPDOWN,
485            placeholder="return [prompt_context['foo']]",
486            placeholder_expression=True,
487            initial_value="return prompt_context['foo']['value']",
488            initial_value_expression=True,
489        )
490        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
491        self.assertEqual(
492            prompt.get_initial_value(context, self.user, self.factory.get("/")),
493            context["foo"]["value"],
494        )
495        self.assertEqual(
496            prompt.get_choices(context, self.user, self.factory.get("/")), (context["foo"],)
497        )

Test placeholder and initial value of dict choice fields with 1 choice

def test_choice_prompts_placeholder_and_initial_value_multiple_choices(self):
499    def test_choice_prompts_placeholder_and_initial_value_multiple_choices(self):
500        """Test placeholder and initial value of choice fields with multiple choices"""
501        context = {}
502
503        prompt: Prompt = Prompt(
504            field_key="fixed_choice_prompt_expression",
505            label="LABEL",
506            type=FieldTypes.RADIO_BUTTON_GROUP,
507            placeholder="return ['test', True, 42]",
508            placeholder_expression=True,
509        )
510        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
511        self.assertEqual(
512            prompt.get_initial_value(context, self.user, self.factory.get("/")), "test"
513        )
514        self.assertEqual(
515            prompt.get_choices(context, self.user, self.factory.get("/")), ("test", True, 42)
516        )
517
518        prompt: Prompt = Prompt(
519            field_key="fixed_choice_prompt_expression",
520            label="LABEL",
521            type=FieldTypes.RADIO_BUTTON_GROUP,
522            placeholder="return ['test', True, 42]",
523            placeholder_expression=True,
524            initial_value="return True",
525            initial_value_expression=True,
526        )
527        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
528        self.assertEqual(prompt.get_initial_value(context, self.user, self.factory.get("/")), True)
529        self.assertEqual(
530            prompt.get_choices(context, self.user, self.factory.get("/")), ("test", True, 42)
531        )

Test placeholder and initial value of choice fields with multiple choices

def test_choice_prompts_placeholder_and_initial_value_multiple_choices_dict(self):
533    def test_choice_prompts_placeholder_and_initial_value_multiple_choices_dict(self):
534        """Test placeholder and initial value of dict choice fields with multiple dict choices"""
535        context = {}
536
537        prompt: Prompt = Prompt(
538            field_key="fixed_choice_prompt_expression",
539            label="LABEL",
540            type=FieldTypes.RADIO_BUTTON_GROUP,
541            placeholder="return ["
542            "{'label': 'Option 1', 'value': 'value1'},"
543            "{'label': 'Option 2', 'value': 'value2'}"
544            "]",
545            placeholder_expression=True,
546        )
547        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
548        self.assertEqual(
549            prompt.get_initial_value(context, self.user, self.factory.get("/")).get("value"),
550            "value1",
551        )
552        self.assertEqual(
553            tuple(
554                (choice["label"], choice["value"])
555                for choice in prompt.get_choices(context, self.user, self.factory.get("/"))
556            ),
557            (("Option 1", "value1"), ("Option 2", "value2")),
558        )
559
560        prompt: Prompt = Prompt(
561            field_key="fixed_choice_prompt_expression",
562            label="LABEL",
563            type=FieldTypes.RADIO_BUTTON_GROUP,
564            placeholder="return ["
565            "{'label': 'Option 1', 'value': 'value1'},"
566            "{'label': 'Option 2', 'value': 'value2'}"
567            "]",
568            placeholder_expression=True,
569            initial_value="return 'value2'",
570            initial_value_expression=True,
571        )
572        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
573        self.assertEqual(
574            prompt.get_initial_value(context, self.user, self.factory.get("/")), "value2"
575        )
576        self.assertEqual(
577            tuple(
578                (choice["label"], choice["value"])
579                for choice in prompt.get_choices(context, self.user, self.factory.get("/"))
580            ),
581            (("Option 1", "value1"), ("Option 2", "value2")),
582        )

Test placeholder and initial value of dict choice fields with multiple dict choices

def test_choice_prompts_placeholder_and_initial_value_from_context(self):
584    def test_choice_prompts_placeholder_and_initial_value_from_context(self):
585        """Test placeholder and initial value of choice fields with values from context"""
586        rand_value = generate_id()
587        context = {
588            "fixed_choice_prompt_expression": rand_value,
589            "fixed_choice_prompt_expression__choices": ["test", 42, rand_value],
590        }
591
592        prompt: Prompt = Prompt(
593            field_key="fixed_choice_prompt_expression",
594            label="LABEL",
595            type=FieldTypes.RADIO_BUTTON_GROUP,
596        )
597        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
598        self.assertEqual(
599            prompt.get_initial_value(context, self.user, self.factory.get("/")), rand_value
600        )
601        self.assertEqual(
602            prompt.get_choices(context, self.user, self.factory.get("/")), ("test", 42, rand_value)
603        )

Test placeholder and initial value of choice fields with values from context

def test_initial_value_not_valid_choice(self):
605    def test_initial_value_not_valid_choice(self):
606        """Test initial_value not a valid choice"""
607        context = {}
608        prompt: Prompt = Prompt(
609            field_key="choice_prompt",
610            label="TEXT_LABEL",
611            type=FieldTypes.DROPDOWN,
612            placeholder="choice",
613            initial_value="another_choice",
614        )
615        self.assertEqual(
616            prompt.get_choices(context, self.user, self.factory.get("/")),
617            ("choice",),
618        )
619        self.assertEqual(
620            prompt.get_initial_value(context, self.user, self.factory.get("/")),
621            "choice",
622        )

Test initial_value not a valid choice

def test_choices_are_none_for_non_choice_fields(self):
624    def test_choices_are_none_for_non_choice_fields(self):
625        """Test choices are None for non choice fields"""
626        context = {}
627        prompt: Prompt = Prompt(
628            field_key="text_prompt_expression",
629            label="TEXT_LABEL",
630            type=FieldTypes.TEXT,
631            placeholder="choice",
632        )
633        self.assertEqual(
634            prompt.get_choices(context, self.user, self.factory.get("/")),
635            None,
636        )

Test choices are None for non choice fields

def test_prompt_placeholder_error(self):
638    def test_prompt_placeholder_error(self):
639        """Test placeholder and expression"""
640        context = {}
641        prompt: Prompt = Prompt(
642            field_key="text_prompt_expression",
643            label="TEXT_LABEL",
644            type=FieldTypes.TEXT,
645            placeholder="something invalid dunno",
646            placeholder_expression=True,
647        )
648        self.assertEqual(
649            prompt.get_placeholder(context, self.user, self.factory.get("/")),
650            "something invalid dunno",
651        )

Test placeholder and expression

def test_prompt_placeholder_disabled(self):
653    def test_prompt_placeholder_disabled(self):
654        """Test placeholder and expression"""
655        context = {}
656        prompt: Prompt = Prompt(
657            field_key="text_prompt_expression",
658            label="TEXT_LABEL",
659            type=FieldTypes.TEXT,
660            placeholder="return prompt_context['foo']",
661            placeholder_expression=False,
662        )
663        self.assertEqual(
664            prompt.get_placeholder(context, self.user, self.factory.get("/")), prompt.placeholder
665        )

Test placeholder and expression

def test_invalid_save(self):
667    def test_invalid_save(self):
668        """Ensure field can't be saved with invalid type"""
669        prompt: Prompt = Prompt(
670            field_key="text_prompt_expression",
671            label="TEXT_LABEL",
672            type="foo",
673            placeholder="foo",
674            placeholder_expression=False,
675            sub_text="test",
676            order=123,
677        )
678        with self.assertRaises(ValueError):
679            prompt.save()

Ensure field can't be saved with invalid type

def test_api_preview(self):
681    def test_api_preview(self):
682        """Test API preview"""
683        self.client.force_login(self.user)
684        response = self.client.post(
685            reverse("authentik_api:prompt-preview"),
686            data={
687                "field_key": "text_prompt_expression",
688                "label": "TEXT_LABEL",
689                "type": FieldTypes.TEXT,
690                "placeholder": 'return "Hello world"',
691                "placeholder_expression": True,
692                "initial_value": 'return "Hello Hello world"',
693                "initial_value_expression": True,
694                "sub_text": "test",
695                "order": 123,
696            },
697        )
698        self.assertEqual(response.status_code, 200)
699        self.assertJSONEqual(
700            response.content.decode(),
701            {
702                "component": "ak-stage-prompt",
703                "fields": [
704                    {
705                        "field_key": "text_prompt_expression",
706                        "label": "TEXT_LABEL",
707                        "type": "text",
708                        "required": True,
709                        "placeholder": "Hello world",
710                        "initial_value": "Hello Hello world",
711                        "order": 123,
712                        "sub_text": "test",
713                        "choices": None,
714                    }
715                ],
716            },
717        )

Test API preview

def test_api_preview_invalid_expression(self):
719    def test_api_preview_invalid_expression(self):
720        """Test API preview"""
721        self.client.force_login(self.user)
722        response = self.client.post(
723            reverse("authentik_api:prompt-preview"),
724            data={
725                "field_key": "text_prompt_expression",
726                "label": "TEXT_LABEL",
727                "type": FieldTypes.TEXT,
728                "placeholder": "return [",
729                "placeholder_expression": True,
730                "sub_text": "test",
731                "order": 123,
732            },
733        )
734        self.assertEqual(response.status_code, 400)
735        self.assertIn("non_field_errors", response.content.decode())

Test API preview

def test_field_type_text_required(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_text_area_required(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_text_read_only_required(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_text_area_read_only_required(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_username_required(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_email_required(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_password_required(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_number_required(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_checkbox_required(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_radio-button-group_required(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_dropdown_required(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_date_required(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_date-time_required(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_file_required(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_separator_required(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_hidden_required(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_static_required(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_ak-locale_required(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_text(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_text_area(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_text_read_only(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_text_area_read_only(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_username(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_email(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_password(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_number(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_checkbox(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_radio-button-group(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_dropdown(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_date(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_date-time(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_file(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_separator(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_hidden(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_static(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_ak-locale(self: TestPromptStage):
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def field_type_tester_factory( field_type: authentik.stages.prompt.models.FieldTypes, required: bool):
738def field_type_tester_factory(field_type: FieldTypes, required: bool):
739    """Test field for field_type"""
740
741    def tester(self: TestPromptStage):
742        prompt: Prompt = Prompt(
743            field_key="text_prompt_expression",
744            label="TEXT_LABEL",
745            type=field_type,
746            placeholder="foo",
747            placeholder_expression=False,
748            sub_text="test",
749            order=123,
750            required=required,
751        )
752        self.assertIsNotNone(prompt.field("foo"))
753
754    return tester

Test field for field_type