Files
welcome-to-codevalet-as-a-p…/tests/test_forge_auth_integration.sh
FanaticPythoner (Nathan Trudeau) 0c159e91fb Initial Commit
2026-04-27 15:56:43 -04:00

238 lines
9.1 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# End-to-end OAuth2 flow against tests/mock_oidc_server.py. Exercises
# forge_login.sh, forge_auth.py, the credential helper, and the
# username-mismatch guard in a sandboxed $HOME. Never touches a real
# Gitea.
set -euo pipefail
here="$(cd "$(dirname "$0")" && pwd -P)"
root="$here/.."
# Disable every interactive credential fallback so git fails loudly
# rather than prompting (VSCode, X11/Wayland askpass, terminal).
export GIT_TERMINAL_PROMPT=0
export GCM_INTERACTIVE=Never
unset GIT_ASKPASS SSH_ASKPASS \
VSCODE_GIT_ASKPASS_MAIN VSCODE_GIT_ASKPASS_NODE \
VSCODE_GIT_ASKPASS_EXTRA_ARGS VSCODE_GIT_IPC_HANDLE \
DISPLAY WAYLAND_DISPLAY
tmp="$(mktemp -d)"
cleanup() {
[ -n "${mock_pid:-}" ] && kill "$mock_pid" 2>/dev/null || true
[ -n "${browser_pid:-}" ] && kill "$browser_pid" 2>/dev/null || true
[ -n "${login_pid:-}" ] && kill "$login_pid" 2>/dev/null || true
rm -rf "$tmp"
}
trap cleanup EXIT INT TERM
export HOME="$tmp/home"
mkdir -p "$HOME/.local/bin" "$HOME/.config"
export GIT_CONFIG_GLOBAL="$HOME/.gitconfig"
export GIT_CONFIG_SYSTEM=/dev/null
touch "$GIT_CONFIG_GLOBAL"
export FSDGG_AUTH_STORE_PATH="$HOME/.forge-stack-devpi-gateway-gitea/client-auth.json"
pass=0
fail=0
assert() {
local msg="$1"; shift
if "$@"; then
printf '[ok] %s\n' "$msg"
pass=$((pass + 1))
else
printf '[FAIL] %s\n' "$msg"
fail=$((fail + 1))
fi
}
# --- Start the mock Gitea ------------------------------------------
MOCK_OIDC_USERNAME=integration-user \
python3 -u "$here/mock_oidc_server.py" >"$tmp/mock.url" 2>"$tmp/mock.log" &
mock_pid=$!
# Wait for the server to announce its URL.
for _ in $(seq 1 40); do
mock_url="$(head -1 "$tmp/mock.url" 2>/dev/null || true)"
[ -n "$mock_url" ] && break
sleep 0.1
done
[ -n "$mock_url" ] || { cat "$tmp/mock.log"; echo 'mock server never came up' >&2; exit 1; }
printf '[info] mock Gitea: %s\n' "$mock_url"
# --- Pick an unused loopback port for the callback -----------------
loopback_port="$(python3 -c '
import socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(("127.0.0.1", 0))
print(s.getsockname()[1])
')"
redirect_uri="http://127.0.0.1:${loopback_port}/callback"
# --- Synthesize .env inside the welcome repo root ------------------
env_file="$tmp/env"
cat >"$env_file" <<EOF
FORGE_GITEA_URL=$mock_url
FORGE_GITEA_USERNAME=integration-user
FSDGG_CLI_CLIENT_ID=integration-client
FSDGG_CLI_REDIRECT_URI=$redirect_uri
FORGE_INSECURE_TLS=0
EOF
# Export mock env; scripts pick these up via load_env.
set -a
# shellcheck disable=SC1090
. "$env_file"
set +a
# Fake browser: once the loopback listener is up, GET the authorise
# URL printed to the login log; the mock 302-redirects to the callback.
(
# Wait for the loopback listener to come up, then drive it.
for _ in $(seq 1 50); do
if ss -tln 2>/dev/null | grep -q ":${loopback_port} " \
|| netstat -tln 2>/dev/null | grep -q ":${loopback_port} "; then
break
fi
sleep 0.1
done
url="$(cat "$tmp/authorize.url" 2>/dev/null || true)"
if [ -n "$url" ]; then
curl -fsSL --max-time 10 "$url" >/dev/null 2>"$tmp/browser.log" || true
fi
) &
browser_pid=$!
python3 "$root/scripts/forge_auth.py" login --no-browser 2> >(tee "$tmp/login.log" >&2) \
< <(printf '') &
login_pid=$!
for _ in $(seq 1 80); do
if grep -qE 'https?://.*authorize\?' "$tmp/login.log" 2>/dev/null; then
grep -oE 'https?://[^ ]+authorize\?[^ ]+' "$tmp/login.log" | head -1 >"$tmp/authorize.url"
break
fi
sleep 0.1
done
wait "$login_pid" || { echo 'forge_auth login failed'; cat "$tmp/login.log"; exit 1; }
wait "$browser_pid" 2>/dev/null || true
assert 'client-auth.json was written' test -f "$FSDGG_AUTH_STORE_PATH"
assert 'client-auth.json is mode 0600' \
bash -c '[ "$(stat -c %a "$FSDGG_AUTH_STORE_PATH" 2>/dev/null || stat -f %A "$FSDGG_AUTH_STORE_PATH")" = "600" ]'
user="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["username"])' "$FSDGG_AUTH_STORE_PATH")"
assert 'username captured from userinfo' test "$user" = 'integration-user'
token="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["gitea_access_token"])' "$FSDGG_AUTH_STORE_PATH")"
assert 'gitea_access_token captured' bash -c "[ -n \"$token\" ] && [[ '$token' == access-* ]]"
refresh="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["_forge_refresh_token"])' "$FSDGG_AUTH_STORE_PATH")"
assert 'refresh token captured' bash -c "[ -n \"$refresh\" ] && [[ '$refresh' == refresh-* ]]"
# --- Install the git credential helper -----------------------------
bash "$root/scripts/install-git-credential-helper.sh" >"$tmp/inst.log" 2>&1 \
|| { cat "$tmp/inst.log"; echo 'helper install failed' >&2; exit 1; }
mock_host="$(python3 -c 'import sys,urllib.parse as u; p=u.urlsplit(sys.argv[1]); print(f"{p.hostname}:{p.port}")' "$mock_url")"
# --- Drive `git credential fill` -----------------------------------
credfill_out="$(
printf 'protocol=http\nhost=%s\npath=someorg/somerepo.git\n\n' "$mock_host" \
| PATH="$HOME/.local/bin:$PATH" git credential fill 2>"$tmp/credfill.err"
)" || { echo 'git credential fill failed'; cat "$tmp/credfill.err"; exit 1; }
got_pass="$(printf '%s\n' "$credfill_out" | awk -F= '/^password=/ {print $2}')"
got_user="$(printf '%s\n' "$credfill_out" | awk -F= '/^username=/ {print $2}')"
assert 'git credential fill returns username from OAuth' test "$got_user" = 'integration-user'
assert 'git credential fill returns the OAuth access token as password' test "$got_pass" = "$token"
# --- Unrelated-host probe: must NOT leak credentials ---------------
other_out="$(
printf 'protocol=https\nhost=github.com\npath=x/y.git\n\n' \
| "$HOME/.local/bin/git-credential-forge" get
)"
assert 'unrelated host gets no username' bash -c "! printf '%s\n' \"$other_out\" | grep -q '^username='"
assert 'unrelated host gets no password' bash -c "! printf '%s\n' \"$other_out\" | grep -q '^password='"
# --- Refresh path: force a refresh and re-run credential fill ------
python3 "$root/scripts/forge_auth.py" refresh --force >"$tmp/refresh.log" 2>&1 \
|| { cat "$tmp/refresh.log"; echo 'refresh failed' >&2; exit 1; }
new_token="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["gitea_access_token"])' "$FSDGG_AUTH_STORE_PATH")"
assert 'refresh rotates the access token' bash -c "[ \"$new_token\" != \"$token\" ]"
credfill_out2="$(
printf 'protocol=http\nhost=%s\n\n' "$mock_host" \
| PATH="$HOME/.local/bin:$PATH" git credential fill 2>>"$tmp/credfill.err"
)"
got_pass2="$(printf '%s\n' "$credfill_out2" | awk -F= '/^password=/ {print $2}')"
assert 'git credential fill picks up the rotated token' test "$got_pass2" = "$new_token"
# --- Logout removes the file ---------------------------------------
python3 "$root/scripts/forge_auth.py" logout >"$tmp/logout.log" 2>&1
assert 'logout removes client-auth.json' bash -c "[ ! -f \"$FSDGG_AUTH_STORE_PATH\" ]"
# --- Username-mismatch guard ---------------------------------------
loopback_port2="$(python3 -c '
import socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(("127.0.0.1", 0))
print(s.getsockname()[1])
')"
export FSDGG_CLI_REDIRECT_URI="http://127.0.0.1:${loopback_port2}/callback"
export FORGE_GITEA_USERNAME="not-the-right-user"
rm -f "$tmp/authorize.url"
(
for _ in $(seq 1 50); do
if ss -tln 2>/dev/null | grep -q ":${loopback_port2} " \
|| netstat -tln 2>/dev/null | grep -q ":${loopback_port2} "; then
break
fi
sleep 0.1
done
url="$(cat "$tmp/authorize.url" 2>/dev/null || true)"
if [ -n "$url" ]; then
curl -fsSL --max-time 10 "$url" >/dev/null 2>"$tmp/browser2.log" || true
fi
) &
browser_pid=$!
set +e
python3 "$root/scripts/forge_auth.py" login --no-browser \
2> >(tee "$tmp/mismatch.log" >&2) &
mismatch_pid=$!
for _ in $(seq 1 80); do
if grep -qE 'https?://.*authorize\?' "$tmp/mismatch.log" 2>/dev/null; then
grep -oE 'https?://[^ ]+authorize\?[^ ]+' "$tmp/mismatch.log" | head -1 >"$tmp/authorize.url"
break
fi
sleep 0.1
done
wait "$mismatch_pid"
mismatch_rc=$?
wait "$browser_pid" 2>/dev/null || true
set -e
assert 'login fails when username mismatches FORGE_GITEA_USERNAME' bash -c "[ $mismatch_rc -ne 0 ]"
assert 'mismatch error references the actual Gitea username' \
grep -q 'integration-user' "$tmp/mismatch.log"
assert 'mismatch error references the expected username' \
grep -q 'not-the-right-user' "$tmp/mismatch.log"
assert 'mismatch error points at Gitea logout URL' \
grep -qE '/user/logout\?redirect_to=%2Fuser%2Flogin' "$tmp/mismatch.log"
assert 'mismatch does NOT create client-auth.json' \
bash -c "[ ! -f \"$FSDGG_AUTH_STORE_PATH\" ]"
auth_url="$(cat "$tmp/authorize.url" 2>/dev/null || true)"
assert 'authorize URL includes prompt=login' \
bash -c "printf '%s\n' \"$auth_url\" | grep -q 'prompt=login'"
assert 'authorize URL carries login_hint' \
bash -c "printf '%s\n' \"$auth_url\" | grep -q 'login_hint=not-the-right-user'"
printf '\n%d pass / %d fail\n' "$pass" "$fail"
[ "$fail" -eq 0 ]