diff --git a/tests/openid-conformance/oidc-conformance.yaml b/blueprints/testing/oidc-conformance.yaml similarity index 90% rename from tests/openid-conformance/oidc-conformance.yaml rename to blueprints/testing/oidc-conformance.yaml index f39c8e68d1..380b1a7feb 100644 --- a/tests/openid-conformance/oidc-conformance.yaml +++ b/blueprints/testing/oidc-conformance.yaml @@ -1,7 +1,8 @@ -# yaml-language-server: $schema=https://goauthentik.io/blueprints/schema.json version: 1 metadata: - name: OIDC conformance testing + name: OpenID Conformance testing + labels: + blueprints.goauthentik.io/instantiate: "false" entries: - identifiers: managed: goauthentik.io/providers/oauth2/scope-address @@ -32,7 +33,7 @@ entries: - model: authentik_providers_oauth2.oauth2provider id: provider identifiers: - name: provider + name: oidc-conformance-1 attrs: authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]] invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]] @@ -43,7 +44,8 @@ entries: - matching_mode: strict url: https://localhost:8443/test/a/authentik/callback - matching_mode: strict - url: https://localhost.emobix.co.uk:8443/test/a/authentik/callback + url: https://10.120.20.76:8443/test/a/authentik/callback + # url: !Format [https://%s:8443/test/a/authentik/callback, !Context host_ip] property_mappings: - !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-openid]] - !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-email]] @@ -72,7 +74,8 @@ entries: - matching_mode: strict url: https://localhost:8443/test/a/authentik/callback - matching_mode: strict - url: https://localhost.emobix.co.uk:8443/test/a/authentik/callback + url: https://10.120.20.76:8443/test/a/authentik/callback + # url: !Format [https://%s:8443/test/a/authentik/callback, !Context host_ip] property_mappings: - !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-openid]] - !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-email]] diff --git a/tests/e2e/utils.py b/tests/e2e/utils.py index 90db46e371..e6e2cfbf9b 100644 --- a/tests/e2e/utils.py +++ b/tests/e2e/utils.py @@ -176,6 +176,7 @@ class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase): def _get_driver(self) -> WebDriver: count = 0 opts = webdriver.ChromeOptions() + opts.accept_insecure_certs = True opts.add_argument("--disable-search-engine-choice-screen") # This breaks selenium when running remotely...? # opts.set_capability("goog:loggingPrefs", {"browser": "ALL"}) @@ -260,7 +261,6 @@ class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase): def login(self, shadow_dom=True): """Do entire login flow""" - if shadow_dom: flow_executor = self.get_shadow_root("ak-flow-executor") identification_stage = self.get_shadow_root("ak-stage-identification", flow_executor) diff --git a/tests/openid-conformance/run.py b/tests/openid-conformance/run.py deleted file mode 100644 index 2177a7dd50..0000000000 --- a/tests/openid-conformance/run.py +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/env python3 - -import asyncio -import json -import os - -from conformance import Conformance - -CONFORMANCE_SERVER = "https://localhost:8443/" - -# This is the name of the Basic OP test plan: -test_plan_name = "oidcc-basic-certification-test-plan" - -# This is the variant configuration of the test, -# i.e. static or dynamic metadata location and client registration: -test_variant_config = {"server_metadata": "discovery", "client_registration": "static_client"} - -# This is the required configuration for the test run: -test_plan_config = { - "alias": "authentik", - "description": "authentik", - "server": { - "discoveryUrl": "http://10.120.20.76:9000/application/o/conformance/.well-known/openid-configuration" - }, - "client": { - "client_id": "4054d882aff59755f2f279968b97ce8806a926e1", - "client_secret": "4c7e4933009437fb486b5389d15b173109a0555dc47e0cc0949104f1925bcc6565351cb1dffd7e6818cf074f5bd50c210b565121a7328ee8bd40107fc4bbd867", - }, - "client_secret_post": { - "client_id": "4054d882aff59755f2f279968b97ce8806a926e1", - "client_secret": "4c7e4933009437fb486b5389d15b173109a0555dc47e0cc0949104f1925bcc6565351cb1dffd7e6818cf074f5bd50c210b565121a7328ee8bd40107fc4bbd867", - }, - "client2": { - "client_id": "ad64aeaf1efe388ecf4d28fcc537e8de08bcae26", - "client_secret": "ff2e34a5b04c99acaf7241e25a950e7f6134c86936923d8c698d8f38bd57647750d661069612c0ee55045e29fe06aa101804bdae38e8360647d595e771fea789", - }, - "consent": {}, - "browser": [ - { - "match": "http://10.120.20.76:9000/application/o/authorize*", - "tasks": [ - { - "task": "Login", - "optional": True, - "match": "http://10.120.20.76:9000/if/flow/default-authentication-flow*", - "commands": [ - ["wait", "css", "[name=uid_field]", 10], - ["text", "css", "[name=uid_field]", "akadmin"], - ["wait", "css", "button[type=submit]", 10], - ["click", "css", "button[type=submit]"], - ["wait", "css", "[name=password]", 10], - ["text", "css", "[name=password]", "foo"], - ["click", "css", "button[type=submit]"], - ["wait", "css", "#loading-text", 10], - ["wait", "css", "#foo", 10], - ["click", "css", "#foo"], - # ["wait", "contains", "application/o/authorize", 10], - ], - }, - { - "task": "Authorize", - "match": "http://10.120.20.76:9000/application/o/authorize*", - "optional": True, - "commands": [ - ["wait", "css", "#loading-text", 10], - ], - }, - { - "task": "Authorize 2", - "optional": True, - "match": "http://10.120.20.76:9000/if/flow/default-provider-authorization-implicit-consent*", - }, - ], - } - ], -} - - -# Create a Conformance instance... -conformance = Conformance(CONFORMANCE_SERVER, None, verify_ssl=False) - -# Create a test plan instance and print the id of it -test_plan = asyncio.run( - conformance.create_test_plan(test_plan_name, json.dumps(test_plan_config), test_variant_config) -) -plan_id = test_plan["id"] - -print(f"----------------\nBegin {test_plan_name}.") -print(f"Plan URL: {CONFORMANCE_SERVER}plan-detail.html?plan={plan_id}\n") - -# Iterate over the tests in the plan and run them one by one -for test in test_plan["modules"]: - - # Fetch name and variant of the next test to run - module_name = test["testModule"] - variant = test["variant"] - print(f"Module name: {module_name}") - print(f"Variant: {json.dumps(variant)}") - - # Create an instance of that test - module_instance = asyncio.run( - conformance.create_test_from_plan_with_variant(plan_id, module_name, variant) - ) - module_id = module_instance["id"] - print(f"Test URL: {CONFORMANCE_SERVER}log-detail.html?log={module_id}") - - # Run the test and wait for it to finish - state = asyncio.run(conformance.wait_for_state(module_id, ["FINISHED"])) - print("") - -print(f"Plan URL: {CONFORMANCE_SERVER}plan-detail.html?plan={plan_id}\n") -print(f"\nEnd {test_plan_name}\n----------------") - -print("Creating certification package") -asyncio.run( - conformance.create_certification_package( - plan_id=plan_id, - conformance_pdf_path="OpenID-Certification-of-Conformance.pdf", - output_zip_directory="./zips/", - ) -) diff --git a/tests/openid-conformance/README.md b/tests/openid_conformance/README.md similarity index 100% rename from tests/openid-conformance/README.md rename to tests/openid_conformance/README.md diff --git a/tests/openid_conformance/__init__.py b/tests/openid_conformance/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/openid-conformance/compose.yml b/tests/openid_conformance/compose.yml similarity index 92% rename from tests/openid-conformance/compose.yml rename to tests/openid_conformance/compose.yml index 8f785506b9..255e70d859 100644 --- a/tests/openid-conformance/compose.yml +++ b/tests/openid_conformance/compose.yml @@ -19,7 +19,7 @@ services: -Xdebug -Xrunjdwp:transport=dt_socket,address=*:9999,server=y,suspend=n -jar /server/fapi-test-suite.jar -Djdk.tls.maxHandshakeMessageSize=65536 - --fintechlabs.base_url=https://localhost.emobix.co.uk:8443 + --fintechlabs.base_url=https://10.120.20.76:8443 --fintechlabs.base_mtls_url=https://localhost.emobix.co.uk:8444 --fintechlabs.devmode=true --fintechlabs.startredir=true diff --git a/tests/openid-conformance/conformance.py b/tests/openid_conformance/conformance.py similarity index 89% rename from tests/openid-conformance/conformance.py rename to tests/openid_conformance/conformance.py index 7f64f9ce87..b40299fe3d 100644 --- a/tests/openid-conformance/conformance.py +++ b/tests/openid_conformance/conformance.py @@ -75,6 +75,17 @@ class Conformance: ) return response.json() + def get_test_status(self,module_id): + """Returns an array containing a dictionary per test module""" + api_url = f"{self.api_url_base}api/runner/{module_id}" + response = self.httpclient.get(api_url) + + if response.status_code != 200: + raise Exception( + f"get_test_status failed - HTTP {response.status_code:d} {response.content}" + ) + return response.json() + async def exporthtml(self, plan_id, path): for i in range(5): api_url = f"{self.api_url_base}api/plan/exporthtml/{plan_id}" @@ -148,7 +159,7 @@ class Conformance: certificationOfConformancePdf.close() clientSideData.close() - async def create_test_plan(self, name, configuration, variant=None): + def create_test_plan(self, name, configuration, variant=None): api_url = f"{self.api_url_base}api/plan" payload = {"planName": name} if variant != None: @@ -161,7 +172,7 @@ class Conformance: ) return response.json() - async def create_test(self, test_name, configuration): + def create_test(self, test_name, configuration): api_url = f"{self.api_url_base}api/runner" payload = {"test": test_name} response = self.httpclient.post(api_url, params=payload, data=configuration) @@ -183,7 +194,7 @@ class Conformance: ) return response.json() - async def create_test_from_plan_with_variant(self, plan_id, test_name, variant): + def create_test_from_plan_with_variant(self, plan_id, test_name, variant): api_url = f"{self.api_url_base}api/runner" payload = {"test": test_name, "plan": plan_id} if variant != None: @@ -196,7 +207,7 @@ class Conformance: ) return response.json() - async def get_module_info(self, module_id): + def get_module_info(self, module_id): api_url = f"{self.api_url_base}api/info/{module_id}" response = self.httpclient.get(api_url) @@ -206,7 +217,7 @@ class Conformance: ) return response.json() - async def get_test_log(self, module_id): + def get_test_log(self, module_id): api_url = f"{self.api_url_base}api/log/{module_id}" response = self.httpclient.get(api_url) @@ -216,6 +227,18 @@ class Conformance: ) return response.json() + def upload_image(self, log_id, placeholder, data): + api_url = f"{self.api_url_base}api/log/{log_id}/images/{placeholder}" + response = self.httpclient.post(api_url, data=data, headers={ + "Content-Type": "text/plain" + }) + + if response.status_code != 200: + raise Exception( + f"upload_image failed - HTTP {response.status_code:d} {response.content}" + ) + return response.json() + async def start_test(self, module_id): api_url = f"{self.api_url_base}api/runner/{module_id}" response = self.httpclient.post(api_url) diff --git a/tests/openid_conformance/test_conformance.py b/tests/openid_conformance/test_conformance.py new file mode 100644 index 0000000000..e61484803f --- /dev/null +++ b/tests/openid_conformance/test_conformance.py @@ -0,0 +1,103 @@ +from json import dumps +from time import sleep +from authentik.blueprints.tests import apply_blueprint, reconcile_app +from tests.e2e.utils import SeleniumTestCase, retry +from tests.openid_conformance.conformance import Conformance +from selenium.webdriver.common.by import By + +from selenium.webdriver.support import expected_conditions as ec + + +class TestOpenIDConformance(SeleniumTestCase): + + @retry() + @apply_blueprint( + "default/flow-default-authentication-flow.yaml", + "default/flow-default-invalidation-flow.yaml", + ) + @apply_blueprint( + "default/flow-default-provider-authorization-implicit-consent.yaml", + "default/flow-default-provider-invalidation.yaml", + ) + @apply_blueprint("system/providers-oauth2.yaml") + @reconcile_app("authentik_crypto") + @apply_blueprint("testing/oidc-conformance.yaml") + def test_oidcc_basic_certification_test(self): + test_plan_name = "oidcc-basic-certification-test-plan" + test_variant_config = { + "server_metadata": "discovery", + "client_registration": "static_client", + } + test_plan_config = { + "alias": "authentik", + "description": "authentik", + "server": { + "discoveryUrl": f"{self.live_server_url}/application/o/conformance/.well-known/openid-configuration" + }, + "client": { + "client_id": "4054d882aff59755f2f279968b97ce8806a926e1", + "client_secret": "4c7e4933009437fb486b5389d15b173109a0555dc47e0cc0949104f1925bcc6565351cb1dffd7e6818cf074f5bd50c210b565121a7328ee8bd40107fc4bbd867", + }, + "client_secret_post": { + "client_id": "4054d882aff59755f2f279968b97ce8806a926e1", + "client_secret": "4c7e4933009437fb486b5389d15b173109a0555dc47e0cc0949104f1925bcc6565351cb1dffd7e6818cf074f5bd50c210b565121a7328ee8bd40107fc4bbd867", + }, + "client2": { + "client_id": "ad64aeaf1efe388ecf4d28fcc537e8de08bcae26", + "client_secret": "ff2e34a5b04c99acaf7241e25a950e7f6134c86936923d8c698d8f38bd57647750d661069612c0ee55045e29fe06aa101804bdae38e8360647d595e771fea789", + }, + "consent": {}, + } + + # Create a Conformance instance... + conformance = Conformance(f"https://{self.host}:8443/", None, verify_ssl=False) + + # Create a test plan instance and print the id of it + test_plan = conformance.create_test_plan( + test_plan_name, dumps(test_plan_config), test_variant_config + ) + plan_id = test_plan["id"] + n = 0 + for test in test_plan["modules"]: + with self.subTest(test["testModule"]): + # Fetch name and variant of the next test to run + module_name = test["testModule"] + variant = test["variant"] + print(f"Module name: {module_name}") + print(f"Variant: {dumps(variant)}") + + # Create an instance of that test + module_instance = conformance.create_test_from_plan_with_variant( + plan_id, module_name, variant + ) + module_id = module_instance["id"] + while True: + test_status = conformance.get_test_status(module_id) + print(test_status) + browser_urls = test_status.get("browser", {}).get("urls", []) + print(browser_urls) + if len(browser_urls) < 1: + continue + self.do_browser(browser_urls[0]) + # Check if we need to upload any items + test_log = conformance.get_test_log(module_id) + upload_items = [x for x in test_log if "upload" in x] + if len(upload_items) > 0: + sleep(10) + for item in upload_items: + conformance.upload_image( + module_id, item, self.driver.get_screenshot_as_base64() + ) + # Close tab we've opened earlier + # self.driver.close() + break + sleep(2) + n += 1 + + def do_browser(self, url): + """For any specific OpenID Conformance test, execute the operations required""" + # self.driver.switch_to.new_window("tab") + self.driver.get(url) + if "if/flow/default-authentication-flow" in self.driver.current_url: + self.login() + self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "#complete")))