mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-12 04:04:26 +00:00
refactor(oidcgate): idempotent Finalize; document and test 307/308 intercept
This commit is contained in:
@@ -8,8 +8,8 @@ import "net/http"
|
|||||||
// silent-probe contracts cannot follow redirects. authInterceptor buffers
|
// silent-probe contracts cannot follow redirects. authInterceptor buffers
|
||||||
// the header/body and, at Finalize() time:
|
// the header/body and, at Finalize() time:
|
||||||
//
|
//
|
||||||
// - if status was 302 or 303 (redirect class we care about), rewrites
|
// - if status was a redirect class (302, 303, 307, 308), rewrites it
|
||||||
// it to 401, moves the original Location header to X-Auth-Redirect
|
// to 401, moves the original Location header to X-Auth-Redirect
|
||||||
// (advisory), strips Location, preserves Set-Cookie headers (state,
|
// (advisory), strips Location, preserves Set-Cookie headers (state,
|
||||||
// PKCE, nonce — the browser will carry them into the next request),
|
// PKCE, nonce — the browser will carry them into the next request),
|
||||||
// and writes an empty body.
|
// and writes an empty body.
|
||||||
@@ -20,6 +20,7 @@ type authInterceptor struct {
|
|||||||
status int
|
status int
|
||||||
body []byte
|
body []byte
|
||||||
wroteHeader bool
|
wroteHeader bool
|
||||||
|
finalized bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAuthInterceptor(inner http.ResponseWriter) *authInterceptor {
|
func newAuthInterceptor(inner http.ResponseWriter) *authInterceptor {
|
||||||
@@ -51,6 +52,10 @@ func (w *authInterceptor) Write(b []byte) (int, error) { //nolint:unparam // sig
|
|||||||
// Finalize flushes the buffered response, applying the 302/303 → 401 rewrite.
|
// Finalize flushes the buffered response, applying the 302/303 → 401 rewrite.
|
||||||
// Must be called exactly once after the wrapped handler returns.
|
// Must be called exactly once after the wrapped handler returns.
|
||||||
func (w *authInterceptor) Finalize() {
|
func (w *authInterceptor) Finalize() {
|
||||||
|
if w.finalized {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.finalized = true
|
||||||
switch w.status {
|
switch w.status {
|
||||||
case http.StatusFound, http.StatusSeeOther, http.StatusTemporaryRedirect, http.StatusPermanentRedirect:
|
case http.StatusFound, http.StatusSeeOther, http.StatusTemporaryRedirect, http.StatusPermanentRedirect:
|
||||||
// Move Location → X-Auth-Redirect, strip Location, force 401, drop body.
|
// Move Location → X-Auth-Redirect, strip Location, force 401, drop body.
|
||||||
|
|||||||
@@ -68,3 +68,41 @@ func TestInterceptor_303SeeOtherAlsoIntercepted(t *testing.T) {
|
|||||||
t.Fatalf("303 should be intercepted to 401, got %d", rec.Code)
|
t.Fatalf("303 should be intercepted to 401, got %d", rec.Code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInterceptor_307TemporaryRedirectIntercepted(t *testing.T) {
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
w := newAuthInterceptor(rec)
|
||||||
|
w.Header().Set("Location", "/elsewhere")
|
||||||
|
w.WriteHeader(http.StatusTemporaryRedirect)
|
||||||
|
w.Finalize()
|
||||||
|
if rec.Code != http.StatusUnauthorized {
|
||||||
|
t.Fatalf("307 should be intercepted to 401, got %d", rec.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInterceptor_308PermanentRedirectIntercepted(t *testing.T) {
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
w := newAuthInterceptor(rec)
|
||||||
|
w.Header().Set("Location", "/elsewhere")
|
||||||
|
w.WriteHeader(http.StatusPermanentRedirect)
|
||||||
|
w.Finalize()
|
||||||
|
if rec.Code != http.StatusUnauthorized {
|
||||||
|
t.Fatalf("308 should be intercepted to 401, got %d", rec.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInterceptor_DoubleFinalizeIsNoop(t *testing.T) {
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
w := newAuthInterceptor(rec)
|
||||||
|
w.Header().Set("X-Forwarded-User", "alice")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Finalize()
|
||||||
|
// Second call must not panic, must not change anything observable.
|
||||||
|
w.Finalize()
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("double Finalize must not change status, got %d", rec.Code)
|
||||||
|
}
|
||||||
|
if got := rec.Header().Get("X-Forwarded-User"); got != "alice" {
|
||||||
|
t.Errorf("double Finalize must not duplicate headers, got %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user