diff options
Diffstat (limited to 'vendor/github.com/authzed/spicedb/internal/services/v1/reflectionapi.go')
| -rw-r--r-- | vendor/github.com/authzed/spicedb/internal/services/v1/reflectionapi.go | 720 |
1 files changed, 720 insertions, 0 deletions
diff --git a/vendor/github.com/authzed/spicedb/internal/services/v1/reflectionapi.go b/vendor/github.com/authzed/spicedb/internal/services/v1/reflectionapi.go new file mode 100644 index 0000000..723a8d3 --- /dev/null +++ b/vendor/github.com/authzed/spicedb/internal/services/v1/reflectionapi.go @@ -0,0 +1,720 @@ +package v1 + +import ( + "sort" + "strings" + + v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" + "golang.org/x/exp/maps" + + "github.com/authzed/spicedb/pkg/caveats" + caveattypes "github.com/authzed/spicedb/pkg/caveats/types" + "github.com/authzed/spicedb/pkg/datastore" + "github.com/authzed/spicedb/pkg/diff" + caveatdiff "github.com/authzed/spicedb/pkg/diff/caveats" + nsdiff "github.com/authzed/spicedb/pkg/diff/namespace" + "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" + "github.com/authzed/spicedb/pkg/zedtoken" +) + +type schemaFilters struct { + filters []*v1.ReflectionSchemaFilter +} + +func newSchemaFilters(filters []*v1.ReflectionSchemaFilter) (*schemaFilters, error) { + for _, filter := range filters { + if filter.OptionalDefinitionNameFilter != "" { + if filter.OptionalCaveatNameFilter != "" { + return nil, NewInvalidFilterErr("cannot filter by both definition and caveat name", filter.String()) + } + } + + if filter.OptionalRelationNameFilter != "" { + if filter.OptionalDefinitionNameFilter == "" { + return nil, NewInvalidFilterErr("relation name match requires definition name match", filter.String()) + } + + if filter.OptionalPermissionNameFilter != "" { + return nil, NewInvalidFilterErr("cannot filter by both relation and permission name", filter.String()) + } + } + + if filter.OptionalPermissionNameFilter != "" { + if filter.OptionalDefinitionNameFilter == "" { + return nil, NewInvalidFilterErr("permission name match requires definition name match", filter.String()) + } + } + } + + return &schemaFilters{filters: filters}, nil +} + +func (sf *schemaFilters) HasNamespaces() bool { + if len(sf.filters) == 0 { + return true + } + + for _, filter := range sf.filters { + if filter.OptionalDefinitionNameFilter != "" { + return true + } + } + + return false +} + +func (sf *schemaFilters) HasCaveats() bool { + if len(sf.filters) == 0 { + return true + } + + for _, filter := range sf.filters { + if filter.OptionalCaveatNameFilter != "" { + return true + } + } + + return false +} + +func (sf *schemaFilters) HasNamespace(namespaceName string) bool { + if len(sf.filters) == 0 { + return true + } + + hasDefinitionFilter := false + for _, filter := range sf.filters { + if filter.OptionalDefinitionNameFilter == "" { + continue + } + + hasDefinitionFilter = true + isMatch := strings.HasPrefix(namespaceName, filter.OptionalDefinitionNameFilter) + if isMatch { + return true + } + } + + return !hasDefinitionFilter +} + +func (sf *schemaFilters) HasCaveat(caveatName string) bool { + if len(sf.filters) == 0 { + return true + } + + hasCaveatFilter := false + for _, filter := range sf.filters { + if filter.OptionalCaveatNameFilter == "" { + continue + } + + hasCaveatFilter = true + isMatch := strings.HasPrefix(caveatName, filter.OptionalCaveatNameFilter) + if isMatch { + return true + } + } + + return !hasCaveatFilter +} + +func (sf *schemaFilters) HasRelation(namespaceName, relationName string) bool { + if len(sf.filters) == 0 { + return true + } + + hasRelationFilter := false + for _, filter := range sf.filters { + if filter.OptionalRelationNameFilter == "" { + continue + } + + hasRelationFilter = true + isMatch := strings.HasPrefix(relationName, filter.OptionalRelationNameFilter) + if !isMatch { + continue + } + + isMatch = strings.HasPrefix(namespaceName, filter.OptionalDefinitionNameFilter) + if isMatch { + return true + } + } + + return !hasRelationFilter +} + +func (sf *schemaFilters) HasPermission(namespaceName, permissionName string) bool { + if len(sf.filters) == 0 { + return true + } + + hasPermissionFilter := false + for _, filter := range sf.filters { + if filter.OptionalPermissionNameFilter == "" { + continue + } + + hasPermissionFilter = true + isMatch := strings.HasPrefix(permissionName, filter.OptionalPermissionNameFilter) + if !isMatch { + continue + } + + isMatch = strings.HasPrefix(namespaceName, filter.OptionalDefinitionNameFilter) + if isMatch { + return true + } + } + + return !hasPermissionFilter +} + +// convertDiff converts a schema diff into an API response. +func convertDiff( + diff *diff.SchemaDiff, + existingSchema *diff.DiffableSchema, + comparisonSchema *diff.DiffableSchema, + atRevision datastore.Revision, + caveatTypeSet *caveattypes.TypeSet, +) (*v1.DiffSchemaResponse, error) { + size := len(diff.AddedNamespaces) + len(diff.RemovedNamespaces) + len(diff.AddedCaveats) + len(diff.RemovedCaveats) + len(diff.ChangedNamespaces) + len(diff.ChangedCaveats) + diffs := make([]*v1.ReflectionSchemaDiff, 0, size) + + // Add/remove namespaces. + for _, ns := range diff.AddedNamespaces { + nsDef, err := namespaceAPIReprForName(ns, comparisonSchema) + if err != nil { + return nil, err + } + + diffs = append(diffs, &v1.ReflectionSchemaDiff{ + Diff: &v1.ReflectionSchemaDiff_DefinitionAdded{ + DefinitionAdded: nsDef, + }, + }) + } + + for _, ns := range diff.RemovedNamespaces { + nsDef, err := namespaceAPIReprForName(ns, existingSchema) + if err != nil { + return nil, err + } + + diffs = append(diffs, &v1.ReflectionSchemaDiff{ + Diff: &v1.ReflectionSchemaDiff_DefinitionRemoved{ + DefinitionRemoved: nsDef, + }, + }) + } + + // Add/remove caveats. + for _, caveat := range diff.AddedCaveats { + caveatDef, err := caveatAPIReprForName(caveat, comparisonSchema, caveatTypeSet) + if err != nil { + return nil, err + } + + diffs = append(diffs, &v1.ReflectionSchemaDiff{ + Diff: &v1.ReflectionSchemaDiff_CaveatAdded{ + CaveatAdded: caveatDef, + }, + }) + } + + for _, caveat := range diff.RemovedCaveats { + caveatDef, err := caveatAPIReprForName(caveat, existingSchema, caveatTypeSet) + if err != nil { + return nil, err + } + + diffs = append(diffs, &v1.ReflectionSchemaDiff{ + Diff: &v1.ReflectionSchemaDiff_CaveatRemoved{ + CaveatRemoved: caveatDef, + }, + }) + } + + // Changed namespaces. + for nsName, nsDiff := range diff.ChangedNamespaces { + for _, delta := range nsDiff.Deltas() { + switch delta.Type { + case nsdiff.AddedPermission: + permission, ok := comparisonSchema.GetRelation(nsName, delta.RelationName) + if !ok { + return nil, spiceerrors.MustBugf("permission %q not found in namespace %q", delta.RelationName, nsName) + } + + perm, err := permissionAPIRepr(permission, nsName, nil) + if err != nil { + return nil, err + } + + diffs = append(diffs, &v1.ReflectionSchemaDiff{ + Diff: &v1.ReflectionSchemaDiff_PermissionAdded{ + PermissionAdded: perm, + }, + }) + + case nsdiff.AddedRelation: + relation, ok := comparisonSchema.GetRelation(nsName, delta.RelationName) + if !ok { + return nil, spiceerrors.MustBugf("relation %q not found in namespace %q", delta.RelationName, nsName) + } + + rel, err := relationAPIRepr(relation, nsName, nil) + if err != nil { + return nil, err + } + + diffs = append(diffs, &v1.ReflectionSchemaDiff{ + Diff: &v1.ReflectionSchemaDiff_RelationAdded{ + RelationAdded: rel, + }, + }) + + case nsdiff.ChangedPermissionComment: + permission, ok := comparisonSchema.GetRelation(nsName, delta.RelationName) + if !ok { + return nil, spiceerrors.MustBugf("permission %q not found in namespace %q", delta.RelationName, nsName) + } + + perm, err := permissionAPIRepr(permission, nsName, nil) + if err != nil { + return nil, err + } + + diffs = append(diffs, &v1.ReflectionSchemaDiff{ + Diff: &v1.ReflectionSchemaDiff_PermissionDocCommentChanged{ + PermissionDocCommentChanged: perm, + }, + }) + + case nsdiff.ChangedPermissionImpl: + permission, ok := comparisonSchema.GetRelation(nsName, delta.RelationName) + if !ok { + return nil, spiceerrors.MustBugf("permission %q not found in namespace %q", delta.RelationName, nsName) + } + + perm, err := permissionAPIRepr(permission, nsName, nil) + if err != nil { + return nil, err + } + + diffs = append(diffs, &v1.ReflectionSchemaDiff{ + Diff: &v1.ReflectionSchemaDiff_PermissionExprChanged{ + PermissionExprChanged: perm, + }, + }) + + case nsdiff.ChangedRelationComment: + relation, ok := comparisonSchema.GetRelation(nsName, delta.RelationName) + if !ok { + return nil, spiceerrors.MustBugf("relation %q not found in namespace %q", delta.RelationName, nsName) + } + + rel, err := relationAPIRepr(relation, nsName, nil) + if err != nil { + return nil, err + } + + diffs = append(diffs, &v1.ReflectionSchemaDiff{ + Diff: &v1.ReflectionSchemaDiff_RelationDocCommentChanged{ + RelationDocCommentChanged: rel, + }, + }) + + case nsdiff.LegacyChangedRelationImpl: + return nil, spiceerrors.MustBugf("legacy relation implementation changes are not supported") + + case nsdiff.NamespaceCommentsChanged: + def, err := namespaceAPIReprForName(nsName, comparisonSchema) + if err != nil { + return nil, err + } + + diffs = append(diffs, &v1.ReflectionSchemaDiff{ + Diff: &v1.ReflectionSchemaDiff_DefinitionDocCommentChanged{ + DefinitionDocCommentChanged: def, + }, + }) + + case nsdiff.RelationAllowedTypeRemoved: + relation, ok := comparisonSchema.GetRelation(nsName, delta.RelationName) + if !ok { + return nil, spiceerrors.MustBugf("relation %q not found in namespace %q", delta.RelationName, nsName) + } + + rel, err := relationAPIRepr(relation, nsName, nil) + if err != nil { + return nil, err + } + + diffs = append(diffs, &v1.ReflectionSchemaDiff{ + Diff: &v1.ReflectionSchemaDiff_RelationSubjectTypeRemoved{ + RelationSubjectTypeRemoved: &v1.ReflectionRelationSubjectTypeChange{ + Relation: rel, + ChangedSubjectType: typeAPIRepr(delta.AllowedType), + }, + }, + }) + + case nsdiff.RelationAllowedTypeAdded: + relation, ok := comparisonSchema.GetRelation(nsName, delta.RelationName) + if !ok { + return nil, spiceerrors.MustBugf("relation %q not found in namespace %q", delta.RelationName, nsName) + } + + rel, err := relationAPIRepr(relation, nsName, nil) + if err != nil { + return nil, err + } + + diffs = append(diffs, &v1.ReflectionSchemaDiff{ + Diff: &v1.ReflectionSchemaDiff_RelationSubjectTypeAdded{ + RelationSubjectTypeAdded: &v1.ReflectionRelationSubjectTypeChange{ + Relation: rel, + ChangedSubjectType: typeAPIRepr(delta.AllowedType), + }, + }, + }) + + case nsdiff.RemovedPermission: + permission, ok := existingSchema.GetRelation(nsName, delta.RelationName) + if !ok { + return nil, spiceerrors.MustBugf("relation %q not found in namespace %q", delta.RelationName, nsName) + } + + perm, err := permissionAPIRepr(permission, nsName, nil) + if err != nil { + return nil, err + } + + diffs = append(diffs, &v1.ReflectionSchemaDiff{ + Diff: &v1.ReflectionSchemaDiff_PermissionRemoved{ + PermissionRemoved: perm, + }, + }) + + case nsdiff.RemovedRelation: + relation, ok := existingSchema.GetRelation(nsName, delta.RelationName) + if !ok { + return nil, spiceerrors.MustBugf("relation %q not found in namespace %q", delta.RelationName, nsName) + } + + rel, err := relationAPIRepr(relation, nsName, nil) + if err != nil { + return nil, err + } + + diffs = append(diffs, &v1.ReflectionSchemaDiff{ + Diff: &v1.ReflectionSchemaDiff_RelationRemoved{ + RelationRemoved: rel, + }, + }) + + case nsdiff.NamespaceAdded: + return nil, spiceerrors.MustBugf("should be handled above") + + case nsdiff.NamespaceRemoved: + return nil, spiceerrors.MustBugf("should be handled above") + + default: + return nil, spiceerrors.MustBugf("unexpected delta type %v", delta.Type) + } + } + } + + // Changed caveats. + for caveatName, caveatDiff := range diff.ChangedCaveats { + for _, delta := range caveatDiff.Deltas() { + switch delta.Type { + case caveatdiff.CaveatCommentsChanged: + caveat, err := caveatAPIReprForName(caveatName, comparisonSchema, caveatTypeSet) + if err != nil { + return nil, err + } + + diffs = append(diffs, &v1.ReflectionSchemaDiff{ + Diff: &v1.ReflectionSchemaDiff_CaveatDocCommentChanged{ + CaveatDocCommentChanged: caveat, + }, + }) + + case caveatdiff.AddedParameter: + paramDef, err := caveatAPIParamRepr(delta.ParameterName, caveatName, comparisonSchema, caveatTypeSet) + if err != nil { + return nil, err + } + + diffs = append(diffs, &v1.ReflectionSchemaDiff{ + Diff: &v1.ReflectionSchemaDiff_CaveatParameterAdded{ + CaveatParameterAdded: paramDef, + }, + }) + + case caveatdiff.RemovedParameter: + paramDef, err := caveatAPIParamRepr(delta.ParameterName, caveatName, existingSchema, caveatTypeSet) + if err != nil { + return nil, err + } + + diffs = append(diffs, &v1.ReflectionSchemaDiff{ + Diff: &v1.ReflectionSchemaDiff_CaveatParameterRemoved{ + CaveatParameterRemoved: paramDef, + }, + }) + + case caveatdiff.ParameterTypeChanged: + previousParamDef, err := caveatAPIParamRepr(delta.ParameterName, caveatName, existingSchema, caveatTypeSet) + if err != nil { + return nil, err + } + + paramDef, err := caveatAPIParamRepr(delta.ParameterName, caveatName, comparisonSchema, caveatTypeSet) + if err != nil { + return nil, err + } + + diffs = append(diffs, &v1.ReflectionSchemaDiff{ + Diff: &v1.ReflectionSchemaDiff_CaveatParameterTypeChanged{ + CaveatParameterTypeChanged: &v1.ReflectionCaveatParameterTypeChange{ + Parameter: paramDef, + PreviousType: previousParamDef.Type, + }, + }, + }) + + case caveatdiff.CaveatExpressionChanged: + caveat, err := caveatAPIReprForName(caveatName, comparisonSchema, caveatTypeSet) + if err != nil { + return nil, err + } + + diffs = append(diffs, &v1.ReflectionSchemaDiff{ + Diff: &v1.ReflectionSchemaDiff_CaveatExprChanged{ + CaveatExprChanged: caveat, + }, + }) + + case caveatdiff.CaveatAdded: + return nil, spiceerrors.MustBugf("should be handled above") + + case caveatdiff.CaveatRemoved: + return nil, spiceerrors.MustBugf("should be handled above") + + default: + return nil, spiceerrors.MustBugf("unexpected delta type %v", delta.Type) + } + } + } + + return &v1.DiffSchemaResponse{ + Diffs: diffs, + ReadAt: zedtoken.MustNewFromRevision(atRevision), + }, nil +} + +// namespaceAPIReprForName builds an API representation of a namespace. +func namespaceAPIReprForName(namespaceName string, schema *diff.DiffableSchema) (*v1.ReflectionDefinition, error) { + nsDef, ok := schema.GetNamespace(namespaceName) + if !ok { + return nil, spiceerrors.MustBugf("namespace %q not found in schema", namespaceName) + } + + return namespaceAPIRepr(nsDef, nil) +} + +func namespaceAPIRepr(nsDef *core.NamespaceDefinition, schemaFilters *schemaFilters) (*v1.ReflectionDefinition, error) { + if schemaFilters != nil && !schemaFilters.HasNamespace(nsDef.Name) { + return nil, nil + } + + relations := make([]*v1.ReflectionRelation, 0, len(nsDef.Relation)) + permissions := make([]*v1.ReflectionPermission, 0, len(nsDef.Relation)) + + for _, rel := range nsDef.Relation { + if namespace.GetRelationKind(rel) == iv1.RelationMetadata_PERMISSION { + permission, err := permissionAPIRepr(rel, nsDef.Name, schemaFilters) + if err != nil { + return nil, err + } + + if permission != nil { + permissions = append(permissions, permission) + } + continue + } + + relation, err := relationAPIRepr(rel, nsDef.Name, schemaFilters) + if err != nil { + return nil, err + } + + if relation != nil { + relations = append(relations, relation) + } + } + + comments := namespace.GetComments(nsDef.Metadata) + return &v1.ReflectionDefinition{ + Name: nsDef.Name, + Comment: strings.Join(comments, "\n"), + Relations: relations, + Permissions: permissions, + }, nil +} + +// permissionAPIRepr builds an API representation of a permission. +func permissionAPIRepr(relation *core.Relation, parentDefName string, schemaFilters *schemaFilters) (*v1.ReflectionPermission, error) { + if schemaFilters != nil && !schemaFilters.HasPermission(parentDefName, relation.Name) { + return nil, nil + } + + comments := namespace.GetComments(relation.Metadata) + return &v1.ReflectionPermission{ + Name: relation.Name, + Comment: strings.Join(comments, "\n"), + ParentDefinitionName: parentDefName, + }, nil +} + +// relationAPIRepresentation builds an API representation of a relation. +func relationAPIRepr(relation *core.Relation, parentDefName string, schemaFilters *schemaFilters) (*v1.ReflectionRelation, error) { + if schemaFilters != nil && !schemaFilters.HasRelation(parentDefName, relation.Name) { + return nil, nil + } + + comments := namespace.GetComments(relation.Metadata) + + var subjectTypes []*v1.ReflectionTypeReference + if relation.TypeInformation != nil { + subjectTypes = make([]*v1.ReflectionTypeReference, 0, len(relation.TypeInformation.AllowedDirectRelations)) + for _, subjectType := range relation.TypeInformation.AllowedDirectRelations { + typeref := typeAPIRepr(subjectType) + subjectTypes = append(subjectTypes, typeref) + } + } + + return &v1.ReflectionRelation{ + Name: relation.Name, + Comment: strings.Join(comments, "\n"), + ParentDefinitionName: parentDefName, + SubjectTypes: subjectTypes, + }, nil +} + +// typeAPIRepr builds an API representation of a type. +func typeAPIRepr(subjectType *core.AllowedRelation) *v1.ReflectionTypeReference { + typeref := &v1.ReflectionTypeReference{ + SubjectDefinitionName: subjectType.Namespace, + Typeref: &v1.ReflectionTypeReference_IsTerminalSubject{}, + } + + if subjectType.GetRelation() != tuple.Ellipsis && subjectType.GetRelation() != "" { + typeref.Typeref = &v1.ReflectionTypeReference_OptionalRelationName{ + OptionalRelationName: subjectType.GetRelation(), + } + } else if subjectType.GetPublicWildcard() != nil { + typeref.Typeref = &v1.ReflectionTypeReference_IsPublicWildcard{ + IsPublicWildcard: true, + } + } + + if subjectType.GetRequiredCaveat() != nil { + typeref.OptionalCaveatName = subjectType.GetRequiredCaveat().CaveatName + } + + return typeref +} + +// caveatAPIReprForName builds an API representation of a caveat. +func caveatAPIReprForName(caveatName string, schema *diff.DiffableSchema, caveatTypeSet *caveattypes.TypeSet) (*v1.ReflectionCaveat, error) { + caveatDef, ok := schema.GetCaveat(caveatName) + if !ok { + return nil, spiceerrors.MustBugf("caveat %q not found in schema", caveatName) + } + + return caveatAPIRepr(caveatDef, nil, caveatTypeSet) +} + +// caveatAPIRepr builds an API representation of a caveat. +func caveatAPIRepr(caveatDef *core.CaveatDefinition, schemaFilters *schemaFilters, caveatTypeSet *caveattypes.TypeSet) (*v1.ReflectionCaveat, error) { + if schemaFilters != nil && !schemaFilters.HasCaveat(caveatDef.Name) { + return nil, nil + } + + parameters := make([]*v1.ReflectionCaveatParameter, 0, len(caveatDef.ParameterTypes)) + paramNames := maps.Keys(caveatDef.ParameterTypes) + sort.Strings(paramNames) + + for _, paramName := range paramNames { + paramType, ok := caveatDef.ParameterTypes[paramName] + if !ok { + return nil, spiceerrors.MustBugf("parameter %q not found in caveat %q", paramName, caveatDef.Name) + } + + decoded, err := caveattypes.DecodeParameterType(caveatTypeSet, paramType) + if err != nil { + return nil, spiceerrors.MustBugf("invalid parameter type on caveat: %v", err) + } + + parameters = append(parameters, &v1.ReflectionCaveatParameter{ + Name: paramName, + Type: decoded.String(), + ParentCaveatName: caveatDef.Name, + }) + } + + parameterTypes, err := caveattypes.DecodeParameterTypes(caveatTypeSet, caveatDef.ParameterTypes) + if err != nil { + return nil, spiceerrors.MustBugf("invalid caveat parameters: %v", err) + } + + deserializedReflectionression, err := caveats.DeserializeCaveatWithTypeSet(caveatTypeSet, caveatDef.SerializedExpression, parameterTypes) + if err != nil { + return nil, spiceerrors.MustBugf("invalid caveat expression bytes: %v", err) + } + + exprString, err := deserializedReflectionression.ExprString() + if err != nil { + return nil, spiceerrors.MustBugf("invalid caveat expression: %v", err) + } + + comments := namespace.GetComments(caveatDef.Metadata) + return &v1.ReflectionCaveat{ + Name: caveatDef.Name, + Comment: strings.Join(comments, "\n"), + Parameters: parameters, + Expression: exprString, + }, nil +} + +// caveatAPIParamRepresentation builds an API representation of a caveat parameter. +func caveatAPIParamRepr(paramName, parentCaveatName string, schema *diff.DiffableSchema, caveatTypeSet *caveattypes.TypeSet) (*v1.ReflectionCaveatParameter, error) { + caveatDef, ok := schema.GetCaveat(parentCaveatName) + if !ok { + return nil, spiceerrors.MustBugf("caveat %q not found in schema", parentCaveatName) + } + + paramType, ok := caveatDef.ParameterTypes[paramName] + if !ok { + return nil, spiceerrors.MustBugf("parameter %q not found in caveat %q", paramName, parentCaveatName) + } + + decoded, err := caveattypes.DecodeParameterType(caveatTypeSet, paramType) + if err != nil { + return nil, spiceerrors.MustBugf("invalid parameter type on caveat: %v", err) + } + + return &v1.ReflectionCaveatParameter{ + Name: paramName, + Type: decoded.String(), + ParentCaveatName: parentCaveatName, + }, nil +} |
