test(19.7-05): phase gate with the go and marker stages, pier test helpers, TestToolsList and TestDispatchMarker
- scripts/check-phase19.7.sh: fail-closed multi-repo gate (self-test, go, marker; plugins, consumers, docker, admin and docs are stubs until the later tasks fill them) - modules/pier/helpers_test.go: the plan 02 fixtures moved out of the smokes plus fakeGuard, fakeVerifier, fakeLinkResolver, fakePersonalResolver, routePlugin, newApp, twoInstances, sdkClient and fireSubjectChanged - TestToolsList (pier), TestDispatchMarker (bouncer, cabana with a Postgres part through the screens harness, which now keeps its app)
This commit is contained in:
124
modules/bouncer/dispatch_test.go
Normal file
124
modules/bouncer/dispatch_test.go
Normal file
@@ -0,0 +1,124 @@
|
||||
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")
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user