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        alert_prompt = Prompt.objects.create(
334            name=generate_id(),
335            field_key="alert_prompt",
336            type=FieldTypes.ALERT_INFO,
337            required=True,
338            placeholder="alert fallback",
339            initial_value="alert content",
340        )
341        self.stage.fields.add(alert_prompt)
342        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
343        plan.context[PLAN_CONTEXT_PROMPT] = {"hidden_prompt": "hidden"}
344        self.prompt_data["hidden_prompt"] = "foo"
345        self.prompt_data["static_prompt"] = "foo"
346        self.prompt_data["alert_prompt"] = "foo"
347        challenge_response = PromptChallengeResponse(
348            None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
349        )
350        self.assertEqual(challenge_response.is_valid(), True)
351        self.assertNotEqual(challenge_response.validated_data["hidden_prompt"], "foo")
352        self.assertEqual(challenge_response.validated_data["hidden_prompt"], "hidden")
353        self.assertNotEqual(challenge_response.validated_data["static_prompt"], "foo")
354        self.assertEqual(challenge_response.validated_data["alert_prompt"], "alert content")
355
356    def test_prompt_placeholder(self):
357        """Test placeholder and expression"""
358        context = {
359            "foo": generate_id(),
360        }
361        prompt: Prompt = Prompt(
362            field_key="text_prompt_expression",
363            label="TEXT_LABEL",
364            type=FieldTypes.TEXT,
365            placeholder="return prompt_context['foo']",
366            placeholder_expression=True,
367        )
368        self.assertEqual(
369            prompt.get_placeholder(context, self.user, self.factory.get("/")), context["foo"]
370        )
371
372    def test_prompt_placeholder_does_not_take_value_from_context(self):
373        """Test placeholder does not automatically take value from context"""
374        context = {
375            "foo": generate_id(),
376        }
377        prompt: Prompt = Prompt(
378            field_key="text_prompt_expression",
379            label="TEXT_LABEL",
380            type=FieldTypes.TEXT,
381            placeholder="return prompt_context['foo']",
382            placeholder_expression=True,
383        )
384        context["text_prompt_expression"] = generate_id()
385
386        self.assertEqual(
387            prompt.get_placeholder(context, self.user, self.factory.get("/")), context["foo"]
388        )
389
390    def test_prompt_initial_value(self):
391        """Test initial_value and expression"""
392        context = {
393            "foo": generate_id(),
394        }
395        prompt: Prompt = Prompt(
396            field_key="text_prompt_expression",
397            label="TEXT_LABEL",
398            type=FieldTypes.TEXT,
399            initial_value="return prompt_context['foo']",
400            initial_value_expression=True,
401        )
402        self.assertEqual(
403            prompt.get_initial_value(context, self.user, self.factory.get("/")), context["foo"]
404        )
405        context["text_prompt_expression"] = generate_id()
406        self.assertEqual(
407            prompt.get_initial_value(context, self.user, self.factory.get("/")),
408            context["text_prompt_expression"],
409        )
410        self.assertNotEqual(
411            prompt.get_initial_value(context, self.user, self.factory.get("/")), context["foo"]
412        )
413
414    def test_choice_prompts_placeholder_and_initial_value_no_choices(self):
415        """Test placeholder and initial value of choice fields with 0 choices"""
416        context = {}
417
418        prompt: Prompt = Prompt(
419            field_key="fixed_choice_prompt_expression",
420            label="LABEL",
421            type=FieldTypes.RADIO_BUTTON_GROUP,
422            placeholder="return []",
423            placeholder_expression=True,
424            initial_value="Invalid choice",
425            initial_value_expression=False,
426        )
427        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
428        self.assertEqual(prompt.get_initial_value(context, self.user, self.factory.get("/")), "")
429        self.assertEqual(prompt.get_choices(context, self.user, self.factory.get("/")), tuple())
430
431    def test_choice_prompts_placeholder_and_initial_value_single_choice(self):
432        """Test placeholder and initial value of choice fields with 1 choice"""
433        context = {"foo": generate_id()}
434
435        prompt: Prompt = Prompt(
436            field_key="fixed_choice_prompt_expression",
437            label="LABEL",
438            type=FieldTypes.DROPDOWN,
439            placeholder=context["foo"],
440            placeholder_expression=False,
441            initial_value=context["foo"],
442            initial_value_expression=False,
443        )
444        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
445        self.assertEqual(
446            prompt.get_initial_value(context, self.user, self.factory.get("/")), context["foo"]
447        )
448        self.assertEqual(
449            prompt.get_choices(context, self.user, self.factory.get("/")), (context["foo"],)
450        )
451
452        prompt: Prompt = Prompt(
453            field_key="fixed_choice_prompt_expression",
454            label="LABEL",
455            type=FieldTypes.DROPDOWN,
456            placeholder="return [prompt_context['foo']]",
457            placeholder_expression=True,
458            initial_value="return prompt_context['foo']",
459            initial_value_expression=True,
460        )
461        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
462        self.assertEqual(
463            prompt.get_initial_value(context, self.user, self.factory.get("/")), context["foo"]
464        )
465        self.assertEqual(
466            prompt.get_choices(context, self.user, self.factory.get("/")), (context["foo"],)
467        )
468
469    def test_choice_prompts_placeholder_and_initial_value_single_choice_dict(self):
470        """Test placeholder and initial value of dict choice fields with 1 choice"""
471        context = {"foo": {"label": "foo", "value": generate_id()}}
472
473        prompt: Prompt = Prompt(
474            field_key="fixed_choice_prompt_expression",
475            label="LABEL",
476            type=FieldTypes.DROPDOWN,
477            placeholder=context["foo"],
478            placeholder_expression=False,
479            initial_value=context["foo"]["value"],
480            initial_value_expression=False,
481        )
482        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
483        self.assertEqual(
484            prompt.get_initial_value(context, self.user, self.factory.get("/")),
485            context["foo"]["value"],
486        )
487        self.assertEqual(
488            prompt.get_choices(context, self.user, self.factory.get("/")), (context["foo"],)
489        )
490
491        prompt: Prompt = Prompt(
492            field_key="fixed_choice_prompt_expression",
493            label="LABEL",
494            type=FieldTypes.DROPDOWN,
495            placeholder="return [prompt_context['foo']]",
496            placeholder_expression=True,
497            initial_value="return prompt_context['foo']['value']",
498            initial_value_expression=True,
499        )
500        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
501        self.assertEqual(
502            prompt.get_initial_value(context, self.user, self.factory.get("/")),
503            context["foo"]["value"],
504        )
505        self.assertEqual(
506            prompt.get_choices(context, self.user, self.factory.get("/")), (context["foo"],)
507        )
508
509    def test_choice_prompts_placeholder_and_initial_value_multiple_choices(self):
510        """Test placeholder and initial value of choice fields with multiple choices"""
511        context = {}
512
513        prompt: Prompt = Prompt(
514            field_key="fixed_choice_prompt_expression",
515            label="LABEL",
516            type=FieldTypes.RADIO_BUTTON_GROUP,
517            placeholder="return ['test', True, 42]",
518            placeholder_expression=True,
519        )
520        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
521        self.assertEqual(
522            prompt.get_initial_value(context, self.user, self.factory.get("/")), "test"
523        )
524        self.assertEqual(
525            prompt.get_choices(context, self.user, self.factory.get("/")), ("test", True, 42)
526        )
527
528        prompt: Prompt = Prompt(
529            field_key="fixed_choice_prompt_expression",
530            label="LABEL",
531            type=FieldTypes.RADIO_BUTTON_GROUP,
532            placeholder="return ['test', True, 42]",
533            placeholder_expression=True,
534            initial_value="return True",
535            initial_value_expression=True,
536        )
537        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
538        self.assertEqual(prompt.get_initial_value(context, self.user, self.factory.get("/")), True)
539        self.assertEqual(
540            prompt.get_choices(context, self.user, self.factory.get("/")), ("test", True, 42)
541        )
542
543    def test_choice_prompts_placeholder_and_initial_value_multiple_choices_dict(self):
544        """Test placeholder and initial value of dict choice fields with multiple dict choices"""
545        context = {}
546
547        prompt: Prompt = Prompt(
548            field_key="fixed_choice_prompt_expression",
549            label="LABEL",
550            type=FieldTypes.RADIO_BUTTON_GROUP,
551            placeholder="return ["
552            "{'label': 'Option 1', 'value': 'value1'},"
553            "{'label': 'Option 2', 'value': 'value2'}"
554            "]",
555            placeholder_expression=True,
556        )
557        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
558        self.assertEqual(
559            prompt.get_initial_value(context, self.user, self.factory.get("/")).get("value"),
560            "value1",
561        )
562        self.assertEqual(
563            tuple(
564                (choice["label"], choice["value"])
565                for choice in prompt.get_choices(context, self.user, self.factory.get("/"))
566            ),
567            (("Option 1", "value1"), ("Option 2", "value2")),
568        )
569
570        prompt: Prompt = Prompt(
571            field_key="fixed_choice_prompt_expression",
572            label="LABEL",
573            type=FieldTypes.RADIO_BUTTON_GROUP,
574            placeholder="return ["
575            "{'label': 'Option 1', 'value': 'value1'},"
576            "{'label': 'Option 2', 'value': 'value2'}"
577            "]",
578            placeholder_expression=True,
579            initial_value="return 'value2'",
580            initial_value_expression=True,
581        )
582        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
583        self.assertEqual(
584            prompt.get_initial_value(context, self.user, self.factory.get("/")), "value2"
585        )
586        self.assertEqual(
587            tuple(
588                (choice["label"], choice["value"])
589                for choice in prompt.get_choices(context, self.user, self.factory.get("/"))
590            ),
591            (("Option 1", "value1"), ("Option 2", "value2")),
592        )
593
594    def test_choice_prompts_placeholder_and_initial_value_from_context(self):
595        """Test placeholder and initial value of choice fields with values from context"""
596        rand_value = generate_id()
597        context = {
598            "fixed_choice_prompt_expression": rand_value,
599            "fixed_choice_prompt_expression__choices": ["test", 42, rand_value],
600        }
601
602        prompt: Prompt = Prompt(
603            field_key="fixed_choice_prompt_expression",
604            label="LABEL",
605            type=FieldTypes.RADIO_BUTTON_GROUP,
606        )
607        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
608        self.assertEqual(
609            prompt.get_initial_value(context, self.user, self.factory.get("/")), rand_value
610        )
611        self.assertEqual(
612            prompt.get_choices(context, self.user, self.factory.get("/")), ("test", 42, rand_value)
613        )
614
615    def test_initial_value_not_valid_choice(self):
616        """Test initial_value not a valid choice"""
617        context = {}
618        prompt: Prompt = Prompt(
619            field_key="choice_prompt",
620            label="TEXT_LABEL",
621            type=FieldTypes.DROPDOWN,
622            placeholder="choice",
623            initial_value="another_choice",
624        )
625        self.assertEqual(
626            prompt.get_choices(context, self.user, self.factory.get("/")),
627            ("choice",),
628        )
629        self.assertEqual(
630            prompt.get_initial_value(context, self.user, self.factory.get("/")),
631            "choice",
632        )
633
634    def test_choices_are_none_for_non_choice_fields(self):
635        """Test choices are None for non choice fields"""
636        context = {}
637        prompt: Prompt = Prompt(
638            field_key="text_prompt_expression",
639            label="TEXT_LABEL",
640            type=FieldTypes.TEXT,
641            placeholder="choice",
642        )
643        self.assertEqual(
644            prompt.get_choices(context, self.user, self.factory.get("/")),
645            None,
646        )
647
648    def test_prompt_placeholder_error(self):
649        """Test placeholder and expression"""
650        context = {}
651        prompt: Prompt = Prompt(
652            field_key="text_prompt_expression",
653            label="TEXT_LABEL",
654            type=FieldTypes.TEXT,
655            placeholder="something invalid dunno",
656            placeholder_expression=True,
657        )
658        self.assertEqual(
659            prompt.get_placeholder(context, self.user, self.factory.get("/")),
660            "something invalid dunno",
661        )
662
663    def test_prompt_placeholder_disabled(self):
664        """Test placeholder and expression"""
665        context = {}
666        prompt: Prompt = Prompt(
667            field_key="text_prompt_expression",
668            label="TEXT_LABEL",
669            type=FieldTypes.TEXT,
670            placeholder="return prompt_context['foo']",
671            placeholder_expression=False,
672        )
673        self.assertEqual(
674            prompt.get_placeholder(context, self.user, self.factory.get("/")), prompt.placeholder
675        )
676
677    def test_invalid_save(self):
678        """Ensure field can't be saved with invalid type"""
679        prompt: Prompt = Prompt(
680            field_key="text_prompt_expression",
681            label="TEXT_LABEL",
682            type="foo",
683            placeholder="foo",
684            placeholder_expression=False,
685            sub_text="test",
686            order=123,
687        )
688        with self.assertRaises(ValueError):
689            prompt.save()
690
691    def test_api_preview(self):
692        """Test API preview"""
693        self.client.force_login(self.user)
694        response = self.client.post(
695            reverse("authentik_api:prompt-preview"),
696            data={
697                "field_key": "text_prompt_expression",
698                "label": "TEXT_LABEL",
699                "type": FieldTypes.TEXT,
700                "placeholder": 'return "Hello world"',
701                "placeholder_expression": True,
702                "initial_value": 'return "Hello Hello world"',
703                "initial_value_expression": True,
704                "sub_text": "test",
705                "order": 123,
706            },
707        )
708        self.assertEqual(response.status_code, 200)
709        self.assertJSONEqual(
710            response.content.decode(),
711            {
712                "component": "ak-stage-prompt",
713                "fields": [
714                    {
715                        "field_key": "text_prompt_expression",
716                        "label": "TEXT_LABEL",
717                        "type": "text",
718                        "required": True,
719                        "placeholder": "Hello world",
720                        "initial_value": "Hello Hello world",
721                        "order": 123,
722                        "sub_text": "test",
723                        "choices": None,
724                    }
725                ],
726            },
727        )
728
729    def test_api_preview_invalid_expression(self):
730        """Test API preview"""
731        self.client.force_login(self.user)
732        response = self.client.post(
733            reverse("authentik_api:prompt-preview"),
734            data={
735                "field_key": "text_prompt_expression",
736                "label": "TEXT_LABEL",
737                "type": FieldTypes.TEXT,
738                "placeholder": "return [",
739                "placeholder_expression": True,
740                "sub_text": "test",
741                "order": 123,
742            },
743        )
744        self.assertEqual(response.status_code, 400)
745        self.assertIn("non_field_errors", response.content.decode())
746
747
748def field_type_tester_factory(field_type: FieldTypes, required: bool):
749    """Test field for field_type"""
750
751    def tester(self: TestPromptStage):
752        prompt: Prompt = Prompt(
753            field_key="text_prompt_expression",
754            label="TEXT_LABEL",
755            type=field_type,
756            placeholder="foo",
757            placeholder_expression=False,
758            sub_text="test",
759            order=123,
760            required=required,
761        )
762        self.assertIsNotNone(prompt.field("foo"))
763
764    return tester
765
766
767for _required in (True, False):
768    for _type in FieldTypes:
769        test_name = f"test_field_type_{_type}"
770        if _required:
771            test_name += "_required"
772        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        alert_prompt = Prompt.objects.create(
335            name=generate_id(),
336            field_key="alert_prompt",
337            type=FieldTypes.ALERT_INFO,
338            required=True,
339            placeholder="alert fallback",
340            initial_value="alert content",
341        )
342        self.stage.fields.add(alert_prompt)
343        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
344        plan.context[PLAN_CONTEXT_PROMPT] = {"hidden_prompt": "hidden"}
345        self.prompt_data["hidden_prompt"] = "foo"
346        self.prompt_data["static_prompt"] = "foo"
347        self.prompt_data["alert_prompt"] = "foo"
348        challenge_response = PromptChallengeResponse(
349            None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
350        )
351        self.assertEqual(challenge_response.is_valid(), True)
352        self.assertNotEqual(challenge_response.validated_data["hidden_prompt"], "foo")
353        self.assertEqual(challenge_response.validated_data["hidden_prompt"], "hidden")
354        self.assertNotEqual(challenge_response.validated_data["static_prompt"], "foo")
355        self.assertEqual(challenge_response.validated_data["alert_prompt"], "alert content")
356
357    def test_prompt_placeholder(self):
358        """Test placeholder and expression"""
359        context = {
360            "foo": generate_id(),
361        }
362        prompt: Prompt = Prompt(
363            field_key="text_prompt_expression",
364            label="TEXT_LABEL",
365            type=FieldTypes.TEXT,
366            placeholder="return prompt_context['foo']",
367            placeholder_expression=True,
368        )
369        self.assertEqual(
370            prompt.get_placeholder(context, self.user, self.factory.get("/")), context["foo"]
371        )
372
373    def test_prompt_placeholder_does_not_take_value_from_context(self):
374        """Test placeholder does not automatically take value from context"""
375        context = {
376            "foo": generate_id(),
377        }
378        prompt: Prompt = Prompt(
379            field_key="text_prompt_expression",
380            label="TEXT_LABEL",
381            type=FieldTypes.TEXT,
382            placeholder="return prompt_context['foo']",
383            placeholder_expression=True,
384        )
385        context["text_prompt_expression"] = generate_id()
386
387        self.assertEqual(
388            prompt.get_placeholder(context, self.user, self.factory.get("/")), context["foo"]
389        )
390
391    def test_prompt_initial_value(self):
392        """Test initial_value and expression"""
393        context = {
394            "foo": generate_id(),
395        }
396        prompt: Prompt = Prompt(
397            field_key="text_prompt_expression",
398            label="TEXT_LABEL",
399            type=FieldTypes.TEXT,
400            initial_value="return prompt_context['foo']",
401            initial_value_expression=True,
402        )
403        self.assertEqual(
404            prompt.get_initial_value(context, self.user, self.factory.get("/")), context["foo"]
405        )
406        context["text_prompt_expression"] = generate_id()
407        self.assertEqual(
408            prompt.get_initial_value(context, self.user, self.factory.get("/")),
409            context["text_prompt_expression"],
410        )
411        self.assertNotEqual(
412            prompt.get_initial_value(context, self.user, self.factory.get("/")), context["foo"]
413        )
414
415    def test_choice_prompts_placeholder_and_initial_value_no_choices(self):
416        """Test placeholder and initial value of choice fields with 0 choices"""
417        context = {}
418
419        prompt: Prompt = Prompt(
420            field_key="fixed_choice_prompt_expression",
421            label="LABEL",
422            type=FieldTypes.RADIO_BUTTON_GROUP,
423            placeholder="return []",
424            placeholder_expression=True,
425            initial_value="Invalid choice",
426            initial_value_expression=False,
427        )
428        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
429        self.assertEqual(prompt.get_initial_value(context, self.user, self.factory.get("/")), "")
430        self.assertEqual(prompt.get_choices(context, self.user, self.factory.get("/")), tuple())
431
432    def test_choice_prompts_placeholder_and_initial_value_single_choice(self):
433        """Test placeholder and initial value of choice fields with 1 choice"""
434        context = {"foo": generate_id()}
435
436        prompt: Prompt = Prompt(
437            field_key="fixed_choice_prompt_expression",
438            label="LABEL",
439            type=FieldTypes.DROPDOWN,
440            placeholder=context["foo"],
441            placeholder_expression=False,
442            initial_value=context["foo"],
443            initial_value_expression=False,
444        )
445        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
446        self.assertEqual(
447            prompt.get_initial_value(context, self.user, self.factory.get("/")), context["foo"]
448        )
449        self.assertEqual(
450            prompt.get_choices(context, self.user, self.factory.get("/")), (context["foo"],)
451        )
452
453        prompt: Prompt = Prompt(
454            field_key="fixed_choice_prompt_expression",
455            label="LABEL",
456            type=FieldTypes.DROPDOWN,
457            placeholder="return [prompt_context['foo']]",
458            placeholder_expression=True,
459            initial_value="return prompt_context['foo']",
460            initial_value_expression=True,
461        )
462        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
463        self.assertEqual(
464            prompt.get_initial_value(context, self.user, self.factory.get("/")), context["foo"]
465        )
466        self.assertEqual(
467            prompt.get_choices(context, self.user, self.factory.get("/")), (context["foo"],)
468        )
469
470    def test_choice_prompts_placeholder_and_initial_value_single_choice_dict(self):
471        """Test placeholder and initial value of dict choice fields with 1 choice"""
472        context = {"foo": {"label": "foo", "value": generate_id()}}
473
474        prompt: Prompt = Prompt(
475            field_key="fixed_choice_prompt_expression",
476            label="LABEL",
477            type=FieldTypes.DROPDOWN,
478            placeholder=context["foo"],
479            placeholder_expression=False,
480            initial_value=context["foo"]["value"],
481            initial_value_expression=False,
482        )
483        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
484        self.assertEqual(
485            prompt.get_initial_value(context, self.user, self.factory.get("/")),
486            context["foo"]["value"],
487        )
488        self.assertEqual(
489            prompt.get_choices(context, self.user, self.factory.get("/")), (context["foo"],)
490        )
491
492        prompt: Prompt = Prompt(
493            field_key="fixed_choice_prompt_expression",
494            label="LABEL",
495            type=FieldTypes.DROPDOWN,
496            placeholder="return [prompt_context['foo']]",
497            placeholder_expression=True,
498            initial_value="return prompt_context['foo']['value']",
499            initial_value_expression=True,
500        )
501        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
502        self.assertEqual(
503            prompt.get_initial_value(context, self.user, self.factory.get("/")),
504            context["foo"]["value"],
505        )
506        self.assertEqual(
507            prompt.get_choices(context, self.user, self.factory.get("/")), (context["foo"],)
508        )
509
510    def test_choice_prompts_placeholder_and_initial_value_multiple_choices(self):
511        """Test placeholder and initial value of choice fields with multiple choices"""
512        context = {}
513
514        prompt: Prompt = Prompt(
515            field_key="fixed_choice_prompt_expression",
516            label="LABEL",
517            type=FieldTypes.RADIO_BUTTON_GROUP,
518            placeholder="return ['test', True, 42]",
519            placeholder_expression=True,
520        )
521        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
522        self.assertEqual(
523            prompt.get_initial_value(context, self.user, self.factory.get("/")), "test"
524        )
525        self.assertEqual(
526            prompt.get_choices(context, self.user, self.factory.get("/")), ("test", True, 42)
527        )
528
529        prompt: Prompt = Prompt(
530            field_key="fixed_choice_prompt_expression",
531            label="LABEL",
532            type=FieldTypes.RADIO_BUTTON_GROUP,
533            placeholder="return ['test', True, 42]",
534            placeholder_expression=True,
535            initial_value="return True",
536            initial_value_expression=True,
537        )
538        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
539        self.assertEqual(prompt.get_initial_value(context, self.user, self.factory.get("/")), True)
540        self.assertEqual(
541            prompt.get_choices(context, self.user, self.factory.get("/")), ("test", True, 42)
542        )
543
544    def test_choice_prompts_placeholder_and_initial_value_multiple_choices_dict(self):
545        """Test placeholder and initial value of dict choice fields with multiple dict choices"""
546        context = {}
547
548        prompt: Prompt = Prompt(
549            field_key="fixed_choice_prompt_expression",
550            label="LABEL",
551            type=FieldTypes.RADIO_BUTTON_GROUP,
552            placeholder="return ["
553            "{'label': 'Option 1', 'value': 'value1'},"
554            "{'label': 'Option 2', 'value': 'value2'}"
555            "]",
556            placeholder_expression=True,
557        )
558        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
559        self.assertEqual(
560            prompt.get_initial_value(context, self.user, self.factory.get("/")).get("value"),
561            "value1",
562        )
563        self.assertEqual(
564            tuple(
565                (choice["label"], choice["value"])
566                for choice in prompt.get_choices(context, self.user, self.factory.get("/"))
567            ),
568            (("Option 1", "value1"), ("Option 2", "value2")),
569        )
570
571        prompt: Prompt = Prompt(
572            field_key="fixed_choice_prompt_expression",
573            label="LABEL",
574            type=FieldTypes.RADIO_BUTTON_GROUP,
575            placeholder="return ["
576            "{'label': 'Option 1', 'value': 'value1'},"
577            "{'label': 'Option 2', 'value': 'value2'}"
578            "]",
579            placeholder_expression=True,
580            initial_value="return 'value2'",
581            initial_value_expression=True,
582        )
583        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
584        self.assertEqual(
585            prompt.get_initial_value(context, self.user, self.factory.get("/")), "value2"
586        )
587        self.assertEqual(
588            tuple(
589                (choice["label"], choice["value"])
590                for choice in prompt.get_choices(context, self.user, self.factory.get("/"))
591            ),
592            (("Option 1", "value1"), ("Option 2", "value2")),
593        )
594
595    def test_choice_prompts_placeholder_and_initial_value_from_context(self):
596        """Test placeholder and initial value of choice fields with values from context"""
597        rand_value = generate_id()
598        context = {
599            "fixed_choice_prompt_expression": rand_value,
600            "fixed_choice_prompt_expression__choices": ["test", 42, rand_value],
601        }
602
603        prompt: Prompt = Prompt(
604            field_key="fixed_choice_prompt_expression",
605            label="LABEL",
606            type=FieldTypes.RADIO_BUTTON_GROUP,
607        )
608        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
609        self.assertEqual(
610            prompt.get_initial_value(context, self.user, self.factory.get("/")), rand_value
611        )
612        self.assertEqual(
613            prompt.get_choices(context, self.user, self.factory.get("/")), ("test", 42, rand_value)
614        )
615
616    def test_initial_value_not_valid_choice(self):
617        """Test initial_value not a valid choice"""
618        context = {}
619        prompt: Prompt = Prompt(
620            field_key="choice_prompt",
621            label="TEXT_LABEL",
622            type=FieldTypes.DROPDOWN,
623            placeholder="choice",
624            initial_value="another_choice",
625        )
626        self.assertEqual(
627            prompt.get_choices(context, self.user, self.factory.get("/")),
628            ("choice",),
629        )
630        self.assertEqual(
631            prompt.get_initial_value(context, self.user, self.factory.get("/")),
632            "choice",
633        )
634
635    def test_choices_are_none_for_non_choice_fields(self):
636        """Test choices are None for non choice fields"""
637        context = {}
638        prompt: Prompt = Prompt(
639            field_key="text_prompt_expression",
640            label="TEXT_LABEL",
641            type=FieldTypes.TEXT,
642            placeholder="choice",
643        )
644        self.assertEqual(
645            prompt.get_choices(context, self.user, self.factory.get("/")),
646            None,
647        )
648
649    def test_prompt_placeholder_error(self):
650        """Test placeholder and expression"""
651        context = {}
652        prompt: Prompt = Prompt(
653            field_key="text_prompt_expression",
654            label="TEXT_LABEL",
655            type=FieldTypes.TEXT,
656            placeholder="something invalid dunno",
657            placeholder_expression=True,
658        )
659        self.assertEqual(
660            prompt.get_placeholder(context, self.user, self.factory.get("/")),
661            "something invalid dunno",
662        )
663
664    def test_prompt_placeholder_disabled(self):
665        """Test placeholder and expression"""
666        context = {}
667        prompt: Prompt = Prompt(
668            field_key="text_prompt_expression",
669            label="TEXT_LABEL",
670            type=FieldTypes.TEXT,
671            placeholder="return prompt_context['foo']",
672            placeholder_expression=False,
673        )
674        self.assertEqual(
675            prompt.get_placeholder(context, self.user, self.factory.get("/")), prompt.placeholder
676        )
677
678    def test_invalid_save(self):
679        """Ensure field can't be saved with invalid type"""
680        prompt: Prompt = Prompt(
681            field_key="text_prompt_expression",
682            label="TEXT_LABEL",
683            type="foo",
684            placeholder="foo",
685            placeholder_expression=False,
686            sub_text="test",
687            order=123,
688        )
689        with self.assertRaises(ValueError):
690            prompt.save()
691
692    def test_api_preview(self):
693        """Test API preview"""
694        self.client.force_login(self.user)
695        response = self.client.post(
696            reverse("authentik_api:prompt-preview"),
697            data={
698                "field_key": "text_prompt_expression",
699                "label": "TEXT_LABEL",
700                "type": FieldTypes.TEXT,
701                "placeholder": 'return "Hello world"',
702                "placeholder_expression": True,
703                "initial_value": 'return "Hello Hello world"',
704                "initial_value_expression": True,
705                "sub_text": "test",
706                "order": 123,
707            },
708        )
709        self.assertEqual(response.status_code, 200)
710        self.assertJSONEqual(
711            response.content.decode(),
712            {
713                "component": "ak-stage-prompt",
714                "fields": [
715                    {
716                        "field_key": "text_prompt_expression",
717                        "label": "TEXT_LABEL",
718                        "type": "text",
719                        "required": True,
720                        "placeholder": "Hello world",
721                        "initial_value": "Hello Hello world",
722                        "order": 123,
723                        "sub_text": "test",
724                        "choices": None,
725                    }
726                ],
727            },
728        )
729
730    def test_api_preview_invalid_expression(self):
731        """Test API preview"""
732        self.client.force_login(self.user)
733        response = self.client.post(
734            reverse("authentik_api:prompt-preview"),
735            data={
736                "field_key": "text_prompt_expression",
737                "label": "TEXT_LABEL",
738                "type": FieldTypes.TEXT,
739                "placeholder": "return [",
740                "placeholder_expression": True,
741                "sub_text": "test",
742                "order": 123,
743            },
744        )
745        self.assertEqual(response.status_code, 400)
746        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        alert_prompt = Prompt.objects.create(
335            name=generate_id(),
336            field_key="alert_prompt",
337            type=FieldTypes.ALERT_INFO,
338            required=True,
339            placeholder="alert fallback",
340            initial_value="alert content",
341        )
342        self.stage.fields.add(alert_prompt)
343        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
344        plan.context[PLAN_CONTEXT_PROMPT] = {"hidden_prompt": "hidden"}
345        self.prompt_data["hidden_prompt"] = "foo"
346        self.prompt_data["static_prompt"] = "foo"
347        self.prompt_data["alert_prompt"] = "foo"
348        challenge_response = PromptChallengeResponse(
349            None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
350        )
351        self.assertEqual(challenge_response.is_valid(), True)
352        self.assertNotEqual(challenge_response.validated_data["hidden_prompt"], "foo")
353        self.assertEqual(challenge_response.validated_data["hidden_prompt"], "hidden")
354        self.assertNotEqual(challenge_response.validated_data["static_prompt"], "foo")
355        self.assertEqual(challenge_response.validated_data["alert_prompt"], "alert content")

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

