package bouncer import ( "context" "errors" "net/http" "net/http/httptest" "testing" ) // headerGuard authenticates a request whose Authorization header is exactly // "Bearer good" as principal 7 and writes its own 401 otherwise, so a test // sees whether the guard or the dispatch marker decided a request. type headerGuard struct{ calls *int } func (g headerGuard) AuthenticateCredential(r *http.Request) (*Principal, any, error) { *g.calls++ if r.Header.Get("Authorization") == "Bearer good" { return &Principal{ID: 7}, "guard-credential", nil } return nil, nil, errors.New("no token") } func (headerGuard) WriteUnauthorized(w http.ResponseWriter, _ error) { w.WriteHeader(http.StatusUnauthorized) } // TestDispatchMarker (phase 19.7 D-17): Registry.Middleware honours a // dispatch marker that lists its guard name (the guard does not run, the // marker's principal and credential reach the handler), ignores a marker for // other guards and a marker without a principal (the guard decides), and // never reads a header, cookie or query string for it: a request with forged // dispatch headers is authenticated by the guard alone. func TestDispatchMarker(t *testing.T) { calls := 0 reg := NewRegistry() if err := reg.Register("acme", "api_token", headerGuard{calls: &calls}); err != nil { t.Fatal(err) } mw, err := reg.Middleware("api_token") if err != nil { t.Fatal(err) } type seen struct { id uint cred any ok bool } var got seen h := mw(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p, ok := User(r.Context()) cred, _ := Credential(r.Context()) got = seen{ok: ok, cred: cred} if ok { got.id = p.ID } w.WriteHeader(http.StatusNoContent) })) serve := func(r *http.Request) int { got = seen{} rec := httptest.NewRecorder() h.ServeHTTP(rec, r) return rec.Code } withMarker := func(r *http.Request, d *Dispatch) *http.Request { return r.WithContext(WithDispatch(r.Context(), d)) } t.Run("marker for the guard is honoured without running it", func(t *testing.T) { calls = 0 r := withMarker(httptest.NewRequest(http.MethodGet, "/x", nil), &Dispatch{Guards: []string{"backend", "api_token"}, Principal: &Principal{ID: 42}, Credential: "marker-credential"}) if code := serve(r); code != http.StatusNoContent || got.id != 42 || got.cred != "marker-credential" || calls != 0 { t.Fatalf("code %d, seen %+v, guard calls %d", code, got, calls) } }) t.Run("marker for other guards is ignored", func(t *testing.T) { calls = 0 r := withMarker(httptest.NewRequest(http.MethodGet, "/x", nil), &Dispatch{Guards: []string{"backend"}, Principal: &Principal{ID: 42}}) if code := serve(r); code != http.StatusUnauthorized || calls != 1 { t.Fatalf("code %d, guard calls %d", code, calls) } r = withMarker(httptest.NewRequest(http.MethodGet, "/x", nil), &Dispatch{Guards: []string{"backend"}, Principal: &Principal{ID: 42}}) r.Header.Set("Authorization", "Bearer good") if code := serve(r); code != http.StatusNoContent || got.id != 7 || got.cred != "guard-credential" { t.Fatalf("guard after an unlisted marker: code %d, seen %+v", code, got) } }) t.Run("marker without a principal is ignored", func(t *testing.T) { calls = 0 r := withMarker(httptest.NewRequest(http.MethodGet, "/x", nil), &Dispatch{Guards: []string{"api_token"}}) if code := serve(r); code != http.StatusUnauthorized || calls != 1 { t.Fatalf("code %d, guard calls %d", code, calls) } var nilMarker *Dispatch r = withMarker(httptest.NewRequest(http.MethodGet, "/x", nil), nilMarker) if code := serve(r); code != http.StatusUnauthorized { t.Fatalf("nil marker: code %d", code) } }) t.Run("forged headers and cookies are the guard's", func(t *testing.T) { calls = 0 r := httptest.NewRequest(http.MethodGet, "/x?dispatch=42&summer_dispatch=api_token", nil) r.Header.Set("X-Summer-Dispatch", "42") r.Header.Set("X-Dispatch-Guards", "api_token") r.Header.Set("Authorization", "Dispatch 42") r.AddCookie(&http.Cookie{Name: "summer_dispatch", Value: "42"}) if code := serve(r); code != http.StatusUnauthorized || calls != 1 || got.ok { t.Fatalf("forged request: code %d, seen %+v, guard calls %d", code, got, calls) } r.Header.Set("Authorization", "Bearer good") if code := serve(r); code != http.StatusNoContent || got.id != 7 { t.Fatalf("forged headers with a real token: code %d, seen %+v", code, got) } }) t.Run("plain context", func(t *testing.T) { if _, ok := Dispatched(context.Background(), "api_token"); ok { t.Fatal("a plain context carries a marker") } }) }