diff options
Diffstat (limited to 'vendor/github.com/authzed/spicedb/pkg/schema/arrows.go')
| -rw-r--r-- | vendor/github.com/authzed/spicedb/pkg/schema/arrows.go | 157 |
1 files changed, 157 insertions, 0 deletions
diff --git a/vendor/github.com/authzed/spicedb/pkg/schema/arrows.go b/vendor/github.com/authzed/spicedb/pkg/schema/arrows.go new file mode 100644 index 0000000..021f282 --- /dev/null +++ b/vendor/github.com/authzed/spicedb/pkg/schema/arrows.go @@ -0,0 +1,157 @@ +package schema + +import ( + "context" + "errors" + "fmt" + "strconv" + + "github.com/authzed/spicedb/pkg/genutil/mapz" + core "github.com/authzed/spicedb/pkg/proto/core/v1" +) + +// ArrowInformation holds information about an arrow (TupleToUserset) in the schema. +type ArrowInformation struct { + Arrow *core.TupleToUserset + Path string + ParentRelationName string +} + +// ArrowSet represents a set of all the arrows (TupleToUserset's) found in the schema. +type ArrowSet struct { + res FullSchemaResolver + ts *TypeSystem + arrowsByFullTuplesetRelation *mapz.MultiMap[string, ArrowInformation] + arrowsByComputedUsersetNamespaceAndRelation *mapz.MultiMap[string, ArrowInformation] + reachableComputedUsersetRelationsByTuplesetRelation *mapz.MultiMap[string, string] +} + +// buildArrowSet builds a new set of all arrows found in the given schema. +func buildArrowSet(ctx context.Context, res FullSchemaResolver) (*ArrowSet, error) { + arrowSet := &ArrowSet{ + res: res, + ts: NewTypeSystem(res), + arrowsByFullTuplesetRelation: mapz.NewMultiMap[string, ArrowInformation](), + arrowsByComputedUsersetNamespaceAndRelation: mapz.NewMultiMap[string, ArrowInformation](), + reachableComputedUsersetRelationsByTuplesetRelation: mapz.NewMultiMap[string, string](), + } + if err := arrowSet.compute(ctx); err != nil { + return nil, err + } + return arrowSet, nil +} + +// AllReachableRelations returns all relations reachable through arrows, including tupleset relations +// and computed userset relations. +func (as *ArrowSet) AllReachableRelations() *mapz.Set[string] { + c := mapz.NewSet(as.reachableComputedUsersetRelationsByTuplesetRelation.Values()...) + c.Extend(as.arrowsByFullTuplesetRelation.Keys()) + return c +} + +// HasPossibleArrowWithComputedUserset returns true if there is a *possible* arrow with the given relation name +// as the arrow's computed userset/for a subject type that has the given namespace. +func (as *ArrowSet) HasPossibleArrowWithComputedUserset(namespaceName string, relationName string) bool { + return as.arrowsByComputedUsersetNamespaceAndRelation.Has(namespaceName + "#" + relationName) +} + +// LookupTuplesetArrows finds all arrows with the given namespace and relation name as the arrows' tupleset. +func (as *ArrowSet) LookupTuplesetArrows(namespaceName string, relationName string) []ArrowInformation { + key := namespaceName + "#" + relationName + found, _ := as.arrowsByFullTuplesetRelation.Get(key) + return found +} + +func (as *ArrowSet) compute(ctx context.Context) error { + for _, name := range as.res.AllDefinitionNames() { + def, err := as.ts.GetValidatedDefinition(ctx, name) + if err != nil { + return err + } + for _, relation := range def.nsDef.Relation { + if err := as.collectArrowInformationForRelation(ctx, def, relation.Name); err != nil { + return err + } + } + } + return nil +} + +func (as *ArrowSet) add(ttu *core.TupleToUserset, path string, namespaceName string, relationName string) { + tsKey := namespaceName + "#" + ttu.Tupleset.Relation + as.arrowsByFullTuplesetRelation.Add(tsKey, ArrowInformation{Path: path, Arrow: ttu, ParentRelationName: relationName}) +} + +func (as *ArrowSet) collectArrowInformationForRelation(ctx context.Context, def *ValidatedDefinition, relationName string) error { + if !def.IsPermission(relationName) { + return nil + } + + relation, ok := def.GetRelation(relationName) + if !ok { + return asTypeError(NewRelationNotFoundErr(def.Namespace().GetName(), relationName)) + } + return as.collectArrowInformationForRewrite(ctx, relation.UsersetRewrite, def, relation, relationName) +} + +func (as *ArrowSet) collectArrowInformationForRewrite(ctx context.Context, rewrite *core.UsersetRewrite, def *ValidatedDefinition, relation *core.Relation, path string) error { + switch rw := rewrite.RewriteOperation.(type) { + case *core.UsersetRewrite_Union: + return as.collectArrowInformationForSetOperation(ctx, rw.Union, def, relation, path) + case *core.UsersetRewrite_Intersection: + return as.collectArrowInformationForSetOperation(ctx, rw.Intersection, def, relation, path) + case *core.UsersetRewrite_Exclusion: + return as.collectArrowInformationForSetOperation(ctx, rw.Exclusion, def, relation, path) + default: + return errors.New("userset rewrite operation not implemented in addArrowRelationsForRewrite") + } +} + +func (as *ArrowSet) collectArrowInformationForSetOperation(ctx context.Context, so *core.SetOperation, def *ValidatedDefinition, relation *core.Relation, path string) error { + for index, childOneof := range so.Child { + updatedPath := path + "." + strconv.Itoa(index) + switch child := childOneof.ChildType.(type) { + case *core.SetOperation_Child_ComputedUserset: + // Nothing to do + + case *core.SetOperation_Child_UsersetRewrite: + err := as.collectArrowInformationForRewrite(ctx, child.UsersetRewrite, def, relation, updatedPath) + if err != nil { + return err + } + + case *core.SetOperation_Child_TupleToUserset: + as.add(child.TupleToUserset, updatedPath, def.Namespace().Name, relation.Name) + + allowedSubjectTypes, err := def.AllowedSubjectRelations(child.TupleToUserset.Tupleset.Relation) + if err != nil { + return err + } + + for _, ast := range allowedSubjectTypes { + def, err := as.ts.GetValidatedDefinition(ctx, ast.Namespace) + if err != nil { + return err + } + + // NOTE: this is explicitly added to the arrowsByComputedUsersetNamespaceAndRelation without + // checking if the relation/permission exists, because its needed for schema diff tracking. + as.arrowsByComputedUsersetNamespaceAndRelation.Add(ast.Namespace+"#"+child.TupleToUserset.ComputedUserset.Relation, ArrowInformation{Path: path, Arrow: child.TupleToUserset, ParentRelationName: relation.Name}) + if def.HasRelation(child.TupleToUserset.ComputedUserset.Relation) { + as.reachableComputedUsersetRelationsByTuplesetRelation.Add(ast.Namespace+"#"+child.TupleToUserset.Tupleset.Relation, ast.Namespace+"#"+child.TupleToUserset.ComputedUserset.Relation) + } + } + + case *core.SetOperation_Child_XThis: + // Nothing to do + + case *core.SetOperation_Child_XNil: + // Nothing to do + + default: + return fmt.Errorf("unknown set operation child `%T` in addArrowRelationsInSetOperation", child) + } + } + + return nil +} |