def test_prompt_placeholder(self):
357    def test_prompt_placeholder(self):
358        """Test placeholder and expression"""
359        context = {
360            "foo": generate_id(),
361        }
362        prompt: Prompt = Prompt(
363            field_key="text_prompt_expression",
364            label="TEXT_LABEL",
365            type=FieldTypes.TEXT,
366            placeholder="return prompt_context['foo']",
367            placeholder_expression=True,
368        )
369        self.assertEqual(
370            prompt.get_placeholder(context, self.user, self.factory.get("/")), context["foo"]
371        )

Test placeholder and expression

def test_prompt_placeholder_does_not_take_value_from_context(self):
373    def test_prompt_placeholder_does_not_take_value_from_context(self):
374        """Test placeholder does not automatically take value from context"""
375        context = {
376            "foo": generate_id(),
377        }
378        prompt: Prompt = Prompt(
379            field_key="text_prompt_expression",
380            label="TEXT_LABEL",
381            type=FieldTypes.TEXT,
382            placeholder="return prompt_context['foo']",
383            placeholder_expression=True,
384        )
385        context["text_prompt_expression"] = generate_id()
386
387        self.assertEqual(
388            prompt.get_placeholder(context, self.user, self.factory.get("/")), context["foo"]
389        )

Test placeholder does not automatically take value from context

