diff options
| author | mo khan <mo@mokhan.ca> | 2025-07-30 12:10:43 -0600 |
|---|---|---|
| committer | mo khan <mo@mokhan.ca> | 2025-07-30 12:10:43 -0600 |
| commit | ebb003ef2beaeee61104d6b88a342c5c9fa73b51 (patch) | |
| tree | 7b72d99a6257b636a9a844602f469975853e3b0e /app/db/authorization.go | |
| parent | a9a093fcc7a4e0c65f363eb2e2439631062de645 (diff) | |
| parent | d4f4f72b5ff586f2ff440f19f61d2fe48be89a37 (diff) | |
Merge branch 'secure-repository' into 'main'
Authorize reading/writing Sparkles using SpiceDB
See merge request gitlab-org/software-supply-chain-security/authorization/sparkled!20
Diffstat (limited to 'app/db/authorization.go')
| -rw-r--r-- | app/db/authorization.go | 112 |
1 files changed, 112 insertions, 0 deletions
diff --git a/app/db/authorization.go b/app/db/authorization.go new file mode 100644 index 0000000..ec8e47b --- /dev/null +++ b/app/db/authorization.go @@ -0,0 +1,112 @@ +package db + +import ( + "context" + "errors" + + v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" + "github.com/authzed/authzed-go/v1" + "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/domain" + "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/pls" +) + +type authorization[T domain.Entity] struct { + client *authzed.Client + repository domain.Repository[T] +} + +func WithAuthorization[T domain.Entity](client *authzed.Client) x.Option[domain.Repository[T]] { + return func(repository domain.Repository[T]) domain.Repository[T] { + return &authorization[T]{ + client: client, + repository: repository, + } + } +} + +func (r *authorization[T]) All(ctx context.Context) []T { + allItems := r.repository.All(ctx) + if len(allItems) == 0 { + return allItems + } + + response, err := r.client.CheckBulkPermissions(ctx, &v1.CheckBulkPermissionsRequest{ + Items: x.Map(allItems, func(item T) *v1.CheckBulkPermissionsRequestItem { + return &v1.CheckBulkPermissionsRequestItem{ + Resource: item.ToGID().ToObjectReference(), + Permission: "read", + Subject: r.subjectFrom(ctx), + } + }), + }) + if err != nil { + pls.LogError(ctx, err) + return x.Default[[]T]() + } + + filteredItems := make([]T, 0) + for i, pair := range response.Pairs { + if item := pair.GetItem(); item != nil { + if item.Permissionship == v1.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION { + filteredItems = append(filteredItems, allItems[i]) + } + } + } + + return filteredItems +} + +func (r *authorization[T]) Find(ctx context.Context, id domain.ID) T { + item := r.repository.Find(ctx, id) + + response, err := r.client.CheckPermission(ctx, &v1.CheckPermissionRequest{ + Resource: item.ToGID().ToObjectReference(), + Permission: "read", + Subject: r.subjectFrom(ctx), + }) + if err != nil { + pls.LogError(ctx, err) + return x.Default[T]() + } + + if response.Permissionship != v1.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION { + return x.Default[T]() + } + + return item +} + +func (r *authorization[T]) Save(ctx context.Context, item T) error { + currentUser := cfg.CurrentUser.From(ctx) + if currentUser == nil { + return errors.New("authentication required for creating or updating entities") + } + + if item.GetID() != "" { + response, err := r.client.CheckPermission(ctx, &v1.CheckPermissionRequest{ + Resource: item.ToGID().ToObjectReference(), + Permission: "update", + Subject: currentUser.ToSubjectReference(), + }) + if err != nil { + return err + } + + if response.Permissionship != v1.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION { + return errors.New("user does not have permission to update this entity") + } + } + + if sparkle, ok := any(item).(*domain.Sparkle); ok && item.GetID() == "" { + sparkle.Author = currentUser + } + + return r.repository.Save(ctx, item) +} + +func (r *authorization[T]) subjectFrom(ctx context.Context) *v1.SubjectReference { + currentUser := cfg.CurrentUser.From(ctx) + return currentUser.ToSubjectReference() +} |
