-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathauthorizer.go
123 lines (93 loc) · 2.64 KB
/
authorizer.go
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
113
114
115
116
117
118
119
120
121
122
123
package cerbos
import (
"context"
"slices"
"sort"
"github.com/cerbos/cerbos-sdk-go/cerbos"
effectv1 "github.com/cerbos/cerbos/api/genpb/cerbos/effect/v1"
"github.com/portward/registry-auth/auth"
"golang.org/x/exp/maps"
)
var _ auth.Authorizer = Authorizer{}
// Authorizer uses [Cerbos] to authorize resource requests.
//
// [Cerbos]: https://cerbos.dev
type Authorizer struct {
client *cerbos.GRPCClient
defaultRoles []string
}
// NewAuthorizer returns a new [Authorizer].
func NewAuthorizer(client *cerbos.GRPCClient, defaultRoles []string) Authorizer {
return Authorizer{
client: client,
defaultRoles: defaultRoles,
}
}
// Authorize implements the [auth.Authorizer] interface.
func (a Authorizer) Authorize(ctx context.Context, subject auth.Subject, requestedScopes []auth.Scope) ([]auth.Scope, error) {
// TODO: authorizer should probably not be called with an empty list of scopes
if len(requestedScopes) == 0 {
return []auth.Scope{}, nil
}
if subject == nil {
return nil, auth.ErrUnauthorized
}
principal := cerbos.NewPrincipal(subject.ID().String()).
WithAttributes(subject.Attributes()) // TODO: allow limiting what attributes are attached to the principal
if rolesAttr, ok := subject.Attribute("roles"); ok { // TODO: allow extracting roles from subject
principal = principal.WithRoles(extractRoles(rolesAttr)...)
}
if len(principal.Roles()) == 0 {
principal = principal.WithRoles(a.defaultRoles...)
}
resourceBatch := cerbos.NewResourceBatch()
for _, scope := range requestedScopes {
resource := cerbos.NewResource(scope.Type, scope.Name)
resourceBatch.Add(resource, scope.Actions...)
}
resp, err := a.client.CheckResources(ctx, principal, resourceBatch)
if err != nil { // TODO: check if error means no authorization
return nil, err
}
var scopes auth.Scopes
for _, result := range resp.GetResults() {
res := result.GetResource()
scope := auth.Scope{
Resource: auth.Resource{
Type: res.GetKind(),
Name: res.GetId(),
},
}
actions := result.GetActions()
actionKeys := maps.Keys(actions)
sort.Strings(actionKeys)
for _, action := range actionKeys {
effect := actions[action]
if effect != effectv1.Effect_EFFECT_ALLOW {
continue
}
scope.Actions = append(scope.Actions, action)
}
if len(scope.Actions) == 0 {
continue
}
scopes = append(scopes, scope)
}
return scopes, nil
}
func extractRoles(attr any) []string {
var roles []string
switch attr := attr.(type) {
case []any:
for _, v := range attr {
role, ok := v.(string)
if !ok {
continue
}
roles = append(roles, role)
}
case []string:
return slices.Clone(attr)
}
return roles
}