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))
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
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.
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
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
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
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
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)
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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