#!/usr/bin/env python3 """OIDC integration test for La Suite Docs + Keycloak.""" import argparse import os import sys import urllib.request import urllib.error sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) from utils.tests.helpers import ( http_get, http_post, load_toml_credentials, resolve_domain, ) def main(): parser = argparse.ArgumentParser() parser.add_argument('--domain', default=os.environ.get('TEST_DOMAIN')) args = parser.parse_args() recipe_dir = os.path.join(os.path.dirname(__file__), '..') creds = load_toml_credentials(recipe_dir, 'keycloak') if creds is None: print("FAIL: Credentials file not found: keycloak-test-credentials..toml") print("Run setup_keycloak_integration.py first.") sys.exit(1) docs_domain = args.domain or resolve_domain('lasuite-docs') kc_domain = resolve_domain('keycloak') docs_url = f"https://{docs_domain}" kc_url = f"https://{kc_domain}" print("Testing OIDC integration: La Suite Docs <-> Keycloak") print() # Step 1: Verify Docs redirects to Keycloak print("Step 1: Checking Docs OIDC redirect ...") try: req = urllib.request.Request(f"{docs_url}/api/v1.0/authenticate/") opener = urllib.request.build_opener(NoRedirectHandler()) resp = opener.open(req, timeout=15) redirect_url = resp.headers.get('Location', '') except urllib.error.HTTPError as e: redirect_url = e.headers.get('Location', '') if e.headers else '' expected_prefix = f"{kc_url}/realms/{creds['kc_realm']}/protocol/openid-connect/auth" if expected_prefix in (redirect_url or ''): print(f" PASS: Docs redirects to Keycloak realm '{creds['kc_realm']}'") else: print(f" FAIL: Expected redirect to Keycloak, got: {redirect_url}") sys.exit(1) # Step 2: Obtain token from Keycloak print("Step 2: Obtaining token from Keycloak ...") token_url = f"{kc_url}/realms/{creds['kc_realm']}/protocol/openid-connect/token" status, data = http_post( token_url, data={ "grant_type": "password", "client_id": creds["kc_client_id"], "client_secret": creds["kc_client_secret"], "username": creds["kc_test_user"], "password": creds["kc_test_pass"], "scope": "openid email", }, content_type="application/x-www-form-urlencoded", ) access_token = (data or {}).get("access_token", "") if not access_token: error = (data or {}).get("error_description", (data or {}).get("error", "unknown")) print(f" FAIL: Could not obtain token from Keycloak: {error}") sys.exit(1) print(f" PASS: Obtained access token from Keycloak ({len(access_token)} chars)") # Step 3: Use token to access Docs API print("Step 3: Accessing Docs API with Keycloak token ...") status, body = http_get( f"{docs_url}/api/v1.0/users/me/", headers={"Authorization": f"Bearer {access_token}"}, ) if status != 200: print(f" FAIL: Docs API returned HTTP {status} (expected 200)") sys.exit(1) user_email = (body or {}).get("email", "") expected_email = f"{creds['kc_test_user']}@test.example.com" if user_email == expected_email: print(f" PASS: Docs API returned user with email '{user_email}'") else: print(f" FAIL: Expected email '{expected_email}', got '{user_email}'") sys.exit(1) print() print("PASS: OIDC integration test passed — Docs authenticates via Keycloak") class NoRedirectHandler(urllib.request.HTTPRedirectHandler): def redirect_request(self, req, fp, code, msg, headers, newurl): raise urllib.error.HTTPError(newurl, code, msg, headers, fp) if __name__ == '__main__': main()