package graph
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sourcecraft.dev/bigbes/sr-ht-ecore/grants"
"sourcecraft.dev/bigbes/sr-ht-spec/authn"
)
// mustGrants parses a grant string or fails the test.
func mustGrants(t *testing.T, s string) grants.Grants {
t.Helper()
g, err := grants.Parse(s)
require.NoError(t, err, "parse grants %q", s)
return g
}
// The gate asks two questions, and they have different answers when they fail.
// Identity — may you read at all — is 401 and unchanged. The grant — was the
// credential you used minted for reading — is 403, and applies only to a
// tokens.sr.ht working token, because it is the only credential that carries
// grants: the owner's cookie and spec's own agent token pass it untouched, which
// is what keeps every client that works today working.
func TestGateChecksIdentityThenGrant(t *testing.T) {
cases := []struct {
name string
principal authn.Principal
wantCode int
wantNext bool
}{
{"anonymous", authn.Anonymous(), http.StatusUnauthorized, false},
{"owner cookie", authn.Principal{Kind: authn.KindOwner, Owner: "bigbes"}, http.StatusOK, true},
{
"local agent token",
authn.Principal{Kind: authn.KindAgent, Owner: "bigbes", Plane: authn.PlaneLocal},
http.StatusOK, true,
},
{
"instance token with spec:read",
authn.Principal{
Kind: authn.KindAgent, Owner: "bigbes", Plane: authn.PlaneInstance,
Grants: mustGrants(t, "spec:read"),
},
http.StatusOK, true,
},
{
"instance token without spec:read",
authn.Principal{
Kind: authn.KindAgent, Owner: "bigbes", Plane: authn.PlaneInstance,
Grants: mustGrants(t, "spec:propose"),
},
http.StatusForbidden, false,
},
{
"instance token, universal grant",
authn.Principal{
Kind: authn.KindAgent, Owner: "bigbes", Plane: authn.PlaneInstance,
Grants: mustGrants(t, "*"),
},
http.StatusOK, true,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
var reached bool
next := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
reached = true
w.WriteHeader(http.StatusOK)
})
req := httptest.NewRequest(http.MethodPost, "/query", nil)
req = req.WithContext(authn.WithPrincipal(req.Context(), tc.principal))
rec := httptest.NewRecorder()
gate(next).ServeHTTP(rec, req)
assert.Equal(t, tc.wantCode, rec.Code)
assert.Equal(t, tc.wantNext, reached)
if tc.wantCode == http.StatusForbidden {
assert.Contains(t, rec.Body.String(), authn.ActionRead,
"a refused caller must be told which grant it lacks")
}
})
}
}