summaryrefslogtreecommitdiff
path: root/vendor/github.com/authzed/spicedb/pkg/validationfile/blocks
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/authzed/spicedb/pkg/validationfile/blocks')
-rw-r--r--vendor/github.com/authzed/spicedb/pkg/validationfile/blocks/assertions.go147
-rw-r--r--vendor/github.com/authzed/spicedb/pkg/validationfile/blocks/errors.go47
-rw-r--r--vendor/github.com/authzed/spicedb/pkg/validationfile/blocks/expectedrelations.go264
-rw-r--r--vendor/github.com/authzed/spicedb/pkg/validationfile/blocks/relationships.go83
-rw-r--r--vendor/github.com/authzed/spicedb/pkg/validationfile/blocks/schema.go70
5 files changed, 611 insertions, 0 deletions
diff --git a/vendor/github.com/authzed/spicedb/pkg/validationfile/blocks/assertions.go b/vendor/github.com/authzed/spicedb/pkg/validationfile/blocks/assertions.go
new file mode 100644
index 0000000..56c4e6c
--- /dev/null
+++ b/vendor/github.com/authzed/spicedb/pkg/validationfile/blocks/assertions.go
@@ -0,0 +1,147 @@
+package blocks
+
+import (
+ "encoding/json"
+ "fmt"
+ "strings"
+
+ "github.com/ccoveille/go-safecast"
+ yamlv3 "gopkg.in/yaml.v3"
+
+ "github.com/authzed/spicedb/pkg/spiceerrors"
+ "github.com/authzed/spicedb/pkg/tuple"
+)
+
+// Assertions represents assertions defined in the validation file.
+type Assertions struct {
+ // AssertTrue is the set of relationships to assert true.
+ AssertTrue []Assertion `yaml:"assertTrue"`
+
+ // AssertCaveated is the set of relationships to assert that are caveated.
+ AssertCaveated []Assertion `yaml:"assertCaveated"`
+
+ // AssertFalse is the set of relationships to assert false.
+ AssertFalse []Assertion `yaml:"assertFalse"`
+
+ // SourcePosition is the position of the assertions in the file.
+ SourcePosition spiceerrors.SourcePosition
+}
+
+// Assertion is a parsed assertion.
+type Assertion struct {
+ // RelationshipWithContextString is the string form of the assertion, including optional context.
+ // Forms:
+ // `document:firstdoc#view@user:tom`
+ // `document:seconddoc#view@user:sarah with {"some":"contexthere"}`
+ RelationshipWithContextString string
+
+ // Relationship is the parsed relationship on which the assertion is being
+ // run.
+ Relationship tuple.Relationship
+
+ // CaveatContext is the caveat context for the assertion, if any.
+ CaveatContext map[string]any
+
+ // SourcePosition is the position of the assertion in the file.
+ SourcePosition spiceerrors.SourcePosition
+}
+
+type internalAssertions struct {
+ // AssertTrue is the set of relationships to assert true.
+ AssertTrue []Assertion `yaml:"assertTrue"`
+
+ // AssertCaveated is the set of relationships to assert that are caveated.
+ AssertCaveated []Assertion `yaml:"assertCaveated"`
+
+ // AssertFalse is the set of relationships to assert false.
+ AssertFalse []Assertion `yaml:"assertFalse"`
+}
+
+// UnmarshalYAML is a custom unmarshaller.
+func (a *Assertions) UnmarshalYAML(node *yamlv3.Node) error {
+ ia := internalAssertions{}
+ if err := node.Decode(&ia); err != nil {
+ return convertYamlError(err)
+ }
+
+ a.AssertTrue = ia.AssertTrue
+ a.AssertFalse = ia.AssertFalse
+ a.AssertCaveated = ia.AssertCaveated
+ a.SourcePosition = spiceerrors.SourcePosition{LineNumber: node.Line, ColumnPosition: node.Column}
+ return nil
+}
+
+// UnmarshalYAML is a custom unmarshaller.
+func (a *Assertion) UnmarshalYAML(node *yamlv3.Node) error {
+ relationshipWithContextString := ""
+
+ if err := node.Decode(&relationshipWithContextString); err != nil {
+ return convertYamlError(err)
+ }
+
+ trimmed := strings.TrimSpace(relationshipWithContextString)
+
+ line, err := safecast.ToUint64(node.Line)
+ if err != nil {
+ return err
+ }
+ column, err := safecast.ToUint64(node.Column)
+ if err != nil {
+ return err
+ }
+
+ // Check for caveat context.
+ parts := strings.SplitN(trimmed, " with ", 2)
+ if len(parts) == 0 {
+ return spiceerrors.NewWithSourceError(
+ fmt.Errorf("error parsing assertion `%s`", trimmed),
+ trimmed,
+ line,
+ column,
+ )
+ }
+
+ relationship, err := tuple.Parse(strings.TrimSpace(parts[0]))
+ if err != nil {
+ return spiceerrors.NewWithSourceError(
+ fmt.Errorf("error parsing relationship in assertion `%s`: %w", trimmed, err),
+ trimmed,
+ line,
+ column,
+ )
+ }
+
+ a.Relationship = relationship
+
+ if len(parts) == 2 {
+ caveatContextMap := make(map[string]any, 0)
+ err := json.Unmarshal([]byte(parts[1]), &caveatContextMap)
+ if err != nil {
+ return spiceerrors.NewWithSourceError(
+ fmt.Errorf("error parsing caveat context in assertion `%s`: %w", trimmed, err),
+ trimmed,
+ line,
+ column,
+ )
+ }
+
+ a.CaveatContext = caveatContextMap
+ }
+
+ a.RelationshipWithContextString = relationshipWithContextString
+ a.SourcePosition = spiceerrors.SourcePosition{LineNumber: node.Line, ColumnPosition: node.Column}
+ return nil
+}
+
+// ParseAssertionsBlock parses the given contents as an assertions block.
+func ParseAssertionsBlock(contents []byte) (*Assertions, error) {
+ a := internalAssertions{}
+ if err := yamlv3.Unmarshal(contents, &a); err != nil {
+ return nil, convertYamlError(err)
+ }
+ return &Assertions{
+ AssertTrue: a.AssertTrue,
+ AssertCaveated: a.AssertCaveated,
+ AssertFalse: a.AssertFalse,
+ }, nil
+}
diff --git a/vendor/github.com/authzed/spicedb/pkg/validationfile/blocks/errors.go b/vendor/github.com/authzed/spicedb/pkg/validationfile/blocks/errors.go
new file mode 100644
index 0000000..b30ef7a
--- /dev/null
+++ b/vendor/github.com/authzed/spicedb/pkg/validationfile/blocks/errors.go
@@ -0,0 +1,47 @@
+package blocks
+
+import (
+ "errors"
+ "fmt"
+ "regexp"
+ "strconv"
+ "strings"
+
+ "github.com/authzed/spicedb/pkg/spiceerrors"
+)
+
+var (
+ yamlLineRegex = regexp.MustCompile(`line ([0-9]+): (.+)`)
+ yamlUnmarshalRegex = regexp.MustCompile("cannot unmarshal !!str `([^`]+)...`")
+)
+
+func convertYamlError(err error) error {
+ linePieces := yamlLineRegex.FindStringSubmatch(err.Error())
+ if len(linePieces) == 3 {
+ lineNumber, parseErr := strconv.ParseUint(linePieces[1], 10, 32)
+ if parseErr != nil {
+ lineNumber = 0
+ }
+
+ message := linePieces[2]
+ source := ""
+ unmarshalPieces := yamlUnmarshalRegex.FindStringSubmatch(message)
+ if len(unmarshalPieces) == 2 {
+ source = unmarshalPieces[1]
+ if strings.Contains(source, " ") {
+ source, _, _ = strings.Cut(source, " ")
+ }
+
+ message = fmt.Sprintf("unexpected value `%s`", source)
+ }
+
+ return spiceerrors.NewWithSourceError(
+ errors.New(message),
+ source,
+ lineNumber,
+ 0,
+ )
+ }
+
+ return err
+}
diff --git a/vendor/github.com/authzed/spicedb/pkg/validationfile/blocks/expectedrelations.go b/vendor/github.com/authzed/spicedb/pkg/validationfile/blocks/expectedrelations.go
new file mode 100644
index 0000000..44f3b88
--- /dev/null
+++ b/vendor/github.com/authzed/spicedb/pkg/validationfile/blocks/expectedrelations.go
@@ -0,0 +1,264 @@
+package blocks
+
+import (
+ "fmt"
+ "regexp"
+ "slices"
+ "strings"
+
+ yamlv3 "gopkg.in/yaml.v3"
+
+ "github.com/ccoveille/go-safecast"
+
+ "github.com/authzed/spicedb/pkg/spiceerrors"
+ "github.com/authzed/spicedb/pkg/tuple"
+)
+
+// ParsedExpectedRelations represents the expected relations defined in the validation
+// file.
+type ParsedExpectedRelations struct {
+ // ValidationMap is the parsed expected relations validation map.
+ ValidationMap ValidationMap
+
+ // SourcePosition is the position of the expected relations in the file.
+ SourcePosition spiceerrors.SourcePosition
+}
+
+// UnmarshalYAML is a custom unmarshaller.
+func (per *ParsedExpectedRelations) UnmarshalYAML(node *yamlv3.Node) error {
+ err := node.Decode(&per.ValidationMap)
+ if err != nil {
+ return convertYamlError(err)
+ }
+
+ per.SourcePosition = spiceerrors.SourcePosition{LineNumber: node.Line, ColumnPosition: node.Column}
+ return nil
+}
+
+// ValidationMap is a map from an Object Relation (as a Relationship) to the
+// validation strings containing the Subjects for that Object Relation.
+type ValidationMap map[ObjectRelation][]ExpectedSubject
+
+// ObjectRelation represents an ONR defined as a string in the key for
+// the ValidationMap.
+type ObjectRelation struct {
+ // ObjectRelationString is the string form of the object relation.
+ ObjectRelationString string
+
+ // ObjectAndRelation is the parsed object and relation.
+ ObjectAndRelation tuple.ObjectAndRelation
+
+ // SourcePosition is the position of the expected relations in the file.
+ SourcePosition spiceerrors.SourcePosition
+}
+
+// UnmarshalYAML is a custom unmarshaller.
+func (ors *ObjectRelation) UnmarshalYAML(node *yamlv3.Node) error {
+ err := node.Decode(&ors.ObjectRelationString)
+ if err != nil {
+ return convertYamlError(err)
+ }
+
+ line, err := safecast.ToUint64(node.Line)
+ if err != nil {
+ return err
+ }
+ column, err := safecast.ToUint64(node.Column)
+ if err != nil {
+ return err
+ }
+
+ parsed, err := tuple.ParseONR(ors.ObjectRelationString)
+ if err != nil {
+ return spiceerrors.NewWithSourceError(
+ fmt.Errorf("could not parse %s: %w", ors.ObjectRelationString, err),
+ ors.ObjectRelationString,
+ line,
+ column,
+ )
+ }
+
+ ors.ObjectAndRelation = parsed
+ ors.SourcePosition = spiceerrors.SourcePosition{LineNumber: node.Line, ColumnPosition: node.Column}
+ return nil
+}
+
+var (
+ vsSubjectRegex = regexp.MustCompile(`(.*?)\[(?P<user_str>.*)](.*?)`)
+ vsObjectAndRelationRegex = regexp.MustCompile(`(.*?)<(?P<onr_str>[^>]+)>(.*?)`)
+ vsSubjectWithExceptionsOrCaveatRegex = regexp.MustCompile(`^(?P<subject_onr>[^]\s]+)(?P<caveat>\[\.\.\.])?(\s+-\s+\{(?P<exceptions>[^}]+)})?$`)
+)
+
+// ExpectedSubject is a subject expected for the ObjectAndRelation.
+type ExpectedSubject struct {
+ // ValidationString holds a validation string containing a Subject and one or
+ // more Relations to the parent Object.
+ // Example: `[tenant/user:someuser#...] is <tenant/document:example#viewer>`
+ ValidationString ValidationString
+
+ // Subject is the subject expected. May be nil if not defined in the line.
+ SubjectWithExceptions *SubjectWithExceptions
+
+ // Resources are the resources under which the subject is found.
+ Resources []tuple.ObjectAndRelation
+
+ // SourcePosition is the position of the expected subject in the file.
+ SourcePosition spiceerrors.SourcePosition
+}
+
+// SubjectAndCaveat returns a subject and whether it is caveated.
+type SubjectAndCaveat struct {
+ // Subject is the subject found.
+ Subject tuple.ObjectAndRelation
+
+ // IsCaveated indicates whether the subject is caveated.
+ IsCaveated bool
+}
+
+// SubjectWithExceptions returns the subject found in a validation string, along with any exceptions.
+type SubjectWithExceptions struct {
+ // Subject is the subject found.
+ Subject SubjectAndCaveat
+
+ // Exceptions are those subjects removed from the subject, if it is a wildcard.
+ Exceptions []SubjectAndCaveat
+}
+
+// UnmarshalYAML is a custom unmarshaller.
+func (es *ExpectedSubject) UnmarshalYAML(node *yamlv3.Node) error {
+ err := node.Decode(&es.ValidationString)
+ if err != nil {
+ return convertYamlError(err)
+ }
+
+ line, err := safecast.ToUint64(node.Line)
+ if err != nil {
+ return err
+ }
+ column, err := safecast.ToUint64(node.Column)
+ if err != nil {
+ return err
+ }
+
+ subjectWithExceptions, subErr := es.ValidationString.Subject()
+ if subErr != nil {
+ return spiceerrors.NewWithSourceError(
+ subErr,
+ subErr.SourceCodeString,
+ line+subErr.LineNumber,
+ column+subErr.ColumnPosition,
+ )
+ }
+
+ onrs, onrErr := es.ValidationString.ONRS()
+ if onrErr != nil {
+ return spiceerrors.NewWithSourceError(
+ onrErr,
+ onrErr.SourceCodeString,
+ line+onrErr.LineNumber,
+ column+onrErr.ColumnPosition,
+ )
+ }
+
+ es.SubjectWithExceptions = subjectWithExceptions
+ es.SourcePosition = spiceerrors.SourcePosition{LineNumber: node.Line, ColumnPosition: node.Column}
+ es.Resources = onrs
+ return nil
+}
+
+// ValidationString holds a validation string containing a Subject and one or
+// more Relations to the parent Object.
+// Example: `[tenant/user:someuser#...] is <tenant/document:example#viewer>`
+type ValidationString string
+
+// SubjectString returns the subject contained in the ValidationString, if any.
+func (vs ValidationString) SubjectString() (string, bool) {
+ result := vsSubjectRegex.FindStringSubmatch(string(vs))
+ if len(result) != 4 {
+ return "", false
+ }
+
+ return result[2], true
+}
+
+// Subject returns the subject contained in the ValidationString, if any. If
+// none, returns nil.
+func (vs ValidationString) Subject() (*SubjectWithExceptions, *spiceerrors.WithSourceError) {
+ subjectStr, ok := vs.SubjectString()
+ if !ok {
+ return nil, nil
+ }
+
+ subjectStr = strings.TrimSpace(subjectStr)
+ groups := vsSubjectWithExceptionsOrCaveatRegex.FindStringSubmatch(subjectStr)
+ if len(groups) == 0 {
+ bracketedSubjectString := "[" + subjectStr + "]"
+ return nil, spiceerrors.NewWithSourceError(fmt.Errorf("invalid subject: `%s`", subjectStr), bracketedSubjectString, 0, 0)
+ }
+
+ subjectONRString := groups[slices.Index(vsSubjectWithExceptionsOrCaveatRegex.SubexpNames(), "subject_onr")]
+ subjectONR, err := tuple.ParseSubjectONR(subjectONRString)
+ if err != nil {
+ return nil, spiceerrors.NewWithSourceError(fmt.Errorf("invalid subject: `%s`: %w", subjectONRString, err), subjectONRString, 0, 0)
+ }
+
+ exceptionsString := strings.TrimSpace(groups[slices.Index(vsSubjectWithExceptionsOrCaveatRegex.SubexpNames(), "exceptions")])
+ var exceptions []SubjectAndCaveat
+
+ if len(exceptionsString) > 0 {
+ exceptionsStringsSlice := strings.Split(exceptionsString, ",")
+ exceptions = make([]SubjectAndCaveat, 0, len(exceptionsStringsSlice))
+ for _, exceptionString := range exceptionsStringsSlice {
+ isCaveated := false
+ if strings.HasSuffix(exceptionString, "[...]") {
+ exceptionString = strings.TrimSuffix(exceptionString, "[...]")
+ isCaveated = true
+ }
+
+ exceptionONR, err := tuple.ParseSubjectONR(strings.TrimSpace(exceptionString))
+ if err != nil {
+ return nil, spiceerrors.NewWithSourceError(fmt.Errorf("invalid subject: `%s`: %w", exceptionString, err), exceptionString, 0, 0)
+ }
+
+ exceptions = append(exceptions, SubjectAndCaveat{exceptionONR, isCaveated})
+ }
+ }
+
+ isCaveated := len(strings.TrimSpace(groups[slices.Index(vsSubjectWithExceptionsOrCaveatRegex.SubexpNames(), "caveat")])) > 0
+ return &SubjectWithExceptions{SubjectAndCaveat{subjectONR, isCaveated}, exceptions}, nil
+}
+
+// ONRStrings returns the ONRs contained in the ValidationString, if any.
+func (vs ValidationString) ONRStrings() []string {
+ results := vsObjectAndRelationRegex.FindAllStringSubmatch(string(vs), -1)
+ onrStrings := []string{}
+ for _, result := range results {
+ onrStrings = append(onrStrings, result[2])
+ }
+ return onrStrings
+}
+
+// ONRS returns the subject ONRs in the ValidationString, if any.
+func (vs ValidationString) ONRS() ([]tuple.ObjectAndRelation, *spiceerrors.WithSourceError) {
+ onrStrings := vs.ONRStrings()
+ onrs := []tuple.ObjectAndRelation{}
+ for _, onrString := range onrStrings {
+ found, err := tuple.ParseONR(onrString)
+ if err != nil {
+ return nil, spiceerrors.NewWithSourceError(fmt.Errorf("invalid resource and relation: `%s`: %w", onrString, err), onrString, 0, 0)
+ }
+
+ onrs = append(onrs, found)
+ }
+ return onrs, nil
+}
+
+// ParseExpectedRelationsBlock parses the given contents as an expected relations block.
+func ParseExpectedRelationsBlock(contents []byte) (*ParsedExpectedRelations, error) {
+ per := ParsedExpectedRelations{}
+ err := yamlv3.Unmarshal(contents, &per)
+ if err != nil {
+ return nil, convertYamlError(err)
+ }
+ return &per, nil
+}
diff --git a/vendor/github.com/authzed/spicedb/pkg/validationfile/blocks/relationships.go b/vendor/github.com/authzed/spicedb/pkg/validationfile/blocks/relationships.go
new file mode 100644
index 0000000..278c7a8
--- /dev/null
+++ b/vendor/github.com/authzed/spicedb/pkg/validationfile/blocks/relationships.go
@@ -0,0 +1,83 @@
+package blocks
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/ccoveille/go-safecast"
+ yamlv3 "gopkg.in/yaml.v3"
+
+ "github.com/authzed/spicedb/pkg/spiceerrors"
+ "github.com/authzed/spicedb/pkg/tuple"
+)
+
+// ParsedRelationships is the parsed relationships in a validationfile.
+type ParsedRelationships struct {
+ // RelationshipsString is the found string of newline-separated relationships.
+ RelationshipsString string
+
+ // SourcePosition is the position of the schema in the file.
+ SourcePosition spiceerrors.SourcePosition
+
+ // Relationships are the fully parsed relationships.
+ Relationships []tuple.Relationship
+}
+
+// UnmarshalYAML is a custom unmarshaller.
+func (pr *ParsedRelationships) UnmarshalYAML(node *yamlv3.Node) error {
+ err := node.Decode(&pr.RelationshipsString)
+ if err != nil {
+ return convertYamlError(err)
+ }
+
+ relationshipsString := pr.RelationshipsString
+ if relationshipsString == "" {
+ return nil
+ }
+
+ seenTuples := map[string]bool{}
+ lines := strings.Split(relationshipsString, "\n")
+ relationships := make([]tuple.Relationship, 0, len(lines))
+ for index, line := range lines {
+ trimmed := strings.TrimSpace(line)
+ if len(trimmed) == 0 || strings.HasPrefix(trimmed, "//") {
+ continue
+ }
+
+ // +1 for the key, and *2 for newlines in YAML
+ errorLine, err := safecast.ToUint64(node.Line + 1 + (index * 2))
+ if err != nil {
+ return err
+ }
+ column, err := safecast.ToUint64(node.Column)
+ if err != nil {
+ return err
+ }
+
+ rel, err := tuple.Parse(trimmed)
+ if err != nil {
+ return spiceerrors.NewWithSourceError(
+ fmt.Errorf("error parsing relationship `%s`: %w", trimmed, err),
+ trimmed,
+ errorLine,
+ column,
+ )
+ }
+
+ _, ok := seenTuples[tuple.StringWithoutCaveatOrExpiration(rel)]
+ if ok {
+ return spiceerrors.NewWithSourceError(
+ fmt.Errorf("found repeated relationship `%s`", trimmed),
+ trimmed,
+ errorLine,
+ column,
+ )
+ }
+ seenTuples[tuple.StringWithoutCaveatOrExpiration(rel)] = true
+ relationships = append(relationships, rel)
+ }
+
+ pr.Relationships = relationships
+ pr.SourcePosition = spiceerrors.SourcePosition{LineNumber: node.Line, ColumnPosition: node.Column}
+ return nil
+}
diff --git a/vendor/github.com/authzed/spicedb/pkg/validationfile/blocks/schema.go b/vendor/github.com/authzed/spicedb/pkg/validationfile/blocks/schema.go
new file mode 100644
index 0000000..814eebc
--- /dev/null
+++ b/vendor/github.com/authzed/spicedb/pkg/validationfile/blocks/schema.go
@@ -0,0 +1,70 @@
+package blocks
+
+import (
+ "errors"
+ "fmt"
+
+ yamlv3 "gopkg.in/yaml.v3"
+
+ "github.com/ccoveille/go-safecast"
+
+ "github.com/authzed/spicedb/pkg/schemadsl/compiler"
+ "github.com/authzed/spicedb/pkg/schemadsl/input"
+ "github.com/authzed/spicedb/pkg/spiceerrors"
+)
+
+// ParsedSchema is the parsed schema in a validationfile.
+type ParsedSchema struct {
+ // Schema is the schema found.
+ Schema string
+
+ // SourcePosition is the position of the schema in the file.
+ SourcePosition spiceerrors.SourcePosition
+
+ // CompiledSchema is the compiled schema.
+ CompiledSchema *compiler.CompiledSchema
+}
+
+// UnmarshalYAML is a custom unmarshaller.
+func (ps *ParsedSchema) UnmarshalYAML(node *yamlv3.Node) error {
+ err := node.Decode(&ps.Schema)
+ if err != nil {
+ return convertYamlError(err)
+ }
+
+ compiled, err := compiler.Compile(compiler.InputSchema{
+ Source: input.Source("schema"),
+ SchemaString: ps.Schema,
+ }, compiler.AllowUnprefixedObjectType())
+ if err != nil {
+ var errWithContext compiler.WithContextError
+ if errors.As(err, &errWithContext) {
+ line, col, lerr := errWithContext.SourceRange.Start().LineAndColumn()
+ if lerr != nil {
+ return lerr
+ }
+
+ uintLine, err := safecast.ToUint64(line)
+ if err != nil {
+ return err
+ }
+ uintCol, err := safecast.ToUint64(col)
+ if err != nil {
+ return err
+ }
+
+ return spiceerrors.NewWithSourceError(
+ fmt.Errorf("error when parsing schema: %s", errWithContext.BaseMessage),
+ errWithContext.ErrorSourceCode,
+ uintLine+1, // source line is 0-indexed
+ uintCol+1, // source col is 0-indexed
+ )
+ }
+
+ return fmt.Errorf("error when parsing schema: %w", err)
+ }
+
+ ps.CompiledSchema = compiled
+ ps.SourcePosition = spiceerrors.SourcePosition{LineNumber: node.Line, ColumnPosition: node.Column}
+ return nil
+}