def test_prompt_initial_value(self):
391    def test_prompt_initial_value(self):
392        """Test initial_value and expression"""
393        context = {
394            "foo": generate_id(),
395        }
396        prompt: Prompt = Prompt(
397            field_key="text_prompt_expression",
398            label="TEXT_LABEL",
399            type=FieldTypes.TEXT,
400            initial_value="return prompt_context['foo']",
401            initial_value_expression=True,
402        )
403        self.assertEqual(
404            prompt.get_initial_value(context, self.user, self.factory.get("/")), context["foo"]
405        )
406        context["text_prompt_expression"] = generate_id()
407        self.assertEqual(
408            prompt.get_initial_value(context, self.user, self.factory.get("/")),
409            context["text_prompt_expression"],
410        )
411        self.assertNotEqual(
412            prompt.get_initial_value(context, self.user, self.factory.get("/")), context["foo"]
413        )

Test initial_value and expression

def test_choice_prompts_placeholder_and_initial_value_no_choices(self):
415    def test_choice_prompts_placeholder_and_initial_value_no_choices(self):
416        """Test placeholder and initial value of choice fields with 0 choices"""
417        context = {}
418
419        prompt: Prompt = Prompt(
420            field_key="fixed_choice_prompt_expression",
421            label="LABEL",
422            type=FieldTypes.RADIO_BUTTON_GROUP,
423            placeholder="return []",
424            placeholder_expression=True,
425            initial_value="Invalid choice",
426            initial_value_expression=False,
427        )
428        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
429        self.assertEqual(prompt.get_initial_value(context, self.user, self.factory.get("/")), "")
430        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):
432    def test_choice_prompts_placeholder_and_initial_value_single_choice(self):
433        """Test placeholder and initial value of choice fields with 1 choice"""
434        context = {"foo": generate_id()}
435
436        prompt: Prompt = Prompt(
437            field_key="fixed_choice_prompt_expression",
438            label="LABEL",
439            type=FieldTypes.DROPDOWN,
440            placeholder=context["foo"],
441            placeholder_expression=False,
442            initial_value=context["foo"],
443            initial_value_expression=False,
444        )
445        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
446        self.assertEqual(
447            prompt.get_initial_value(context, self.user, self.factory.get("/")), context["foo"]
448        )
449        self.assertEqual(
450            prompt.get_choices(context, self.user, self.factory.get("/")), (context["foo"],)
451        )
452
453        prompt: Prompt = Prompt(
454            field_key="fixed_choice_prompt_expression",
455            label="LABEL",
456            type=FieldTypes.DROPDOWN,
457            placeholder="return [prompt_context['foo']]",
458            placeholder_expression=True,
459            initial_value="return prompt_context['foo']",
460            initial_value_expression=True,
461        )
462        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
463        self.assertEqual(
464            prompt.get_initial_value(context, self.user, self.factory.get("/")), context["foo"]
465        )
466        self.assertEqual(
467            prompt.get_choices(context, self.user, self.factory.get("/")), (context["foo"],)
468        )

