mirror of
https://github.com/CloudNebulaProject/barycenter.git
synced 2026-04-10 21:20:41 +00:00
Implement the remaining items from docs/next-iteration-plan.md: - Add POST /introspect endpoint (RFC 7662) with client authentication, support for access and refresh tokens, and token_type_hint - Add raw token lookup functions in storage for introspection - Add revocation_endpoint and introspection_endpoint to discovery metadata - Create docs/flows.md with end-to-end curl examples for all OIDC flows - Create scripts/validate-oidc.sh to verify discovery, JWKS, registration, introspection, and revocation endpoints - Update docs/oidc-conformance.md to reflect actual implementation status - Update README.md and CLAUDE.md pending sections to be accurate https://claude.ai/code/session_01JBxVy75XfwwZB8iBXjTxT3
170 lines
7.7 KiB
Bash
Executable file
170 lines
7.7 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# validate-oidc.sh — Validate OIDC endpoints on a running Barycenter instance.
|
|
#
|
|
# Usage: ./scripts/validate-oidc.sh [BASE_URL]
|
|
# BASE_URL defaults to http://localhost:9090
|
|
|
|
set -euo pipefail
|
|
|
|
BASE_URL="${1:-http://localhost:9090}"
|
|
PASS=0
|
|
FAIL=0
|
|
|
|
pass() { echo " PASS: $1"; PASS=$((PASS + 1)); }
|
|
fail() { echo " FAIL: $1"; FAIL=$((FAIL + 1)); }
|
|
check() {
|
|
local desc="$1" actual="$2" expected="$3"
|
|
if [ "$actual" = "$expected" ]; then
|
|
pass "$desc"
|
|
else
|
|
fail "$desc (expected '$expected', got '$actual')"
|
|
fi
|
|
}
|
|
|
|
echo "=== Barycenter OIDC Validation ==="
|
|
echo "Base URL: $BASE_URL"
|
|
echo
|
|
|
|
# ─── 1. Discovery ───────────────────────────────────────────────────────────
|
|
echo "--- 1. Discovery Document ---"
|
|
DISCO=$(curl -sf "$BASE_URL/.well-known/openid-configuration" 2>/dev/null) || {
|
|
fail "Discovery endpoint unreachable"
|
|
echo "Cannot continue without discovery. Is the server running?"
|
|
exit 1
|
|
}
|
|
|
|
check "issuer present" "$(echo "$DISCO" | jq -r '.issuer')" "$BASE_URL"
|
|
check "authorization_endpoint" "$(echo "$DISCO" | jq -r '.authorization_endpoint')" "$BASE_URL/authorize"
|
|
check "token_endpoint" "$(echo "$DISCO" | jq -r '.token_endpoint')" "$BASE_URL/token"
|
|
check "userinfo_endpoint" "$(echo "$DISCO" | jq -r '.userinfo_endpoint')" "$BASE_URL/userinfo"
|
|
check "jwks_uri" "$(echo "$DISCO" | jq -r '.jwks_uri')" "$BASE_URL/.well-known/jwks.json"
|
|
check "registration_endpoint" "$(echo "$DISCO" | jq -r '.registration_endpoint')" "$BASE_URL/connect/register"
|
|
check "revocation_endpoint" "$(echo "$DISCO" | jq -r '.revocation_endpoint')" "$BASE_URL/revoke"
|
|
check "introspection_endpoint" "$(echo "$DISCO" | jq -r '.introspection_endpoint')" "$BASE_URL/introspect"
|
|
|
|
# Check supported values
|
|
check "response_types includes code" \
|
|
"$(echo "$DISCO" | jq '[.response_types_supported[] | select(. == "code")] | length')" "1"
|
|
check "code_challenge_methods includes S256" \
|
|
"$(echo "$DISCO" | jq '[.code_challenge_methods_supported[] | select(. == "S256")] | length')" "1"
|
|
check "auth methods include client_secret_basic" \
|
|
"$(echo "$DISCO" | jq '[.token_endpoint_auth_methods_supported[] | select(. == "client_secret_basic")] | length')" "1"
|
|
check "auth methods include client_secret_post" \
|
|
"$(echo "$DISCO" | jq '[.token_endpoint_auth_methods_supported[] | select(. == "client_secret_post")] | length')" "1"
|
|
check "signing alg includes RS256" \
|
|
"$(echo "$DISCO" | jq '[.id_token_signing_alg_values_supported[] | select(. == "RS256")] | length')" "1"
|
|
echo
|
|
|
|
# ─── 2. JWKS ────────────────────────────────────────────────────────────────
|
|
echo "--- 2. JWKS ---"
|
|
JWKS=$(curl -sf "$BASE_URL/.well-known/jwks.json" 2>/dev/null) || {
|
|
fail "JWKS endpoint unreachable"
|
|
JWKS="{}"
|
|
}
|
|
|
|
KEY_COUNT=$(echo "$JWKS" | jq '.keys | length' 2>/dev/null || echo 0)
|
|
if [ "$KEY_COUNT" -gt 0 ]; then
|
|
pass "JWKS has $KEY_COUNT key(s)"
|
|
HAS_KID=$(echo "$JWKS" | jq -r '.keys[0].kid // empty')
|
|
if [ -n "$HAS_KID" ]; then
|
|
pass "First key has kid: $HAS_KID"
|
|
else
|
|
fail "First key missing kid"
|
|
fi
|
|
else
|
|
fail "JWKS has no keys"
|
|
fi
|
|
echo
|
|
|
|
# ─── 3. Dynamic Client Registration ─────────────────────────────────────────
|
|
echo "--- 3. Client Registration ---"
|
|
REG_RESP=$(curl -sf -X POST "$BASE_URL/connect/register" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"redirect_uris": ["http://localhost:8080/callback"],
|
|
"client_name": "Validation Script Client",
|
|
"token_endpoint_auth_method": "client_secret_basic"
|
|
}' 2>/dev/null) || {
|
|
fail "Client registration failed"
|
|
REG_RESP="{}"
|
|
}
|
|
|
|
CLIENT_ID=$(echo "$REG_RESP" | jq -r '.client_id // empty')
|
|
CLIENT_SECRET=$(echo "$REG_RESP" | jq -r '.client_secret // empty')
|
|
|
|
if [ -n "$CLIENT_ID" ] && [ -n "$CLIENT_SECRET" ]; then
|
|
pass "Client registered (client_id: ${CLIENT_ID:0:12}...)"
|
|
else
|
|
fail "Client registration did not return client_id/client_secret"
|
|
fi
|
|
echo
|
|
|
|
# ─── 4. PKCE Generation ─────────────────────────────────────────────────────
|
|
echo "--- 4. PKCE Generation ---"
|
|
CODE_VERIFIER=$(openssl rand -base64 32 | tr -d '=' | tr '+/' '-_')
|
|
CODE_CHALLENGE=$(printf '%s' "$CODE_VERIFIER" | openssl dgst -binary -sha256 | base64 | tr -d '=' | tr '+/' '-_')
|
|
pass "code_verifier generated (${#CODE_VERIFIER} chars)"
|
|
pass "code_challenge generated (S256)"
|
|
echo
|
|
|
|
# ─── 5. Authorization URL ───────────────────────────────────────────────────
|
|
echo "--- 5. Authorization ---"
|
|
AUTH_URL="$BASE_URL/authorize?client_id=$CLIENT_ID&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fcallback&response_type=code&scope=openid%20profile%20email&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256&state=validate123&nonce=nonce456"
|
|
|
|
AUTH_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$AUTH_URL" 2>/dev/null)
|
|
# Expect 302 redirect (to login) or 200 (if already logged in)
|
|
if [ "$AUTH_STATUS" = "302" ] || [ "$AUTH_STATUS" = "303" ] || [ "$AUTH_STATUS" = "200" ]; then
|
|
pass "Authorization endpoint responds ($AUTH_STATUS)"
|
|
else
|
|
fail "Authorization endpoint returned $AUTH_STATUS"
|
|
fi
|
|
echo
|
|
|
|
# ─── 6. UserInfo (unauthenticated — expect 401) ─────────────────────────────
|
|
echo "--- 6. UserInfo (unauthenticated) ---"
|
|
UI_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/userinfo" 2>/dev/null)
|
|
check "UserInfo without token returns 401" "$UI_STATUS" "401"
|
|
echo
|
|
|
|
# ─── 7. Token Introspection (with invalid token) ────────────────────────────
|
|
echo "--- 7. Token Introspection ---"
|
|
if [ -n "$CLIENT_ID" ] && [ -n "$CLIENT_SECRET" ]; then
|
|
AUTH_HEADER=$(printf '%s:%s' "$CLIENT_ID" "$CLIENT_SECRET" | base64 -w0 2>/dev/null || printf '%s:%s' "$CLIENT_ID" "$CLIENT_SECRET" | base64 2>/dev/null)
|
|
INTROSPECT_RESP=$(curl -sf -X POST "$BASE_URL/introspect" \
|
|
-H "Authorization: Basic $AUTH_HEADER" \
|
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
-d "token=invalid_token_12345" 2>/dev/null) || INTROSPECT_RESP="{}"
|
|
|
|
ACTIVE=$(echo "$INTROSPECT_RESP" | jq -r '.active // empty')
|
|
check "Introspection of invalid token returns active=false" "$ACTIVE" "false"
|
|
else
|
|
fail "Skipping introspection (no client credentials)"
|
|
fi
|
|
echo
|
|
|
|
# ─── 8. Token Revocation (with invalid token — should return 200) ───────────
|
|
echo "--- 8. Token Revocation ---"
|
|
if [ -n "$CLIENT_ID" ] && [ -n "$CLIENT_SECRET" ]; then
|
|
REVOKE_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$BASE_URL/revoke" \
|
|
-H "Authorization: Basic $AUTH_HEADER" \
|
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
-d "token=invalid_token_12345" 2>/dev/null)
|
|
check "Revocation of unknown token returns 200" "$REVOKE_STATUS" "200"
|
|
else
|
|
fail "Skipping revocation (no client credentials)"
|
|
fi
|
|
echo
|
|
|
|
# ─── Summary ─────────────────────────────────────────────────────────────────
|
|
echo "=== Summary ==="
|
|
echo " Passed: $PASS"
|
|
echo " Failed: $FAIL"
|
|
echo
|
|
|
|
if [ "$FAIL" -gt 0 ]; then
|
|
echo "Some checks failed. Ensure the server is running and configured correctly."
|
|
exit 1
|
|
else
|
|
echo "All checks passed."
|
|
exit 0
|
|
fi
|