diff options
| -rw-r--r-- | app/controllers/sparkles/controller.go | 12 | ||||
| -rw-r--r-- | app/controllers/sparkles/controller_test.go | 58 | ||||
| -rw-r--r-- | app/domain/permission.go (renamed from app/middleware/permission.go) | 9 | ||||
| -rw-r--r-- | app/init.go | 4 | ||||
| -rw-r--r-- | app/middleware/require_permission.go | 2 | ||||
| -rw-r--r-- | app/middleware/require_permission_test.go | 2 | ||||
| -rw-r--r-- | internal/stub/check.go | 6 | ||||
| -rw-r--r-- | pkg/authz/spice.go | 2 |
8 files changed, 56 insertions, 39 deletions
diff --git a/app/controllers/sparkles/controller.go b/app/controllers/sparkles/controller.go index 80e95cb..ef2ecd5 100644 --- a/app/controllers/sparkles/controller.go +++ b/app/controllers/sparkles/controller.go @@ -14,11 +14,15 @@ import ( ) type Controller struct { - db domain.Repository[*domain.Sparkle] + db domain.Repository[*domain.Sparkle] + check authz.CheckPermissionService } -func New(db domain.Repository[*domain.Sparkle], check authz.PermissionService) *Controller { - return &Controller{db: db} +func New(db domain.Repository[*domain.Sparkle], check authz.CheckPermissionService) *Controller { + return &Controller{ + check: check, + db: db, + } } func (c *Controller) MountTo(mux *http.ServeMux) { @@ -26,7 +30,7 @@ func (c *Controller) MountTo(mux *http.ServeMux) { mux.Handle("POST /sparkles", x.Middleware[http.Handler]( http.HandlerFunc(c.Create), middleware.RequireUser(), - // middleware.RequirePermission("create", x.Try(ioc.Resolve[authz.CheckPermission](ioc.Default))), + middleware.RequirePermission("create", c.check), )) // This is a temporary endpoint to restore a backup diff --git a/app/controllers/sparkles/controller_test.go b/app/controllers/sparkles/controller_test.go index 006c3fa..64b4dc5 100644 --- a/app/controllers/sparkles/controller_test.go +++ b/app/controllers/sparkles/controller_test.go @@ -62,16 +62,16 @@ func TestSparkles(t *testing.T) { }) t.Run("POST /sparkles", func(t *testing.T) { - t.Run("when a user is logged in", func(t *testing.T) { + t.Run("when a user is authenticated", func(t *testing.T) { currentUser := domain.NewUser(domain.WithID[*domain.User](domain.ID("1"))) + repository := db.NewRepository[*domain.Sparkle]() - t.Run("when the user is authorized to create sparkles", func(t *testing.T) { - t.Run("saves a new sparkle", func(t *testing.T) { - repository := db.NewRepository[*domain.Sparkle]() - mux := http.NewServeMux() - controller := New(repository, stub.AllowWith(t, "user:1", "create", "sparkle:*")) - controller.MountTo(mux) + t.Run("when the user is authorized", func(t *testing.T) { + mux := http.NewServeMux() + controller := New(repository, stub.AllowWith(t, "user:1", "create", "sparkle:*")) + controller.MountTo(mux) + t.Run("saves a new sparkle", func(t *testing.T) { sparkle, _ := domain.NewSparkle("@tanuki for reviewing my code!") request, response := test.RequestResponse( "POST", @@ -103,24 +103,42 @@ func TestSparkles(t *testing.T) { assert.Equal(t, currentUser, item.Author) }) }) + + t.Run("prevents double WriteHeader when serialization fails", func(t *testing.T) { + currentUser := domain.NewUser(domain.WithID[*domain.User](domain.ID("1"))) + sparkle, _ := domain.NewSparkle("@user for testing") + + request, response := test.RequestResponse( + "POST", + "/sparkles", + test.WithAcceptHeader(serde.JSON), + test.WithContentType(sparkle, serde.JSON), + test.WithContextKeyValue(t.Context(), cfg.CurrentUser, currentUser), + ) + + mux.ServeHTTP(&FailingResponseWriter{T: t, ResponseRecorder: response}, request) + }) }) - t.Run("prevents double WriteHeader when serialization fails", func(t *testing.T) { - repository := db.NewRepository[*domain.Sparkle]() - controller := New(repository, stub.Allow()) + t.Run("when the user is not authorized", func(t *testing.T) { + mux := http.NewServeMux() + controller := New(repository, stub.Deny()) + controller.MountTo(mux) - currentUser := domain.NewUser(domain.WithID[*domain.User](domain.ID("1"))) - sparkle, _ := domain.NewSparkle("@user for testing") + t.Run("returns an error", func(t *testing.T) { + sparkle, _ := domain.NewSparkle("@tanuki for reviewing my code!") + request, response := test.RequestResponse( + "POST", + "/sparkles", + test.WithAcceptHeader(serde.JSON), + test.WithContentType(sparkle, serde.JSON), + test.WithContextKeyValue(t.Context(), cfg.CurrentUser, currentUser), + ) - request, response := test.RequestResponse( - "POST", - "/sparkles", - test.WithAcceptHeader(serde.JSON), - test.WithContentType(sparkle, serde.JSON), - test.WithContextKeyValue(t.Context(), cfg.CurrentUser, currentUser), - ) + mux.ServeHTTP(response, request) - controller.Create(&FailingResponseWriter{T: t, ResponseRecorder: response}, request) + require.Equal(t, http.StatusForbidden, response.Code) + }) }) }) }) diff --git a/app/middleware/permission.go b/app/domain/permission.go index 36a7ea0..53d4754 100644 --- a/app/middleware/permission.go +++ b/app/domain/permission.go @@ -1,17 +1,12 @@ -package middleware +package domain import ( v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/domain" ) type Permission string -func (p Permission) ToGID() string { - return "gid://sparkle/Permission/" + p.String() -} - -func (p Permission) RequestFor(user domain.Identifiable, resource domain.Identifiable) *v1.CheckPermissionRequest { +func (p Permission) RequestFor(user Identifiable, resource Identifiable) *v1.CheckPermissionRequest { return &v1.CheckPermissionRequest{ Subject: &v1.SubjectReference{ Object: user.ToObjectReference(), diff --git a/app/init.go b/app/init.go index 2986809..960102a 100644 --- a/app/init.go +++ b/app/init.go @@ -30,7 +30,7 @@ func init() { env.Fetch("ZED_TOKEN", "secret"), ) }) - ioc.Register[authz.PermissionService](ioc.Default, func() authz.PermissionService { + ioc.Register[authz.CheckPermissionService](ioc.Default, func() authz.CheckPermissionService { return ioc.MustResolve[*authzed.Client](ioc.Default) }) ioc.RegisterSingleton[domain.Repository[*domain.Sparkle]](ioc.Default, func() domain.Repository[*domain.Sparkle] { @@ -45,7 +45,7 @@ func init() { ioc.Register[*sparkles.Controller](ioc.Default, func() *sparkles.Controller { return sparkles.New( ioc.MustResolve[domain.Repository[*domain.Sparkle]](ioc.Default), - ioc.MustResolve[authz.PermissionService](ioc.Default), + ioc.MustResolve[authz.CheckPermissionService](ioc.Default), ) }) ioc.RegisterSingleton[*http.Client](ioc.Default, func() *http.Client { diff --git a/app/middleware/require_permission.go b/app/middleware/require_permission.go index cfcae0c..441b334 100644 --- a/app/middleware/require_permission.go +++ b/app/middleware/require_permission.go @@ -10,7 +10,7 @@ import ( "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/pls" ) -func RequirePermission(permission Permission, client authz.PermissionService) func(http.Handler) http.Handler { +func RequirePermission(permission domain.Permission, client authz.CheckPermissionService) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { user := cfg.CurrentUser.From(r.Context()) diff --git a/app/middleware/require_permission_test.go b/app/middleware/require_permission_test.go index b11a33c..2023345 100644 --- a/app/middleware/require_permission_test.go +++ b/app/middleware/require_permission_test.go @@ -14,7 +14,7 @@ import ( func TestRequirePermission(t *testing.T) { user := &domain.User{ID: domain.ID("1")} ctx := cfg.CurrentUser.With(t.Context(), user) - permission := Permission("read") + permission := domain.Permission("read") t.Run("when the permission is granted", func(t *testing.T) { r, w := test.RequestResponse("GET", "/sparkles", test.WithContext(ctx)) diff --git a/internal/stub/check.go b/internal/stub/check.go index ec257e3..073b35b 100644 --- a/internal/stub/check.go +++ b/internal/stub/check.go @@ -17,7 +17,7 @@ func (m Check) CheckPermission(ctx context.Context, r *v1.CheckPermissionRequest return m(ctx, r) } -func AllowWith(t *testing.T, subject string, permission string, resource string) authz.PermissionService { +func AllowWith(t *testing.T, subject string, permission string, resource string) authz.CheckPermissionService { user := strings.SplitN(subject, ":", 2) model := strings.SplitN(resource, ":", 2) @@ -36,7 +36,7 @@ func AllowWith(t *testing.T, subject string, permission string, resource string) }) } -func Allow() authz.PermissionService { +func Allow() authz.CheckPermissionService { return Check(func(ctx context.Context, r *v1.CheckPermissionRequest) (*v1.CheckPermissionResponse, error) { return &v1.CheckPermissionResponse{ Permissionship: v1.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION, @@ -44,7 +44,7 @@ func Allow() authz.PermissionService { }) } -func Deny() authz.PermissionService { +func Deny() authz.CheckPermissionService { return Check(func(ctx context.Context, r *v1.CheckPermissionRequest) (*v1.CheckPermissionResponse, error) { return &v1.CheckPermissionResponse{ Permissionship: v1.CheckPermissionResponse_PERMISSIONSHIP_NO_PERMISSION, diff --git a/pkg/authz/spice.go b/pkg/authz/spice.go index 47468d9..cd534a1 100644 --- a/pkg/authz/spice.go +++ b/pkg/authz/spice.go @@ -21,6 +21,6 @@ func NewSpiceDBClient(ctx context.Context, host string, presharedKey string) *au return client } -type PermissionService interface { +type CheckPermissionService interface { CheckPermission(ctx context.Context, in *v1.CheckPermissionRequest, opts ...grpc.CallOption) (*v1.CheckPermissionResponse, error) } |
