e2e: add OIDC Provider test against grafana, more formatting, minor bug fixes
This commit is contained in:
		
							
								
								
									
										300
									
								
								e2e/passbook.side
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										300
									
								
								e2e/passbook.side
									
									
									
									
									
										Normal file
									
								
							| @ -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": [] | ||||||
|  | } | ||||||
| @ -4,14 +4,14 @@ from selenium import webdriver | |||||||
| from selenium.webdriver.common.by import By | from selenium.webdriver.common.by import By | ||||||
| from selenium.webdriver.common.desired_capabilities import DesiredCapabilities | 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.flows.models import Flow, FlowDesignation, FlowStageBinding | ||||||
| from passbook.policies.expression.models import ExpressionPolicy | from passbook.policies.expression.models import ExpressionPolicy | ||||||
| from passbook.policies.models import PolicyBinding | 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.prompt.models import FieldTypes, Prompt, PromptStage | ||||||
| from passbook.stages.user_login.models import UserLoginStage | from passbook.stages.user_login.models import UserLoginStage | ||||||
| from passbook.stages.user_write.models import UserWriteStage | 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): | class TestEnroll2Step(StaticLiveServerTestCase): | ||||||
|  | |||||||
| @ -4,6 +4,7 @@ from selenium import webdriver | |||||||
| from selenium.webdriver.common.by import By | from selenium.webdriver.common.by import By | ||||||
| from selenium.webdriver.common.desired_capabilities import DesiredCapabilities | from selenium.webdriver.common.desired_capabilities import DesiredCapabilities | ||||||
| from selenium.webdriver.common.keys import Keys | from selenium.webdriver.common.keys import Keys | ||||||
|  |  | ||||||
| from e2e.utils import apply_default_data | from e2e.utils import apply_default_data | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -24,9 +25,7 @@ class TestLogin(StaticLiveServerTestCase): | |||||||
|  |  | ||||||
|     def test_login(self): |     def test_login(self): | ||||||
|         """test default login flow""" |         """test default login flow""" | ||||||
|         self.driver.get( |         self.driver.get(f"{self.live_server_url}/flows/default-authentication-flow/") | ||||||
|             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").click() | ||||||
|         self.driver.find_element(By.ID, "id_uid_field").send_keys("pbadmin") |         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_uid_field").send_keys(Keys.ENTER) | ||||||
|  | |||||||
							
								
								
									
										176
									
								
								e2e/test_provider_oidc.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								e2e/test_provider_oidc.py
									
									
									
									
									
										Normal file
									
								
							| @ -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", | ||||||
|  |         ) | ||||||
							
								
								
									
										16
									
								
								e2e/utils.py
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								e2e/utils.py
									
									
									
									
									
								
							| @ -1,8 +1,10 @@ | |||||||
