feat(workbench): persist operational workflows and events
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Provision the isolated public Keycloak client used by the 15173 UI."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import getpass
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(REPOSITORY_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(REPOSITORY_ROOT))
|
||||
|
||||
from scripts.provision_operational_worker_identity import ( # noqa: E402
|
||||
KeycloakProvisioner,
|
||||
ProvisionError,
|
||||
_request,
|
||||
)
|
||||
|
||||
|
||||
REDIRECT_URIS = [
|
||||
"http://127.0.0.1:15173/*",
|
||||
"http://localhost:15173/*",
|
||||
]
|
||||
WEB_ORIGINS = [
|
||||
"http://127.0.0.1:15173",
|
||||
"http://localhost:15173",
|
||||
]
|
||||
|
||||
|
||||
def ensure_public_client(
|
||||
keycloak: KeycloakProvisioner, client_id: str
|
||||
) -> tuple[bool, dict]:
|
||||
matches = _request(
|
||||
keycloak.client,
|
||||
"GET",
|
||||
f"{keycloak.admin_base}/clients",
|
||||
params={"clientId": client_id},
|
||||
).json()
|
||||
exact = [item for item in matches if item.get("clientId") == client_id]
|
||||
if len(exact) > 1:
|
||||
raise ProvisionError("multiple Keycloak clients use the integration client id")
|
||||
created = not exact
|
||||
if created:
|
||||
_request(
|
||||
keycloak.client,
|
||||
"POST",
|
||||
f"{keycloak.admin_base}/clients",
|
||||
json={
|
||||
"clientId": client_id,
|
||||
"name": "TJWater isolated integration UI",
|
||||
"description": "Local 15173 UI; authorization code flow with PKCE only",
|
||||
"enabled": True,
|
||||
"protocol": "openid-connect",
|
||||
"publicClient": True,
|
||||
"bearerOnly": False,
|
||||
"serviceAccountsEnabled": False,
|
||||
"standardFlowEnabled": True,
|
||||
"implicitFlowEnabled": False,
|
||||
"directAccessGrantsEnabled": False,
|
||||
"fullScopeAllowed": False,
|
||||
"redirectUris": REDIRECT_URIS,
|
||||
"webOrigins": WEB_ORIGINS,
|
||||
"attributes": {"pkce.code.challenge.method": "S256"},
|
||||
},
|
||||
)
|
||||
matches = _request(
|
||||
keycloak.client,
|
||||
"GET",
|
||||
f"{keycloak.admin_base}/clients",
|
||||
params={"clientId": client_id},
|
||||
).json()
|
||||
exact = [item for item in matches if item.get("clientId") == client_id]
|
||||
if len(exact) != 1 or not isinstance(exact[0].get("id"), str):
|
||||
raise ProvisionError("could not resolve the integration UI client")
|
||||
internal_id = exact[0]["id"]
|
||||
representation = _request(
|
||||
keycloak.client,
|
||||
"GET",
|
||||
f"{keycloak.admin_base}/clients/{internal_id}",
|
||||
).json()
|
||||
attributes = dict(representation.get("attributes") or {})
|
||||
attributes["pkce.code.challenge.method"] = "S256"
|
||||
representation.update(
|
||||
{
|
||||
"enabled": True,
|
||||
"publicClient": True,
|
||||
"bearerOnly": False,
|
||||
"serviceAccountsEnabled": False,
|
||||
"standardFlowEnabled": True,
|
||||
"implicitFlowEnabled": False,
|
||||
"directAccessGrantsEnabled": False,
|
||||
"fullScopeAllowed": False,
|
||||
"redirectUris": REDIRECT_URIS,
|
||||
"webOrigins": WEB_ORIGINS,
|
||||
"attributes": attributes,
|
||||
}
|
||||
)
|
||||
_request(
|
||||
keycloak.client,
|
||||
"PUT",
|
||||
f"{keycloak.admin_base}/clients/{internal_id}",
|
||||
json=representation,
|
||||
)
|
||||
verified = _request(
|
||||
keycloak.client,
|
||||
"GET",
|
||||
f"{keycloak.admin_base}/clients/{internal_id}",
|
||||
).json()
|
||||
expected = {
|
||||
"enabled": True,
|
||||
"publicClient": True,
|
||||
"bearerOnly": False,
|
||||
"serviceAccountsEnabled": False,
|
||||
"standardFlowEnabled": True,
|
||||
"implicitFlowEnabled": False,
|
||||
"directAccessGrantsEnabled": False,
|
||||
"fullScopeAllowed": False,
|
||||
}
|
||||
if any(verified.get(key) != value for key, value in expected.items()):
|
||||
raise ProvisionError("integration UI client flow settings did not persist")
|
||||
if set(verified.get("redirectUris") or []) != set(REDIRECT_URIS):
|
||||
raise ProvisionError("integration UI redirect URI allowlist did not persist")
|
||||
if set(verified.get("webOrigins") or []) != set(WEB_ORIGINS):
|
||||
raise ProvisionError("integration UI web origin allowlist did not persist")
|
||||
if (verified.get("attributes") or {}).get("pkce.code.challenge.method") != "S256":
|
||||
raise ProvisionError("integration UI PKCE policy did not persist")
|
||||
return created, {
|
||||
"client_id": client_id,
|
||||
"public_client": True,
|
||||
"standard_flow": True,
|
||||
"pkce": "S256",
|
||||
"implicit_flow": False,
|
||||
"direct_grants": False,
|
||||
"service_accounts": False,
|
||||
"redirect_uris": REDIRECT_URIS,
|
||||
"web_origins": WEB_ORIGINS,
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--keycloak-url", default="http://192.168.1.114:8088")
|
||||
parser.add_argument("--realm", default="tjwater")
|
||||
parser.add_argument("--admin-username", default="admin")
|
||||
parser.add_argument("--client-id", default="tjwater-integration-ui")
|
||||
args = parser.parse_args()
|
||||
password = os.environ.get("KEYCLOAK_ADMIN_PASSWORD") or getpass.getpass(
|
||||
"Keycloak admin password: "
|
||||
)
|
||||
if not password:
|
||||
raise SystemExit("administrator password is required")
|
||||
try:
|
||||
keycloak = KeycloakProvisioner(
|
||||
args.keycloak_url, args.realm, args.admin_username, password
|
||||
)
|
||||
try:
|
||||
created, result = ensure_public_client(keycloak, args.client_id)
|
||||
finally:
|
||||
keycloak.close()
|
||||
except ProvisionError as exc:
|
||||
raise SystemExit(f"provisioning failed: {exc}") from None
|
||||
print(json.dumps({"created": created, **result}, sort_keys=True))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user