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 | |
| 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')
| -rw-r--r-- | app/db/authorization.go (renamed from app/db/secure_repository.go) | 29 | ||||
| -rw-r--r-- | app/db/in_memory_repository.go | 18 | ||||
| -rw-r--r-- | app/db/in_memory_repository_test.go | 25 | ||||
| -rw-r--r-- | app/db/publisher.go | 31 | ||||
| -rw-r--r-- | app/db/publisher_test.go | 34 |
5 files changed, 86 insertions, 51 deletions
diff --git a/app/db/secure_repository.go b/app/db/authorization.go index 26b85c9..ec8e47b 100644 --- a/app/db/secure_repository.go +++ b/app/db/authorization.go @@ -12,19 +12,21 @@ import ( "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/pls" ) -type SecureRepository[T domain.Entity] struct { +type authorization[T domain.Entity] struct { client *authzed.Client repository domain.Repository[T] } -func NewSecureRepository[T domain.Entity](client *authzed.Client, repository domain.Repository[T]) domain.Repository[T] { - return &SecureRepository[T]{ - client: client, - repository: repository, +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 *SecureRepository[T]) All(ctx context.Context) []T { +func (r *authorization[T]) All(ctx context.Context) []T { allItems := r.repository.All(ctx) if len(allItems) == 0 { return allItems @@ -56,7 +58,7 @@ func (r *SecureRepository[T]) All(ctx context.Context) []T { return filteredItems } -func (r *SecureRepository[T]) Find(ctx context.Context, id domain.ID) T { +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{ @@ -76,7 +78,7 @@ func (r *SecureRepository[T]) Find(ctx context.Context, id domain.ID) T { return item } -func (r *SecureRepository[T]) Save(ctx context.Context, item T) error { +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") @@ -104,16 +106,7 @@ func (r *SecureRepository[T]) Save(ctx context.Context, item T) error { return r.repository.Save(ctx, item) } -func (r *SecureRepository[T]) subjectFrom(ctx context.Context) *v1.SubjectReference { +func (r *authorization[T]) subjectFrom(ctx context.Context) *v1.SubjectReference { currentUser := cfg.CurrentUser.From(ctx) - if currentUser == nil { - return &v1.SubjectReference{ - Object: &v1.ObjectReference{ - ObjectType: "user", - ObjectId: "*", - }, - } - } - return currentUser.ToSubjectReference() } diff --git a/app/db/in_memory_repository.go b/app/db/in_memory_repository.go index 1177662..5ee4fea 100644 --- a/app/db/in_memory_repository.go +++ b/app/db/in_memory_repository.go @@ -5,24 +5,21 @@ import ( "sort" "sync" - "github.com/xlgmokha/x/pkg/event" "github.com/xlgmokha/x/pkg/x" "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 inMemoryRepository[T domain.Entity] struct { - aggregator *event.TypedAggregator[T] - items []T - mu sync.RWMutex + items []T + mu sync.RWMutex } -func NewRepository[T domain.Entity](aggregator *event.TypedAggregator[T]) domain.Repository[T] { - return &inMemoryRepository[T]{ - aggregator: aggregator, - items: []T{}, - mu: sync.RWMutex{}, - } +func NewRepository[T domain.Entity](options ...x.Option[domain.Repository[T]]) domain.Repository[T] { + return x.NewWith[domain.Repository[T]](&inMemoryRepository[T]{ + items: []T{}, + mu: sync.RWMutex{}, + }, options...) } func (r *inMemoryRepository[T]) All(ctx context.Context) []T { @@ -53,6 +50,5 @@ func (r *inMemoryRepository[T]) Save(ctx context.Context, item T) error { sort.Slice(r.items, func(i, j int) bool { return r.items[i].GetID() > r.items[j].GetID() }) - r.aggregator.Publish("after.create", item) return nil } diff --git a/app/db/in_memory_repository_test.go b/app/db/in_memory_repository_test.go index 5bb220e..b89f16a 100644 --- a/app/db/in_memory_repository_test.go +++ b/app/db/in_memory_repository_test.go @@ -7,14 +7,12 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/xlgmokha/x/pkg/event" "github.com/xlgmokha/x/pkg/x" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/domain" ) func TestInMemoryRepository(t *testing.T) { - aggregator := event.New[*domain.Sparkle]() - storage := NewRepository[*domain.Sparkle](aggregator) + storage := NewRepository[*domain.Sparkle]() t.Run("Save", func(t *testing.T) { t.Run("an invalid Sparkle", func(t *testing.T) { @@ -33,25 +31,8 @@ func TestInMemoryRepository(t *testing.T) { assert.Equal(t, "because", sparkles[0].Reason) }) - t.Run("publishes an event", func(t *testing.T) { - called := false - var payload *domain.Sparkle - - aggregator.SubscribeTo("after.create", func(item *domain.Sparkle) { - called = true - payload = item - }) - - sparkle := &domain.Sparkle{Sparklee: "@tanuki", Reason: "because"} - require.NoError(t, storage.Save(t.Context(), sparkle)) - - require.True(t, called) - require.NotNil(t, payload) - assert.Equal(t, sparkle, payload) - }) - t.Run("prevents race conditions", func(t *testing.T) { - repository := NewRepository[*domain.Sparkle](aggregator) + repository := NewRepository[*domain.Sparkle]() ctx := context.Background() numGoroutines := 100 @@ -112,7 +93,7 @@ func TestInMemoryRepository(t *testing.T) { }) t.Run("All", func(t *testing.T) { - repository := NewRepository[*domain.Sparkle](aggregator) + repository := NewRepository[*domain.Sparkle]() require.NoError(t, repository.Save(t.Context(), &domain.Sparkle{ Sparklee: "@tanuki", Reason: "because", diff --git a/app/db/publisher.go b/app/db/publisher.go new file mode 100644 index 0000000..ee2a966 --- /dev/null +++ b/app/db/publisher.go @@ -0,0 +1,31 @@ +package db + +import ( + "context" + + "github.com/xlgmokha/x/pkg/event" + "github.com/xlgmokha/x/pkg/x" + "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/domain" +) + +type publisher[T domain.Entity] struct { + aggregator *event.TypedAggregator[T] + domain.Repository[T] +} + +func WithPublishing[T domain.Entity](aggregator *event.TypedAggregator[T]) x.Option[domain.Repository[T]] { + return func(repository domain.Repository[T]) domain.Repository[T] { + return &publisher[T]{ + aggregator: aggregator, + Repository: repository, + } + } +} + +func (r *publisher[T]) Save(ctx context.Context, item T) error { + err := r.Repository.Save(ctx, item) + if err == nil { + r.aggregator.Publish("after.create", item) + } + return err +} diff --git a/app/db/publisher_test.go b/app/db/publisher_test.go new file mode 100644 index 0000000..eeb912a --- /dev/null +++ b/app/db/publisher_test.go @@ -0,0 +1,34 @@ +package db + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/xlgmokha/x/pkg/event" + "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/domain" +) + +func TestWithPublishing(t *testing.T) { + aggregator := event.New[*domain.Sparkle]() + storage := NewRepository[*domain.Sparkle](WithPublishing(aggregator)) + + t.Run("Save", func(t *testing.T) { + t.Run("publishes an event", func(t *testing.T) { + called := false + var payload *domain.Sparkle + + aggregator.SubscribeTo("after.create", func(item *domain.Sparkle) { + called = true + payload = item + }) + + sparkle := &domain.Sparkle{Sparklee: "@tanuki", Reason: "because"} + require.NoError(t, storage.Save(t.Context(), sparkle)) + + require.True(t, called) + require.NotNil(t, payload) + assert.Equal(t, sparkle, payload) + }) + }) +} |
