diff options
Diffstat (limited to 'app')
| -rw-r--r-- | app/domain/entity.go | 3 | ||||
| -rw-r--r-- | app/domain/identifiable.go | 7 | ||||
| -rw-r--r-- | app/domain/sparkle.go | 4 | ||||
| -rw-r--r-- | app/domain/user.go | 4 | ||||
| -rw-r--r-- | app/middleware/permission.go | 24 | ||||
| -rw-r--r-- | app/middleware/require_permission.go | 33 | ||||
| -rw-r--r-- | app/middleware/require_permission_test.go | 58 |
7 files changed, 131 insertions, 2 deletions
diff --git a/app/domain/entity.go b/app/domain/entity.go index fb1cab8..0377c51 100644 --- a/app/domain/entity.go +++ b/app/domain/entity.go @@ -1,7 +1,6 @@ package domain type Entity interface { - GetID() ID - SetID(id ID) error + Identifiable Validate() error } diff --git a/app/domain/identifiable.go b/app/domain/identifiable.go new file mode 100644 index 0000000..8fbc1e4 --- /dev/null +++ b/app/domain/identifiable.go @@ -0,0 +1,7 @@ +package domain + +type Identifiable interface { + GetID() ID + SetID(id ID) error + ToGID() string +} diff --git a/app/domain/sparkle.go b/app/domain/sparkle.go index 68aed67..d4f70b2 100644 --- a/app/domain/sparkle.go +++ b/app/domain/sparkle.go @@ -49,6 +49,10 @@ func (s *Sparkle) SetID(id ID) error { return nil } +func (s *Sparkle) ToGID() string { + return "gid://sparkle/Sparkle/" + s.ID.String() +} + func (s *Sparkle) Validate() error { if s.Sparklee == "" { return SparkleeIsRequired diff --git a/app/domain/user.go b/app/domain/user.go index aae17f6..02ddd26 100644 --- a/app/domain/user.go +++ b/app/domain/user.go @@ -32,3 +32,7 @@ func (self *User) Sparkle(sparklee string, reason string) *Sparkle { Reason: reason, } } + +func (self *User) ToGID() string { + return "gid://sparkle/User/" + self.ID.String() +} diff --git a/app/middleware/permission.go b/app/middleware/permission.go new file mode 100644 index 0000000..03e7cf9 --- /dev/null +++ b/app/middleware/permission.go @@ -0,0 +1,24 @@ +package middleware + +import ( + "gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd.git/pkg/rpc" + "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) *rpc.AllowRequest { + return &rpc.AllowRequest{ + Subject: user.ToGID(), + Permission: p.ToGID(), + Resource: resource.ToGID(), + } +} + +func (p Permission) String() string { + return string(p) +} diff --git a/app/middleware/require_permission.go b/app/middleware/require_permission.go new file mode 100644 index 0000000..563278e --- /dev/null +++ b/app/middleware/require_permission.go @@ -0,0 +1,33 @@ +package middleware + +import ( + "net/http" + + "gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd.git/pkg/rpc" + "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/cfg" + "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/domain" + "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/pls" +) + +func RequirePermission(permission Permission, ability rpc.Ability) 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()) + + reply, err := ability.Allowed(r.Context(), + permission.RequestFor(user, &domain.Sparkle{ID: "*"}), + ) + if err != nil { + pls.LogError(r.Context(), err) + w.WriteHeader(http.StatusForbidden) + return + } + + if reply.Result { + next.ServeHTTP(w, r) + } else { + w.WriteHeader(http.StatusForbidden) + } + }) + } +} diff --git a/app/middleware/require_permission_test.go b/app/middleware/require_permission_test.go new file mode 100644 index 0000000..34a04a7 --- /dev/null +++ b/app/middleware/require_permission_test.go @@ -0,0 +1,58 @@ +package middleware + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/require" + "github.com/xlgmokha/x/pkg/test" + "gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd.git/pkg/rpc" + "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/cfg" + "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/domain" +) + +type MockAbility func(context.Context, *rpc.AllowRequest) (*rpc.AllowReply, error) + +func (m MockAbility) Allowed(ctx context.Context, r *rpc.AllowRequest) (*rpc.AllowReply, error) { + return m(ctx, r) +} + +func TestRequirePermission(t *testing.T) { + user := &domain.User{ID: domain.ID("1")} + ctx := cfg.CurrentUser.With(t.Context(), user) + permission := Permission("read_sparkles") + + t.Run("when the permission is granted", func(t *testing.T) { + r, w := test.RequestResponse("GET", "/sparkles", test.WithContext(ctx)) + + middleware := RequirePermission(permission, MockAbility(func(ctx context.Context, r *rpc.AllowRequest) (*rpc.AllowReply, error) { + require.Equal(t, "gid://sparkle/User/"+user.ID.String(), r.Subject) + require.Equal(t, permission.ToGID(), r.Permission) + require.Equal(t, "gid://sparkle/Sparkle/*", r.Resource) + + return &rpc.AllowReply{Result: true}, nil + })) + server := middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusTeapot) + })) + server.ServeHTTP(w, r) + + require.Equal(t, http.StatusTeapot, w.Code) + }) + + t.Run("when the permission is denied", func(t *testing.T) { + r, w := test.RequestResponse("GET", "/sparkles", test.WithContext(ctx)) + + middleware := RequirePermission(permission, MockAbility(func(ctx context.Context, r *rpc.AllowRequest) (*rpc.AllowReply, error) { + return &rpc.AllowReply{Result: false}, nil + })) + server := middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Fail(t, "unexpected call to handler") + })) + + server.ServeHTTP(w, r) + + require.Equal(t, http.StatusForbidden, w.Code) + }) +} |
