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") } }) } }