mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-21 04:57:48 +00:00
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
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user