summaryrefslogtreecommitdiff
path: root/app/db/authorization.go
blob: ec8e47b869a92b07c0f6651c4f97f6dc994847bc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
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()
}