Test placeholder and initial value of choice fields with 1 choice

def test_choice_prompts_placeholder_and_initial_value_single_choice_dict(self):
470    def test_choice_prompts_placeholder_and_initial_value_single_choice_dict(self):
471        """Test placeholder and initial value of dict choice fields with 1 choice"""
472        context = {"foo": {"label": "foo", "value": generate_id()}}
473
474        prompt: Prompt = Prompt(
475            field_key="fixed_choice_prompt_expression",
476            label="LABEL",
477            type=FieldTypes.DROPDOWN,
478            placeholder=context["foo"],
479            placeholder_expression=False,
480            initial_value=context["foo"]["value"],
481            initial_value_expression=False,
482        )
483        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
484        self.assertEqual(
485            prompt.get_initial_value(context, self.user, self.factory.get("/")),
486            context["foo"]["value"],
487        )
488        self.assertEqual(
489            prompt.get_choices(context, self.user, self.factory.get("/")), (context["foo"],)
490        )
491
492        prompt: Prompt = Prompt(
493            field_key="fixed_choice_prompt_expression",
494            label="LABEL",
495            type=FieldTypes.DROPDOWN,
496            placeholder="return [prompt_context['foo']]",
497            placeholder_expression=True,
498            initial_value="return prompt_context['foo']['value']",
499            initial_value_expression=True,
500        )
501        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
502        self.assertEqual(
503            prompt.get_initial_value(context, self.user, self.factory.get("/")),
504            context["foo"]["value"],
505        )
506        self.assertEqual(
507            prompt.get_choices(context, self.user, self.factory.get("/")), (context["foo"],)
508        )

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

