summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/controllers/dashboard/controller.go7
-rw-r--r--app/controllers/sparkles/controller.go8
-rw-r--r--app/domain/entity.go3
-rw-r--r--app/domain/identifiable.go7
-rw-r--r--app/domain/sparkle.go4
-rw-r--r--app/domain/user.go4
-rw-r--r--app/init.go27
-rw-r--r--app/middleware/id_token_test.go1
-rw-r--r--app/middleware/permission.go24
-rw-r--r--app/middleware/require_permission.go33
-rw-r--r--app/middleware/require_permission_test.go58
-rw-r--r--app/middleware/user.go2
12 files changed, 168 insertions, 10 deletions
diff --git a/app/controllers/dashboard/controller.go b/app/controllers/dashboard/controller.go
index 097834f..04a7ed1 100644
--- a/app/controllers/dashboard/controller.go
+++ b/app/controllers/dashboard/controller.go
@@ -3,6 +3,7 @@ package dashboard
import (
"net/http"
+ "github.com/xlgmokha/x/pkg/x"
"gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/cfg"
"gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/middleware"
"gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/views"
@@ -17,9 +18,11 @@ func New() *Controller {
}
func (c *Controller) MountTo(mux *http.ServeMux) {
- requireUser := middleware.RequireUser()
+ mux.Handle("GET /dashboard", x.Middleware[http.Handler](
+ http.HandlerFunc(c.Show),
+ middleware.RequireUser(),
+ ))
- mux.Handle("GET /dashboard", requireUser(http.HandlerFunc(c.Show)))
mux.Handle("GET /dashboard/nav", http.HandlerFunc(c.Navigation))
}
diff --git a/app/controllers/sparkles/controller.go b/app/controllers/sparkles/controller.go
index cd86cd2..5167c2a 100644
--- a/app/controllers/sparkles/controller.go
+++ b/app/controllers/sparkles/controller.go
@@ -21,10 +21,12 @@ func New(db domain.Repository[*domain.Sparkle]) *Controller {
}
func (c *Controller) MountTo(mux *http.ServeMux) {
- requireUser := middleware.RequireUser()
-
mux.HandleFunc("GET /sparkles", c.Index)
- mux.Handle("POST /sparkles", requireUser(http.HandlerFunc(c.Create)))
+ mux.Handle("POST /sparkles", x.Middleware[http.Handler](
+ http.HandlerFunc(c.Create),
+ middleware.RequireUser(),
+ // middleware.RequirePermission("create_sparkle", ioc.MustResolve[rpc.Ability](ioc.Default)),
+ ))
// This is a temporary endpoint to restore a backup
mux.HandleFunc("POST /sparkles/restore", c.Restore)
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/init.go b/app/init.go
index d9ca3de..935c962 100644
--- a/app/init.go
+++ b/app/init.go
@@ -7,8 +7,11 @@ import (
"github.com/coreos/go-oidc/v3/oidc"
"github.com/rs/zerolog"
+ "github.com/xlgmokha/x/pkg/env"
"github.com/xlgmokha/x/pkg/ioc"
"github.com/xlgmokha/x/pkg/log"
+ "github.com/xlgmokha/x/pkg/mapper"
+ "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/controllers/dashboard"
"gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/controllers/sparkles"
@@ -44,8 +47,12 @@ func init() {
},
}
})
- ioc.Register[*oidc.Provider](ioc.Default, func() *oidc.Provider {
- ctx := context.WithValue(context.Background(), oauth2.HTTPClient, ioc.MustResolve[*http.Client](ioc.Default))
+ ioc.RegisterSingleton[*oidc.Provider](ioc.Default, func() *oidc.Provider {
+ ctx := context.WithValue(
+ context.Background(),
+ oauth2.HTTPClient,
+ ioc.MustResolve[*http.Client](ioc.Default),
+ )
return web.NewOIDCProvider(ctx, cfg.OIDCIssuer, func(err error) {
ioc.MustResolve[*zerolog.Logger](ioc.Default).Err(err).Send()
})
@@ -55,6 +62,22 @@ func init() {
ClientID: cfg.OAuthClientID,
}
})
+ ioc.Register[rpc.Ability](ioc.Default, func() rpc.Ability {
+ return rpc.NewAbilityProtobufClient(
+ env.Fetch("AUTHZD_HOST", ""),
+ ioc.MustResolve[*http.Client](ioc.Default),
+ )
+ })
http.DefaultClient = ioc.MustResolve[*http.Client](ioc.Default)
+
+ mapper.Register[*http.Request, log.Fields](func(r *http.Request) log.Fields {
+ return log.Fields{
+ "host": r.URL.Host,
+ "method": r.Method,
+ "path": r.URL.Path,
+ "remote_host": r.RemoteAddr,
+ "request_id": r.Header.Get("x-request-id"),
+ }
+ })
}
diff --git a/app/middleware/id_token_test.go b/app/middleware/id_token_test.go
index 5487ada..9d8521a 100644
--- a/app/middleware/id_token_test.go
+++ b/app/middleware/id_token_test.go
@@ -23,7 +23,6 @@ func TestIDToken(t *testing.T) {
t.Run("when an active id_token cookie is provided", func(t *testing.T) {
t.Run("attaches the token to the request context", func(t *testing.T) {
user := mockoidc.DefaultUser()
-
_, rawIDToken := srv.CreateTokensFor(user)
server := middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
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)
+ })
+}
diff --git a/app/middleware/user.go b/app/middleware/user.go
index 6c018f4..2a6bf71 100644
--- a/app/middleware/user.go
+++ b/app/middleware/user.go
@@ -4,6 +4,7 @@ import (
"net/http"
"github.com/coreos/go-oidc/v3/oidc"
+ "github.com/xlgmokha/x/pkg/log"
"github.com/xlgmokha/x/pkg/mapper"
"github.com/xlgmokha/x/pkg/x"
"gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/cfg"
@@ -15,6 +16,7 @@ func User(db domain.Repository[*domain.User]) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
subject := r.Header.Get("x-jwt-claim-sub")
+ log.WithFields(r.Context(), log.Fields{"sub": subject})
user := db.Find(r.Context(), domain.ID(subject))
if !x.IsPresent(user) {