diff options
Diffstat (limited to 'vendor/github.com/authzed/spicedb/pkg/schema/definition.go')
| -rw-r--r-- | vendor/github.com/authzed/spicedb/pkg/schema/definition.go | 386 |
1 files changed, 386 insertions, 0 deletions
diff --git a/vendor/github.com/authzed/spicedb/pkg/schema/definition.go b/vendor/github.com/authzed/spicedb/pkg/schema/definition.go new file mode 100644 index 0000000..83ad73d --- /dev/null +++ b/vendor/github.com/authzed/spicedb/pkg/schema/definition.go @@ -0,0 +1,386 @@ +package schema + +import ( + "fmt" + + "github.com/authzed/spicedb/pkg/genutil/mapz" + nspkg "github.com/authzed/spicedb/pkg/namespace" + core "github.com/authzed/spicedb/pkg/proto/core/v1" + iv1 "github.com/authzed/spicedb/pkg/proto/impl/v1" + "github.com/authzed/spicedb/pkg/spiceerrors" + "github.com/authzed/spicedb/pkg/tuple" +) + +// AllowedDirectRelation indicates whether a relation is allowed on the right side of another relation. +type AllowedDirectRelation int + +const ( + // UnknownIfRelationAllowed indicates that no type information is defined for + // this relation. + UnknownIfRelationAllowed AllowedDirectRelation = iota + + // DirectRelationValid indicates that the specified subject relation is valid as + // part of a *direct* tuple on the relation. + DirectRelationValid + + // DirectRelationNotValid indicates that the specified subject relation is not + // valid as part of a *direct* tuple on the relation. + DirectRelationNotValid +) + +// AllowedPublicSubject indicates whether a public subject of a particular kind is allowed on the right side of another relation. +type AllowedPublicSubject int + +const ( + // UnknownIfPublicAllowed indicates that no type information is defined for + // this relation. + UnknownIfPublicAllowed AllowedPublicSubject = iota + + // PublicSubjectAllowed indicates that the specified subject wildcard is valid as + // part of a *direct* tuple on the relation. + PublicSubjectAllowed + + // PublicSubjectNotAllowed indicates that the specified subject wildcard is not + // valid as part of a *direct* tuple on the relation. + PublicSubjectNotAllowed +) + +// AllowedRelationOption indicates whether an allowed relation of a particular kind is allowed on the right side of another relation. +type AllowedRelationOption int + +const ( + // UnknownIfAllowed indicates that no type information is defined for + // this relation. + UnknownIfAllowed AllowedRelationOption = iota + + // AllowedRelationValid indicates that the specified subject relation is valid. + AllowedRelationValid + + // AllowedRelationNotValid indicates that the specified subject relation is not valid. + AllowedRelationNotValid +) + +// AllowedDefinitionOption indicates whether an allowed definition of a particular kind is allowed on the right side of another relation. +type AllowedDefinitionOption int + +const ( + // UnknownIfAllowedDefinition indicates that no type information is defined for + // this relation. + UnknownIfAllowedDefinition AllowedDefinitionOption = iota + + // AllowedDefinitionValid indicates that the specified subject definition is valid. + AllowedDefinitionValid + + // AllowedDefinitionNotValid indicates that the specified subject definition is not valid. + AllowedDefinitionNotValid +) + +// NewDefinition returns a new type definition for the given definition proto. +func NewDefinition(ts *TypeSystem, nsDef *core.NamespaceDefinition) (*Definition, error) { + relationMap := make(map[string]*core.Relation, len(nsDef.GetRelation())) + for _, relation := range nsDef.GetRelation() { + _, existing := relationMap[relation.Name] + if existing { + return nil, NewTypeWithSourceError( + NewDuplicateRelationError(nsDef.Name, relation.Name), + relation, + relation.Name, + ) + } + + relationMap[relation.Name] = relation + } + + return &Definition{ + ts: ts, + nsDef: nsDef, + relationMap: relationMap, + }, nil +} + +// Definition represents typing information found in a definition. +// It also provides better ergonomic accessors to the defintion's type information. +type Definition struct { + ts *TypeSystem + nsDef *core.NamespaceDefinition + relationMap map[string]*core.Relation +} + +// Namespace is the NamespaceDefinition for which the type system was constructed. +func (def *Definition) Namespace() *core.NamespaceDefinition { + return def.nsDef +} + +// TypeSystem returns the typesystem for this definition +func (def *Definition) TypeSystem() *TypeSystem { + return def.ts +} + +// HasTypeInformation returns true if the relation with the given name exists and has type +// information defined. +func (def *Definition) HasTypeInformation(relationName string) bool { + rel, ok := def.relationMap[relationName] + return ok && rel.GetTypeInformation() != nil +} + +// HasRelation returns true if the definition has the given relation defined. +func (def *Definition) HasRelation(relationName string) bool { + _, ok := def.relationMap[relationName] + return ok +} + +// GetRelation returns the relation that's defined with the give name in the type system or returns false. +func (def *Definition) GetRelation(relationName string) (*core.Relation, bool) { + rel, ok := def.relationMap[relationName] + return rel, ok +} + +// IsPermission returns true if the definition has the given relation defined and it is +// a permission. +func (def *Definition) IsPermission(relationName string) bool { + found, ok := def.relationMap[relationName] + if !ok { + return false + } + + return nspkg.GetRelationKind(found) == iv1.RelationMetadata_PERMISSION +} + +// GetAllowedDirectNamespaceSubjectRelations returns the subject relations for the target definition, if it is defined as appearing +// somewhere on the right side of a relation (except wildcards). Returns nil if there is no type information or it is not allowed. +func (def *Definition) GetAllowedDirectNamespaceSubjectRelations(sourceRelationName string, targetNamespaceName string) (*mapz.Set[string], error) { + found, ok := def.relationMap[sourceRelationName] + if !ok { + return nil, asTypeError(NewRelationNotFoundErr(def.nsDef.Name, sourceRelationName)) + } + + typeInfo := found.GetTypeInformation() + if typeInfo == nil { + return nil, nil + } + + allowedRelations := typeInfo.GetAllowedDirectRelations() + allowedSubjectRelations := mapz.NewSet[string]() + for _, allowedRelation := range allowedRelations { + if allowedRelation.GetNamespace() == targetNamespaceName && allowedRelation.GetPublicWildcard() == nil { + allowedSubjectRelations.Add(allowedRelation.GetRelation()) + } + } + + return allowedSubjectRelations, nil +} + +// IsAllowedDirectNamespace returns whether the target definition is defined as appearing somewhere on the +// right side of a relation (except public). +func (def *Definition) IsAllowedDirectNamespace(sourceRelationName string, targetNamespaceName string) (AllowedDefinitionOption, error) { + found, ok := def.relationMap[sourceRelationName] + if !ok { + return UnknownIfAllowedDefinition, asTypeError(NewRelationNotFoundErr(def.nsDef.Name, sourceRelationName)) + } + + typeInfo := found.GetTypeInformation() + if typeInfo == nil { + return UnknownIfAllowedDefinition, nil + } + + allowedRelations := typeInfo.GetAllowedDirectRelations() + for _, allowedRelation := range allowedRelations { + if allowedRelation.GetNamespace() == targetNamespaceName && allowedRelation.GetPublicWildcard() == nil { + return AllowedDefinitionValid, nil + } + } + + return AllowedDefinitionNotValid, nil +} + +// IsAllowedPublicNamespace returns whether the target definition is defined as public on the source relation. +func (def *Definition) IsAllowedPublicNamespace(sourceRelationName string, targetNamespaceName string) (AllowedPublicSubject, error) { + found, ok := def.relationMap[sourceRelationName] + if !ok { + return UnknownIfPublicAllowed, asTypeError(NewRelationNotFoundErr(def.nsDef.Name, sourceRelationName)) + } + + typeInfo := found.GetTypeInformation() + if typeInfo == nil { + return UnknownIfPublicAllowed, nil + } + + allowedRelations := typeInfo.GetAllowedDirectRelations() + for _, allowedRelation := range allowedRelations { + if allowedRelation.GetNamespace() == targetNamespaceName && allowedRelation.GetPublicWildcard() != nil { + return PublicSubjectAllowed, nil + } + } + + return PublicSubjectNotAllowed, nil +} + +// IsAllowedDirectRelation returns whether the subject relation is allowed to appear on the right +// hand side of a tuple placed in the source relation with the given name. +func (def *Definition) IsAllowedDirectRelation(sourceRelationName string, targetNamespaceName string, targetRelationName string) (AllowedDirectRelation, error) { + found, ok := def.relationMap[sourceRelationName] + if !ok { + return UnknownIfRelationAllowed, asTypeError(NewRelationNotFoundErr(def.nsDef.Name, sourceRelationName)) + } + + typeInfo := found.GetTypeInformation() + if typeInfo == nil { + return UnknownIfRelationAllowed, nil + } + + allowedRelations := typeInfo.GetAllowedDirectRelations() + for _, allowedRelation := range allowedRelations { + if allowedRelation.GetNamespace() == targetNamespaceName && allowedRelation.GetRelation() == targetRelationName { + return DirectRelationValid, nil + } + } + + return DirectRelationNotValid, nil +} + +// HasAllowedRelation returns whether the source relation has the given allowed relation. +func (def *Definition) HasAllowedRelation(sourceRelationName string, toCheck *core.AllowedRelation) (AllowedRelationOption, error) { + found, ok := def.relationMap[sourceRelationName] + if !ok { + return UnknownIfAllowed, asTypeError(NewRelationNotFoundErr(def.nsDef.Name, sourceRelationName)) + } + + typeInfo := found.GetTypeInformation() + if typeInfo == nil { + return UnknownIfAllowed, nil + } + + allowedRelations := typeInfo.GetAllowedDirectRelations() + for _, allowedRelation := range allowedRelations { + if SourceForAllowedRelation(allowedRelation) == SourceForAllowedRelation(toCheck) { + return AllowedRelationValid, nil + } + } + + return AllowedRelationNotValid, nil +} + +// AllowedDirectRelationsAndWildcards returns the allowed subject relations for a source relation. +// Note that this function will return wildcards. +func (def *Definition) AllowedDirectRelationsAndWildcards(sourceRelationName string) ([]*core.AllowedRelation, error) { + found, ok := def.relationMap[sourceRelationName] + if !ok { + return []*core.AllowedRelation{}, asTypeError(NewRelationNotFoundErr(def.nsDef.Name, sourceRelationName)) + } + + typeInfo := found.GetTypeInformation() + if typeInfo == nil { + return []*core.AllowedRelation{}, nil + } + + return typeInfo.GetAllowedDirectRelations(), nil +} + +// AllowedSubjectRelations returns the allowed subject relations for a source relation. Note that this function will *not* +// return wildcards, and returns without the marked caveats and expiration. +func (def *Definition) AllowedSubjectRelations(sourceRelationName string) ([]*core.RelationReference, error) { + allowedDirect, err := def.AllowedDirectRelationsAndWildcards(sourceRelationName) + if err != nil { + return []*core.RelationReference{}, asTypeError(err) + } + + filtered := make([]*core.RelationReference, 0, len(allowedDirect)) + for _, allowed := range allowedDirect { + if allowed.GetPublicWildcard() != nil { + continue + } + + if allowed.GetRelation() == "" { + return nil, spiceerrors.MustBugf("got an empty relation for a non-wildcard type definition under namespace") + } + + filtered = append(filtered, &core.RelationReference{ + Namespace: allowed.GetNamespace(), + Relation: allowed.GetRelation(), + }) + } + return filtered, nil +} + +// WildcardTypeReference represents a relation that references a wildcard type. +type WildcardTypeReference struct { + // ReferencingRelation is the relation referencing the wildcard type. + ReferencingRelation *core.RelationReference + + // WildcardType is the wildcard type referenced. + WildcardType *core.AllowedRelation +} + +// SourceForAllowedRelation returns the source code representation of an allowed relation. +func SourceForAllowedRelation(allowedRelation *core.AllowedRelation) string { + caveatAndTraitsStr := "" + + hasCaveat := allowedRelation.GetRequiredCaveat() != nil + hasExpirationTrait := allowedRelation.GetRequiredExpiration() != nil + hasTraits := hasCaveat || hasExpirationTrait + + if hasTraits { + caveatAndTraitsStr = " with " + if hasCaveat { + caveatAndTraitsStr += allowedRelation.RequiredCaveat.CaveatName + } + + if hasCaveat && hasExpirationTrait { + caveatAndTraitsStr += " and " + } + + if hasExpirationTrait { + caveatAndTraitsStr += "expiration" + } + } + + if allowedRelation.GetPublicWildcard() != nil { + return tuple.JoinObjectRef(allowedRelation.Namespace, "*") + caveatAndTraitsStr + } + + if rel := allowedRelation.GetRelation(); rel != tuple.Ellipsis { + return tuple.JoinRelRef(allowedRelation.Namespace, rel) + caveatAndTraitsStr + } + + return allowedRelation.Namespace + caveatAndTraitsStr +} + +// RelationDoesNotAllowCaveatsOrTraitsForSubject returns true if and only if it can be conclusively determined that +// the given subject type does not accept any caveats or traits on the given relation. If the relation does not have type information, +// returns an error. +func (def *Definition) RelationDoesNotAllowCaveatsOrTraitsForSubject(relationName string, subjectTypeName string) (bool, error) { + relation, ok := def.relationMap[relationName] + if !ok { + return false, NewRelationNotFoundErr(def.nsDef.Name, relationName) + } + + typeInfo := relation.GetTypeInformation() + if typeInfo == nil { + return false, NewTypeWithSourceError( + fmt.Errorf("relation `%s` does not have type information", relationName), + relation, relationName, + ) + } + + foundSubjectType := false + for _, allowedRelation := range typeInfo.GetAllowedDirectRelations() { + if allowedRelation.GetNamespace() == subjectTypeName { + foundSubjectType = true + if allowedRelation.GetRequiredCaveat() != nil && allowedRelation.GetRequiredCaveat().CaveatName != "" { + return false, nil + } + if allowedRelation.GetRequiredExpiration() != nil { + return false, nil + } + } + } + + if !foundSubjectType { + return false, NewTypeWithSourceError( + fmt.Errorf("relation `%s` does not allow subject type `%s`", relationName, subjectTypeName), + relation, relationName, + ) + } + + return true, nil +} |
