diff --git a/e2e/passbook.side b/e2e/passbook.side index af2abbb3c..631e930f8 100644 --- a/e2e/passbook.side +++ b/e2e/passbook.side @@ -286,6 +286,204 @@ ], "value": "foo@bar.baz" }] + }, { + "id": "1a3172e0-ac23-4781-9367-19afccee4f4a", + "name": "flows stage setup password", + "commands": [{ + "id": "77784f77-d840-4b3d-a42f-7928f02fb7e1", + "comment": "", + "command": "open", + "target": "/flows/default-authentication-flow/?next=%2F", + "targets": [], + "value": "" + }, { + "id": "783aa9a6-81e5-49c6-8789-2f360a5750b1", + "comment": "", + "command": "setWindowSize", + "target": "1699x1417", + "targets": [], + "value": "" + }, { + "id": "cb0cd63e-30e9-4443-af59-5345fe26dc88", + "comment": "", + "command": "click", + "target": "id=id_uid_field", + "targets": [ + ["id=id_uid_field", "id"], + ["name=uid_field", "name"], + ["css=#id_uid_field", "css:finder"], + ["xpath=//input[@id='id_uid_field']", "xpath:attributes"], + ["xpath=//main[@id='flow-body']/div/form/div/input", "xpath:idRelative"], + ["xpath=//div/input", "xpath:position"] + ], + "value": "" + }, { + "id": "8466ded1-c5f6-451c-b63f-0889da38503a", + "comment": "", + "command": "type", + "target": "id=id_uid_field", + "targets": [ + ["id=id_uid_field", "id"], + ["name=uid_field", "name"], + ["css=#id_uid_field", "css:finder"], + ["xpath=//input[@id='id_uid_field']", "xpath:attributes"], + ["xpath=//main[@id='flow-body']/div/form/div/input", "xpath:idRelative"], + ["xpath=//div/input", "xpath:position"] + ], + "value": "pbadmin" + }, { + "id": "27383093-d01a-4416-8fc6-9caad4926cd3", + "comment": "", + "command": "sendKeys", + "target": "id=id_uid_field", + "targets": [ + ["id=id_uid_field", "id"], + ["name=uid_field", "name"], + ["css=#id_uid_field", "css:finder"], + ["xpath=//input[@id='id_uid_field']", "xpath:attributes"], + ["xpath=//main[@id='flow-body']/div/form/div/input", "xpath:idRelative"], + ["xpath=//div/input", "xpath:position"] + ], + "value": "${KEY_ENTER}" + }, { + "id": "4602745a-0ebb-4425-a841-a1ed4899659d", + "comment": "", + "command": "type", + "target": "id=id_password", + "targets": [ + ["id=id_password", "id"], + ["name=password", "name"], + ["css=#id_password", "css:finder"], + ["xpath=//input[@id='id_password']", "xpath:attributes"], + ["xpath=//main[@id='flow-body']/div/form/div[2]/input", "xpath:idRelative"], + ["xpath=//div[2]/input", "xpath:position"] + ], + "value": "pbadmin" + }, { + "id": "d1ff4f81-d8f9-45dc-ad5d-f99b54c0cd18", + "comment": "", + "command": "sendKeys", + "target": "id=id_password", + "targets": [ + ["id=id_password", "id"], + ["name=password", "name"], + ["css=#id_password", "css:finder"], + ["xpath=//input[@id='id_password']", "xpath:attributes"], + ["xpath=//main[@id='flow-body']/div/form/div[2]/input", "xpath:idRelative"], + ["xpath=//div[2]/input", "xpath:position"] + ], + "value": "${KEY_ENTER}" + }, { + "id": "014c8f57-7ef2-469c-b700-efa94ba81b66", + "comment": "", + "command": "click", + "target": "css=.pf-c-page__header", + "targets": [ + ["css=.pf-c-page__header", "css:finder"], + ["xpath=//div[@id='page-default-nav-example']/header", "xpath:idRelative"], + ["xpath=//header", "xpath:position"] + ], + "value": "" + }, { + "id": "14e86b6f-6add-4bcc-913a-42b1e7322c79", + "comment": "", + "command": "click", + "target": "linkText=pbadmin", + "targets": [ + ["linkText=pbadmin", "linkText"], + ["css=.pf-c-page__header-tools-group:nth-child(2) > .pf-c-button", "css:finder"], + ["xpath=//a[contains(text(),'pbadmin')]", "xpath:link"], + ["xpath=//div[@id='page-default-nav-example']/header/div[3]/div[2]/a", "xpath:idRelative"], + ["xpath=//a[contains(@href, '/-/user/')]", "xpath:href"], + ["xpath=//div[2]/a", "xpath:position"], + ["xpath=//a[contains(.,'pbadmin')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "8280da13-632e-4cba-9e18-ecae0d57d052", + "comment": "", + "command": "click", + "target": "linkText=Change password", + "targets": [ + ["linkText=Change password", "linkText"], + ["css=.pf-c-nav__section:nth-child(2) .pf-c-nav__link", "css:finder"], + ["xpath=//a[contains(text(),'Change password')]", "xpath:link"], + ["xpath=//nav[@id='page-default-nav-example-primary-nav']/section[2]/ul/li/a", "xpath:idRelative"], + ["xpath=//a[contains(@href, '/-/user/stage/password/b929b529-e384-4409-8d40-ac4a195fcab2/change/?next=%2F-%2Fuser%2F')]", "xpath:href"], + ["xpath=//section[2]/ul/li/a", "xpath:position"], + ["xpath=//a[contains(.,'Change password')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "716d7e0c-79dc-469b-a31f-dceaa0765e9c", + "comment": "", + "command": "click", + "target": "id=id_password", + "targets": [ + ["id=id_password", "id"], + ["name=password", "name"], + ["css=#id_password", "css:finder"], + ["xpath=//input[@id='id_password']", "xpath:attributes"], + ["xpath=//main[@id='flow-body']/div/form/div/input", "xpath:idRelative"], + ["xpath=//div/input", "xpath:position"] + ], + "value": "" + }, { + "id": "77005d70-adf0-4add-8329-b092d43f829a", + "comment": "", + "command": "type", + "target": "id=id_password", + "targets": [ + ["id=id_password", "id"], + ["name=password", "name"], + ["css=#id_password", "css:finder"], + ["xpath=//input[@id='id_password']", "xpath:attributes"], + ["xpath=//main[@id='flow-body']/div/form/div/input", "xpath:idRelative"], + ["xpath=//div/input", "xpath:position"] + ], + "value": "test" + }, { + "id": "965ca365-99f4-45d1-97c3-c944269341b9", + "comment": "", + "command": "click", + "target": "id=id_password_repeat", + "targets": [ + ["id=id_password_repeat", "id"], + ["name=password_repeat", "name"], + ["css=#id_password_repeat", "css:finder"], + ["xpath=//input[@id='id_password_repeat']", "xpath:attributes"], + ["xpath=//main[@id='flow-body']/div/form/div[2]/input", "xpath:idRelative"], + ["xpath=//div[2]/input", "xpath:position"] + ], + "value": "" + }, { + "id": "9b421468-c65e-4943-b6b1-1e80410a6b87", + "comment": "", + "command": "type", + "target": "id=id_password_repeat", + "targets": [ + ["id=id_password_repeat", "id"], + ["name=password_repeat", "name"], + ["css=#id_password_repeat", "css:finder"], + ["xpath=//input[@id='id_password_repeat']", "xpath:attributes"], + ["xpath=//main[@id='flow-body']/div/form/div[2]/input", "xpath:idRelative"], + ["xpath=//div[2]/input", "xpath:position"] + ], + "value": "test" + }, { + "id": "572c1400-a0f2-499f-808a-18c1f56bf13f", + "comment": "", + "command": "click", + "target": "css=.pf-c-button", + "targets": [ + ["css=.pf-c-button", "css:finder"], + ["xpath=//button[@type='submit']", "xpath:attributes"], + ["xpath=//main[@id='flow-body']/div/form/div[3]/button", "xpath:idRelative"], + ["xpath=//button", "xpath:position"], + ["xpath=//button[contains(.,'Continue')]", "xpath:innerText"] + ], + "value": "" + }] }], "suites": [{ "id": "495657fb-3f5e-4431-877c-4d0b248c0841", diff --git a/e2e/test_enroll.py b/e2e/test_enroll.py deleted file mode 100644 index 38935bc94..000000000 --- a/e2e/test_enroll.py +++ /dev/null @@ -1,477 +0,0 @@ -"""Test Enroll flow""" -from time import sleep - -from django.test import override_settings -from selenium.webdriver.common.by import By -from selenium.webdriver.common.keys import Keys -from selenium.webdriver.support import expected_conditions as ec - -from docker import DockerClient, from_env -from docker.models.containers import Container -from docker.types import Healthcheck -from e2e.utils import USER, SeleniumTestCase -from passbook.flows.models import Flow, FlowDesignation, FlowStageBinding -from passbook.policies.expression.models import ExpressionPolicy -from passbook.policies.models import PolicyBinding -from passbook.stages.email.models import EmailStage, EmailTemplates -from passbook.stages.identification.models import IdentificationStage -from passbook.stages.prompt.models import FieldTypes, Prompt, PromptStage -from passbook.stages.user_login.models import UserLoginStage -from passbook.stages.user_write.models import UserWriteStage - - -class TestEnroll(SeleniumTestCase): - """Test Enroll flow""" - - def setUp(self): - super().setUp() - self.container = self.setup_client() - - def setup_client(self) -> Container: - """Setup test IdP container""" - client: DockerClient = from_env() - container = client.containers.run( - image="mailhog/mailhog", - detach=True, - network_mode="host", - auto_remove=True, - healthcheck=Healthcheck( - test=["CMD", "wget", "-s", "http://localhost:8025"], - interval=5 * 100 * 1000000, - start_period=1 * 100 * 1000000, - ), - ) - while True: - container.reload() - status = container.attrs.get("State", {}).get("Health", {}).get("Status") - if status == "healthy": - return container - sleep(1) - - def tearDown(self): - self.container.kill() - super().tearDown() - - # pylint: disable=too-many-statements - def setup_test_enroll_2_step(self): - """Setup all required objects""" - self.driver.find_element(By.ID, "id_uid_field").send_keys(USER().username) - self.driver.find_element(By.ID, "id_uid_field").send_keys(Keys.ENTER) - self.driver.find_element(By.ID, "id_password").send_keys(USER().username) - self.driver.find_element(By.ID, "id_password").send_keys(Keys.ENTER) - self.driver.find_element(By.LINK_TEXT, "Administrate").click() - self.driver.find_element(By.LINK_TEXT, "Prompts").click() - - # Create Password Prompt - self.driver.find_element(By.LINK_TEXT, "Create").click() - self.driver.find_element(By.ID, "id_field_key").send_keys("password") - self.driver.find_element(By.ID, "id_label").send_keys("Password") - dropdown = self.driver.find_element(By.ID, "id_type") - dropdown.find_element(By.XPATH, "//option[. = 'Password']").click() - self.driver.find_element(By.ID, "id_placeholder").send_keys("Password") - self.driver.find_element(By.ID, "id_order").send_keys("1") - self.driver.find_element( - By.CSS_SELECTOR, ".pf-c-form__actions > .pf-m-primary" - ).click() - - # Create Password Repeat Prompt - self.driver.find_element(By.LINK_TEXT, "Create").click() - self.driver.find_element(By.ID, "id_field_key").send_keys("password_repeat") - self.driver.find_element(By.ID, "id_label").send_keys("Password (repeat)") - dropdown = self.driver.find_element(By.ID, "id_type") - dropdown.find_element(By.XPATH, "//option[. = 'Password']").click() - self.driver.find_element(By.ID, "id_placeholder").send_keys("Password (repeat)") - self.driver.find_element(By.ID, "id_order").send_keys("2") - self.driver.find_element( - By.CSS_SELECTOR, ".pf-c-form__actions > .pf-m-primary" - ).click() - - # Create Name Prompt - self.driver.find_element(By.LINK_TEXT, "Create").click() - self.driver.find_element(By.ID, "id_field_key").send_keys("name") - self.driver.find_element(By.ID, "id_label").send_keys("Name") - dropdown = self.driver.find_element(By.ID, "id_type") - dropdown.find_element(By.XPATH, "//option[. = 'Text']").click() - self.driver.find_element(By.ID, "id_placeholder").send_keys("Name") - self.driver.find_element(By.ID, "id_order").send_keys("0") - self.driver.find_element( - By.CSS_SELECTOR, ".pf-c-form__actions > .pf-m-primary" - ).click() - - # Create Email Prompt - self.driver.find_element(By.LINK_TEXT, "Create").click() - self.driver.find_element(By.ID, "id_field_key").send_keys("email") - self.driver.find_element(By.ID, "id_label").send_keys("Email") - dropdown = self.driver.find_element(By.ID, "id_type") - dropdown.find_element(By.XPATH, "//option[. = 'Email']").click() - self.driver.find_element(By.ID, "id_placeholder").send_keys("Email") - self.driver.find_element(By.ID, "id_order").send_keys("1") - self.driver.find_element( - By.CSS_SELECTOR, ".pf-c-form__actions > .pf-m-primary" - ).click() - - self.driver.find_element(By.LINK_TEXT, "Stages").click() - - # Create first enroll prompt stage - self.driver.find_element(By.CSS_SELECTOR, ".pf-c-dropdown__toggle").click() - self.driver.find_element( - By.CSS_SELECTOR, "li:nth-child(9) > .pf-c-dropdown__menu-item > small" - ).click() - self.driver.find_element(By.ID, "id_name").send_keys( - "enroll-prompt-stage-first" - ) - dropdown = self.driver.find_element(By.ID, "id_fields") - dropdown.find_element( - By.XPATH, "//option[. = \"Prompt 'username' type=text\"]" - ).click() - dropdown.find_element( - By.XPATH, "//option[. = \"Prompt 'password' type=password\"]" - ).click() - dropdown.find_element( - By.XPATH, "//option[. = \"Prompt 'password_repeat' type=password\"]" - ).click() - self.driver.find_element( - By.CSS_SELECTOR, ".pf-c-form__actions > .pf-m-primary" - ).click() - - # Create second enroll prompt stage - self.driver.find_element(By.CSS_SELECTOR, ".pf-c-dropdown__toggle").click() - self.driver.find_element( - By.CSS_SELECTOR, "li:nth-child(9) > .pf-c-dropdown__menu-item" - ).click() - self.driver.find_element(By.ID, "id_name").send_keys( - "enroll-prompt-stage-second" - ) - dropdown = self.driver.find_element(By.ID, "id_fields") - dropdown.find_element( - By.XPATH, "//option[. = \"Prompt 'name' type=text\"]" - ).click() - dropdown.find_element( - By.XPATH, "//option[. = \"Prompt 'email' type=email\"]" - ).click() - self.driver.find_element( - By.CSS_SELECTOR, ".pf-c-form__actions > .pf-m-primary" - ).click() - - # Create user write stage - self.driver.find_element(By.CSS_SELECTOR, ".pf-c-dropdown__toggle").click() - self.driver.find_element( - By.CSS_SELECTOR, "li:nth-child(13) > .pf-c-dropdown__menu-item" - ).click() - self.driver.find_element(By.ID, "id_name").send_keys("enroll-user-write") - self.driver.find_element(By.ID, "id_name").send_keys(Keys.ENTER) - self.driver.find_element(By.CSS_SELECTOR, ".pf-c-dropdown__toggle").click() - - # Create user login stage - self.driver.find_element( - By.CSS_SELECTOR, "li:nth-child(11) > .pf-c-dropdown__menu-item" - ).click() - self.driver.find_element(By.ID, "id_name").send_keys("enroll-user-login") - self.driver.find_element( - By.CSS_SELECTOR, ".pf-c-form__actions > .pf-m-primary" - ).click() - - self.driver.find_element( - By.CSS_SELECTOR, - ".pf-c-nav__item:nth-child(7) .pf-c-nav__item:nth-child(1) > .pf-c-nav__link", - ).click() - - # Create password policy - self.driver.find_element(By.CSS_SELECTOR, ".pf-c-dropdown__toggle").click() - self.driver.find_element( - By.CSS_SELECTOR, "li:nth-child(2) > .pf-c-dropdown__menu-item > small" - ).click() - self.driver.find_element(By.ID, "id_name").send_keys( - "policy-enrollment-password-equals" - ) - self.wait.until( - ec.presence_of_element_located((By.CSS_SELECTOR, ".CodeMirror-scroll")) - ) - self.driver.find_element(By.CSS_SELECTOR, ".CodeMirror-scroll").click() - self.driver.find_element(By.CSS_SELECTOR, ".CodeMirror textarea").send_keys( - "return request.context['password'] == request.context['password_repeat']" - ) - self.driver.find_element( - By.CSS_SELECTOR, ".pf-c-form__actions > .pf-m-primary" - ).click() - - # Create password policy binding - self.driver.find_element( - By.CSS_SELECTOR, - ".pf-c-nav__item:nth-child(7) .pf-c-nav__item:nth-child(2) > .pf-c-nav__link", - ).click() - self.driver.find_element(By.LINK_TEXT, "Create").click() - dropdown = self.driver.find_element(By.ID, "id_policy") - dropdown.find_element( - By.XPATH, '//option[. = "Policy policy-enrollment-password-equals"]' - ).click() - self.driver.find_element(By.ID, "id_target").click() - dropdown = self.driver.find_element(By.ID, "id_target") - dropdown.find_element( - By.XPATH, '//option[. = "Prompt Stage enroll-prompt-stage-first"]' - ).click() - self.driver.find_element(By.ID, "id_order").send_keys("0") - self.driver.find_element( - By.CSS_SELECTOR, ".pf-c-form__actions > .pf-m-primary" - ).click() - - # Create Flow - self.driver.find_element( - By.CSS_SELECTOR, - ".pf-c-nav__item:nth-child(6) .pf-c-nav__item:nth-child(1) > .pf-c-nav__link", - ).click() - self.driver.find_element(By.LINK_TEXT, "Create").click() - self.driver.find_element(By.ID, "id_name").send_keys("Welcome") - self.driver.find_element(By.ID, "id_slug").clear() - self.driver.find_element(By.ID, "id_slug").send_keys("default-enrollment-flow") - dropdown = self.driver.find_element(By.ID, "id_designation") - dropdown.find_element(By.XPATH, '//option[. = "Enrollment"]').click() - self.driver.find_element( - By.CSS_SELECTOR, ".pf-c-form__actions > .pf-m-primary" - ).click() - - self.driver.find_element(By.LINK_TEXT, "Stages").click() - - # Edit identification stage - self.driver.find_element( - By.CSS_SELECTOR, "tr:nth-child(11) .pf-m-secondary" - ).click() - self.driver.find_element( - By.CSS_SELECTOR, - ".pf-c-form__group:nth-child(5) .pf-c-form__horizontal-group", - ).click() - self.driver.find_element(By.ID, "id_enrollment_flow").click() - dropdown = self.driver.find_element(By.ID, "id_enrollment_flow") - dropdown.find_element( - By.XPATH, '//option[. = "Flow Welcome (default-enrollment-flow)"]' - ).click() - self.driver.find_element(By.ID, "id_user_fields_add_all_link").click() - self.driver.find_element( - By.CSS_SELECTOR, ".pf-c-form__actions > .pf-m-primary" - ).click() - - self.driver.find_element(By.LINK_TEXT, "Bindings").click() - - # Create Stage binding for first prompt stage - self.driver.find_element(By.LINK_TEXT, "Create").click() - self.driver.find_element(By.ID, "id_flow").click() - dropdown = self.driver.find_element(By.ID, "id_flow") - dropdown.find_element( - By.XPATH, '//option[. = "Flow Welcome (default-enrollment-flow)"]' - ).click() - self.driver.find_element(By.CSS_SELECTOR, ".pf-c-form").click() - self.driver.find_element(By.ID, "id_stage").click() - dropdown = self.driver.find_element(By.ID, "id_stage") - dropdown.find_element( - By.XPATH, '//option[. = "Stage enroll-prompt-stage-first"]' - ).click() - self.driver.find_element(By.ID, "id_order").click() - self.driver.find_element(By.ID, "id_order").send_keys("0") - self.driver.find_element( - By.CSS_SELECTOR, ".pf-c-form__actions > .pf-m-primary" - ).click() - - # Create Stage binding for second prompt stage - self.driver.find_element(By.LINK_TEXT, "Create").click() - self.driver.find_element(By.ID, "id_flow").click() - dropdown = self.driver.find_element(By.ID, "id_flow") - dropdown.find_element( - By.XPATH, '//option[. = "Flow Welcome (default-enrollment-flow)"]' - ).click() - self.driver.find_element(By.ID, "id_stage").click() - dropdown = self.driver.find_element(By.ID, "id_stage") - dropdown.find_element( - By.XPATH, '//option[. = "Stage enroll-prompt-stage-second"]' - ).click() - self.driver.find_element(By.ID, "id_order").click() - self.driver.find_element(By.ID, "id_order").send_keys("1") - self.driver.find_element( - By.CSS_SELECTOR, ".pf-c-form__actions > .pf-m-primary" - ).click() - - # Create Stage binding for user write stage - self.driver.find_element(By.LINK_TEXT, "Create").click() - self.driver.find_element(By.ID, "id_flow").click() - dropdown = self.driver.find_element(By.ID, "id_flow") - dropdown.find_element( - By.XPATH, '//option[. = "Flow Welcome (default-enrollment-flow)"]' - ).click() - self.driver.find_element(By.ID, "id_stage").click() - dropdown = self.driver.find_element(By.ID, "id_stage") - dropdown.find_element( - By.XPATH, '//option[. = "Stage enroll-user-write"]' - ).click() - self.driver.find_element(By.ID, "id_order").click() - self.driver.find_element(By.ID, "id_order").send_keys("2") - self.driver.find_element( - By.CSS_SELECTOR, ".pf-c-form__actions > .pf-m-primary" - ).click() - - # Create Stage binding for user login stage - self.driver.find_element(By.LINK_TEXT, "Create").click() - dropdown = self.driver.find_element(By.ID, "id_flow") - dropdown.find_element( - By.XPATH, '//option[. = "Flow Welcome (default-enrollment-flow)"]' - ).click() - dropdown = self.driver.find_element(By.ID, "id_stage") - dropdown.find_element( - By.XPATH, '//option[. = "Stage enroll-user-login"]' - ).click() - self.driver.find_element(By.ID, "id_order").send_keys("3") - self.driver.find_element( - By.CSS_SELECTOR, ".pf-c-form__actions > .pf-m-primary" - ).click() - - self.driver.find_element(By.CSS_SELECTOR, "[aria-label=logout]").click() - - def test_enroll_2_step(self): - """Test 2-step enroll flow""" - self.driver.get(self.live_server_url) - self.setup_test_enroll_2_step() - self.wait.until( - ec.presence_of_element_located((By.CSS_SELECTOR, "[role=enroll]")) - ) - self.driver.find_element(By.CSS_SELECTOR, "[role=enroll]").click() - - self.wait.until(ec.presence_of_element_located((By.ID, "id_username"))) - self.driver.find_element(By.ID, "id_username").send_keys("foo") - self.driver.find_element(By.ID, "id_password").send_keys(USER().username) - self.driver.find_element(By.ID, "id_password_repeat").send_keys(USER().username) - self.driver.find_element(By.CSS_SELECTOR, ".pf-c-button").click() - self.driver.find_element(By.ID, "id_name").send_keys("some name") - self.driver.find_element(By.ID, "id_email").send_keys("foo@bar.baz") - self.driver.find_element(By.CSS_SELECTOR, ".pf-c-button").click() - - self.wait.until(ec.presence_of_element_located((By.LINK_TEXT, "foo"))) - self.driver.find_element(By.LINK_TEXT, "foo").click() - - self.wait_for_url(self.url("passbook_core:user-settings")) - self.assertEqual( - self.driver.find_element(By.XPATH, "//a[contains(@href, '/-/user/')]").text, - "foo", - ) - self.assertEqual( - self.driver.find_element(By.ID, "id_username").get_attribute("value"), "foo" - ) - self.assertEqual( - self.driver.find_element(By.ID, "id_name").get_attribute("value"), - "some name", - ) - self.assertEqual( - self.driver.find_element(By.ID, "id_email").get_attribute("value"), - "foo@bar.baz", - ) - - @override_settings(EMAIL_BACKEND="django.core.mail.backends.smtp.EmailBackend") - def test_enroll_email(self): - """Test enroll with Email verification""" - # First stage fields - username_prompt = Prompt.objects.create( - field_key="username", label="Username", order=0, type=FieldTypes.TEXT - ) - password = Prompt.objects.create( - field_key="password", label="Password", order=1, type=FieldTypes.PASSWORD - ) - password_repeat = Prompt.objects.create( - field_key="password_repeat", - label="Password (repeat)", - order=2, - type=FieldTypes.PASSWORD, - ) - - # Second stage fields - name_field = Prompt.objects.create( - field_key="name", label="Name", order=0, type=FieldTypes.TEXT - ) - email = Prompt.objects.create( - field_key="email", label="E-Mail", order=1, type=FieldTypes.EMAIL - ) - - # Stages - first_stage = PromptStage.objects.create(name="prompt-stage-first") - first_stage.fields.set([username_prompt, password, password_repeat]) - first_stage.save() - second_stage = PromptStage.objects.create(name="prompt-stage-second") - second_stage.fields.set([name_field, email]) - second_stage.save() - email_stage = EmailStage.objects.create( - name="enroll-email", - host="localhost", - port=1025, - template=EmailTemplates.ACCOUNT_CONFIRM, - ) - user_write = UserWriteStage.objects.create(name="enroll-user-write") - user_login = UserLoginStage.objects.create(name="enroll-user-login") - - # Password checking policy - password_policy = ExpressionPolicy.objects.create( - name="policy-enrollment-password-equals", - expression="return request.context['password'] == request.context['password_repeat']", - ) - PolicyBinding.objects.create( - target=first_stage, policy=password_policy, order=0 - ) - - flow = Flow.objects.create( - name="default-enrollment-flow", - slug="default-enrollment-flow", - designation=FlowDesignation.ENROLLMENT, - ) - - # Attach enrollment flow to identification stage - ident_stage: IdentificationStage = IdentificationStage.objects.first() - ident_stage.enrollment_flow = flow - ident_stage.save() - - FlowStageBinding.objects.create(flow=flow, stage=first_stage, order=0) - FlowStageBinding.objects.create(flow=flow, stage=second_stage, order=1) - FlowStageBinding.objects.create(flow=flow, stage=user_write, order=2) - FlowStageBinding.objects.create(flow=flow, stage=email_stage, order=3) - FlowStageBinding.objects.create(flow=flow, stage=user_login, order=4) - - self.driver.get(self.live_server_url) - self.driver.find_element(By.CSS_SELECTOR, "[role=enroll]").click() - self.driver.find_element(By.ID, "id_username").send_keys("foo") - self.driver.find_element(By.ID, "id_password").send_keys(USER().username) - self.driver.find_element(By.ID, "id_password_repeat").send_keys(USER().username) - self.driver.find_element(By.CSS_SELECTOR, ".pf-c-button").click() - self.driver.find_element(By.ID, "id_name").send_keys("some name") - self.driver.find_element(By.ID, "id_email").send_keys("foo@bar.baz") - self.driver.find_element(By.CSS_SELECTOR, ".pf-c-button").click() - sleep(3) - - # Open Mailhog - self.driver.get("http://localhost:8025") - - # Click on first message - self.driver.find_element(By.CLASS_NAME, "msglist-message").click() - sleep(3) - self.driver.switch_to.frame(self.driver.find_element(By.CLASS_NAME, "tab-pane")) - self.driver.find_element(By.ID, "confirm").click() - self.driver.close() - self.driver.switch_to.window(self.driver.window_handles[0]) - - # We're now logged in - sleep(3) - self.wait.until( - ec.presence_of_element_located( - (By.XPATH, "//a[contains(@href, '/-/user/')]") - ) - ) - self.driver.find_element(By.XPATH, "//a[contains(@href, '/-/user/')]").click() - - self.assertEqual( - self.driver.find_element(By.XPATH, "//a[contains(@href, '/-/user/')]").text, - "foo", - ) - self.assertEqual( - self.driver.find_element(By.ID, "id_username").get_attribute("value"), "foo" - ) - self.assertEqual( - self.driver.find_element(By.ID, "id_name").get_attribute("value"), - "some name", - ) - self.assertEqual( - self.driver.find_element(By.ID, "id_email").get_attribute("value"), - "foo@bar.baz", - ) diff --git a/e2e/test_flows_enroll.py b/e2e/test_flows_enroll.py new file mode 100644 index 000000000..0fec78d3f --- /dev/null +++ b/e2e/test_flows_enroll.py @@ -0,0 +1,260 @@ +"""Test Enroll flow""" +from time import sleep + +from django.test import override_settings +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as ec + +from docker import DockerClient, from_env +from docker.models.containers import Container +from docker.types import Healthcheck +from e2e.utils import USER, SeleniumTestCase +from passbook.flows.models import Flow, FlowDesignation, FlowStageBinding +from passbook.policies.expression.models import ExpressionPolicy +from passbook.policies.models import PolicyBinding +from passbook.stages.email.models import EmailStage, EmailTemplates +from passbook.stages.identification.models import IdentificationStage +from passbook.stages.prompt.models import FieldTypes, Prompt, PromptStage +from passbook.stages.user_login.models import UserLoginStage +from passbook.stages.user_write.models import UserWriteStage + + +class TestFlowsEnroll(SeleniumTestCase): + """Test Enroll flow""" + + def setUp(self): + super().setUp() + self.container = self.setup_client() + + def setup_client(self) -> Container: + """Setup test IdP container""" + client: DockerClient = from_env() + container = client.containers.run( + image="mailhog/mailhog", + detach=True, + network_mode="host", + auto_remove=True, + healthcheck=Healthcheck( + test=["CMD", "wget", "-s", "http://localhost:8025"], + interval=5 * 100 * 1000000, + start_period=1 * 100 * 1000000, + ), + ) + while True: + container.reload() + status = container.attrs.get("State", {}).get("Health", {}).get("Status") + if status == "healthy": + return container + sleep(1) + + def tearDown(self): + self.container.kill() + super().tearDown() + + def test_enroll_2_step(self): + """Test 2-step enroll flow""" + # First stage fields + username_prompt = Prompt.objects.create( + field_key="username", label="Username", order=0, type=FieldTypes.TEXT + ) + password = Prompt.objects.create( + field_key="password", label="Password", order=1, type=FieldTypes.PASSWORD + ) + password_repeat = Prompt.objects.create( + field_key="password_repeat", + label="Password (repeat)", + order=2, + type=FieldTypes.PASSWORD, + ) + + # Second stage fields + name_field = Prompt.objects.create( + field_key="name", label="Name", order=0, type=FieldTypes.TEXT + ) + email = Prompt.objects.create( + field_key="email", label="E-Mail", order=1, type=FieldTypes.EMAIL + ) + + # Stages + first_stage = PromptStage.objects.create(name="prompt-stage-first") + first_stage.fields.set([username_prompt, password, password_repeat]) + first_stage.save() + second_stage = PromptStage.objects.create(name="prompt-stage-second") + second_stage.fields.set([name_field, email]) + second_stage.save() + user_write = UserWriteStage.objects.create(name="enroll-user-write") + user_login = UserLoginStage.objects.create(name="enroll-user-login") + + # Password checking policy + password_policy = ExpressionPolicy.objects.create( + name="policy-enrollment-password-equals", + expression="return request.context['password'] == request.context['password_repeat']", + ) + PolicyBinding.objects.create( + target=first_stage, policy=password_policy, order=0 + ) + + flow = Flow.objects.create( + name="default-enrollment-flow", + slug="default-enrollment-flow", + designation=FlowDesignation.ENROLLMENT, + ) + + # Attach enrollment flow to identification stage + ident_stage: IdentificationStage = IdentificationStage.objects.first() + ident_stage.enrollment_flow = flow + ident_stage.save() + + FlowStageBinding.objects.create(flow=flow, stage=first_stage, order=0) + FlowStageBinding.objects.create(flow=flow, stage=second_stage, order=1) + FlowStageBinding.objects.create(flow=flow, stage=user_write, order=2) + FlowStageBinding.objects.create(flow=flow, stage=user_login, order=3) + + self.driver.get(self.live_server_url) + self.wait.until( + ec.presence_of_element_located((By.CSS_SELECTOR, "[role=enroll]")) + ) + self.driver.find_element(By.CSS_SELECTOR, "[role=enroll]").click() + + self.wait.until(ec.presence_of_element_located((By.ID, "id_username"))) + self.driver.find_element(By.ID, "id_username").send_keys("foo") + self.driver.find_element(By.ID, "id_password").send_keys(USER().username) + self.driver.find_element(By.ID, "id_password_repeat").send_keys(USER().username) + self.driver.find_element(By.CSS_SELECTOR, ".pf-c-button").click() + self.driver.find_element(By.ID, "id_name").send_keys("some name") + self.driver.find_element(By.ID, "id_email").send_keys("foo@bar.baz") + self.driver.find_element(By.CSS_SELECTOR, ".pf-c-button").click() + + self.wait.until(ec.presence_of_element_located((By.LINK_TEXT, "foo"))) + self.driver.find_element(By.LINK_TEXT, "foo").click() + + self.wait_for_url(self.url("passbook_core:user-settings")) + self.assertEqual( + self.driver.find_element(By.XPATH, "//a[contains(@href, '/-/user/')]").text, + "foo", + ) + self.assertEqual( + self.driver.find_element(By.ID, "id_username").get_attribute("value"), "foo" + ) + self.assertEqual( + self.driver.find_element(By.ID, "id_name").get_attribute("value"), + "some name", + ) + self.assertEqual( + self.driver.find_element(By.ID, "id_email").get_attribute("value"), + "foo@bar.baz", + ) + + @override_settings(EMAIL_BACKEND="django.core.mail.backends.smtp.EmailBackend") + def test_enroll_email(self): + """Test enroll with Email verification""" + # First stage fields + username_prompt = Prompt.objects.create( + field_key="username", label="Username", order=0, type=FieldTypes.TEXT + ) + password = Prompt.objects.create( + field_key="password", label="Password", order=1, type=FieldTypes.PASSWORD + ) + password_repeat = Prompt.objects.create( + field_key="password_repeat", + label="Password (repeat)", + order=2, + type=FieldTypes.PASSWORD, + ) + + # Second stage fields + name_field = Prompt.objects.create( + field_key="name", label="Name", order=0, type=FieldTypes.TEXT + ) + email = Prompt.objects.create( + field_key="email", label="E-Mail", order=1, type=FieldTypes.EMAIL + ) + + # Stages + first_stage = PromptStage.objects.create(name="prompt-stage-first") + first_stage.fields.set([username_prompt, password, password_repeat]) + first_stage.save() + second_stage = PromptStage.objects.create(name="prompt-stage-second") + second_stage.fields.set([name_field, email]) + second_stage.save() + email_stage = EmailStage.objects.create( + name="enroll-email", + host="localhost", + port=1025, + template=EmailTemplates.ACCOUNT_CONFIRM, + ) + user_write = UserWriteStage.objects.create(name="enroll-user-write") + user_login = UserLoginStage.objects.create(name="enroll-user-login") + + # Password checking policy + password_policy = ExpressionPolicy.objects.create( + name="policy-enrollment-password-equals", + expression="return request.context['password'] == request.context['password_repeat']", + ) + PolicyBinding.objects.create( + target=first_stage, policy=password_policy, order=0 + ) + + flow = Flow.objects.create( + name="default-enrollment-flow", + slug="default-enrollment-flow", + designation=FlowDesignation.ENROLLMENT, + ) + + # Attach enrollment flow to identification stage + ident_stage: IdentificationStage = IdentificationStage.objects.first() + ident_stage.enrollment_flow = flow + ident_stage.save() + + FlowStageBinding.objects.create(flow=flow, stage=first_stage, order=0) + FlowStageBinding.objects.create(flow=flow, stage=second_stage, order=1) + FlowStageBinding.objects.create(flow=flow, stage=user_write, order=2) + FlowStageBinding.objects.create(flow=flow, stage=email_stage, order=3) + FlowStageBinding.objects.create(flow=flow, stage=user_login, order=4) + + self.driver.get(self.live_server_url) + self.driver.find_element(By.CSS_SELECTOR, "[role=enroll]").click() + self.driver.find_element(By.ID, "id_username").send_keys("foo") + self.driver.find_element(By.ID, "id_password").send_keys(USER().username) + self.driver.find_element(By.ID, "id_password_repeat").send_keys(USER().username) + self.driver.find_element(By.CSS_SELECTOR, ".pf-c-button").click() + self.driver.find_element(By.ID, "id_name").send_keys("some name") + self.driver.find_element(By.ID, "id_email").send_keys("foo@bar.baz") + self.driver.find_element(By.CSS_SELECTOR, ".pf-c-button").click() + sleep(3) + + # Open Mailhog + self.driver.get("http://localhost:8025") + + # Click on first message + self.driver.find_element(By.CLASS_NAME, "msglist-message").click() + sleep(3) + self.driver.switch_to.frame(self.driver.find_element(By.CLASS_NAME, "tab-pane")) + self.driver.find_element(By.ID, "confirm").click() + self.driver.close() + self.driver.switch_to.window(self.driver.window_handles[0]) + + # We're now logged in + sleep(3) + self.wait.until( + ec.presence_of_element_located( + (By.XPATH, "//a[contains(@href, '/-/user/')]") + ) + ) + self.driver.find_element(By.XPATH, "//a[contains(@href, '/-/user/')]").click() + + self.assertEqual( + self.driver.find_element(By.XPATH, "//a[contains(@href, '/-/user/')]").text, + "foo", + ) + self.assertEqual( + self.driver.find_element(By.ID, "id_username").get_attribute("value"), "foo" + ) + self.assertEqual( + self.driver.find_element(By.ID, "id_name").get_attribute("value"), + "some name", + ) + self.assertEqual( + self.driver.find_element(By.ID, "id_email").get_attribute("value"), + "foo@bar.baz", + ) diff --git a/e2e/test_login_default.py b/e2e/test_flows_login.py similarity index 95% rename from e2e/test_login_default.py rename to e2e/test_flows_login.py index 5b0d8bcad..c92b3cbc6 100644 --- a/e2e/test_login_default.py +++ b/e2e/test_flows_login.py @@ -5,7 +5,7 @@ from selenium.webdriver.common.keys import Keys from e2e.utils import USER, SeleniumTestCase -class TestLogin(SeleniumTestCase): +class TestFlowsLogin(SeleniumTestCase): """test default login flow""" def test_login(self): diff --git a/e2e/test_flows_stage_setup.py b/e2e/test_flows_stage_setup.py new file mode 100644 index 000000000..ca32a99d2 --- /dev/null +++ b/e2e/test_flows_stage_setup.py @@ -0,0 +1,41 @@ +"""test stage setup flows (password change)""" +import string +from random import SystemRandom +from time import sleep + +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys + +from e2e.utils import USER, SeleniumTestCase +from passbook.core.models import User + + +class TestFlowsStageSetup(SeleniumTestCase): + """test stage setup flows""" + + def test_password_change(self): + """test password change flow""" + new_password = "".join( + SystemRandom().choice(string.ascii_uppercase + string.digits) + for _ in range(8) + ) + + self.driver.get( + f"{self.live_server_url}/flows/default-authentication-flow/?next=%2F" + ) + self.driver.find_element(By.ID, "id_uid_field").send_keys(USER().username) + self.driver.find_element(By.ID, "id_uid_field").send_keys(Keys.ENTER) + self.driver.find_element(By.ID, "id_password").send_keys(USER().username) + self.driver.find_element(By.ID, "id_password").send_keys(Keys.ENTER) + self.driver.find_element(By.CSS_SELECTOR, ".pf-c-page__header").click() + self.driver.find_element(By.XPATH, "//a[contains(@href, '/-/user/')]").click() + self.driver.find_element(By.LINK_TEXT, "Change password").click() + self.driver.find_element(By.ID, "id_password").send_keys(new_password) + self.driver.find_element(By.ID, "id_password_repeat").click() + self.driver.find_element(By.ID, "id_password_repeat").send_keys(new_password) + self.driver.find_element(By.CSS_SELECTOR, ".pf-c-button").click() + + sleep(2) + # Because USER() is cached, we need to get the user manually here + user = User.objects.get(username=USER().username) + self.assertTrue(user.check_password(new_password)) diff --git a/e2e/test_provider_saml.py b/e2e/test_provider_saml.py index 790a0ec37..197f244f1 100644 --- a/e2e/test_provider_saml.py +++ b/e2e/test_provider_saml.py @@ -88,6 +88,7 @@ class TestProviderSAML(SeleniumTestCase): self.driver.find_element(By.ID, "id_uid_field").send_keys(Keys.ENTER) self.driver.find_element(By.ID, "id_password").send_keys(USER().username) self.driver.find_element(By.ID, "id_password").send_keys(Keys.ENTER) + self.wait_for_url("http://localhost:9009/") self.assertEqual( self.driver.find_element(By.XPATH, "/html/body/pre").text, f"Hello, {USER().name}!", @@ -128,6 +129,7 @@ class TestProviderSAML(SeleniumTestCase): ).text, ) self.driver.find_element(By.CSS_SELECTOR, "[type=submit]").click() + self.wait_for_url("http://localhost:9009/") self.assertEqual( self.driver.find_element(By.XPATH, "/html/body/pre").text, f"Hello, {USER().name}!", @@ -166,6 +168,7 @@ class TestProviderSAML(SeleniumTestCase): self.driver.find_element(By.ID, "id_uid_field").send_keys(Keys.ENTER) self.driver.find_element(By.ID, "id_password").send_keys(USER().username) self.driver.find_element(By.ID, "id_password").send_keys(Keys.ENTER) + self.wait_for_url("http://localhost:9009/") self.assertEqual( self.driver.find_element(By.XPATH, "/html/body/pre").text, f"Hello, {USER().name}!", diff --git a/passbook/admin/templates/administration/flow/list.html b/passbook/admin/templates/administration/flow/list.html index 2e913d5d5..c013e9348 100644 --- a/passbook/admin/templates/administration/flow/list.html +++ b/passbook/admin/templates/administration/flow/list.html @@ -27,7 +27,7 @@
{% trans 'Name' %} | +{% trans 'Identifier' %} | {% trans 'Designation' %} | {% trans 'Stages' %} | {% trans 'Policies' %} | @@ -39,8 +39,8 @@
---|---|---|---|---|
-
{{ flow.name }}
- {{ flow.slug }}
+ {{ flow.slug }}
+ {{ flow.name }}
|
diff --git a/passbook/audit/models.py b/passbook/audit/models.py
index 66c4b7980..e5dfebab6 100644
--- a/passbook/audit/models.py
+++ b/passbook/audit/models.py
@@ -12,6 +12,7 @@ from django.core.exceptions import ValidationError
from django.db import models
from django.http import HttpRequest
from django.utils.translation import gettext as _
+from django.views.debug import CLEANSED_SUBSTITUTE, HIDDEN_SETTINGS
from guardian.shortcuts import get_anonymous_user
from structlog import get_logger
@@ -20,6 +21,22 @@ from passbook.lib.utils.http import get_client_ip
LOGGER = get_logger()
+def cleanse_dict(source: Dict[Any, Any]) -> Dict[Any, Any]:
+ """Cleanse a dictionary, recursively"""
+ final_dict = {}
+ for key, value in source.items():
+ try:
+ if HIDDEN_SETTINGS.search(key):
+ final_dict[key] = CLEANSED_SUBSTITUTE
+ else:
+ final_dict[key] = value
+ except TypeError:
+ final_dict[key] = value
+ if isinstance(value, dict):
+ final_dict[key] = cleanse_dict(value)
+ return final_dict
+
+
def sanitize_dict(source: Dict[Any, Any]) -> Dict[Any, Any]:
"""clean source of all Models that would interfere with the JSONField.
Models are replaced with a dictionary of {
@@ -27,15 +44,16 @@ def sanitize_dict(source: Dict[Any, Any]) -> Dict[Any, Any]:
name: str,
pk: Any
}"""
+ final_dict = {}
for key, value in source.items():
if isinstance(value, dict):
- source[key] = sanitize_dict(value)
+ final_dict[key] = sanitize_dict(value)
elif isinstance(value, models.Model):
model_content_type = ContentType.objects.get_for_model(value)
name = str(value)
if hasattr(value, "name"):
name = value.name
- source[key] = sanitize_dict(
+ final_dict[key] = sanitize_dict(
{
"app": model_content_type.app_label,
"model_name": model_content_type.model,
@@ -44,8 +62,10 @@ def sanitize_dict(source: Dict[Any, Any]) -> Dict[Any, Any]:
}
)
elif isinstance(value, UUID):
- source[key] = value.hex
- return source
+ final_dict[key] = value.hex
+ else:
+ final_dict[key] = value
+ return final_dict
class EventAction(Enum):
@@ -104,7 +124,7 @@ class Event(models.Model):
)
if not app:
app = getmodule(stack()[_inspect_offset][0]).__name__
- cleaned_kwargs = sanitize_dict(kwargs)
+ cleaned_kwargs = cleanse_dict(sanitize_dict(kwargs))
event = Event(action=action.value, app=app, context=cleaned_kwargs)
return event
diff --git a/passbook/core/templates/user/base.html b/passbook/core/templates/user/base.html
index 4c0c6798b..944684480 100644
--- a/passbook/core/templates/user/base.html
+++ b/passbook/core/templates/user/base.html
@@ -25,8 +25,7 @@
|