| """passbook e2e testing utilities""" | """passbook e2e testing utilities""" | ||||||
|  |  | ||||||
| from glob import glob | from glob import glob | ||||||
|  | from importlib.util import module_from_spec, spec_from_file_location | ||||||
| from inspect import getmembers, isfunction | 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.apps import apps | ||||||
| from django.db import connection, transaction | from django.db import connection, transaction | ||||||
| from django.db.utils import IntegrityError | from django.db.utils import IntegrityError | ||||||
| @ -15,7 +17,7 @@ def apply_default_data(): | |||||||
|     migration_files = glob("**/migrations/*.py", recursive=True) |     migration_files = glob("**/migrations/*.py", recursive=True) | ||||||
|     matches = [] |     matches = [] | ||||||
|     for migration in migration_files: |     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` |             # Check if they have a `RunPython` | ||||||
|             if "RunPython" in migration_file.read(): |             if "RunPython" in migration_file.read(): | ||||||
|                 matches.append(migration) |                 matches.append(migration) | ||||||
| @ -34,3 +36,13 @@ def apply_default_data(): | |||||||
|                         func(apps, schema_editor) |                         func(apps, schema_editor) | ||||||
|                     except IntegrityError: |                     except IntegrityError: | ||||||
|                         pass |                         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() | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ | |||||||
| """Django manage.py""" | """Django manage.py""" | ||||||
| import os | import os | ||||||
| import sys | import sys | ||||||
|  |  | ||||||
| from defusedxml import defuse_stdlib | from defusedxml import defuse_stdlib | ||||||
|  |  | ||||||
| defuse_stdlib() | defuse_stdlib() | ||||||
|  | |||||||
| @ -39,6 +39,11 @@ class FlowPlan: | |||||||
|     context: Dict[str, Any] = field(default_factory=dict) |     context: Dict[str, Any] = field(default_factory=dict) | ||||||
|     markers: List[StageMarker] = field(default_factory=list) |     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]: |     def next(self) -> Optional[Stage]: | ||||||
|         """Return next pending stage from the bottom of the list""" |         """Return next pending stage from the bottom of the list""" | ||||||
|         if not self.has_stages: |         if not self.has_stages: | ||||||
| @ -54,7 +59,6 @@ class FlowPlan: | |||||||
|             self.markers.remove(marker) |             self.markers.remove(marker) | ||||||
|             if not self.has_stages: |             if not self.has_stages: | ||||||
|                 return None |                 return None | ||||||
|             # pylint: disable=not-callable |  | ||||||
|             return self.next() |             return self.next() | ||||||
|         return marked_stage |         return marked_stage | ||||||
|  |  | ||||||
|  | |||||||
| @ -27,6 +27,7 @@ LOGGER = get_logger() | |||||||
| # Argument used to redirect user after login | # Argument used to redirect user after login | ||||||
| NEXT_ARG_NAME = "next" | NEXT_ARG_NAME = "next" | ||||||
| SESSION_KEY_PLAN = "passbook_flows_plan" | SESSION_KEY_PLAN = "passbook_flows_plan" | ||||||
|  | SESSION_KEY_NEXT = "passbook_flows_shell_next" | ||||||
|  |  | ||||||
|  |  | ||||||
| @method_decorator(xframe_options_sameorigin, name="dispatch") | @method_decorator(xframe_options_sameorigin, name="dispatch") | ||||||
| @ -127,7 +128,10 @@ class FlowExecutorView(View): | |||||||
|     def _flow_done(self) -> HttpResponse: |     def _flow_done(self) -> HttpResponse: | ||||||
|         """User Successfully passed all stages""" |         """User Successfully passed all stages""" | ||||||
|         self.cancel() |         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) |         return redirect_with_qs(next_param) | ||||||
|  |  | ||||||
|     def stage_ok(self) -> HttpResponse: |     def stage_ok(self) -> HttpResponse: | ||||||
| @ -210,6 +214,8 @@ class FlowExecutorShellView(TemplateView): | |||||||
|     def get_context_data(self, **kwargs) -> Dict[str, Any]: |     def get_context_data(self, **kwargs) -> Dict[str, Any]: | ||||||
|         kwargs["exec_url"] = reverse("passbook_flows:flow-executor", kwargs=self.kwargs) |         kwargs["exec_url"] = reverse("passbook_flows:flow-executor", kwargs=self.kwargs) | ||||||
|         kwargs["msg_url"] = reverse("passbook_api:messages-list") |         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 |         return kwargs | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -10,5 +10,5 @@ def userinfo(claims: Dict[str, Any], user: User) -> Dict[str, Any]: | |||||||
|     claims["given_name"] = user.name |     claims["given_name"] = user.name | ||||||
|     claims["family_name"] = user.name |     claims["family_name"] = user.name | ||||||
|     claims["email"] = user.email |     claims["email"] = user.email | ||||||
|  |     claims["preferred_username"] = user.username | ||||||
|     return claims |     return claims | ||||||
|  | |||||||
| @ -61,7 +61,7 @@ class AuthorizationFlowInitView(AccessMixin, LoginRequiredMixin, View): | |||||||
|                 PLAN_CONTEXT_PARAMS: endpoint.params, |                 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 |         self.request.session[SESSION_KEY_PLAN] = plan | ||||||
|         return redirect_with_qs( |         return redirect_with_qs( | ||||||
|             "passbook_flows:flow-executor-shell", |             "passbook_flows:flow-executor-shell", | ||||||
|  | |||||||
| @ -1,9 +1,9 @@ | |||||||
| #!/bin/bash -xe | #!/bin/bash -xe | ||||||
| isort -rc passbook | isort -rc . | ||||||
| pyright | pyright | ||||||
| black passbook | black . | ||||||
| ./manage.py generate_swagger -o swagger.yaml -f yaml | ./manage.py generate_swagger -o swagger.yaml -f yaml | ||||||
| scripts/coverage.sh | scripts/coverage.sh | ||||||
| bandit -r passbook | bandit -r . | ||||||
| pylint passbook | pylint passbook | ||||||
| prospector | prospector | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Jens Langhammer
					Jens Langhammer