calendar-plugin/fixture-tests/fixture_security_smoke.sh

111 lines
3.9 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
BASE_URL="${BASE_URL:-http://127.0.0.1:8080}"
DB_PATH="${DB_PATH:-fixture/fixture.db}"
RUN_ID="${RUN_ID:-$(date +%s)}"
echo "[security-smoke] checking service readiness"
curl -fsS -H 'X-WP-User: admin' "$BASE_URL/wp-json/calendar/v1/events" >/dev/null
echo "[security-smoke] checking register token non-disclosure"
register_email="sec-nodisclose-${RUN_ID}@example.test"
reg_code="$(curl -sS -o /tmp/sec_register.json -w '%{http_code}' -H 'Content-Type: application/json' \
-d "{\"email\":\"${register_email}\",\"password\":\"strongpass123\"}" \
"$BASE_URL/wp-json/calendar/v1/users/register")"
if [ "$reg_code" != "200" ] && [ "$reg_code" != "201" ] && [ "$reg_code" != "409" ]; then
echo "[security-smoke] FAIL: unexpected register status $reg_code"
exit 1
fi
reg_payload="$(cat /tmp/sec_register.json)"
if echo "$reg_payload" | grep -q 'verification_token_fixture'; then
echo "[security-smoke] FAIL: register response leaked verification token"
exit 1
fi
echo "[security-smoke] checking forgot-password token non-disclosure"
forgot_payload="$(curl -fsS -H 'Content-Type: application/json' \
-d '{"email":"adrians@chezstephens.org.uk"}' \
"$BASE_URL/wp-json/calendar/v1/users/forgot-password")"
if echo "$forgot_payload" | grep -q 'reset_token_fixture'; then
echo "[security-smoke] FAIL: forgot-password response leaked reset token"
exit 1
fi
echo "[security-smoke] checking register rate limiting"
seen_429=0
rate_prefix="sec-rate-${RUN_ID}"
for i in $(seq 1 10); do
code="$(curl -sS -o /tmp/sec_reg_$i.json -w '%{http_code}' \
-H 'Content-Type: application/json' \
-d "{\"email\":\"${rate_prefix}-$i@example.test\",\"password\":\"strongpass123\"}" \
"$BASE_URL/wp-json/calendar/v1/users/register")"
if [ "$code" = "429" ]; then
seen_429=1
break
fi
done
if [ "$seen_429" -ne 1 ]; then
echo "[security-smoke] FAIL: expected 429 from register rate limiter"
exit 1
fi
echo "[security-smoke] checking password hash format in database"
python3 - "$DB_PATH" <<'PY'
import sqlite3
import sys
db_path = sys.argv[1]
conn = sqlite3.connect(db_path)
cur = conn.cursor()
rows = cur.execute("SELECT email, password_hash FROM caldav_users").fetchall()
conn.close()
if not rows:
print("[security-smoke] FAIL: no users found")
raise SystemExit(1)
for email, pw_hash in rows:
if not isinstance(pw_hash, str) or not pw_hash.startswith("pbkdf2_sha256$"):
print(f"[security-smoke] FAIL: non-pbkdf2 hash for {email}: {pw_hash!r}")
raise SystemExit(1)
print("[security-smoke] password hash format OK")
PY
echo "[security-smoke] checking strict CalDAV resource filename semantics"
resource="sec-$(date +%s).ics"
cat > /tmp/sec_caldav.ics <<'ICS'
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Security Smoke//EN
BEGIN:VEVENT
UID:sec-smoke-uid@example.test
SUMMARY:Security Smoke Event
DTSTART:20260415T100000
DTEND:20260415T110000
END:VEVENT
END:VCALENDAR
ICS
put_code="$(curl -sS -o /tmp/sec_caldav_put.json -w '%{http_code}' \
-u adrians@chezstephens.org.uk:brillig1 \
-X PUT --data-binary @/tmp/sec_caldav.ics \
"$BASE_URL/caldav/calendars/public/$resource")"
if [ "$put_code" != "201" ] && [ "$put_code" != "200" ]; then
echo "[security-smoke] FAIL: expected 200/201 for CalDAV PUT, got $put_code"
exit 1
fi
get_code="$(curl -sS -o /tmp/sec_caldav_get.ics -w '%{http_code}' \
-u adrians@chezstephens.org.uk:brillig1 \
"$BASE_URL/caldav/calendars/public/$resource")"
if [ "$get_code" != "200" ]; then
echo "[security-smoke] FAIL: expected 200 for CalDAV GET on same resource, got $get_code"
exit 1
fi
missing_code="$(curl -sS -o /tmp/sec_caldav_missing.out -w '%{http_code}' \
-u adrians@chezstephens.org.uk:brillig1 \
"$BASE_URL/caldav/calendars/public/999999.ics")"
if [ "$missing_code" != "404" ]; then
echo "[security-smoke] FAIL: expected 404 for unknown numeric resource, got $missing_code"
exit 1
fi
echo "[security-smoke] all checks passed"