def test_choice_prompts_placeholder_and_initial_value_multiple_choices(self):
510    def test_choice_prompts_placeholder_and_initial_value_multiple_choices(self):
511        """Test placeholder and initial value of choice fields with multiple choices"""
512        context = {}
513
514        prompt: Prompt = Prompt(
515            field_key="fixed_choice_prompt_expression",
516            label="LABEL",
517            type=FieldTypes.RADIO_BUTTON_GROUP,
518            placeholder="return ['test', True, 42]",
519            placeholder_expression=True,
520        )
521        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
522        self.assertEqual(
523            prompt.get_initial_value(context, self.user, self.factory.get("/")), "test"
524        )
525        self.assertEqual(
526            prompt.get_choices(context, self.user, self.factory.get("/")), ("test", True, 42)
527        )
528
529        prompt: Prompt = Prompt(
530            field_key="fixed_choice_prompt_expression",
531            label="LABEL",
532            type=FieldTypes.RADIO_BUTTON_GROUP,
533            placeholder="return ['test', True, 42]",
534            placeholder_expression=True,
535            initial_value="return True",
536            initial_value_expression=True,
537        )
538        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
539        self.assertEqual(prompt.get_initial_value(context, self.user, self.factory.get("/")), True)
540        self.assertEqual(
541            prompt.get_choices(context, self.user, self.factory.get("/")), ("test", True, 42)
542        )

