@@ -11,24 +11,41 @@ import (
11
11
12
12
type Authorizer interface {
13
13
ByRoleName (ctx context.Context , subjectID string , roleNames []string , action Action , object Object ) error
14
+ PrepareByRoleName (ctx context.Context , subjectID string , roleNames []string , action Action , objectType string ) (PreparedAuthorized , error )
15
+ }
16
+
17
+ type PreparedAuthorized interface {
18
+ Authorize (ctx context.Context , object Object ) error
14
19
}
15
20
16
21
// Filter takes in a list of objects, and will filter the list removing all
17
- // the elements the subject does not have permission for.
18
- // Filter does not allocate a new slice, and will use the existing one
19
- // passed in. This can cause memory leaks if the slice is held for a prolonged
20
- // period of time.
21
- func Filter [O Objecter ](ctx context.Context , auth Authorizer , subjID string , subjRoles []string , action Action , objects []O ) []O {
22
+ // the elements the subject does not have permission for. All objects must be
23
+ // of the same type.
24
+ func Filter [O Objecter ](ctx context.Context , auth Authorizer , subjID string , subjRoles []string , action Action , objects []O ) ([]O , error ) {
25
+ if len (objects ) == 0 {
26
+ // Nothing to filter
27
+ return objects , nil
28
+ }
29
+ objectType := objects [0 ].RBACObject ().Type
30
+
22
31
filtered := make ([]O , 0 )
32
+ prepared , err := auth .PrepareByRoleName (ctx , subjID , subjRoles , action , objectType )
33
+ if err != nil {
34
+ return nil , xerrors .Errorf ("prepare: %w" , err )
35
+ }
23
36
24
37
for i := range objects {
25
38
object := objects [i ]
26
- err := auth .ByRoleName (ctx , subjID , subjRoles , action , object .RBACObject ())
39
+ rbacObj := object .RBACObject ()
40
+ if rbacObj .Type != objectType {
41
+ return nil , xerrors .Errorf ("object types must be uniform across the set (%s), found %s" , objectType , object .RBACObject ().Type )
42
+ }
43
+ err := prepared .Authorize (ctx , rbacObj )
27
44
if err == nil {
28
45
filtered = append (filtered , object )
29
46
}
30
47
}
31
- return filtered
48
+ return filtered , nil
32
49
}
33
50
34
51
// RegoAuthorizer will use a prepared rego query for performing authorize()
@@ -45,7 +62,7 @@ func NewAuthorizer() (*RegoAuthorizer, error) {
45
62
query , err := rego .New (
46
63
// allowed is the `allow` field from the prepared query. This is the field to check if authorization is
47
64
// granted.
48
- rego .Query ("allowed = data.authz.allow" ),
65
+ rego .Query ("data.authz.allow" ),
49
66
rego .Module ("policy.rego" , policy ),
50
67
).PrepareForEval (ctx )
51
68
@@ -64,14 +81,11 @@ type authSubject struct {
64
81
// This is the function intended to be used outside this package.
65
82
// The role is fetched from the builtin map located in memory.
66
83
func (a RegoAuthorizer ) ByRoleName (ctx context.Context , subjectID string , roleNames []string , action Action , object Object ) error {
67
- roles := make ([]Role , 0 , len (roleNames ))
68
- for _ , n := range roleNames {
69
- r , err := RoleByName (n )
70
- if err != nil {
71
- return xerrors .Errorf ("get role permissions: %w" , err )
72
- }
73
- roles = append (roles , r )
84
+ roles , err := RolesByNames (roleNames )
85
+ if err != nil {
86
+ return err
74
87
}
88
+
75
89
return a .Authorize (ctx , subjectID , roles , action , object )
76
90
}
77
91
@@ -92,18 +106,29 @@ func (a RegoAuthorizer) Authorize(ctx context.Context, subjectID string, roles [
92
106
return ForbiddenWithInternal (xerrors .Errorf ("eval rego: %w" , err ), input , results )
93
107
}
94
108
95
- if len ( results ) != 1 {
96
- return ForbiddenWithInternal (xerrors .Errorf ("expect only 1 result, got %d" , len ( results ) ), input , results )
109
+ if ! results . Allowed () {
110
+ return ForbiddenWithInternal (xerrors .Errorf ("policy disallows request" ), input , results )
97
111
}
98
112
99
- allowedResult , ok := (results [0 ].Bindings ["allowed" ]).(bool )
100
- if ! ok {
101
- return ForbiddenWithInternal (xerrors .Errorf ("expected allowed to be a bool but got %T" , allowedResult ), input , results )
113
+ return nil
114
+ }
115
+
116
+ // Prepare will partially execute the rego policy leaving the object fields unknown (except for the type).
117
+ // This will vastly speed up performance if batch authorization on the same type of objects is needed.
118
+ func (RegoAuthorizer ) Prepare (ctx context.Context , subjectID string , roles []Role , action Action , objectType string ) (* PartialAuthorizer , error ) {
119
+ auth , err := newPartialAuthorizer (ctx , subjectID , roles , action , objectType )
120
+ if err != nil {
121
+ return nil , xerrors .Errorf ("new partial authorizer: %w" , err )
102
122
}
103
123
104
- if ! allowedResult {
105
- return ForbiddenWithInternal (xerrors .Errorf ("policy disallows request" ), input , results )
124
+ return auth , nil
125
+ }
126
+
127
+ func (a RegoAuthorizer ) PrepareByRoleName (ctx context.Context , subjectID string , roleNames []string , action Action , objectType string ) (PreparedAuthorized , error ) {
128
+ roles , err := RolesByNames (roleNames )
129
+ if err != nil {
130
+ return nil , err
106
131
}
107
132
108
- return nil
133
+ return a . Prepare ( ctx , subjectID , roles , action , objectType )
109
134
}
0 commit comments