@ -108,6 +108,7 @@ class ChallengeStageView(StageView):
|
|||||||
|
|
||||||
def post(self, request: Request, *args, **kwargs) -> HttpResponse:
|
def post(self, request: Request, *args, **kwargs) -> HttpResponse:
|
||||||
"""Handle challenge response"""
|
"""Handle challenge response"""
|
||||||
|
print(request.data)
|
||||||
valid = False
|
valid = False
|
||||||
try:
|
try:
|
||||||
challenge: ChallengeResponse = self.get_response_instance(data=request.data)
|
challenge: ChallengeResponse = self.get_response_instance(data=request.data)
|
||||||
|
|||||||
@ -38,6 +38,6 @@ class FlowInterfaceView(InterfaceView):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def get_template_names(self) -> list[str]:
|
def get_template_names(self) -> list[str]:
|
||||||
if self.compat_needs_sfe() or "sfe" in self.request.GET:
|
# if self.compat_needs_sfe() or "sfe" in self.request.GET:
|
||||||
return ["if/flow-sfe.html"]
|
return ["if/flow-sfe.html"]
|
||||||
return ["if/flow.html"]
|
# return ["if/flow.html"]
|
||||||
|
|||||||
@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"alias": "authentik",
|
|
||||||
"description": "authentik",
|
|
||||||
"server": {
|
|
||||||
"discoveryUrl": "http://host.docker.internal: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": {}
|
|
||||||
}
|
|
||||||
@ -1,7 +1,6 @@
|
|||||||
# #Test files for OpenID Conformance testing.
|
# #Test files for OpenID Conformance testing.
|
||||||
|
|
||||||
These config files assume testing is being done using the [OpenID Conformance Suite
|
These config files assume testing is being done using the [OpenID Conformance Suite](https://openid.net/certification/about-conformance-suite/), locally.
|
||||||
](https://openid.net/certification/about-conformance-suite/), locally.
|
|
||||||
|
|
||||||
See https://gitlab.com/openid/conformance-suite/-/wikis/Developers/Build-&-Run for running the conformance suite locally.
|
See https://gitlab.com/openid/conformance-suite/-/wikis/Developers/Build-&-Run for running the conformance suite locally.
|
||||||
|
|
||||||
32
tests/openid-conformance/compose.yml
Normal file
32
tests/openid-conformance/compose.yml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
services:
|
||||||
|
mongodb:
|
||||||
|
image: mongo:6.0.13
|
||||||
|
volumes:
|
||||||
|
- mongo:/data/db
|
||||||
|
httpd:
|
||||||
|
image: ghcr.io/beryju/oidc-conformance-suite-httpd:v5.1.32
|
||||||
|
ports:
|
||||||
|
- "8443:8443"
|
||||||
|
- "8444:8444"
|
||||||
|
depends_on:
|
||||||
|
- server
|
||||||
|
server:
|
||||||
|
image: ghcr.io/beryju/oidc-conformance-suite-server:v5.1.32
|
||||||
|
ports:
|
||||||
|
- "9999:9999"
|
||||||
|
command: >
|
||||||
|
java
|
||||||
|
-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_mtls_url=https://localhost.emobix.co.uk:8444
|
||||||
|
--fintechlabs.devmode=true
|
||||||
|
--fintechlabs.startredir=true
|
||||||
|
links:
|
||||||
|
- mongodb:mongodb
|
||||||
|
depends_on:
|
||||||
|
- mongodb
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mongo:
|
||||||
249
tests/openid-conformance/conformance.py
Normal file
249
tests/openid-conformance/conformance.py
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
# python wrapper for conformance suite API
|
||||||
|
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
|
||||||
|
class RetryTransport(httpx.HTTPTransport):
|
||||||
|
def handle_request(
|
||||||
|
self,
|
||||||
|
request: httpx.Request,
|
||||||
|
) -> httpx.Response:
|
||||||
|
retry = 0
|
||||||
|
resp = None
|
||||||
|
while retry < 5:
|
||||||
|
retry += 1
|
||||||
|
if retry > 2:
|
||||||
|
time.sleep(1)
|
||||||
|
try:
|
||||||
|
if resp is not None:
|
||||||
|
resp.close()
|
||||||
|
resp = super().handle_request(request)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"httpx {request.url} exception {e} caught - retrying")
|
||||||
|
continue
|
||||||
|
if resp.status_code >= 500 and resp.status_code < 600:
|
||||||
|
print(f"httpx {request.url} 5xx response - retrying")
|
||||||
|
continue
|
||||||
|
content_type = resp.headers.get("Content-Type")
|
||||||
|
if content_type is not None:
|
||||||
|
mime_type, _, _ = content_type.partition(";")
|
||||||
|
if mime_type == "application/json":
|
||||||
|
try:
|
||||||
|
resp.read()
|
||||||
|
resp.json()
|
||||||
|
except Exception as e:
|
||||||
|
traceback.print_exc()
|
||||||
|
print(
|
||||||
|
f"httpx {request.url} response not decodable as json '{e}' - retrying"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
class Conformance:
|
||||||
|
def __init__(self, api_url_base, api_token, verify_ssl):
|
||||||
|
if not api_url_base.endswith("/"):
|
||||||
|
api_url_base += "/"
|
||||||
|
self.api_url_base = api_url_base
|
||||||
|
transport = RetryTransport(verify=verify_ssl)
|
||||||
|
self.httpclient = httpx.Client(verify=verify_ssl, transport=transport, timeout=20)
|
||||||
|
headers = {"Content-Type": "application/json"}
|
||||||
|
if api_token is not None:
|
||||||
|
headers["Authorization"] = f"Bearer {api_token}"
|
||||||
|
self.httpclient.headers = headers
|
||||||
|
|
||||||
|
async def get_all_test_modules(self):
|
||||||
|
"""Returns an array containing a dictionary per test module"""
|
||||||
|
api_url = f"{self.api_url_base}api/runner/available"
|
||||||
|
response = self.httpclient.get(api_url)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise Exception(
|
||||||
|
f"get_all_test_modules 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}"
|
||||||
|
try:
|
||||||
|
with self.httpclient.stream("GET", api_url) as response:
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise Exception(
|
||||||
|
f"exporthtml failed - HTTP {response.status_code:d} {response.content}"
|
||||||
|
)
|
||||||
|
d = response.headers["content-disposition"]
|
||||||
|
local_filename = re.findall('filename="(.+)"', d)[0]
|
||||||
|
full_path = os.path.join(path, local_filename)
|
||||||
|
with open(full_path, "wb") as f:
|
||||||
|
for chunk in response.iter_bytes():
|
||||||
|
f.write(chunk)
|
||||||
|
zip_file = zipfile.ZipFile(full_path)
|
||||||
|
ret = zip_file.testzip()
|
||||||
|
if ret is not None:
|
||||||
|
raise Exception(
|
||||||
|
f"exporthtml for {plan_id} downloaded corrupt zip file {full_path} - {str(ret)}"
|
||||||
|
)
|
||||||
|
return full_path
|
||||||
|
except Exception as e:
|
||||||
|
print(f"httpx {api_url} exception {e} caught - retrying")
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
raise Exception(f"exporthtml for {plan_id} failed even after retries")
|
||||||
|
|
||||||
|
async def create_certification_package(
|
||||||
|
self, plan_id, conformance_pdf_path, rp_logs_zip_path=None, output_zip_directory="./"
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Create a complete certification package zip file which is written
|
||||||
|
to the directory specified by the 'output_zip_directory' parameter.
|
||||||
|
Calling this function will additionally publish and mark the test plan as immutable.
|
||||||
|
|
||||||
|
:param plan_id: The plan id for which to create the package.
|
||||||
|
:conformance_pdf_path: The path to the signed Certification of Conformance PDF document.
|
||||||
|
:rp_logs_zip_path: Required for RP tests and is the path to the client logs zip file.
|
||||||
|
:output_zip_directory: The (already existing) directory to which the certification package zip file is written.
|
||||||
|
"""
|
||||||
|
certificationOfConformancePdf = open(conformance_pdf_path, "rb")
|
||||||
|
clientSideData = (
|
||||||
|
open(rp_logs_zip_path, "rb") if rp_logs_zip_path is not None else open(os.devnull, "rb")
|
||||||
|
)
|
||||||
|
files = {
|
||||||
|
"certificationOfConformancePdf": certificationOfConformancePdf,
|
||||||
|
"clientSideData": clientSideData,
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
with httpx.Client() as multipartClient:
|
||||||
|
multipartClient.headers = self.httpclient.headers.copy()
|
||||||
|
multipartClient.headers.pop("content-type")
|
||||||
|
api_url = f"{self.api_url_base}api/plan/{plan_id}/certificationpackage"
|
||||||
|
|
||||||
|
response = multipartClient.post(api_url, files=files)
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise Exception(
|
||||||
|
f"certificationpackage failed - HTTP {response.status_code:d} {response.content}"
|
||||||
|
)
|
||||||
|
|
||||||
|
d = response.headers["content-disposition"]
|
||||||
|
local_filename = re.findall('filename="(.+)"', d)[0]
|
||||||
|
full_path = os.path.join(output_zip_directory, local_filename)
|
||||||
|
with open(full_path, "wb") as f:
|
||||||
|
for chunk in response.iter_bytes():
|
||||||
|
f.write(chunk)
|
||||||
|
print(
|
||||||
|
f"Certification package zip for plan id {plan_id} written to {full_path}"
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
certificationOfConformancePdf.close()
|
||||||
|
clientSideData.close()
|
||||||
|
|
||||||
|
async def create_test_plan(self, name, configuration, variant=None):
|
||||||
|
api_url = f"{self.api_url_base}api/plan"
|
||||||
|
payload = {"planName": name}
|
||||||
|
if variant != None:
|
||||||
|
payload["variant"] = json.dumps(variant)
|
||||||
|
response = self.httpclient.post(api_url, params=payload, data=configuration)
|
||||||
|
|
||||||
|
if response.status_code != 201:
|
||||||
|
raise Exception(
|
||||||
|
f"create_test_plan failed - HTTP {response.status_code:d} {response.content}"
|
||||||
|
)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
async 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)
|
||||||
|
|
||||||
|
if response.status_code != 201:
|
||||||
|
raise Exception(
|
||||||
|
f"create_test failed - HTTP {response.status_code:d} {response.content}"
|
||||||
|
)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
async def create_test_from_plan(self, plan_id, test_name):
|
||||||
|
api_url = f"{self.api_url_base}api/runner"
|
||||||
|
payload = {"test": test_name, "plan": plan_id}
|
||||||
|
response = self.httpclient.post(api_url, params=payload)
|
||||||
|
|
||||||
|
if response.status_code != 201:
|
||||||
|
raise Exception(
|
||||||
|
f"create_test_from_plan failed - HTTP {response.status_code:d} {response.content}"
|
||||||
|
)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
async 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:
|
||||||
|
payload["variant"] = json.dumps(variant)
|
||||||
|
response = self.httpclient.post(api_url, params=payload)
|
||||||
|
|
||||||
|
if response.status_code != 201:
|
||||||
|
raise Exception(
|
||||||
|
f"create_test_from_plan failed - HTTP {response.status_code:d} {response.content}"
|
||||||
|
)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
async def get_module_info(self, module_id):
|
||||||
|
api_url = f"{self.api_url_base}api/info/{module_id}"
|
||||||
|
response = self.httpclient.get(api_url)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise Exception(
|
||||||
|
f"get_module_info failed - HTTP {response.status_code:d} {response.content}"
|
||||||
|
)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
async def get_test_log(self, module_id):
|
||||||
|
api_url = f"{self.api_url_base}api/log/{module_id}"
|
||||||
|
response = self.httpclient.get(api_url)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise Exception(
|
||||||
|
f"get_test_log 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)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise Exception(
|
||||||
|
f"start_test failed - HTTP {response.status_code:d} {response.content}"
|
||||||
|
)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
async def wait_for_state(self, module_id, required_states, timeout=240):
|
||||||
|
timeout_at = time.time() + timeout
|
||||||
|
while True:
|
||||||
|
if time.time() > timeout_at:
|
||||||
|
raise Exception(
|
||||||
|
f"Timed out waiting for test module {module_id} to be in one of states: {required_states}"
|
||||||
|
)
|
||||||
|
|
||||||
|
info = await self.get_module_info(module_id)
|
||||||
|
|
||||||
|
status = info["status"]
|
||||||
|
print(f"module id {module_id} status is {status}")
|
||||||
|
if status in required_states:
|
||||||
|
return status
|
||||||
|
if status == "INTERRUPTED":
|
||||||
|
raise Exception(f"Test module {module_id} has moved to INTERRUPTED")
|
||||||
|
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
|
async def close_client(self):
|
||||||
|
self.httpclient.close()
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
# yaml-language-server: $schema=https://goauthentik.io/blueprints/schema.json
|
||||||
version: 1
|
version: 1
|
||||||
metadata:
|
metadata:
|
||||||
name: OIDC conformance testing
|
name: OIDC conformance testing
|
||||||
@ -34,12 +35,15 @@ entries:
|
|||||||
name: provider
|
name: provider
|
||||||
attrs:
|
attrs:
|
||||||
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
|
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
|
||||||
|
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||||
issuer_mode: global
|
issuer_mode: global
|
||||||
client_id: 4054d882aff59755f2f279968b97ce8806a926e1
|
client_id: 4054d882aff59755f2f279968b97ce8806a926e1
|
||||||
client_secret: 4c7e4933009437fb486b5389d15b173109a0555dc47e0cc0949104f1925bcc6565351cb1dffd7e6818cf074f5bd50c210b565121a7328ee8bd40107fc4bbd867
|
client_secret: 4c7e4933009437fb486b5389d15b173109a0555dc47e0cc0949104f1925bcc6565351cb1dffd7e6818cf074f5bd50c210b565121a7328ee8bd40107fc4bbd867
|
||||||
redirect_uris: |
|
redirect_uris:
|
||||||
https://localhost:8443/test/a/authentik/callback
|
- matching_mode: strict
|
||||||
https://localhost.emobix.co.uk:8443/test/a/authentik/callback
|
url: https://localhost:8443/test/a/authentik/callback
|
||||||
|
- matching_mode: strict
|
||||||
|
url: https://localhost.emobix.co.uk:8443/test/a/authentik/callback
|
||||||
property_mappings:
|
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-openid]]
|
||||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-email]]
|
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-email]]
|
||||||
@ -60,12 +64,15 @@ entries:
|
|||||||
name: oidc-conformance-2
|
name: oidc-conformance-2
|
||||||
attrs:
|
attrs:
|
||||||
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
|
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
|
||||||
|
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||||
issuer_mode: global
|
issuer_mode: global
|
||||||
client_id: ad64aeaf1efe388ecf4d28fcc537e8de08bcae26
|
client_id: ad64aeaf1efe388ecf4d28fcc537e8de08bcae26
|
||||||
client_secret: ff2e34a5b04c99acaf7241e25a950e7f6134c86936923d8c698d8f38bd57647750d661069612c0ee55045e29fe06aa101804bdae38e8360647d595e771fea789
|
client_secret: ff2e34a5b04c99acaf7241e25a950e7f6134c86936923d8c698d8f38bd57647750d661069612c0ee55045e29fe06aa101804bdae38e8360647d595e771fea789
|
||||||
redirect_uris: |
|
redirect_uris:
|
||||||
https://localhost:8443/test/a/authentik/callback
|
- matching_mode: strict
|
||||||
https://localhost.emobix.co.uk:8443/test/a/authentik/callback
|
url: https://localhost:8443/test/a/authentik/callback
|
||||||
|
- matching_mode: strict
|
||||||
|
url: https://localhost.emobix.co.uk:8443/test/a/authentik/callback
|
||||||
property_mappings:
|
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-openid]]
|
||||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-email]]
|
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-email]]
|
||||||
114
tests/openid-conformance/run.py
Normal file
114
tests/openid-conformance/run.py
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
#!/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", "contains", "application/o/authorize", 10],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"task": "Authorize",
|
||||||
|
"match": "http://10.120.20.76:9000/application/o/authorize*",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"task": "Authorize 2",
|
||||||
|
"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/",
|
||||||
|
)
|
||||||
|
)
|
||||||
@ -1,20 +1,12 @@
|
|||||||
import { fromByteArray } from "base64-js";
|
import { fromByteArray } from "base64-js";
|
||||||
import "formdata-polyfill";
|
// import "formdata-polyfill";
|
||||||
import $ from "jquery";
|
import $ from "jquery";
|
||||||
import "weakmap-polyfill";
|
import "weakmap-polyfill";
|
||||||
|
|
||||||
import {
|
|
||||||
type AuthenticatorValidationChallenge,
|
|
||||||
type AutosubmitChallenge,
|
import { type AuthenticatorValidationChallenge, type AutosubmitChallenge, type ChallengeTypes, ChallengeTypesFromJSON, type ContextualFlowInfo, type DeviceChallenge, type ErrorDetail, type IdentificationChallenge, type PasswordChallenge, type RedirectChallenge } from "@goauthentik/api";
|
||||||
type ChallengeTypes,
|
|
||||||
ChallengeTypesFromJSON,
|
|
||||||
type ContextualFlowInfo,
|
|
||||||
type DeviceChallenge,
|
|
||||||
type ErrorDetail,
|
|
||||||
type IdentificationChallenge,
|
|
||||||
type PasswordChallenge,
|
|
||||||
type RedirectChallenge,
|
|
||||||
} from "@goauthentik/api";
|
|
||||||
|
|
||||||
interface GlobalAuthentik {
|
interface GlobalAuthentik {
|
||||||
brand: {
|
brand: {
|
||||||
@ -67,15 +59,17 @@ class SimpleFlowExecutor {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
submit(data: { [key: string]: unknown } | FormData) {
|
submit(data: { [key: string]: unknown } | HTMLFormElement) {
|
||||||
$("button[type=submit]").addClass("disabled")
|
$("button[type=submit]").addClass("disabled")
|
||||||
.html(`<span class="spinner-border spinner-border-sm" aria-hidden="true"></span>
|
.html(`<span class="spinner-border spinner-border-sm" aria-hidden="true"></span>
|
||||||
<span role="status">Loading...</span>`);
|
<span id="loading-text" role="status">Loading...</span>`);
|
||||||
let finalData: { [key: string]: unknown } = {};
|
let finalData: { [key: string]: unknown } = {};
|
||||||
if (data instanceof FormData) {
|
if (data.tagName === "FORM") {
|
||||||
finalData = {};
|
// @ts-expect-error
|
||||||
data.forEach((value, key) => {
|
const rawData = $(data as unknown as string).serializeArray();
|
||||||
finalData[key] = value;
|
|
||||||
|
rawData.forEach((entry) => {
|
||||||
|
finalData[entry.name] = entry.value;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
finalData = data;
|
finalData = data;
|
||||||
@ -198,8 +192,7 @@ class IdentificationStage extends Stage<IdentificationChallenge> {
|
|||||||
$("#ident-form input[name=uid_field]").trigger("focus");
|
$("#ident-form input[name=uid_field]").trigger("focus");
|
||||||
$("#ident-form").on("submit", (ev) => {
|
$("#ident-form").on("submit", (ev) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
const data = new FormData(ev.target as HTMLFormElement);
|
this.executor.submit(ev.target as HTMLFormElement);
|
||||||
this.executor.submit(data);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -222,8 +215,7 @@ class PasswordStage extends Stage<PasswordChallenge> {
|
|||||||
$("#password-form input").trigger("focus");
|
$("#password-form input").trigger("focus");
|
||||||
$("#password-form").on("submit", (ev) => {
|
$("#password-form").on("submit", (ev) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
const data = new FormData(ev.target as HTMLFormElement);
|
this.executor.submit(ev.target as HTMLFormElement);
|
||||||
this.executor.submit(data);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -485,8 +477,7 @@ class AuthenticatorValidateStage extends Stage<AuthenticatorValidationChallenge>
|
|||||||
$("#totp-form input").trigger("focus");
|
$("#totp-form input").trigger("focus");
|
||||||
$("#totp-form").on("submit", (ev) => {
|
$("#totp-form").on("submit", (ev) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
const data = new FormData(ev.target as HTMLFormElement);
|
this.executor.submit(ev.target as HTMLFormElement);
|
||||||
this.executor.submit(data);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user