@@ -11,6 +11,11 @@ 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 , roles []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
@@ -31,10 +36,26 @@ func Filter[O Objecter](ctx context.Context, auth Authorizer, subjID string, sub
31
36
return filtered
32
37
}
33
38
39
+ func FilterPart [O Objecter ](ctx context.Context , auth Authorizer , subjID string , subjRoles []string , action Action , objectType string , objects []O ) []O {
40
+ filtered := make ([]O , 0 )
41
+ prepared , err := auth .PrepareByRoleName (ctx , subjID , subjRoles , action , objectType )
42
+ if err != nil {
43
+ return filtered
44
+ }
45
+
46
+ for i := range objects {
47
+ object := objects [i ]
48
+ err := prepared .Authorize (ctx , object .RBACObject ())
49
+ if err == nil {
50
+ filtered = append (filtered , object )
51
+ }
52
+ }
53
+ return filtered
54
+ }
55
+
34
56
// RegoAuthorizer will use a prepared rego query for performing authorize()
35
57
type RegoAuthorizer struct {
36
- query rego.PreparedEvalQuery
37
- partial rego.PreparedPartialQuery
58
+ query rego.PreparedEvalQuery
38
59
}
39
60
40
61
// Load the policy from policy.rego in this directory.
@@ -46,23 +67,14 @@ func NewAuthorizer() (*RegoAuthorizer, error) {
46
67
query , err := rego .New (
47
68
// allowed is the `allow` field from the prepared query. This is the field to check if authorization is
48
69
// granted.
49
- rego .Query ("allowed = data.authz.allow" ),
70
+ rego .Query ("data.authz.allow" ),
50
71
rego .Module ("policy.rego" , policy ),
51
72
).PrepareForEval (ctx )
52
73
53
- partial , err := rego .New (
54
- rego .Query ("allowed = data.authz.allow" ),
55
- rego .Module ("policy.rego" , policy ),
56
- rego .Unknowns ([]string {
57
- "input.object.owner" ,
58
- "input.object.org_owner" ,
59
- }),
60
- ).PrepareForPartial (ctx )
61
-
62
74
if err != nil {
63
75
return nil , xerrors .Errorf ("prepare query: %w" , err )
64
76
}
65
- return & RegoAuthorizer {query : query , partial : partial }, nil
77
+ return & RegoAuthorizer {query : query }, nil
66
78
}
67
79
68
80
type authSubject struct {
@@ -74,14 +86,11 @@ type authSubject struct {
74
86
// This is the function intended to be used outside this package.
75
87
// The role is fetched from the builtin map located in memory.
76
88
func (a RegoAuthorizer ) ByRoleName (ctx context.Context , subjectID string , roleNames []string , action Action , object Object ) error {
77
- roles := make ([]Role , 0 , len (roleNames ))
78
- for _ , n := range roleNames {
79
- r , err := RoleByName (n )
80
- if err != nil {
81
- return xerrors .Errorf ("get role permissions: %w" , err )
82
- }
83
- roles = append (roles , r )
89
+ roles , err := a .rolesByNames (roleNames )
90
+ if err != nil {
91
+ return err
84
92
}
93
+
85
94
return a .Authorize (ctx , subjectID , roles , action , object )
86
95
}
87
96
@@ -102,28 +111,23 @@ func (a RegoAuthorizer) Authorize(ctx context.Context, subjectID string, roles [
102
111
return ForbiddenWithInternal (xerrors .Errorf ("eval rego: %w" , err ), input , results )
103
112
}
104
113
105
- if len ( results ) != 1 {
106
- return ForbiddenWithInternal (xerrors .Errorf ("expect only 1 result, got %d" , len ( results ) ), input , results )
114
+ if ! results . Allowed () {
115
+ return ForbiddenWithInternal (xerrors .Errorf ("policy disallows request" ), input , results )
107
116
}
108
117
109
- allowedResult , ok := (results [0 ].Bindings ["allowed" ]).(bool )
110
- if ! ok {
111
- return ForbiddenWithInternal (xerrors .Errorf ("expected allowed to be a bool but got %T" , allowedResult ), input , results )
112
- }
118
+ return nil
119
+ }
113
120
114
- if ! allowedResult {
115
- return ForbiddenWithInternal (xerrors .Errorf ("policy disallows request" ), input , results )
121
+ func (a RegoAuthorizer ) PrepareByRoleName (ctx context.Context , subjectID string , roleNames []string , action Action , objectType string ) (PreparedAuthorized , error ) {
122
+ roles , err := a .rolesByNames (roleNames )
123
+ if err != nil {
124
+ return nil , err
116
125
}
117
126
118
- return nil
127
+ return a . Prepare ( ctx , subjectID , roles , action , objectType )
119
128
}
120
129
121
- // CheckPartial will not authorize the request. This function is to be used for unit testing to verify the rego policy
122
- // can be converted into ONLY queries. This ensures we can convert the queries into SQL WHERE clauses in the future.
123
- // If this function returns an error, then there is a set of inputs that also returns support rules, which cannot
124
- // be converted.
125
- // This function will not be used to actually perform authorization on partial queries.
126
- func (a RegoAuthorizer ) CheckPartial (ctx context.Context , subjectID string , roles []Role , action Action , objectType string ) (* rego.PartialQueries , interface {}, error ) {
130
+ func (a RegoAuthorizer ) Prepare (ctx context.Context , subjectID string , roles []Role , action Action , objectType string ) (* partialAuthorizer , error ) {
127
131
input := map [string ]interface {}{
128
132
"subject" : authSubject {
129
133
ID : subjectID ,
@@ -134,6 +138,57 @@ func (a RegoAuthorizer) CheckPartial(ctx context.Context, subjectID string, role
134
138
},
135
139
"action" : action ,
136
140
}
137
- result , err := a .partial .Partial (ctx , rego .EvalInput (input ))
138
- return result , input , err
141
+
142
+ rego := rego .New (
143
+ rego .Query ("data.authz.allow" ),
144
+ rego .Module ("policy.rego" , policy ),
145
+ rego .Unknowns ([]string {
146
+ "input.object.owner" ,
147
+ "input.object.org_owner" ,
148
+ }),
149
+ rego .Input (input ),
150
+ )
151
+
152
+ auth , err := newPartialAuthorizer (ctx , rego , input )
153
+ if err != nil {
154
+ return nil , xerrors .Errorf ("new partial authorizer: %w" , err )
155
+ }
156
+
157
+ return auth , nil
158
+ }
159
+
160
+ func (a RegoAuthorizer ) rolesByNames (roleNames []string ) ([]Role , error ) {
161
+ roles := make ([]Role , 0 , len (roleNames ))
162
+ for _ , n := range roleNames {
163
+ r , err := RoleByName (n )
164
+ if err != nil {
165
+ return nil , xerrors .Errorf ("get role permissions: %w" , err )
166
+ }
167
+ roles = append (roles , r )
168
+ }
169
+ return roles , nil
170
+ }
171
+
172
+ // CheckPartial will not authorize the request. This function is to be used for unit testing to verify the rego policy
173
+ // can be converted into ONLY queries. This ensures we can convert the queries into SQL WHERE clauses in the future.
174
+ // If this function returns an error, then there is a set of inputs that also returns support rules, which cannot
175
+ // be converted.
176
+ // Unfortunately we cannot reuse a.Prepare for this purpose as a Partial() requires an expression,
177
+ // whereas a PartialResult() requires a reference. It's an annoying detail that we need to work around.
178
+ func (a RegoAuthorizer ) CheckPartial (ctx context.Context , subjectID string , roles []Role , action Action , objectType string ) (* rego.PartialQueries , interface {}, error ) {
179
+ partAuth , err := a .Prepare (ctx , subjectID , roles , action , objectType )
180
+ if err != nil {
181
+ return nil , nil , err
182
+ }
183
+
184
+ result , err := rego .New (
185
+ rego .Query ("true = data.authz.allow" ),
186
+ rego .Module ("policy.rego" , policy ),
187
+ rego .Unknowns ([]string {
188
+ "input.object.owner" ,
189
+ "input.object.org_owner" ,
190
+ }),
191
+ rego .Input (partAuth .Input ),
192
+ ).Partial (ctx )
193
+ return result , partAuth .Input , err
139
194
}
0 commit comments