Test placeholder and initial value of choice fields with multiple choices

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

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

def test_choice_prompts_placeholder_and_initial_value_from_context(self):
595    def test_choice_prompts_placeholder_and_initial_value_from_context(self):
596        """Test placeholder and initial value of choice fields with values from context"""
597        rand_value = generate_id()
598        context = {
599            "fixed_choice_prompt_expression": rand_value,
600            "fixed_choice_prompt_expression__choices": ["test", 42, rand_value],
601        }
602
603        prompt: Prompt = Prompt(
604            field_key="fixed_choice_prompt_expression",
605            label="LABEL",
606            type=FieldTypes.RADIO_BUTTON_GROUP,
607        )
608        self.assertEqual(prompt.get_placeholder(context, self.user, self.factory.get("/")), "")
609        self.assertEqual(
610            prompt.get_initial_value(context, self.user, self.factory.get("/")), rand_value
611        )
612        self.assertEqual(
613            prompt.get_choices(context, self.user, self.factory.get("/")), ("test", 42, rand_value)
614        )

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

def test_initial_value_not_valid_choice(self):
616    def test_initial_value_not_valid_choice(self):
617        """Test initial_value not a valid choice"""
618        context = {}
619        prompt: Prompt = Prompt(
620            field_key="choice_prompt",
621            label="TEXT_LABEL",
622            type=FieldTypes.DROPDOWN,
623            placeholder="choice",
624            initial_value="another_choice",
625        )
626        self.assertEqual(
627            prompt.get_choices(context, self.user, self.factory.get("/")),
628            ("choice",),
629        )
630        self.assertEqual(
631            prompt.get_initial_value(context, self.user, self.factory.get("/")),
632            "choice",
633        )

