Filter takes in a list of objects, and will filter the list removing all the elements the subject does not have permission for. All objects must be of the same type. Ideally the 'CompileToSQL' is used instead for large sets. This cost scales linearly with the number of objects passed in.
(ctx context.Context, auth Authorizer, subject Subject, action policy.Action, objects []O)
| 213 | // Ideally the 'CompileToSQL' is used instead for large sets. This cost scales |
| 214 | // linearly with the number of objects passed in. |
| 215 | func Filter[O Objecter](ctx context.Context, auth Authorizer, subject Subject, action policy.Action, objects []O) ([]O, error) { |
| 216 | if len(objects) == 0 { |
| 217 | // Nothing to filter |
| 218 | return objects, nil |
| 219 | } |
| 220 | objectType := objects[0].RBACObject().Type |
| 221 | filtered := make([]O, 0) |
| 222 | |
| 223 | // Start the span after the object type is detected. If we are filtering 0 |
| 224 | // objects, then the span is not interesting. It would just add excessive |
| 225 | // 0 time spans that provide no insight. |
| 226 | ctx, span := tracing.StartSpan(ctx, |
| 227 | rbacTraceAttributes(subject, action, objectType, |
| 228 | // For filtering, we are only measuring the total time for the entire |
| 229 | // set of objects. This and the 'Prepare' span time |
| 230 | // is all that is required to measure the performance of this |
| 231 | // function on a per-object basis. |
| 232 | attribute.Int("num_objects", len(objects)), |
| 233 | ), |
| 234 | ) |
| 235 | defer span.End() |
| 236 | |
| 237 | // Running benchmarks on this function, it is **always** faster to call |
| 238 | // auth.Authorize on <10 objects. This is because the overhead of |
| 239 | // 'Prepare'. Once we cross 10 objects, then it starts to become |
| 240 | // faster |
| 241 | if len(objects) < 10 { |
| 242 | for _, o := range objects { |
| 243 | rbacObj := o.RBACObject() |
| 244 | if rbacObj.Type != objectType { |
| 245 | return nil, xerrors.Errorf("object types must be uniform across the set (%s), found %s", objectType, rbacObj.Type) |
| 246 | } |
| 247 | err := auth.Authorize(ctx, subject, action, o.RBACObject()) |
| 248 | if err == nil { |
| 249 | filtered = append(filtered, o) |
| 250 | } else if !IsUnauthorizedError(err) { |
| 251 | // If the error is not the expected "Unauthorized" error, then |
| 252 | // it is something unexpected. |
| 253 | return nil, err |
| 254 | } |
| 255 | } |
| 256 | return filtered, nil |
| 257 | } |
| 258 | |
| 259 | prepared, err := auth.Prepare(ctx, subject, action, objectType) |
| 260 | if err != nil { |
| 261 | return nil, xerrors.Errorf("prepare: %w", err) |
| 262 | } |
| 263 | |
| 264 | for _, object := range objects { |
| 265 | rbacObj := object.RBACObject() |
| 266 | if rbacObj.Type != objectType { |
| 267 | return nil, xerrors.Errorf("object types must be uniform across the set (%s), found %s", objectType, object.RBACObject().Type) |
| 268 | } |
| 269 | err := prepared.Authorize(ctx, rbacObj) |
| 270 | if err == nil { |
| 271 | filtered = append(filtered, object) |
| 272 | } else if !IsUnauthorizedError(err) { |