diff --git a/e2e/passbook.side b/e2e/passbook.side new file mode 100644 index 000000000..af2abbb3c --- /dev/null +++ b/e2e/passbook.side @@ -0,0 +1,300 @@ +{ + "id": "7d9b2407-1520-4c04-b040-68e8ada9aecc", + "version": "2.0", + "name": "passbook", + "url": "http://localhost:8000", + "tests": [{ + "id": "94b39863-74ec-4b7d-98c5-2b380b6d2c55", + "name": "passbook login simple", + "commands": [{ + "id": "e60e4382-4f96-44c3-ba06-5e18609c9c2b", + "comment": "", + "command": "open", + "target": "/flows/default-authentication-flow/?next=%2F", + "targets": [], + "value": "" + }, { + "id": "b2652f24-931e-45b0-b01d-2f0ac0f74db8", + "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": "f1930f8a-984a-4076-a925-20937bb2f8d3", + "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": "admin@example.tld" + }, { + "id": "0b568ee3-1bed-4821-a3bc-f6b960dbed9d", + "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": "6d98e479-2825-484d-996a-ccf350d2761f", + "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": "6f7abec6-ff44-4eb5-ae23-520c1c29a706", + "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": "04c5876f-1405-4077-a98b-e911f09113d7", + "comment": "", + "command": "assertText", + "target": "xpath=//a[contains(@href, '/-/user/')]", + "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": "pbadmin" + }] + }, { + "id": "61948b3c-3012-4f97-aa52-bc8f34fec333", + "name": "passbook enroll simple", + "commands": [{ + "id": "0f4884b3-4891-41bc-956d-1fa433e892e9", + "comment": "", + "command": "open", + "target": "/flows/default-authentication-flow/?next=%2F", + "targets": [], + "value": "" + }, { + "id": "84d3861f-a60c-4650-8689-535f82b39577", + "comment": "", + "command": "click", + "target": "linkText=Sign up.", + "targets": [ + ["linkText=Sign up.", "linkText"], + ["css=.pf-c-login__main-footer-band-item > a", "css:finder"], + ["xpath=//a[contains(text(),'Sign up.')]", "xpath:link"], + ["xpath=//main[@id='flow-body']/footer/div/p/a", "xpath:idRelative"], + ["xpath=//a[contains(@href, '/flows/default-enrollment-flow/')]", "xpath:href"], + ["xpath=//a", "xpath:position"], + ["xpath=//a[contains(.,'Sign up.')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "a32435ca-d84a-41e7-a915-fcbbc5f88341", + "comment": "", + "command": "type", + "target": "id=id_username", + "targets": [ + ["id=id_username", "id"], + ["name=username", "name"], + ["css=#id_username", "css:finder"], + ["xpath=//input[@id='id_username']", "xpath:attributes"], + ["xpath=//main[@id='flow-body']/div/form/div/input", "xpath:idRelative"], + ["xpath=//div/input", "xpath:position"] + ], + "value": "foo" + }, { + "id": "3b5dcf53-8297-46c5-88b7-11c2eb25f34f", + "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": "e948d61c-dae6-4994-b56f-ff130892b342", + "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[3]/input", "xpath:idRelative"], + ["xpath=//div[3]/input", "xpath:position"] + ], + "value": "pbadmin" + }, { + "id": "e7527bfc-ec74-4d96-86f0-5a3a55a59025", + "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[4]/button", "xpath:idRelative"], + ["xpath=//button", "xpath:position"], + ["xpath=//button[contains(.,'Continue')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "434b842c-a659-4ff5-aca8-06a6a3489597", + "comment": "", + "command": "type", + "target": "id=id_name", + "targets": [ + ["id=id_name", "id"], + ["name=name", "name"], + ["css=#id_name", "css:finder"], + ["xpath=//input[@id='id_name']", "xpath:attributes"], + ["xpath=//main[@id='flow-body']/div/form/div/input", "xpath:idRelative"], + ["xpath=//div/input", "xpath:position"] + ], + "value": "some name" + }, { + "id": "cbc43a1b-2cfe-46e2-85bc-476fb32c6cb1", + "comment": "", + "command": "type", + "target": "id=id_email", + "targets": [ + ["id=id_email", "id"], + ["name=email", "name"], + ["css=#id_email", "css:finder"], + ["xpath=//input[@id='id_email']", "xpath:attributes"], + ["xpath=//main[@id='flow-body']/div/form/div[2]/input", "xpath:idRelative"], + ["xpath=//div[2]/input", "xpath:position"] + ], + "value": "foo@bar.baz" + }, { + "id": "e74389a0-228b-4312-9677-e9add6358de3", + "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": "" + }, { + "id": "3e22f9c2-5ebd-49c2-81b1-340fa0435bbc", + "comment": "", + "command": "click", + "target": "linkText=foo", + "targets": [ + ["linkText=foo", "linkText"], + ["css=.pf-c-page__header-tools-group:nth-child(2) > .pf-c-button", "css:finder"], + ["xpath=//a[contains(text(),'foo')]", "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(.,'foo')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "60124cfd-f11c-4d7f-8b01-bef54c8cbd73", + "comment": "", + "command": "assertText", + "target": "xpath=//a[contains(@href, '/-/user/')]", + "targets": [ + ["linkText=foo", "linkText"], + ["css=.pf-c-page__header-tools-group:nth-child(2) > .pf-c-button", "css:finder"], + ["xpath=//a[contains(text(),'foo')]", "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(.,'foo')]", "xpath:innerText"] + ], + "value": "foo" + }, { + "id": "429ee61b-9991-4919-8131-55f8e1bd9a0d", + "comment": "", + "command": "assertValue", + "target": "id=id_username", + "targets": [], + "value": "foo" + }, { + "id": "f6c50760-52ed-4c1d-b232-30f8afe144eb", + "comment": "", + "command": "assertText", + "target": "id=id_name", + "targets": [ + ["id=id_name", "id"], + ["name=name", "name"], + ["css=#id_name", "css:finder"], + ["xpath=//input[@id='id_name']", "xpath:attributes"], + ["xpath=//main[@id='main-content']/section/div/div/div/div[2]/form/div[2]/div/input", "xpath:idRelative"], + ["xpath=//div[2]/div/input", "xpath:position"] + ], + "value": "some name" + }, { + "id": "b26905b5-89b5-4b41-abf5-a9f848f08622", + "comment": "", + "command": "assertText", + "target": "id=id_email", + "targets": [ + ["id=id_email", "id"], + ["name=email", "name"], + ["css=#id_email", "css:finder"], + ["xpath=//input[@id='id_email']", "xpath:attributes"], + ["xpath=//main[@id='main-content']/section/div/div/div/div[2]/form/div[3]/div/input", "xpath:idRelative"], + ["xpath=//div[3]/div/input", "xpath:position"] + ], + "value": "foo@bar.baz" + }] + }], + "suites": [{ + "id": "495657fb-3f5e-4431-877c-4d0b248c0841", + "name": "Default Suite", + "persistSession": false, + "parallel": false, + "timeout": 300, + "tests": ["94b39863-74ec-4b7d-98c5-2b380b6d2c55"] + }], + "urls": ["http://localhost:8000/"], + "plugins": [] +} \ No newline at end of file diff --git a/e2e/test_enroll_2_step.py b/e2e/test_enroll_2_step.py index b8f83cd33..d517d0144 100644 --- a/e2e/test_enroll_2_step.py +++ b/e2e/test_enroll_2_step.py @@ -4,14 +4,14 @@ from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.desired_capabilities import DesiredCapabilities +from e2e.utils import apply_default_data from passbook.flows.models import Flow, FlowDesignation, FlowStageBinding from passbook.policies.expression.models import ExpressionPolicy from passbook.policies.models import PolicyBinding +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 -from passbook.stages.identification.models import IdentificationStage -from e2e.utils import apply_default_data class TestEnroll2Step(StaticLiveServerTestCase): diff --git a/e2e/test_login_default.py b/e2e/test_login_default.py index f4db6a5b8..c8bf5f445 100644 --- a/e2e/test_login_default.py +++ b/e2e/test_login_default.py @@ -4,6 +4,7 @@ from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.desired_capabilities import DesiredCapabilities from selenium.webdriver.common.keys import Keys + from e2e.utils import apply_default_data @@ -24,9 +25,7 @@ class TestLogin(StaticLiveServerTestCase): def test_login(self): """test default login flow""" - self.driver.get( - f"{self.live_server_url}/flows/default-authentication-flow/" - ) + self.driver.get(f"{self.live_server_url}/flows/default-authentication-flow/") self.driver.find_element(By.ID, "id_uid_field").click() self.driver.find_element(By.ID, "id_uid_field").send_keys("pbadmin") self.driver.find_element(By.ID, "id_uid_field").send_keys(Keys.ENTER) diff --git a/e2e/test_provider_oidc.py b/e2e/test_provider_oidc.py new file mode 100644 index 000000000..df966b137 --- /dev/null +++ b/e2e/test_provider_oidc.py @@ -0,0 +1,176 @@ +"""test OpenID Provider flow""" +from time import sleep + +from django.contrib.staticfiles.testing import StaticLiveServerTestCase +from oauth2_provider.generators import generate_client_id, generate_client_secret +from oidc_provider.models import Client, ResponseType +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities +from selenium.webdriver.common.keys import Keys + +from docker import DockerClient, from_env +from docker.models.containers import Container +from docker.types import Healthcheck +from e2e.utils import apply_default_data, ensure_rsa_key +from passbook.core.models import Application +from passbook.flows.models import Flow +from passbook.providers.oidc.models import OpenIDProvider + + +class TestProviderOIDC(StaticLiveServerTestCase): + """test OpenID Provider flow""" + + def setUp(self): + self.driver = webdriver.Remote( + command_executor="http://localhost:4444/wd/hub", + desired_capabilities=DesiredCapabilities.CHROME, + ) + self.driver.implicitly_wait(30) + apply_default_data() + self.client_id = generate_client_id() + self.client_secret = generate_client_secret() + self.container = self.setup_client() + + def setup_client(self) -> Container: + """Setup client grafana container which we test OIDC against""" + client: DockerClient = from_env() + container = client.containers.run( + image="grafana/grafana:latest", + detach=True, + name="passbook-e2e-grafana-client", + network_mode="host", + auto_remove=True, + healthcheck=Healthcheck( + test=["CMD", "wget", "--spider", "http://localhost:3000"], + interval=5 * 100 * 1000000, + start_period=1 * 100 * 1000000, + ), + environment={ + "GF_AUTH_GENERIC_OAUTH_ENABLED": "true", + "GF_AUTH_GENERIC_OAUTH_CLIENT_ID": self.client_id, + "GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET": self.client_secret, + "GF_AUTH_GENERIC_OAUTH_SCOPES": "openid email profile", + "GF_AUTH_GENERIC_OAUTH_AUTH_URL": ( + f"{self.live_server_url}/application/oidc/authorize" + ), + "GF_AUTH_GENERIC_OAUTH_TOKEN_URL": ( + f"{self.live_server_url}/application/oidc/token" + ), + "GF_AUTH_GENERIC_OAUTH_API_URL": ( + f"{self.live_server_url}/application/oidc/userinfo" + ), + "GF_LOG_LEVEL": "debug", + }, + ) + while True: + container.reload() + status = container.attrs.get("State", {}).get("Health", {}).get("Status") + if status == "healthy": + return container + sleep(1) + + def tearDown(self): + super().tearDown() + self.driver.quit() + self.container.kill() + + def test_redirect_uri_error(self): + """test OpenID Provider flow (invalid redirect URI, check error message)""" + # Bootstrap all needed objects + authorization_flow = Flow.objects.get(slug="default-provider-authorization") + client = Client.objects.create( + name="grafana", + client_type="confidential", + client_id=self.client_id, + client_secret=self.client_secret, + _redirect_uris="http://localhost:3000/", + _scope="openid userinfo", + ) + # At least one of these objects must exist + ensure_rsa_key() + # This response_code object might exist or not, depending on the order the tests are run + rp_type, _ = ResponseType.objects.get_or_create(value="code") + client.response_types.set([rp_type]) + client.save() + provider = OpenIDProvider.objects.create( + oidc_client=client, authorization_flow=authorization_flow, + ) + Application.objects.create( + name="Grafana", slug="grafana", provider=provider, + ) + + self.driver.get(f"http://localhost:3000") + self.driver.find_element(By.CLASS_NAME, "btn-service--oauth").click() + self.driver.find_element(By.ID, "id_uid_field").click() + self.driver.find_element(By.ID, "id_uid_field").send_keys("pbadmin") + self.driver.find_element(By.ID, "id_uid_field").send_keys(Keys.ENTER) + self.driver.find_element(By.ID, "id_password").send_keys("pbadmin") + self.driver.find_element(By.ID, "id_password").send_keys(Keys.ENTER) + sleep(2) + self.assertEqual( + self.driver.find_element(By.CLASS_NAME, "pf-c-title").text, + "Redirect URI Error", + ) + + def test_authorization_no_consent(self): + """test OpenID Provider flow (default authorization flow without consent)""" + # Bootstrap all needed objects + authorization_flow = Flow.objects.get(slug="default-provider-authorization") + client = Client.objects.create( + name="grafana", + client_type="confidential", + client_id=self.client_id, + client_secret=self.client_secret, + _redirect_uris="http://localhost:3000/login/generic_oauth", + _scope="openid profile email", + reuse_consent=False, + require_consent=False, + ) + # At least one of these objects must exist + ensure_rsa_key() + # This response_code object might exist or not, depending on the order the tests are run + rp_type, _ = ResponseType.objects.get_or_create(value="code") + client.response_types.set([rp_type]) + client.save() + provider = OpenIDProvider.objects.create( + oidc_client=client, authorization_flow=authorization_flow, + ) + Application.objects.create( + name="Grafana", slug="grafana", provider=provider, + ) + + self.driver.get("http://localhost:3000") + self.driver.find_element(By.CLASS_NAME, "btn-service--oauth").click() + self.driver.find_element(By.ID, "id_uid_field").click() + self.driver.find_element(By.ID, "id_uid_field").send_keys("pbadmin") + self.driver.find_element(By.ID, "id_uid_field").send_keys(Keys.ENTER) + self.driver.find_element(By.ID, "id_password").send_keys("pbadmin") + self.driver.find_element(By.ID, "id_password").send_keys(Keys.ENTER) + self.driver.find_element(By.XPATH, "//a[contains(@href, '/profile')]").click() + # sleep() + self.assertEqual( + self.driver.find_element(By.CLASS_NAME, "page-header__title").text, + "passbook Default Admin", + ) + self.assertEqual( + self.driver.find_element( + By.XPATH, + "/html/body/grafana-app/div/div/div/react-profile-wrapper/form[1]/div[1]/div/input", + ).get_attribute("value"), + "passbook Default Admin", + ) + self.assertEqual( + self.driver.find_element( + By.XPATH, + "/html/body/grafana-app/div/div/div/react-profile-wrapper/form[1]/div[2]/div/input", + ).get_attribute("value"), + "root@localhost", + ) + self.assertEqual( + self.driver.find_element( + By.XPATH, + "/html/body/grafana-app/div/div/div/react-profile-wrapper/form[1]/div[3]/div/input", + ).get_attribute("value"), + "root@localhost", + ) diff --git a/e2e/utils.py b/e2e/utils.py index d10aaa5b2..5434e7da5 100644 --- a/e2e/utils.py +++ b/e2e/utils.py @@ -1,8 +1,10 @@ """passbook e2e testing utilities""" from glob import glob +from importlib.util import module_from_spec, spec_from_file_location from inspect import getmembers, isfunction -from importlib.util import spec_from_file_location, module_from_spec + +from Cryptodome.PublicKey import RSA from django.apps import apps from django.db import connection, transaction from django.db.utils import IntegrityError @@ -15,7 +17,7 @@ def apply_default_data(): migration_files = glob("**/migrations/*.py", recursive=True) matches = [] for migration in migration_files: - with open(migration, 'r+') as migration_file: + with open(migration, "r+") as migration_file: # Check if they have a `RunPython` if "RunPython" in migration_file.read(): matches.append(migration) @@ -34,3 +36,13 @@ def apply_default_data(): func(apps, schema_editor) except IntegrityError: pass + + +def ensure_rsa_key(): + """Ensure that at least one RSAKey Object exists, create one if none exist""" + from oidc_provider.models import RSAKey + + if not RSAKey.objects.exists(): + key = RSA.generate(2048) + rsakey = RSAKey(key=key.exportKey("PEM").decode("utf8")) + rsakey.save() diff --git a/manage.py b/manage.py index 5a70f4ada..8b16cf9a3 100755 --- a/manage.py +++ b/manage.py @@ -2,6 +2,7 @@ """Django manage.py""" import os import sys + from defusedxml import defuse_stdlib defuse_stdlib() diff --git a/passbook/flows/planner.py b/passbook/flows/planner.py index 86acdb630..f91bc08da 100644 --- a/passbook/flows/planner.py +++ b/passbook/flows/planner.py @@ -39,6 +39,11 @@ class FlowPlan: context: Dict[str, Any] = field(default_factory=dict) markers: List[StageMarker] = field(default_factory=list) + def append(self, stage: Stage, marker: Optional[StageMarker] = None): + """Append `stage` to all stages, optionall with stage marker""" + self.stages.append(stage) + self.markers.append(marker or StageMarker()) + def next(self) -> Optional[Stage]: """Return next pending stage from the bottom of the list""" if not self.has_stages: @@ -54,7 +59,6 @@ class FlowPlan: self.markers.remove(marker) if not self.has_stages: return None - # pylint: disable=not-callable return self.next() return marked_stage diff --git a/passbook/flows/views.py b/passbook/flows/views.py index affbe4536..1625e2c01 100644 --- a/passbook/flows/views.py +++ b/passbook/flows/views.py @@ -27,6 +27,7 @@ LOGGER = get_logger() # Argument used to redirect user after login NEXT_ARG_NAME = "next" SESSION_KEY_PLAN = "passbook_flows_plan" +SESSION_KEY_NEXT = "passbook_flows_shell_next" @method_decorator(xframe_options_sameorigin, name="dispatch") @@ -127,7 +128,10 @@ class FlowExecutorView(View): def _flow_done(self) -> HttpResponse: """User Successfully passed all stages""" self.cancel() - next_param = self.request.GET.get(NEXT_ARG_NAME, "passbook_core:overview") + # Since this is wrapped by the ExecutorShell, the next argument is saved in the session + next_param = self.request.session.get( + SESSION_KEY_NEXT, "passbook_core:overview" + ) return redirect_with_qs(next_param) def stage_ok(self) -> HttpResponse: @@ -210,6 +214,8 @@ class FlowExecutorShellView(TemplateView): def get_context_data(self, **kwargs) -> Dict[str, Any]: kwargs["exec_url"] = reverse("passbook_flows:flow-executor", kwargs=self.kwargs) kwargs["msg_url"] = reverse("passbook_api:messages-list") + if NEXT_ARG_NAME in self.request.GET: + self.request.session[SESSION_KEY_NEXT] = self.request.GET[NEXT_ARG_NAME] return kwargs diff --git a/passbook/providers/oidc/claims.py b/passbook/providers/oidc/claims.py index 8ad905a22..f53c90f33 100644 --- a/passbook/providers/oidc/claims.py +++ b/passbook/providers/oidc/claims.py @@ -10,5 +10,5 @@ def userinfo(claims: Dict[str, Any], user: User) -> Dict[str, Any]: claims["given_name"] = user.name claims["family_name"] = user.name claims["email"] = user.email - + claims["preferred_username"] = user.username return claims diff --git a/passbook/providers/oidc/views.py b/passbook/providers/oidc/views.py index 2d9e5bb89..abc0039ca 100644 --- a/passbook/providers/oidc/views.py +++ b/passbook/providers/oidc/views.py @@ -61,7 +61,7 @@ class AuthorizationFlowInitView(AccessMixin, LoginRequiredMixin, View): PLAN_CONTEXT_PARAMS: endpoint.params, }, ) - plan.stages.append(in_memory_stage(OIDCStage)) + plan.append(in_memory_stage(OIDCStage)) self.request.session[SESSION_KEY_PLAN] = plan return redirect_with_qs( "passbook_flows:flow-executor-shell", diff --git a/scripts/pre-commit.sh b/scripts/pre-commit.sh index 1cf64708d..e8335a1a9 100755 --- a/scripts/pre-commit.sh +++ b/scripts/pre-commit.sh @@ -1,9 +1,9 @@ #!/bin/bash -xe -isort -rc passbook +isort -rc . pyright -black passbook +black . ./manage.py generate_swagger -o swagger.yaml -f yaml scripts/coverage.sh -bandit -r passbook +bandit -r . pylint passbook prospector