Test initial_value not a valid choice

def test_choices_are_none_for_non_choice_fields(self):
635    def test_choices_are_none_for_non_choice_fields(self):
636        """Test choices are None for non choice fields"""
637        context = {}
638        prompt: Prompt = Prompt(
639            field_key="text_prompt_expression",
640            label="TEXT_LABEL",
641            type=FieldTypes.TEXT,
642            placeholder="choice",
643        )
644        self.assertEqual(
645            prompt.get_choices(context, self.user, self.factory.get("/")),
646            None,
647        )

Test choices are None for non choice fields

def test_prompt_placeholder_error(self):
649    def test_prompt_placeholder_error(self):
650        """Test placeholder and expression"""
651        context = {}
652        prompt: Prompt = Prompt(
653            field_key="text_prompt_expression",
654            label="TEXT_LABEL",
655            type=FieldTypes.TEXT,
656            placeholder="something invalid dunno",
657            placeholder_expression=True,
658        )
659        self.assertEqual(
660            prompt.get_placeholder(context, self.user, self.factory.get("/")),
661            "something invalid dunno",
662        )

Test placeholder and expression

def test_prompt_placeholder_disabled(self):
664    def test_prompt_placeholder_disabled(self):
665        """Test placeholder and expression"""
666        context = {}
667        prompt: Prompt = Prompt(
668            field_key="text_prompt_expression",
669            label="TEXT_LABEL",
670            type=FieldTypes.TEXT,
671            placeholder="return prompt_context['foo']",
672            placeholder_expression=False,
673        )
674        self.assertEqual(
675            prompt.get_placeholder(context, self.user, self.factory.get("/")), prompt.placeholder
676        )

