|
| 1 | +package v1 |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + |
| 6 | + v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" |
| 7 | + |
| 8 | + caveatsimpl "github.com/authzed/spicedb/internal/caveats" |
| 9 | + datastoremw "github.com/authzed/spicedb/internal/middleware/datastore" |
| 10 | + "github.com/authzed/spicedb/pkg/datastore" |
| 11 | + "github.com/authzed/spicedb/pkg/middleware/consistency" |
| 12 | + "github.com/authzed/spicedb/pkg/query" |
| 13 | + "github.com/authzed/spicedb/pkg/schema/v2" |
| 14 | +) |
| 15 | + |
| 16 | +// checkPermissionWithQueryPlan executes a permission check using the query plan API. |
| 17 | +// This builds an iterator tree from the schema and executes it against the datastore. |
| 18 | +func (ps *permissionServer) checkPermissionWithQueryPlan(ctx context.Context, req *v1.CheckPermissionRequest) (*v1.CheckPermissionResponse, error) { |
| 19 | + atRevision, checkedAt, err := consistency.RevisionFromContext(ctx) |
| 20 | + if err != nil { |
| 21 | + return nil, ps.rewriteError(ctx, err) |
| 22 | + } |
| 23 | + |
| 24 | + ds := datastoremw.MustFromContext(ctx) |
| 25 | + reader := ds.SnapshotReader(atRevision) |
| 26 | + |
| 27 | + // Load all namespace and caveat definitions to build the schema |
| 28 | + // TODO: Better schema caching |
| 29 | + namespaces, err := reader.ListAllNamespaces(ctx) |
| 30 | + if err != nil { |
| 31 | + return nil, ps.rewriteError(ctx, err) |
| 32 | + } |
| 33 | + |
| 34 | + caveats, err := reader.ListAllCaveats(ctx) |
| 35 | + if err != nil { |
| 36 | + return nil, ps.rewriteError(ctx, err) |
| 37 | + } |
| 38 | + |
| 39 | + // Build schema from definitions |
| 40 | + fullSchema, err := schema.BuildSchemaFromDefinitions( |
| 41 | + datastore.DefinitionsOf(namespaces), |
| 42 | + datastore.DefinitionsOf(caveats), |
| 43 | + ) |
| 44 | + if err != nil { |
| 45 | + return nil, ps.rewriteError(ctx, err) |
| 46 | + } |
| 47 | + |
| 48 | + // Build iterator tree from schema |
| 49 | + // TODO: Better iterator caching |
| 50 | + it, err := query.BuildIteratorFromSchema(fullSchema, req.Resource.ObjectType, req.Permission) |
| 51 | + if err != nil { |
| 52 | + return nil, ps.rewriteError(ctx, err) |
| 53 | + } |
| 54 | + |
| 55 | + // Parse caveat context if provided |
| 56 | + caveatContext, err := GetCaveatContext(ctx, req.Context, ps.config.MaxCaveatContextSize) |
| 57 | + if err != nil { |
| 58 | + return nil, ps.rewriteError(ctx, err) |
| 59 | + } |
| 60 | + |
| 61 | + // Create query context with optional tracing |
| 62 | + qctx := &query.Context{ |
| 63 | + Context: ctx, |
| 64 | + Executor: query.LocalExecutor{}, |
| 65 | + Reader: reader, |
| 66 | + CaveatContext: caveatContext, |
| 67 | + CaveatRunner: caveatsimpl.NewCaveatRunner(ps.config.CaveatTypeSet), |
| 68 | + } |
| 69 | + |
| 70 | + // Execute the check |
| 71 | + resource := query.Object{ |
| 72 | + ObjectType: req.Resource.ObjectType, |
| 73 | + ObjectID: req.Resource.ObjectId, |
| 74 | + } |
| 75 | + |
| 76 | + subject := query.ObjectAndRelation{ |
| 77 | + ObjectType: req.Subject.Object.ObjectType, |
| 78 | + ObjectID: req.Subject.Object.ObjectId, |
| 79 | + Relation: normalizeSubjectRelation(req.Subject), |
| 80 | + } |
| 81 | + |
| 82 | + pathSeq, err := qctx.Check(it, []query.Object{resource}, subject) |
| 83 | + if err != nil { |
| 84 | + return nil, ps.rewriteError(ctx, err) |
| 85 | + } |
| 86 | + |
| 87 | + // Collect results and convert to response |
| 88 | + permissionship, partialCaveat, err := convertPathsToPermissionship(pathSeq) |
| 89 | + if err != nil { |
| 90 | + return nil, ps.rewriteError(ctx, err) |
| 91 | + } |
| 92 | + |
| 93 | + resp := &v1.CheckPermissionResponse{ |
| 94 | + CheckedAt: checkedAt, |
| 95 | + Permissionship: permissionship, |
| 96 | + PartialCaveatInfo: partialCaveat, |
| 97 | + } |
| 98 | + |
| 99 | + return resp, nil |
| 100 | +} |
| 101 | + |
| 102 | +// convertPathsToPermissionship iterates over paths and determines the permissionship result. |
| 103 | +// Returns the first path's permissionship status, as any path indicates access. |
| 104 | +func convertPathsToPermissionship(pathSeq query.PathSeq) (v1.CheckPermissionResponse_Permissionship, *v1.PartialCaveatInfo, error) { |
| 105 | + // Iterate over paths to find the first valid result |
| 106 | + for path, err := range pathSeq { |
| 107 | + if err != nil { |
| 108 | + return v1.CheckPermissionResponse_PERMISSIONSHIP_UNSPECIFIED, nil, err |
| 109 | + } |
| 110 | + |
| 111 | + // Found a path - determine permissionship based on caveat presence |
| 112 | + if path.Caveat != nil { |
| 113 | + // TODO: Extract missing required context from caveat expression |
| 114 | + return v1.CheckPermissionResponse_PERMISSIONSHIP_CONDITIONAL_PERMISSION, &v1.PartialCaveatInfo{ |
| 115 | + MissingRequiredContext: []string{}, |
| 116 | + }, nil |
| 117 | + } |
| 118 | + |
| 119 | + // Path exists without caveat - has permission |
| 120 | + return v1.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION, nil, nil |
| 121 | + } |
| 122 | + |
| 123 | + // No paths found - no permission |
| 124 | + return v1.CheckPermissionResponse_PERMISSIONSHIP_NO_PERMISSION, nil, nil |
| 125 | +} |
0 commit comments