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