196 lines
8.6 KiB
Bash
Executable File
196 lines
8.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# AUTOMATED SSO-only integration test — two phases.
|
|
#
|
|
# Both phases run against the SAME fake IdP + SAME database (spawned
|
|
# once) but restart OxiCloud between them so the boot config differs:
|
|
#
|
|
# Phase A — server-with-oidc-only-no-policy.env
|
|
# OXICLOUD_AUTH_METHODS=oidc, no AUTH_POLICIES
|
|
# → GET /login must return 200 (SPA shell, no redirect)
|
|
# → providers.auto_redirect_to_oidc == false
|
|
# Driven by sso-only-no-policy.hurl.
|
|
#
|
|
# Phase B — server-with-oidc-only.env
|
|
# OXICLOUD_AUTH_METHODS=oidc AND
|
|
# OXICLOUD_AUTH_POLICIES=auto_redirect_if_standalone_oidc
|
|
# → GET /login must return 307 to /api/auth/oidc/authorize
|
|
# → providers.auto_redirect_to_oidc == true
|
|
# → full OIDC dance + RP-initiated logout assertions
|
|
# Driven by sso-only.hurl.
|
|
#
|
|
# Phase order matters: A runs first because it doesn't touch DB state
|
|
# (no admin bootstrap). B runs second and does the admin bootstrap via
|
|
# JIT provisioning. Restarting OxiCloud between phases is cheap
|
|
# (~500ms) and cleaner than a hot config reload.
|
|
#
|
|
# Sibling script tests/oidc/run-manual-sso-only.sh runs Phase B only
|
|
# and stops after "server ready" so a human can eyeball the browser
|
|
# flow — keep both: this script proves the wire contract, the manual
|
|
# one proves the UX.
|
|
#
|
|
# Ports: OxiCloud on 8090, fake IdP on 1081 (distinct from 8087 / 1080
|
|
# so this can run alongside `just api-test` or a local dev server).
|
|
#
|
|
# Prerequisites: docker, cargo, node >= 20, npm, hurl >= 4.0.
|
|
set -euo pipefail
|
|
|
|
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
COMMON="$REPO_ROOT/tests/common"
|
|
OIDC_DIR="$REPO_ROOT/tests/oidc"
|
|
FAKE_IDP_DIR="$OIDC_DIR/fake_idp"
|
|
|
|
# shellcheck source=sso-only.env
|
|
source "$OIDC_DIR/sso-only.env"
|
|
|
|
SERVER_PORT="${base_url##*:}"
|
|
IDP_PORT="${oidc_issuer##*:}"
|
|
|
|
# ── Helpers ────────────────────────────────────────────────────────────────
|
|
log() { echo "[sso-only] $*"; }
|
|
die() { echo "[sso-only] ERROR: $*" >&2; exit 1; }
|
|
|
|
wait_for_http() {
|
|
local url="$1" timeout="${2:-60}"
|
|
local deadline=$(( $(date +%s) + timeout ))
|
|
until curl -sf "$url" >/dev/null 2>&1; do
|
|
[[ $(date +%s) -ge $deadline ]] && die "Timeout waiting for $url"
|
|
sleep 0.5
|
|
done
|
|
}
|
|
|
|
# ── Fake-IdP process management (mirrors tests/oidc/run.sh) ────────────────
|
|
kill_fake_idp() {
|
|
pkill -f "tests/oidc/fake_idp/server.js" 2>/dev/null || true
|
|
if command -v lsof >/dev/null 2>&1; then
|
|
local pids
|
|
pids=$(lsof -ti :"$IDP_PORT" 2>/dev/null || true)
|
|
if [[ -n "$pids" ]]; then
|
|
# shellcheck disable=SC2086
|
|
kill -9 $pids 2>/dev/null || true
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# ── Server process management ──────────────────────────────────────────────
|
|
SERVER_PID=""
|
|
|
|
start_oxicloud() {
|
|
local env_file="$1"
|
|
set -a
|
|
# shellcheck disable=SC1090
|
|
source "$env_file"
|
|
OXICLOUD_SERVER_PORT=$SERVER_PORT
|
|
OXICLOUD_STORAGE_PATH="$REPO_ROOT/tests/oidc/storage-sso-only"
|
|
set +a
|
|
log "Starting OxiCloud (config: $(basename "$env_file"))..."
|
|
"$OXICLOUD_BIN" --config "$env_file" &
|
|
SERVER_PID=$!
|
|
wait_for_http "$base_url/ready" 120
|
|
log "Server is ready (pid $SERVER_PID)."
|
|
}
|
|
|
|
stop_oxicloud() {
|
|
if [[ -n "$SERVER_PID" ]]; then
|
|
log "Stopping OxiCloud (pid $SERVER_PID)..."
|
|
kill "$SERVER_PID" 2>/dev/null || true
|
|
wait "$SERVER_PID" 2>/dev/null || true
|
|
SERVER_PID=""
|
|
# Give the OS a moment to release the port; without this a fast
|
|
# restart occasionally loses the bind on macOS.
|
|
sleep 0.3
|
|
fi
|
|
}
|
|
|
|
# ── Teardown (always runs on exit) ─────────────────────────────────────────
|
|
cleanup() {
|
|
stop_oxicloud
|
|
log "Stopping fake-idp..."
|
|
kill_fake_idp
|
|
bash "$COMMON/stop-db.sh" || true
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
# ── 1. Postgres ────────────────────────────────────────────────────────────
|
|
bash "$COMMON/spawn-db.sh"
|
|
|
|
# ── 2. Fake IdP (Node) ─────────────────────────────────────────────────────
|
|
log "Installing fake-idp dependencies..."
|
|
if [[ -f "$FAKE_IDP_DIR/package-lock.json" ]]; then
|
|
(cd "$FAKE_IDP_DIR" && npm ci --silent --no-audit --no-fund)
|
|
else
|
|
(cd "$FAKE_IDP_DIR" && npm install --silent --no-audit --no-fund)
|
|
fi
|
|
|
|
log "Sweeping any orphan fake-idp processes from prior runs..."
|
|
kill_fake_idp
|
|
sleep 0.3
|
|
|
|
log "Starting fake-idp on port $IDP_PORT..."
|
|
FAKE_IDP_ISSUER="$oidc_issuer" FAKE_IDP_PORT="$IDP_PORT" \
|
|
OXICLOUD_BASE_URL_FOR_BCL="$base_url" \
|
|
node "$FAKE_IDP_DIR/server.js" > /tmp/fake-idp-sso-only.log 2>&1 &
|
|
log "Waiting for fake-idp discovery endpoint..."
|
|
wait_for_http "$oidc_issuer/.well-known/openid-configuration" 30
|
|
log "fake-idp is ready (logs: /tmp/fake-idp-sso-only.log)"
|
|
|
|
# ── 3. Wipe storage once ───────────────────────────────────────────────────
|
|
export OXICLOUD_STORAGE_PATH="$REPO_ROOT/tests/oidc/storage-sso-only"
|
|
# shellcheck source=../common/wipe-storage.sh
|
|
source "$COMMON/wipe-storage.sh"
|
|
wipe_storage "$OXICLOUD_STORAGE_PATH"
|
|
|
|
# ── 3.5. Ensure the SPA is built (static-dist/) ────────────────────────────
|
|
# Both phases hit /login and expect the SPA shell response (Phase A as
|
|
# the primary assertion, Phase B as the loop-guard fallthrough). Without
|
|
# static-dist/ the ServeDir fallback would 404 those calls.
|
|
DIST_DIR="$REPO_ROOT/static-dist"
|
|
if [[ ! -f "$DIST_DIR/index.html" ]]; then
|
|
log "Building SvelteKit SPA (static-dist/index.html missing)..."
|
|
(cd "$REPO_ROOT/frontend" \
|
|
&& npm ci --silent --no-audit --no-fund \
|
|
&& npm run build) || die "Frontend build failed; static-dist/ is required"
|
|
fi
|
|
|
|
# ── 4. Build OxiCloud once ─────────────────────────────────────────────────
|
|
BUILD_TARGET="${BUILD_TARGET:-debug}"
|
|
OXICLOUD_BIN="$REPO_ROOT/target/$BUILD_TARGET/oxicloud"
|
|
|
|
if [[ ! -x "$OXICLOUD_BIN" ]]; then
|
|
log "Building OxiCloud server ($BUILD_TARGET)..."
|
|
case "$BUILD_TARGET" in
|
|
debug) (cd "$REPO_ROOT" && cargo build 2>&1 | tail -n 20) || die "cargo build failed" ;;
|
|
release) (cd "$REPO_ROOT" && cargo build --release 2>&1 | tail -n 20) || die "cargo build --release failed" ;;
|
|
*) die "Unsupported BUILD_TARGET='$BUILD_TARGET' (expected 'debug' or 'release')" ;;
|
|
esac
|
|
fi
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════
|
|
# Phase A — SSO-only, NO auto-redirect policy
|
|
# ══════════════════════════════════════════════════════════════════════════
|
|
log ""
|
|
log "════════════ Phase A: SSO-only, no auto-redirect ════════════"
|
|
start_oxicloud "$COMMON/server-with-oidc-only-no-policy.env"
|
|
log "Running sso-only-no-policy.hurl..."
|
|
hurl --variables-file "$OIDC_DIR/sso-only.env" \
|
|
--file-root "$REPO_ROOT/tests" \
|
|
--test --jobs 1 \
|
|
"$OIDC_DIR/sso-only-no-policy.hurl"
|
|
log "Phase A passed."
|
|
stop_oxicloud
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════
|
|
# Phase B — SSO-only, auto_redirect_if_standalone_oidc policy on
|
|
# ══════════════════════════════════════════════════════════════════════════
|
|
log ""
|
|
log "════════════ Phase B: SSO-only, auto-redirect ON ════════════"
|
|
start_oxicloud "$COMMON/server-with-oidc-only.env"
|
|
log "Running sso-only.hurl..."
|
|
hurl --variables-file "$OIDC_DIR/sso-only.env" \
|
|
--file-root "$REPO_ROOT/tests" \
|
|
--test --jobs 1 \
|
|
"$OIDC_DIR/sso-only.hurl"
|
|
log "Phase B passed."
|
|
|
|
log ""
|
|
log "SSO-only tests (both phases) passed."
|