summaryrefslogtreecommitdiff
path: root/vendor/github.com/authzed/spicedb/pkg/validationfile/blocks/expectedrelations.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/authzed/spicedb/pkg/validationfile/blocks/expectedrelations.go')
-rw-r--r--vendor/github.com/authzed/spicedb/pkg/validationfile/blocks/expectedrelations.go264
1 files changed, 264 insertions, 0 deletions
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
+}