Loading internal/api/api.go +2 −2 Changes for internal/api/api.go: 2 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -752,10 +752,10 @@ func (a *API) buildFace(face string, routes []apiRoute, guard func(http.Handler) h = callersOnly(rt.Callers, h) } if rt.Owner { h = a.ownerOnly(rt.h) h = a.ownerOnly(h) } if rt.Admin { h = a.adminOnly(rt.h) h = a.adminOnly(h) } // Default-deny setup-lockdown: wrap every authenticated route unless it // explicitly opts out. The wrapper is nil-principal safe, so it is inert on Loading internal/api/route_tiers_test.go 0 → 100644 +41 −0 Changes for internal/api/route_tiers_test.go: 41 added lines, 0 removed lines. Original line number Diff line number Diff line package api import ( "context" "net/http" "net/http/httptest" "testing" ) // Each tier a route sets is one more gate in front of its handler: a route marked // both Owner and Admin still refuses an admin who is not the owner. func TestBuildFaceStacksTierGates(t *testing.T) { a := newTestAPI(newFakeRepo(), newFakeCluster()) ran := false ok := func(w http.ResponseWriter, r *http.Request) { ran = true } as := func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p := &Principal{UserID: "u1", Role: r.Header.Get("X-Test-Role"), ViaAdminAccess: true, EmailVerified: true} h.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), ctxKeyPrincipal, p))) }) } h := a.buildFace("external", []apiRoute{{Method: "GET", Pattern: "/api/v1/tiered", Owner: true, Admin: true, h: ok}}, as) for _, c := range []struct { role string code int }{ {"admin", http.StatusForbidden}, {"user", http.StatusForbidden}, {"owner", http.StatusOK}, } { ran = false r := httptest.NewRequest("GET", "/api/v1/tiered", nil) r.Header.Set("X-Test-Role", c.role) w := httptest.NewRecorder() h.ServeHTTP(w, r) if w.Code != c.code || ran != (c.code == http.StatusOK) { t.Errorf("%s: status %d, handler ran %v; want %d", c.role, w.Code, ran, c.code) } } } Loading
internal/api/api.go +2 −2 Changes for internal/api/api.go: 2 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -752,10 +752,10 @@ func (a *API) buildFace(face string, routes []apiRoute, guard func(http.Handler) h = callersOnly(rt.Callers, h) } if rt.Owner { h = a.ownerOnly(rt.h) h = a.ownerOnly(h) } if rt.Admin { h = a.adminOnly(rt.h) h = a.adminOnly(h) } // Default-deny setup-lockdown: wrap every authenticated route unless it // explicitly opts out. The wrapper is nil-principal safe, so it is inert on Loading
internal/api/route_tiers_test.go 0 → 100644 +41 −0 Changes for internal/api/route_tiers_test.go: 41 added lines, 0 removed lines. Original line number Diff line number Diff line package api import ( "context" "net/http" "net/http/httptest" "testing" ) // Each tier a route sets is one more gate in front of its handler: a route marked // both Owner and Admin still refuses an admin who is not the owner. func TestBuildFaceStacksTierGates(t *testing.T) { a := newTestAPI(newFakeRepo(), newFakeCluster()) ran := false ok := func(w http.ResponseWriter, r *http.Request) { ran = true } as := func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p := &Principal{UserID: "u1", Role: r.Header.Get("X-Test-Role"), ViaAdminAccess: true, EmailVerified: true} h.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), ctxKeyPrincipal, p))) }) } h := a.buildFace("external", []apiRoute{{Method: "GET", Pattern: "/api/v1/tiered", Owner: true, Admin: true, h: ok}}, as) for _, c := range []struct { role string code int }{ {"admin", http.StatusForbidden}, {"user", http.StatusForbidden}, {"owner", http.StatusOK}, } { ran = false r := httptest.NewRequest("GET", "/api/v1/tiered", nil) r.Header.Set("X-Test-Role", c.role) w := httptest.NewRecorder() h.ServeHTTP(w, r) if w.Code != c.code || ran != (c.code == http.StatusOK) { t.Errorf("%s: status %d, handler ran %v; want %d", c.role, w.Code, ran, c.code) } } }