- 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)
125 lines
4.5 KiB
Go
125 lines
4.5 KiB
Go
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")
|
|
}
|
|
})
|
|
}
|