Test placeholder and expression

def test_invalid_save(self):
678    def test_invalid_save(self):
679        """Ensure field can't be saved with invalid type"""
680        prompt: Prompt = Prompt(
681            field_key="text_prompt_expression",
682            label="TEXT_LABEL",
683            type="foo",
684            placeholder="foo",
685            placeholder_expression=False,
686            sub_text="test",
687            order=123,
688        )
689        with self.assertRaises(ValueError):
690            prompt.save()

Ensure field can't be saved with invalid type

def test_api_preview(self):
692    def test_api_preview(self):
693        """Test API preview"""
694        self.client.force_login(self.user)
695        response = self.client.post(
696            reverse("authentik_api:prompt-preview"),
697            data={
698                "field_key": "text_prompt_expression",
699                "label": "TEXT_LABEL",
700                "type": FieldTypes.TEXT,
701                "placeholder": 'return "Hello world"',
702                "placeholder_expression": True,
703                "initial_value": 'return "Hello Hello world"',
704                "initial_value_expression": True,
705                "sub_text": "test",
706                "order": 123,
707            },
708        )
709        self.assertEqual(response.status_code, 200)
710        self.assertJSONEqual(
711            response.content.decode(),
712            {
713                "component": "ak-stage-prompt",
714                "fields": [
715                    {
716                        "field_key": "text_prompt_expression",
717                        "label": "TEXT_LABEL",
718                        "type": "text",
719                        "required": True,
720                        "placeholder": "Hello world",
721                        "initial_value": "Hello Hello world",
722                        "order": 123,
723                        "sub_text": "test",
724                        "choices": None,
725                    }
726                ],
727            },
728        )

Test API preview

def test_api_preview_invalid_expression(self):
730    def test_api_preview_invalid_expression(self):
731        """Test API preview"""
732        self.client.force_login(self.user)
733        response = self.client.post(
734            reverse("authentik_api:prompt-preview"),
735            data={
736                "field_key": "text_prompt_expression",
737                "label": "TEXT_LABEL",
738                "type": FieldTypes.TEXT,
739                "placeholder": "return [",
740                "placeholder_expression": True,
741                "sub_text": "test",
742                "order": 123,
743            },
744        )
745        self.assertEqual(response.status_code, 400)
746        self.assertIn("non_field_errors", response.content.decode())

Test API preview

def test_field_type_text_required(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_text_area_required(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_text_read_only_required(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_text_area_read_only_required(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_username_required(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_email_required(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_password_required(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_number_required(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_checkbox_required(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_radio-button-group_required(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_dropdown_required(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_date_required(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_date-time_required(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_file_required(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_separator_required(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_hidden_required(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_static_required(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_alert_info_required(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_alert_warning_required(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_alert_danger_required(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_ak-locale_required(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_text(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_text_area(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_text_read_only(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_text_area_read_only(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_username(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_email(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_password(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_number(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_checkbox(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_radio-button-group(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_dropdown(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_date(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_date-time(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_file(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_separator(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_hidden(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_static(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_alert_info(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_alert_warning(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_alert_danger(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))

The type of the None singleton.

def test_field_type_ak-locale(self: TestPromptStage):
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        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):
749def field_type_tester_factory(field_type: FieldTypes, required: bool):
750    """Test field for field_type"""
751
752    def tester(self: TestPromptStage):
753        prompt: Prompt = Prompt(
754            field_key="text_prompt_expression",
755            label="TEXT_LABEL",
756            type=field_type,
757            placeholder="foo",
758            placeholder_expression=False,
759            sub_text="test",
760            order=123,
761            required=required,
762        )
763        self.assertIsNotNone(prompt.field("foo"))
764
765    return tester

Test field for field_type