Files
traefikoidc/review_r182_regression_test.go
T
lukaszraczylo 6d96e8869b test: add R-round regression and security suite
- fail-on-old regression tests for every shipped fix (R-rounds)
- fuzz targets (parseJWT, bearer), concurrency-stress, shuffle-flake guards
- coverage-closing contract tests (health-check backend, TLS-posture reuse)
- r154 ID-token fixture built from parts to avoid false-positive secret scan
2026-08-14 16:22:39 +01:00

84 lines
3.0 KiB
Go

package traefikoidc
import (
"net/http"
"net/http/httptest"
"testing"
)
// ============================================================================
// R182: OIDC callback must be HTTP GET only (RFC 6749 §3.1.2)
// ---------------------------------------------------------------------------
// The callback branch dispatched handleCallback on any method matched by
// path. handleCallback reads code/state/error only from the query string
// (form_post is not supported), so a HEAD or other non-GET probe reached
// the full exchange logic and consumed one-time state, or failed
// confusingly ("No authorization code received in callback"). The branch
// now rejects non-GET with 405 + no-store.
// Fail-on-old: POST to the callback reached handleCallback and returned
// 400 ("CSRF token missing" on a fresh session) instead of 405.
func TestR182_CallbackRejectsNonGetMethod(t *testing.T) {
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
oidc := &TraefikOidc{
next: next,
logger: NewLogger("error"),
initComplete: make(chan struct{}),
sessionManager: createTestSessionManager(t),
firstRequestStarted: 1,
metadataRefreshStartedAtomic: 1,
redirURLPath: "/callback",
issuerURL: "https://provider.example.com",
}
close(oidc.initComplete)
rw := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPost, "/callback", nil)
req.Header.Set("Accept", "text/html")
oidc.ServeHTTP(rw, req)
if rw.Code != http.StatusMethodNotAllowed {
t.Fatalf("callback with method POST: got status %d, want 405", rw.Code)
}
if cc := rw.Header().Get("Cache-Control"); cc != "no-store" {
t.Errorf("callback 405 must be no-store; got %q", cc)
}
if loc := rw.Header().Get("Location"); loc != "" {
t.Errorf("405 must not emit a redirect Location; got %q", loc)
}
}
// A legitimate GET that reaches the callback still proceeds (does not hit
// the method gate). With no session CSRF it returns 400 for the missing
// CSRF rather than 405.
func TestR182_GetCallbackNotRejectedByMethodGate(t *testing.T) {
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
oidc := &TraefikOidc{
next: next,
logger: NewLogger("error"),
initComplete: make(chan struct{}),
sessionManager: createTestSessionManager(t),
firstRequestStarted: 1,
metadataRefreshStartedAtomic: 1,
redirURLPath: "/callback",
issuerURL: "https://provider.example.com",
}
close(oidc.initComplete)
rw := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/callback?state=x&code=y", nil)
req.Header.Set("Accept", "text/html")
oidc.ServeHTTP(rw, req)
if rw.Code == http.StatusMethodNotAllowed {
t.Fatalf("GET callback must not be rejected by the method gate; got 405")
}
}