79 lines
3 KiB
Go
79 lines
3 KiB
Go
package auth
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// principal_test.go: that "authenticated, but nobody can revoke them" is not a state this package
|
|
// can hand out.
|
|
//
|
|
// PD-379 was exactly that state, and the first shape of its fix kept it reachable: the identity and
|
|
// the means to re-ask travelled as two separate context values, so a route mounted outside the guard
|
|
// — or a future one that minted a principal some other way — produced a caller with an id and no way
|
|
// to check it, and a handler could only fail closed by remembering to. They are ONE value now, and
|
|
// what that buys is checked here rather than described.
|
|
|
|
// A principal that did not come from Require answers "not live". The direction is the whole point:
|
|
// the only way this can be wrong must be the way that refuses.
|
|
//
|
|
// Mutation caught: returning nil from StillLive when the store is absent.
|
|
func TestAPrincipalNobodyAuthenticatedIsNotLive(t *testing.T) {
|
|
for name, p := range map[string]Principal{
|
|
"the zero value": {},
|
|
"an id someone set": {UserID: "u1"},
|
|
"an id and a presence": {UserID: "u1", Via: ViaBearer},
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
if err := p.StillLive(context.Background()); !errors.Is(err, ErrNoSession) {
|
|
t.Errorf("StillLive = %v, want %v: a caller whose session nothing can look up must "+
|
|
"not be able to hold a long-lived response open", err, ErrNoSession)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// The probe a real principal carries asks about the SAME credential the guard admitted, and asks the
|
|
// store rather than remembering the guard's answer.
|
|
//
|
|
// Mutation caught: binding an empty digest; caching Lookup's verdict instead of re-asking.
|
|
func TestTheProbeAsksTheStoreAboutTheCredentialThatWasPresented(t *testing.T) {
|
|
now := time.Now()
|
|
token := NewToken()
|
|
store := &fakeStore{session: Session{
|
|
UserID: "u1", IdleExpiresAt: now.Add(time.Hour), AbsoluteExpiresAt: now.Add(time.Hour),
|
|
}}
|
|
a, _ := newAuth(store, now)
|
|
|
|
var got Principal
|
|
h := a.Require(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
|
|
got, _ = FromContext(r.Context())
|
|
}))
|
|
r := httptest.NewRequest("GET", "/v0/books/bk_1/events", nil)
|
|
r.Header.Set("Authorization", "Bearer "+token)
|
|
h.ServeHTTP(httptest.NewRecorder(), r)
|
|
|
|
if got.UserID != "u1" {
|
|
t.Fatalf("the guard produced %+v", got)
|
|
}
|
|
// Live now…
|
|
if err := got.StillLive(context.Background()); err != nil {
|
|
t.Fatalf("a freshly admitted caller is not live: %v", err)
|
|
}
|
|
if want := Digest(token); string(store.digest) != string(want) {
|
|
t.Errorf("the probe asked about a different credential than the one presented")
|
|
}
|
|
if store.asked == 0 {
|
|
t.Error("the probe answered without asking the store: a verdict remembered from the door is " +
|
|
"the defect, not the fix")
|
|
}
|
|
// …and gone a moment later, WITHOUT the request being made again. That is the whole mechanism.
|
|
store.live = ErrNoSession
|
|
if err := got.StillLive(context.Background()); !errors.Is(err, ErrNoSession) {
|
|
t.Errorf("StillLive = %v after the session was revoked, want %v", err, ErrNoSession)
|
|
}
|
|
}
|