diff --git a/.golangci.yaml b/.golangci.yaml index 1739429004..1bcb08577e 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,69 +1,73 @@ --- run: - timeout: "5m" + timeout: '5m' output: sort-results: true linters-settings: goimports: - local-prefixes: "github.com/authzed/spicedb" + local-prefixes: 'github.com/authzed/spicedb' rowserrcheck: packages: - - "github.com/jmoiron/sqlx" - - "github.com/jackc/pgx" + - 'github.com/jmoiron/sqlx' + - 'github.com/jackc/pgx' gosec: excludes: - - "G404" # Allow the usage of math/rand + - 'G404' # Allow the usage of math/rand linters: enable: - - "bidichk" - - "bodyclose" - - "deadcode" - - "errcheck" - - "errname" - - "errorlint" - - "gofumpt" - - "goimports" - - "goprintffuncname" - - "gosec" - - "gosimple" - - "govet" - - "importas" - - "ineffassign" - - "makezero" - - "prealloc" - - "predeclared" - - "promlinter" - - "revive" - - "rowserrcheck" - - "staticcheck" - - "structcheck" - - "stylecheck" - - "tenv" - - "typecheck" - - "unconvert" - - "unused" - - "varcheck" - - "wastedassign" - - "whitespace" + - 'bidichk' + - 'bodyclose' + - 'deadcode' + - 'errcheck' + - 'errname' + - 'errorlint' + - 'gofumpt' + - 'goimports' + - 'goprintffuncname' + - 'gosec' + - 'gosimple' + - 'govet' + - 'importas' + - 'ineffassign' + - 'makezero' + - 'prealloc' + - 'predeclared' + - 'promlinter' + - 'revive' + - 'rowserrcheck' + - 'staticcheck' + - 'structcheck' + - 'stylecheck' + - 'tenv' + - 'typecheck' + - 'unconvert' + - 'unused' + - 'varcheck' + - 'wastedassign' + - 'whitespace' issues: exclude-rules: - - text: "tx.Rollback()" + - text: 'tx.Rollback()' linters: - - "errcheck" + - 'errcheck' # NOTE: temporarily disable deprecation checks for v0. - - path: "internal/services/" + - path: 'internal/services/' linters: - - "staticcheck" - text: "SA1019" - - path: "internal/middleware/consistency/" + - 'staticcheck' + text: 'SA1019' + - path: 'internal/middleware/consistency/' linters: - - "staticcheck" - text: "SA1019" - - path: "pkg/proto/core/v1/core.pb.validate.manual.go" # Ignore manual definition of metadata map + - 'staticcheck' + text: 'SA1019' + - path: 'pkg/proto/core/v1/core.pb.validate.manual.go' # Ignore manual definition of metadata map linters: - - "stylecheck" - text: "ST1003" - - path: "pkg/proto/core/v1/core.pb.validate.manual.go" # Ignore manual definition of metadata map + - 'stylecheck' + text: 'ST1003' + - path: 'pkg/proto/core/v1/core.pb.validate.manual.go' # Ignore manual definition of metadata map linters: - - "revive" - text: "var-naming" + - 'revive' + text: 'var-naming' + # Ignore receiver errors for generic types not understood by the linter. + - linters: + - 'revive' + text: 'receiver-naming: receiver name \S+ should be consistent with previous receiver name \S+ for invalid-type' diff --git a/buf.gen.yaml b/buf.gen.yaml index e162566564..82d95a3262 100755 --- a/buf.gen.yaml +++ b/buf.gen.yaml @@ -1,25 +1,25 @@ #!/usr/bin/env -S buf generate -o pkg/proto proto/internal --template --- -version: "v1" +version: 'v1' managed: enabled: true go_package_prefix: - default: "github.com/authzed/spicedb/pkg/proto" + default: 'github.com/authzed/spicedb/pkg/proto' except: - - "buf.build/envoyproxy/protoc-gen-validate" - - "buf.build/authzed/api" - - "buf.build/googleapis/googleapis" + - 'buf.build/envoyproxy/protoc-gen-validate' + - 'buf.build/authzed/api' + - 'buf.build/googleapis/googleapis' plugins: - - name: "go" - out: "." - opt: "paths=source_relative" - - name: "go-grpc" - out: "." - opt: "paths=source_relative" - - name: "go-vtproto" - out: "." + - name: 'go' + out: '.' + opt: 'paths=source_relative' + - name: 'go-grpc' + out: '.' + opt: 'paths=source_relative' + - name: 'go-vtproto' + out: '.' # To generate pooling methods, you must add an additional `pool=fully/qualified.ProtoMessageType` - opt: "paths=source_relative,features=marshal+unmarshal+size+clone+pool" - - name: "validate" - out: "." - opt: "paths=source_relative,lang=go" + opt: 'paths=source_relative,features=marshal+unmarshal+size+clone+pool+equal' + - name: 'validate' + out: '.' + opt: 'paths=source_relative,lang=go' diff --git a/internal/dispatch/graph/lookupsubjects_test.go b/internal/dispatch/graph/lookupsubjects_test.go index 873d35539a..f8131e5a20 100644 --- a/internal/dispatch/graph/lookupsubjects_test.go +++ b/internal/dispatch/graph/lookupsubjects_test.go @@ -142,7 +142,7 @@ func TestSimpleLookupSubjects(t *testing.T) { foundSubjectIds := []string{} for _, result := range stream.Results() { for _, found := range result.FoundSubjects { - if len(found.ExcludedSubjectIds) > 0 { + if len(found.ExcludedSubjects) > 0 { continue } diff --git a/internal/membership/foundsubject.go b/internal/membership/foundsubject.go index fbb3a03d2b..2f01a53dfe 100644 --- a/internal/membership/foundsubject.go +++ b/internal/membership/foundsubject.go @@ -6,13 +6,14 @@ import ( "strings" core "github.com/authzed/spicedb/pkg/proto/core/v1" + v1 "github.com/authzed/spicedb/pkg/proto/dispatch/v1" "github.com/authzed/spicedb/pkg/tuple" ) // NewFoundSubject creates a new FoundSubject for a subject and a set of its resources. func NewFoundSubject(subject *core.ObjectAndRelation, resources ...*core.ObjectAndRelation) FoundSubject { - return FoundSubject{subject, nil, tuple.NewONRSet(resources...)} + return FoundSubject{subject, nil, nil, tuple.NewONRSet(resources...)} } // FoundSubject contains a single found subject and all the relationships in which that subject @@ -22,7 +23,10 @@ type FoundSubject struct { subject *core.ObjectAndRelation // excludedSubjects are any subjects excluded. Only should be set if subject is a wildcard. - excludedSubjectIds []string + excludedSubjects []FoundSubject + + // caveatExpression is the conditional expression on the found subject. + caveatExpression *v1.CaveatExpression // relations are the relations under which the subject lives that informed the locating // of this subject for the root ONR. @@ -36,8 +40,12 @@ func (fs FoundSubject) GetSubjectId() string { return fs.subject.ObjectId } -func (fs FoundSubject) GetExcludedSubjectIds() []string { - return fs.excludedSubjectIds +func (fs FoundSubject) GetCaveatExpression() *v1.CaveatExpression { + return fs.caveatExpression +} + +func (fs FoundSubject) GetExcludedSubjects() []FoundSubject { + return fs.excludedSubjects } // Subject returns the Subject of the FoundSubject. @@ -58,13 +66,14 @@ func (fs FoundSubject) WildcardType() (string, bool) { // If not a wildcard subject, returns false. func (fs FoundSubject) ExcludedSubjectsFromWildcard() ([]*core.ObjectAndRelation, bool) { if fs.subject.ObjectId == tuple.PublicWildcard { - excludedSubjects := make([]*core.ObjectAndRelation, 0, len(fs.excludedSubjectIds)) - for _, excludedID := range fs.excludedSubjectIds { - excludedSubjects = append(excludedSubjects, &core.ObjectAndRelation{ - Namespace: fs.subject.Namespace, - ObjectId: excludedID, - Relation: fs.subject.Relation, - }) + excludedSubjects := make([]*core.ObjectAndRelation, 0, len(fs.excludedSubjects)) + for _, excludedSubject := range fs.excludedSubjects { + // TODO(jschorr): Fix once we add caveats support to debug tooling + if excludedSubject.caveatExpression != nil { + panic("not yet supported") + } + + excludedSubjects = append(excludedSubjects, excludedSubject.subject) } return excludedSubjects, true @@ -73,6 +82,20 @@ func (fs FoundSubject) ExcludedSubjectsFromWildcard() ([]*core.ObjectAndRelation return []*core.ObjectAndRelation{}, false } +func (fs FoundSubject) excludedSubjectIDs() []string { + excludedSubjects := make([]string, 0, len(fs.excludedSubjects)) + for _, excludedSubject := range fs.excludedSubjects { + // TODO(jschorr): Fix once we add caveats support to debug tooling + if excludedSubject.caveatExpression != nil { + panic("not yet supported") + } + + excludedSubjects = append(excludedSubjects, excludedSubject.subject.ObjectId) + } + + return excludedSubjects +} + // Relationships returns all the relationships in which the subject was found as per the expand. func (fs FoundSubject) Relationships() []*core.ObjectAndRelation { return fs.relationships.AsSlice() @@ -81,6 +104,11 @@ func (fs FoundSubject) Relationships() []*core.ObjectAndRelation { // ToValidationString returns the FoundSubject in a format that is consumable by the validationfile // package. func (fs FoundSubject) ToValidationString() string { + if fs.caveatExpression != nil { + // TODO(jschorr): Implement once we have a format for this. + panic("conditional found subjects not yet supported") + } + onrString := tuple.StringONR(fs.Subject()) excluded, isWildcard := fs.ExcludedSubjectsFromWildcard() if isWildcard && len(excluded) > 0 { diff --git a/internal/membership/trackingsubjectset.go b/internal/membership/trackingsubjectset.go index e55e71f3dc..22ac781ddc 100644 --- a/internal/membership/trackingsubjectset.go +++ b/internal/membership/trackingsubjectset.go @@ -4,7 +4,10 @@ import ( "fmt" "strings" + "github.com/authzed/spicedb/pkg/tuple" + core "github.com/authzed/spicedb/pkg/proto/core/v1" + v1 "github.com/authzed/spicedb/pkg/proto/dispatch/v1" "github.com/authzed/spicedb/internal/util" ) @@ -62,24 +65,21 @@ func (tss *TrackingSubjectSet) getSetForKey(key string) util.BaseSubjectSet[Foun parts := strings.Split(key, "#") created := util.NewBaseSubjectSet[FoundSubject]( - func(subjectID string, excludedSubjectIDs []string, sources ...FoundSubject) FoundSubject { + func(subjectID string, caveatExpression *v1.CaveatExpression, excludedSubjects []FoundSubject, sources ...FoundSubject) FoundSubject { fs := NewFoundSubject(&core.ObjectAndRelation{ Namespace: parts[0], ObjectId: subjectID, Relation: parts[1], }) - fs.excludedSubjectIds = excludedSubjectIDs + fs.excludedSubjects = excludedSubjects + fs.caveatExpression = caveatExpression for _, source := range sources { - fs.relationships.UpdateFrom(source.relationships) + if source.relationships != nil { + fs.relationships.UpdateFrom(source.relationships) + } } return fs }, - func(existing FoundSubject, added FoundSubject) FoundSubject { - fs := NewFoundSubject(existing.subject) - fs.excludedSubjectIds = existing.excludedSubjectIds - fs.relationships = existing.relationships.Union(added.relationships) - return fs - }, ) tss.setByType[key] = created return created @@ -151,6 +151,16 @@ func (tss TrackingSubjectSet) removeExact(subjects ...*core.ObjectAndRelation) { } } +func (tss TrackingSubjectSet) getSubjects() []string { + var subjects []string + for _, subjectSet := range tss.setByType { + for _, foundSubject := range subjectSet.AsSlice() { + subjects = append(subjects, tuple.StringONR(foundSubject.subject)) + } + } + return subjects +} + // ToSlice returns a slice of all subjects found in the set. func (tss TrackingSubjectSet) ToSlice() []FoundSubject { subjects := []FoundSubject{} diff --git a/internal/membership/trackingsubjectset_test.go b/internal/membership/trackingsubjectset_test.go index c5d627b2d0..306053f0a9 100644 --- a/internal/membership/trackingsubjectset_test.go +++ b/internal/membership/trackingsubjectset_test.go @@ -45,10 +45,15 @@ func subtract(firstSet *TrackingSubjectSet, sets ...*TrackingSubjectSet) *Tracki } func fs(subjectType string, subjectID string, subjectRel string, excludedSubjectIDs ...string) FoundSubject { + excludedSubjects := make([]FoundSubject, 0, len(excludedSubjectIDs)) + for _, excludedSubjectID := range excludedSubjectIDs { + excludedSubjects = append(excludedSubjects, FoundSubject{subject: ONR(subjectType, excludedSubjectID, subjectRel)}) + } + return FoundSubject{ - subject: ONR(subjectType, subjectID, subjectRel), - excludedSubjectIds: excludedSubjectIDs, - relationships: tuple.NewONRSet(), + subject: ONR(subjectType, subjectID, subjectRel), + excludedSubjects: excludedSubjects, + relationships: tuple.NewONRSet(), } } @@ -330,8 +335,8 @@ func TestTrackingSubjectSet(t *testing.T) { found, ok := tc.set.Get(fs.subject) require.True(ok, "missing expected subject %s", fs.subject) - expectedExcluded := util.NewSet[string](fs.excludedSubjectIds...) - foundExcluded := util.NewSet[string](found.excludedSubjectIds...) + expectedExcluded := util.NewSet[string](fs.excludedSubjectIDs()...) + foundExcluded := util.NewSet[string](found.excludedSubjectIDs()...) require.Len(expectedExcluded.Subtract(foundExcluded).AsSlice(), 0, "mismatch on excluded subjects on %s: expected: %s, found: %s", fs.subject, expectedExcluded, foundExcluded) require.Len(foundExcluded.Subtract(expectedExcluded).AsSlice(), 0, "mismatch on excluded subjects on %s: expected: %s, found: %s", fs.subject, expectedExcluded, foundExcluded) } else { @@ -340,7 +345,7 @@ func TestTrackingSubjectSet(t *testing.T) { tc.set.removeExact(fs.subject) } - require.True(tc.set.IsEmpty()) + require.True(tc.set.IsEmpty(), "Found remaining: %v", tc.set.getSubjects()) }) } } diff --git a/internal/services/v1/permissions.go b/internal/services/v1/permissions.go index ef4bbee20e..8986359a2b 100644 --- a/internal/services/v1/permissions.go +++ b/internal/services/v1/permissions.go @@ -430,9 +430,14 @@ func (ps *permissionServer) LookupSubjects(req *v1.LookupSubjectsRequest, resp v stream := dispatchpkg.NewHandlingDispatchStream(ctx, func(result *dispatch.DispatchLookupSubjectsResponse) error { for _, foundSubject := range result.FoundSubjects { + excludedSubjectIDs := make([]string, 0, len(foundSubject.ExcludedSubjects)) + for _, excludedSubject := range foundSubject.ExcludedSubjects { + excludedSubjectIDs = append(excludedSubjectIDs, excludedSubject.SubjectId) + } + err := resp.Send(&v1.LookupSubjectsResponse{ SubjectObjectId: foundSubject.SubjectId, - ExcludedSubjectIds: foundSubject.ExcludedSubjectIds, + ExcludedSubjectIds: excludedSubjectIDs, LookedUpAt: revisionReadAt, }) if err != nil { diff --git a/internal/util/basesubjectset.go b/internal/util/basesubjectset.go index b47bb47903..31477ceafd 100644 --- a/internal/util/basesubjectset.go +++ b/internal/util/basesubjectset.go @@ -1,212 +1,172 @@ package util import ( + "fmt" + "golang.org/x/exp/maps" + v1 "github.com/authzed/spicedb/pkg/proto/dispatch/v1" "github.com/authzed/spicedb/pkg/tuple" ) -// Subject is a subject that can be placed into a BaseSubjectSet. -type Subject interface { +// Subject is a subject that can be placed into a BaseSubjectSet. It is defined in a generic +// manner to allow implementations that wrap BaseSubjectSet to add their own additional bookkeeping +// to the base implementation. +type Subject[T any] interface { // GetSubjectId returns the ID of the subject. For wildcards, this should be `*`. GetSubjectId() string - // GetExcludedSubjectIds returns the list of subject IDs excluded. Should only have values - // for wildcards. - GetExcludedSubjectIds() []string + // GetCaveatExpression returns the caveat expression for this subject, if it is conditional. + GetCaveatExpression() *v1.CaveatExpression + + // GetExcludedSubjects returns the list of subjects excluded. Must only have values + // for wildcards and must never be nested. + GetExcludedSubjects() []T } -// BaseSubjectSet defines a set that tracks accessible subjects. It is generic to allow -// other implementations to define the kind of tracking information associated with each subject. +// BaseSubjectSet defines a set that tracks accessible subjects, their exclusions (if wildcards), +// and all conditional expressions applied due to caveats. +// +// It is generic to allow other implementations to define the kind of tracking information +// associated with each subject. // // NOTE: Unlike a traditional set, unions between wildcards and a concrete subject will result // in *both* being present in the set, to maintain the proper set semantics around wildcards. -type BaseSubjectSet[T Subject] struct { - values map[string]T - constructor func(subjectID string, excludedSubjectIDs []string, sources ...T) T - combiner func(existing T, added T) T +type BaseSubjectSet[T Subject[T]] struct { + constructor constructor[T] + concrete map[string]T + wildcard *handle[T] } // NewBaseSubjectSet creates a new base subject set for use underneath well-typed implementation. // -// The constructor function is a function that returns a new instancre of type T for a particular -// subject ID. -// The combiner function is optional, and if given, is used to combine existing elements in the -// set into a new element. This is typically used in debug packages for tracking of additional -// metadata. -func NewBaseSubjectSet[T Subject]( - constructor func(subjectID string, excludedSubjectIDs []string, sources ...T) T, - combiner func(existing T, added T) T, -) BaseSubjectSet[T] { +// The constructor function returns a new instance of type T for a particular subject ID. +func NewBaseSubjectSet[T Subject[T]](constructor constructor[T]) BaseSubjectSet[T] { return BaseSubjectSet[T]{ - values: map[string]T{}, constructor: constructor, - combiner: combiner, + concrete: map[string]T{}, + wildcard: newHandle[T](), } } -// Add adds the found subject to the set. This is equivalent to a Union operation between the -// existing set of subjects and a set containing the single subject. -func (bss BaseSubjectSet[T]) Add(foundSubject T) bool { - existing, ok := bss.values[foundSubject.GetSubjectId()] - if !ok { - bss.values[foundSubject.GetSubjectId()] = foundSubject - } +// constructor defines a function for constructing a new instance of the Subject type T for +// a subject ID, its (optional) conditional expression, any excluded subjects, and any sources +// for bookkeeping. The sources are those other subjects that were combined to create the current +// subject. +type constructor[T Subject[T]] func(subjectID string, conditionalExpression *v1.CaveatExpression, excludedSubjects []T, sources ...T) T +// Add adds the found subject to the set. This is equivalent to a Union operation between the +// existing set of subjects and a set containing the single subject, but modifies the set +// *in place*. +func (bss BaseSubjectSet[T]) Add(foundSubject T) { if foundSubject.GetSubjectId() == tuple.PublicWildcard { - if ok { - // Intersect any exceptions, as union between one wildcard and another is a wildcard - // with the exceptions intersected. - // - // As a concrete example, given `user:* - user:tom` and `user:* - user:sarah`, the union - // of the two will be `*`, since each handles the other user. - excludedIds := NewSet[string](existing.GetExcludedSubjectIds()...).IntersectionDifference(NewSet[string](foundSubject.GetExcludedSubjectIds()...)) - bss.values[tuple.PublicWildcard] = bss.constructor(tuple.PublicWildcard, excludedIds.AsSlice(), existing, foundSubject) - } - } else { - // If there is an existing wildcard, remove the subject from its exclusions list. - if existingWildcard, ok := bss.values[tuple.PublicWildcard]; ok { - excludedIds := NewSet[string](existingWildcard.GetExcludedSubjectIds()...) - excludedIds.Remove(foundSubject.GetSubjectId()) - bss.values[tuple.PublicWildcard] = bss.constructor(tuple.PublicWildcard, excludedIds.AsSlice(), existingWildcard) + existing := bss.wildcard.getOrNil() + existing = unionWildcardWithWildcard(existing, foundSubject, bss.constructor) + bss.wildcard.setOrNil(existing) + + for _, concrete := range bss.concrete { + existing = unionWildcardWithConcrete(existing, concrete, bss.constructor) } + bss.wildcard.setOrNil(existing) + return } - if bss.combiner != nil && ok { - bss.values[foundSubject.GetSubjectId()] = bss.combiner(bss.values[foundSubject.GetSubjectId()], foundSubject) + var existingOrNil *T + if existing, ok := bss.concrete[foundSubject.GetSubjectId()]; ok { + existingOrNil = &existing } + bss.setConcrete(foundSubject.GetSubjectId(), unionConcreteWithConcrete(existingOrNil, &foundSubject, bss.constructor)) - return !ok + wildcard := bss.wildcard.getOrNil() + wildcard = unionWildcardWithConcrete(wildcard, foundSubject, bss.constructor) + bss.wildcard.setOrNil(wildcard) } -// Subtract subtracts the given subject found the set. -func (bss BaseSubjectSet[T]) Subtract(foundSubject T) { - // If the subject being removed is a wildcard, then remove any non-excluded items and adjust - // the existing wildcard. - if foundSubject.GetSubjectId() == tuple.PublicWildcard { - exclusions := NewSet[string](foundSubject.GetExcludedSubjectIds()...) - for existingSubjectID := range bss.values { - if existingSubjectID == tuple.PublicWildcard { - continue - } +func (bss BaseSubjectSet[T]) setConcrete(subjectID string, subjectOrNil *T) { + if subjectOrNil == nil { + delete(bss.concrete, subjectID) + return + } - if !exclusions.Has(existingSubjectID) { - delete(bss.values, existingSubjectID) - } - } + subject := *subjectOrNil + bss.concrete[subject.GetSubjectId()] = subject +} - // Check for an existing wildcard and adjust accordingly. - if existing, ok := bss.values[tuple.PublicWildcard]; ok { - // A subtraction of a wildcard from another wildcard subtracts the exclusions from the second. - // from the first, and places them into the subject set directly. - // - // As a concrete example, given `user:* - user:tom` - `user:* - user:sarah`, the subtraction - // of the two will be `user:sarah`, since sarah is in the first set and not in the second. - existingExclusions := NewSet[string](existing.GetExcludedSubjectIds()...) - for _, subjectID := range foundSubject.GetExcludedSubjectIds() { - if !existingExclusions.Has(subjectID) { - bss.values[subjectID] = bss.constructor(subjectID, nil, foundSubject) - } - } +// Subtract subtracts the given subject found the set. +func (bss BaseSubjectSet[T]) Subtract(toRemove T) { + if toRemove.GetSubjectId() == tuple.PublicWildcard { + for _, concrete := range bss.concrete { + bss.setConcrete(concrete.GetSubjectId(), subtractWildcardFromConcrete(concrete, toRemove, bss.constructor)) } - delete(bss.values, tuple.PublicWildcard) + existing := bss.wildcard.getOrNil() + updatedWildcard, concretesToAdd := subtractWildcardFromWildcard(existing, toRemove, bss.constructor) + bss.wildcard.setOrNil(updatedWildcard) + for _, concrete := range concretesToAdd { + concrete := concrete + bss.setConcrete(concrete.GetSubjectId(), &concrete) + } return } - // Remove the subject itself from the set. - delete(bss.values, foundSubject.GetSubjectId()) + if existing, ok := bss.concrete[toRemove.GetSubjectId()]; ok { + bss.setConcrete(toRemove.GetSubjectId(), subtractConcreteFromConcrete(existing, toRemove, bss.constructor)) + } - // If wildcard exists within the subject set, add the found subject to the exclusion list. - if wildcard, ok := bss.values[tuple.PublicWildcard]; ok { - exclusions := NewSet[string](wildcard.GetExcludedSubjectIds()...) - exclusions.Add(foundSubject.GetSubjectId()) - bss.values[tuple.PublicWildcard] = bss.constructor(tuple.PublicWildcard, exclusions.AsSlice(), wildcard) + wildcard, ok := bss.wildcard.get() + if ok { + bss.wildcard.setOrNil(subtractConcreteFromWildcard(wildcard, toRemove, bss.constructor)) } } // SubtractAll subtracts the other set of subjects from this set of subtracts, modifying this -// set in place. +// set *in place*. func (bss BaseSubjectSet[T]) SubtractAll(other BaseSubjectSet[T]) { - for _, fs := range other.values { - bss.Subtract(fs) + for _, otherSubject := range other.AsSlice() { + bss.Subtract(otherSubject) } } // IntersectionDifference performs an intersection between this set and the other set, modifying -// this set in place. +// this set *in place*. func (bss BaseSubjectSet[T]) IntersectionDifference(other BaseSubjectSet[T]) { - // Check if the other set has a wildcard. If so, remove any subjects found in the exclusion - // list. - // - // As a concrete example, given `user:tom` and `user:* - user:sarah`, the intersection should - // return `user:tom`, because everyone but `sarah` (including `tom`) is in the second set. - otherWildcard, hasOtherWildcard := other.values[tuple.PublicWildcard] - if hasOtherWildcard { - exclusion := NewSet[string](otherWildcard.GetExcludedSubjectIds()...) - for subjectID := range bss.values { - if subjectID != tuple.PublicWildcard { - if exclusion.Has(subjectID) { - delete(bss.values, subjectID) - } - } - } - } + // Intersect the wildcards of the sets, if any. + existingWildcard := bss.wildcard.getOrNil() + otherWildcard := other.wildcard.getOrNil() + bss.wildcard.setOrNil(intersectWildcardWithWildcard(existingWildcard, otherWildcard, bss.constructor)) - // Remove any concrete subjects, if the other does not have a wildcard. - if !hasOtherWildcard { - for subjectID := range bss.values { - if subjectID != tuple.PublicWildcard { - if _, ok := other.values[subjectID]; !ok { - delete(bss.values, subjectID) - } - } - } - } + // Intersect the concretes of each set, as well as with the wildcards. + updatedConcretes := make(map[string]T, len(bss.concrete)) - // Handle the case where the current set has a wildcard. We have to do two operations: - // - // 1) If the current set has a wildcard, either add the exclusions together if the other set - // also has a wildcard, or remove it if it did not. - // - // 2) We also add in any other set members that are not in the wildcard's exclusion set, as - // an intersection between a wildcard with exclusions and concrete types will always return - // concrete types as well. - if wildcard, ok := bss.values[tuple.PublicWildcard]; ok { - exclusions := NewSet[string](wildcard.GetExcludedSubjectIds()...) - - if hasOtherWildcard { - toBeExcluded := NewSet[string]() - toBeExcluded.Extend(wildcard.GetExcludedSubjectIds()) - toBeExcluded.Extend(otherWildcard.GetExcludedSubjectIds()) - bss.values[tuple.PublicWildcard] = bss.constructor(tuple.PublicWildcard, toBeExcluded.AsSlice(), wildcard, otherWildcard) - } else { - // Remove this wildcard. - delete(bss.values, tuple.PublicWildcard) + for _, concreteSubject := range bss.concrete { + var otherConcreteOrNil *T + if otherConcrete, ok := other.concrete[concreteSubject.GetSubjectId()]; ok { + otherConcreteOrNil = &otherConcrete } - // Add any concrete items from the other set into this set. This is necebssary because an - // intersection between a wildcard and a concrete should always return that concrete, except - // if it is within the wildcard's exclusion list. - // - // As a concrete example, given `user:* - user:tom` and `user:sarah`, the first set contains - // all users except `tom` (and thus includes `sarah`) and the second is `sarah`, so the result - // must include `sarah`. - for subjectID, fs := range other.values { - if subjectID != tuple.PublicWildcard && !exclusions.Has(subjectID) { - bss.values[subjectID] = fs - } + concreteIntersected := intersectConcreteWithConcrete(concreteSubject, otherConcreteOrNil, bss.constructor) + otherWildcardIntersected := intersectConcreteWithWildcard(concreteSubject, otherWildcard, bss.constructor) + + result := unionConcreteWithConcrete(concreteIntersected, otherWildcardIntersected, bss.constructor) + if result != nil { + updatedConcretes[concreteSubject.GetSubjectId()] = *result } } - // If a combiner is defined, run it over all values from both sets. - if bss.combiner != nil { - for subjectID := range bss.values { - if added, ok := other.values[subjectID]; ok { - bss.values[subjectID] = bss.combiner(bss.values[subjectID], added) + if existingWildcard != nil { + for _, otherSubject := range other.concrete { + existingWildcardIntersect := intersectConcreteWithWildcard(otherSubject, existingWildcard, bss.constructor) + if existingUpdated, ok := updatedConcretes[otherSubject.GetSubjectId()]; ok { + result := unionConcreteWithConcrete(&existingUpdated, existingWildcardIntersect, bss.constructor) + updatedConcretes[otherSubject.GetSubjectId()] = *result + } else if existingWildcardIntersect != nil { + updatedConcretes[otherSubject.GetSubjectId()] = *existingWildcardIntersect } } } + + maps.Clear(bss.concrete) + maps.Copy(bss.concrete, updatedConcretes) } // UnionWith adds the given subjects to this set, via a union call. @@ -217,39 +177,655 @@ func (bss BaseSubjectSet[T]) UnionWith(foundSubjects []T) { } // UnionWithSet performs a union operation between this set and the other set, modifying this -// set in place. +// set *in place*. func (bss BaseSubjectSet[T]) UnionWithSet(other BaseSubjectSet[T]) { bss.UnionWith(other.AsSlice()) } // Get returns the found subject with the given ID in the set, if any. func (bss BaseSubjectSet[T]) Get(id string) (T, bool) { - found, ok := bss.values[id] + if id == tuple.PublicWildcard { + return bss.wildcard.get() + } + + found, ok := bss.concrete[id] return found, ok } // IsEmpty returns whether the subject set is empty. func (bss BaseSubjectSet[T]) IsEmpty() bool { - return len(bss.values) == 0 + return bss.wildcard.getOrNil() == nil && len(bss.concrete) == 0 } // AsSlice returns the contents of the subject set as a slice of found subjects. func (bss BaseSubjectSet[T]) AsSlice() []T { - slice := make([]T, 0, len(bss.values)) - for _, fs := range bss.values { - slice = append(slice, fs) + values := maps.Values(bss.concrete) + if wildcard, ok := bss.wildcard.get(); ok { + values = append(values, wildcard) } - return slice + return values } // Clone returns a clone of this subject set. Note that this is a shallow clone. // NOTE: Should only be used when performance is not a concern. func (bss BaseSubjectSet[T]) Clone() BaseSubjectSet[T] { - return BaseSubjectSet[T]{maps.Clone(bss.values), bss.constructor, bss.combiner} + return BaseSubjectSet[T]{ + constructor: bss.constructor, + concrete: maps.Clone(bss.concrete), + wildcard: bss.wildcard.clone(), + } } // UnsafeRemoveExact removes the *exact* matching subject, with no wildcard handling. // This should ONLY be used for testing. func (bss BaseSubjectSet[T]) UnsafeRemoveExact(foundSubject T) { - delete(bss.values, foundSubject.GetSubjectId()) + if foundSubject.GetSubjectId() == tuple.PublicWildcard { + bss.wildcard.clear() + return + } + + delete(bss.concrete, foundSubject.GetSubjectId()) +} + +// unionWildcardWithWildcard performs a union operation over two wildcards, returning the updated +// wildcard (if any). +func unionWildcardWithWildcard[T Subject[T]](existing *T, adding T, constructor constructor[T]) *T { + // If there is no existing wildcard, return the added one. + if existing == nil { + return &adding + } + + // Otherwise, union together the conditionals for the wildcards and *intersect* their exclusion + // sets. + existingWildcard := *existing + expression := shortcircuitedOr(existingWildcard.GetCaveatExpression(), adding.GetCaveatExpression()) + + // Exclusion sets are intersected because if an exclusion is missing from one wildcard + // but not the other, the missing element will be, by definition, in that other wildcard. + // + // Examples: + // + // {*} + {*} => {*} + // {* - {user:tom}} + {*} => {*} + // {* - {user:tom}} + {* - {user:sarah}} => {*} + // {* - {user:tom, user:sarah}} + {* - {user:sarah}} => {* - {user:sarah}} + // {*}[c1] + {*} => {*} + // {*}[c1] + {*}[c2] => {*}[c1 || c2] + + // NOTE: since we're only using concretes here, it is safe to reuse the BaseSubjectSet itself. + exisingConcreteExclusions := NewBaseSubjectSet(constructor) + for _, excludedSubject := range existingWildcard.GetExcludedSubjects() { + if excludedSubject.GetSubjectId() == tuple.PublicWildcard { + panic("wildcards are not allowed in exclusions") + } + exisingConcreteExclusions.Add(excludedSubject) + } + + foundConcreteExclusions := NewBaseSubjectSet(constructor) + for _, excludedSubject := range adding.GetExcludedSubjects() { + if excludedSubject.GetSubjectId() == tuple.PublicWildcard { + panic("wildcards are not allowed in exclusions") + } + foundConcreteExclusions.Add(excludedSubject) + } + + exisingConcreteExclusions.IntersectionDifference(foundConcreteExclusions) + + constructed := constructor( + tuple.PublicWildcard, + expression, + exisingConcreteExclusions.AsSlice(), + *existing, + adding) + return &constructed +} + +// unionWildcardWithConcrete performs a union operation between a wildcard and a concrete subject +// being added to the set, returning the updated wildcard (if applciable). +func unionWildcardWithConcrete[T Subject[T]](existing *T, adding T, constructor constructor[T]) *T { + // If there is no existing wildcard, nothing more to do. + if existing == nil { + return nil + } + + // If the concrete is in the exclusion set, remove it if not conditional. Otherwise, mark + // it as conditional. + // + // Examples: + // {*} | {user:tom} => {*} (and user:tom in the concrete) + // {* - {user:tom}} | {user:tom} => {*} (and user:tom in the concrete) + // {* - {user:tom}[c1]} | {user:tom}[c2] => {* - {user:tom}[c1 && !c2]} (and user:tom in the concrete) + existingWildcard := *existing + updatedExclusions := make([]T, 0, len(existingWildcard.GetExcludedSubjects())) + for _, existingExclusion := range existingWildcard.GetExcludedSubjects() { + if existingExclusion.GetSubjectId() == adding.GetSubjectId() { + // If the conditional on the concrete is empty, then the concrete is always present, so + // we remove the exclusion entirely. + if adding.GetCaveatExpression() == nil { + continue + } + + // Otherwise, the conditional expression for the new exclusion is the existing expression && + // the *inversion* of the concrete's expression, as the exclusion will only apply if the + // concrete subject is not present and the exclusion's expression is true. + exclusionConditionalExpression := caveatAnd( + existingExclusion.GetCaveatExpression(), + caveatInvert(adding.GetCaveatExpression()), + ) + + updatedExclusions = append(updatedExclusions, constructor( + adding.GetSubjectId(), + exclusionConditionalExpression, + nil, + existingExclusion, + adding), + ) + } else { + updatedExclusions = append(updatedExclusions, existingExclusion) + } + } + + constructed := constructor( + tuple.PublicWildcard, + existingWildcard.GetCaveatExpression(), + updatedExclusions, + existingWildcard) + return &constructed +} + +// unionConcreteWithConcrete performs a union operation between two concrete subjects and returns +// the concrete subject produced, if any. +func unionConcreteWithConcrete[T Subject[T]](existing *T, adding *T, constructor constructor[T]) *T { + // Check for union with other concretes. + if existing == nil { + return adding + } + + if adding == nil { + return existing + } + + existingConcrete := *existing + addingConcrete := *adding + + // A union of a concrete subjects has the conditionals of each concrete merged. + constructed := constructor( + existingConcrete.GetSubjectId(), + shortcircuitedOr( + existingConcrete.GetCaveatExpression(), + addingConcrete.GetCaveatExpression(), + ), + nil, + existingConcrete, addingConcrete) + return &constructed +} + +// subtractWildcardFromWildcard performs a subtraction operation of wildcard from another, returning +// the updated wildcard (if any), as well as any concrete subjects produced by the subtraction +// operation due to exclusions. +func subtractWildcardFromWildcard[T Subject[T]](existing *T, toRemove T, constructor constructor[T]) (*T, []T) { + // If there is no existing wildcard, nothing more to do. + if existing == nil { + return nil, nil + } + + // If there is no condition on the wildcard and the new wildcard has no exclusions, then this wildcard goes away. + // Example: {*} - {*} => {} + if toRemove.GetCaveatExpression() == nil && len(toRemove.GetExcludedSubjects()) == 0 { + return nil, nil + } + + // Otherwise, we construct a new wildcard and return any concrete subjects that might result from this subtraction. + existingWildcard := *existing + existingExclusions := exclusionsMapFor(existingWildcard) + + // Calculate the exclusions which turn into concrete subjects. + // This occurs when a wildcard with exclusions is subtracted from a wildcard + // (with, or without *matching* exclusions). + // + // Example: + // Given the two wildcards `* - {user:sarah}` and `* - {user:tom, user:amy, user:sarah}`, + // the resulting concrete subjects are {user:tom, user:amy} because the first set contains + // `tom` and `amy` (but not `sarah`) and the second set contains all three. + resultingConcreteSubjects := make([]T, 0, len(toRemove.GetExcludedSubjects())) + for _, excludedSubject := range toRemove.GetExcludedSubjects() { + if existingExclusion, isExistingExclusion := existingExclusions[excludedSubject.GetSubjectId()]; !isExistingExclusion || existingExclusion.GetCaveatExpression() != nil { + // The conditional expression for the now-concrete subject type is the conditional on the provided exclusion + // itself. + // + // As an example, subtracting the wildcards + // {*[caveat1] - {user:tom}} + // - + // {*[caveat3] - {user:sarah[caveat4]}} + // + // the resulting expression to produce a *concrete* `user:sarah` is + // `caveat1 && caveat3 && caveat4`, because the concrete subject only appears if the first + // wildcard applies, the *second* wildcard applies and its exclusion applies. + exclusionConditionalExpression := caveatAnd( + caveatAnd( + existingWildcard.GetCaveatExpression(), + toRemove.GetCaveatExpression(), + ), + excludedSubject.GetCaveatExpression(), + ) + + // If there is an existing exclusion, then its caveat expression is added as well, but inverted. + // + // As an example, subtracting the wildcards + // {*[caveat1] - {user:tom[caveat2]}} + // - + // {*[caveat3] - {user:sarah[caveat4]}} + // + // the resulting expression to produce a *concrete* `user:sarah` is + // `caveat1 && !caveat2 && caveat3 && caveat4`, because the concrete subject only appears + // if the first wildcard applies, the *second* wildcard applies, the first exclusion + // does *not* apply (ensuring the concrete is in the first wildcard) and the second exclusion + // *does* apply (ensuring it is not in the second wildcard). + if existingExclusion.GetCaveatExpression() != nil { + exclusionConditionalExpression = caveatAnd( + caveatAnd( + caveatAnd( + existingWildcard.GetCaveatExpression(), + toRemove.GetCaveatExpression(), + ), + caveatInvert(existingExclusion.GetCaveatExpression()), + ), + excludedSubject.GetCaveatExpression(), + ) + } + + resultingConcreteSubjects = append(resultingConcreteSubjects, constructor( + excludedSubject.GetSubjectId(), + exclusionConditionalExpression, + nil, excludedSubject)) + } + } + + // Create the combined conditional: the wildcard can only exist when it is present and the other wildcard is not. + combinedConditionalExpression := caveatAnd(existingWildcard.GetCaveatExpression(), caveatInvert(toRemove.GetCaveatExpression())) + if combinedConditionalExpression != nil { + constructed := constructor( + tuple.PublicWildcard, + combinedConditionalExpression, + existingWildcard.GetExcludedSubjects(), + existingWildcard, + toRemove) + return &constructed, resultingConcreteSubjects + } + + return nil, resultingConcreteSubjects +} + +// subtractWildcardFromConcrete subtracts a wildcard from a concrete element, returning the updated +// concrete subject, if any. +func subtractWildcardFromConcrete[T Subject[T]](existingConcrete T, wildcardToRemove T, constructor constructor[T]) *T { + // Subtraction of a wildcard removes *all* elements of the concrete set, except those that + // are found in the excluded list. If the wildcard *itself* is conditional, then instead of + // items being removed, they are made conditional on the inversion of the wildcard's expression, + // and the exclusion's conditional, if any. + // + // Examples: + // {user:sarah, user:tom} - {*} => {} + // {user:sarah, user:tom} - {*[somecaveat]} => {user:sarah[!somecaveat], user:tom[!somecaveat]} + // {user:sarah, user:tom} - {* - {user:tom}} => {user:tom} + // {user:sarah, user:tom} - {*[somecaveat] - {user:tom}} => {user:sarah[!somecaveat], user:tom} + // {user:sarah, user:tom} - {* - {user:tom[c2]}}[somecaveat] => {user:sarah[!somecaveat], user:tom[c2]} + // {user:sarah[c1], user:tom} - {*[somecaveat] - {user:tom}} => {user:sarah[c1 && !somecaveat], user:tom} + exclusions := exclusionsMapFor(wildcardToRemove) + exclusion, isExcluded := exclusions[existingConcrete.GetSubjectId()] + if !isExcluded { + // If the subject was not excluded within the wildcard, it is either removed directly + // (in the case where the wildcard is not conditional), or has its condition updated to + // reflect that it is only present when the condition for the wildcard is *false*. + if wildcardToRemove.GetCaveatExpression() == nil { + return nil + } + + constructed := constructor( + existingConcrete.GetSubjectId(), + caveatAnd(existingConcrete.GetCaveatExpression(), caveatInvert(wildcardToRemove.GetCaveatExpression())), + nil, + existingConcrete) + return &constructed + } + + // If the exclusion is not conditional, then the subject is always present. + if exclusion.GetCaveatExpression() == nil { + return &existingConcrete + } + + // The conditional of the exclusion is that of the exclusion itself OR the caveatInverted case of + // the wildcard, which would mean the wildcard itself does not apply. + exclusionConditional := caveatOr(caveatInvert(wildcardToRemove.GetCaveatExpression()), exclusion.GetCaveatExpression()) + + constructed := constructor( + existingConcrete.GetSubjectId(), + caveatAnd(existingConcrete.GetCaveatExpression(), exclusionConditional), + nil, + existingConcrete) + return &constructed +} + +// subtractConcreteFromConcrete subtracts a concrete subject from another concrete subject. +func subtractConcreteFromConcrete[T Subject[T]](existingConcrete T, toRemove T, constructor constructor[T]) *T { + // Subtraction of a concrete type removes the entry from the concrete list + // *unless* the subtraction is conditional, in which case the conditional is updated + // to remove the element when it is true. + // + // Examples: + // {user:sarah} - {user:tom} => {user:sarah} + // {user:tom} - {user:tom} => {} + // {user:tom[c1]} - {user:tom} => {user:tom} + // {user:tom} - {user:tom[c2]} => {user:tom[!c2]} + // {user:tom[c1]} - {user:tom[c2]} => {user:tom[c1 && !c2]} + if toRemove.GetCaveatExpression() == nil { + return nil + } + + // Otherwise, adjust the conditional of the existing item to remove it if it is true. + expression := caveatAnd( + existingConcrete.GetCaveatExpression(), + caveatInvert( + toRemove.GetCaveatExpression(), + ), + ) + + constructed := constructor( + existingConcrete.GetSubjectId(), + expression, + nil, + existingConcrete, toRemove) + return &constructed +} + +// subtractConcreteFromWildcard subtracts a concrete element from a wildcard. +func subtractConcreteFromWildcard[T Subject[T]](wildcard T, concreteToRemove T, constructor constructor[T]) *T { + // Subtracting a concrete type from a wildcard adds the concrete to the exclusions for the wildcard. + // Examples: + // {*} - {user:tom} => {* - {user:tom}} + // {*} - {user:tom[c1]} => {* - {user:tom[c1]}} + // {* - {user:tom[c1]}} - {user:tom} => {* - {user:tom}} + // {* - {user:tom[c1]}} - {user:tom[c2]} => {* - {user:tom[c1 || c2]}} + updatedExclusions := make([]T, 0, len(wildcard.GetExcludedSubjects())+1) + wasFound := false + for _, existingExclusion := range wildcard.GetExcludedSubjects() { + if existingExclusion.GetSubjectId() == concreteToRemove.GetSubjectId() { + // The conditional expression for the exclusion is a combination on the existing exclusion or + // the new expression. The caveat is short-circuited here because if either the exclusion or + // the concrete is non-caveated, then the whole exclusion is non-caveated. + exclusionConditionalExpression := shortcircuitedOr( + existingExclusion.GetCaveatExpression(), + concreteToRemove.GetCaveatExpression(), + ) + + updatedExclusions = append(updatedExclusions, constructor( + concreteToRemove.GetSubjectId(), + exclusionConditionalExpression, + nil, + existingExclusion, + concreteToRemove), + ) + wasFound = true + } else { + updatedExclusions = append(updatedExclusions, existingExclusion) + } + } + + if !wasFound { + updatedExclusions = append(updatedExclusions, concreteToRemove) + } + + constructed := constructor( + tuple.PublicWildcard, + wildcard.GetCaveatExpression(), + updatedExclusions, + wildcard) + return &constructed +} + +// intersectConcreteWithConcrete performs intersection between two concrete subjects, returning the +// resolved concrete subject, if any. +func intersectConcreteWithConcrete[T Subject[T]](first T, second *T, constructor constructor[T]) *T { + // Intersection of concrete subjects is a standard intersection operation, where subjects + // must be in both sets, with a combination of the two elements into one for conditionals. + // Otherwise, `and` together conditionals. + if second == nil { + return nil + } + + secondConcrete := *second + constructed := constructor( + first.GetSubjectId(), + caveatAnd(first.GetCaveatExpression(), secondConcrete.GetCaveatExpression()), + nil, + first, + secondConcrete) + + return &constructed +} + +// intersectWildcardWithWildcard performs intersection between two wildcards, returning the resolved +// wildcard subject, if any. +func intersectWildcardWithWildcard[T Subject[T]](first *T, second *T, constructor constructor[T]) *T { + // If either wildcard does not exist, then no wildcard is placed into the resulting set. + if first == nil || second == nil { + return nil + } + + // If the other wildcard exists, then the intersection between the two wildcards is an && of + // their conditionals, and a *union* of their exclusions. + firstWildcard := *first + secondWildcard := *second + + concreteExclusions := NewBaseSubjectSet(constructor) + for _, excludedSubject := range firstWildcard.GetExcludedSubjects() { + if excludedSubject.GetSubjectId() == tuple.PublicWildcard { + panic("wildcards are not allowed in exclusions") + } + concreteExclusions.Add(excludedSubject) + } + + for _, excludedSubject := range secondWildcard.GetExcludedSubjects() { + if excludedSubject.GetSubjectId() == tuple.PublicWildcard { + panic("wildcards are not allowed in exclusions") + } + concreteExclusions.Add(excludedSubject) + } + + constructed := constructor( + tuple.PublicWildcard, + caveatAnd(firstWildcard.GetCaveatExpression(), secondWildcard.GetCaveatExpression()), + concreteExclusions.AsSlice(), + firstWildcard, + secondWildcard) + return &constructed +} + +// intersectConcreteWithWildcard performs intersection between a concrete subject and a wildcard +// subject, returning the concrete, if any. +func intersectConcreteWithWildcard[T Subject[T]](concrete T, wildcard *T, constructor constructor[T]) *T { + // If no wildcard exists, then the concrete cannot exist (for this branch) + if wildcard == nil { + return nil + } + + wildcardToIntersect := *wildcard + exclusionsMap := exclusionsMapFor(wildcardToIntersect) + exclusion, isExcluded := exclusionsMap[concrete.GetSubjectId()] + + // Cases: + // - The concrete subject is not excluded and the wildcard is not conditional => concrete is kept + // - The concrete subject is excluded and the wildcard is not conditional but the exclusion *is* conditional => concrete is made conditional + // - The concrete subject is excluded and the wildcard is not conditional => concrete is removed + // - The concrete subject is not excluded but the wildcard is conditional => concrete is kept, but made conditional + // - The concrete subject is excluded and the wildcard is conditional => concrete is removed, since it is always excluded + // - The concrete subject is excluded and the wildcard is conditional and the exclusion is conditional => combined conditional + switch { + case !isExcluded && wildcardToIntersect.GetCaveatExpression() == nil: + // If the concrete is not excluded and the wildcard conditional is empty, then the concrete is always found. + // Example: {user:tom} & {*} => {user:tom} + return &concrete + + case !isExcluded && wildcardToIntersect.GetCaveatExpression() != nil: + // The concrete subject is only included if the wildcard's caveat is true. + // Example: {user:tom}[acaveat] & {* - user:tom}[somecaveat] => {user:tom}[acaveat && somecaveat] + constructed := constructor( + concrete.GetSubjectId(), + caveatAnd(concrete.GetCaveatExpression(), wildcardToIntersect.GetCaveatExpression()), + nil, + concrete, + wildcardToIntersect) + return &constructed + + case isExcluded && exclusion.GetCaveatExpression() == nil: + // If the concrete is excluded and the exclusion is not conditional, then the concrete can never show up, + // regardless of whether the wildcard is conditional. + // Example: {user:tom} & {* - user:tom}[somecaveat] => {} + return nil + + case isExcluded && exclusion.GetCaveatExpression() != nil: + // NOTE: whether the wildcard is itself conditional or not is handled within the expression combinators below. + // The concrete subject is included if the wildcard's caveat is true and the exclusion's caveat is *false*. + // Example: {user:tom}[acaveat] & {* - user:tom[ecaveat]}[wcaveat] => {user:tom[acaveat && wcaveat && !ecaveat]} + constructed := constructor( + concrete.GetSubjectId(), + caveatAnd( + concrete.GetCaveatExpression(), + caveatAnd( + wildcardToIntersect.GetCaveatExpression(), + caveatInvert(exclusion.GetCaveatExpression()), + )), + nil, + concrete, + wildcardToIntersect, + exclusion) + return &constructed + } + + panic(fmt.Sprintf("unhandled case in basesubjectset intersectConcreteWithWildcard: %v & %v", concrete, wildcardToIntersect)) +} + +type handle[T any] struct { + value *T +} + +func newHandle[T any]() *handle[T] { + return &handle[T]{} +} + +func (h *handle[T]) getOrNil() *T { + return h.value +} + +func (h *handle[T]) setOrNil(value *T) { + h.value = value +} + +func (h *handle[T]) get() (T, bool) { + if h.value != nil { + return *h.value, true + } + + return *new(T), false +} + +func (h *handle[T]) clear() { + h.value = nil +} + +func (h *handle[T]) clone() *handle[T] { + return &handle[T]{ + value: h.value, + } +} + +// exclusionsMapFor creates a map of all the exclusions on a wildcard, by subject ID. +func exclusionsMapFor[T Subject[T]](wildcard T) map[string]T { + exclusions := make(map[string]T, len(wildcard.GetExcludedSubjects())) + for _, excludedSubject := range wildcard.GetExcludedSubjects() { + exclusions[excludedSubject.GetSubjectId()] = excludedSubject + } + return exclusions +} + +// shortcircuitedOr combines two caveat expressions via an `||`. If one of the expressions is nil, +// then the entire expression is *short-circuited*, and a nil is returned. +func shortcircuitedOr(first *v1.CaveatExpression, second *v1.CaveatExpression) *v1.CaveatExpression { + if first == nil || second == nil { + return nil + } + + return &v1.CaveatExpression{ + OperationOrCaveat: &v1.CaveatExpression_Operation{ + Operation: &v1.CaveatOperation{ + Op: v1.CaveatOperation_OR, + Children: []*v1.CaveatExpression{first, second}, + }, + }, + } +} + +// caveatOr `||`'s together two caveat expressions. If one expression is nil, the other is returned. +func caveatOr(first *v1.CaveatExpression, second *v1.CaveatExpression) *v1.CaveatExpression { + if first == nil { + return second + } + + if second == nil { + return first + } + + if first.EqualVT(second) { + return first + } + + return &v1.CaveatExpression{ + OperationOrCaveat: &v1.CaveatExpression_Operation{ + Operation: &v1.CaveatOperation{ + Op: v1.CaveatOperation_OR, + Children: []*v1.CaveatExpression{first, second}, + }, + }, + } +} + +// caveatAnd `&&`'s together two caveat expressions. If one expression is nil, the other is returned. +func caveatAnd(first *v1.CaveatExpression, second *v1.CaveatExpression) *v1.CaveatExpression { + if first == nil { + return second + } + + if second == nil { + return first + } + + if first.EqualVT(second) { + return first + } + + return &v1.CaveatExpression{ + OperationOrCaveat: &v1.CaveatExpression_Operation{ + Operation: &v1.CaveatOperation{ + Op: v1.CaveatOperation_AND, + Children: []*v1.CaveatExpression{first, second}, + }, + }, + } +} + +// caveatInvert returns the caveat expression with a `!` placed in front of it. If the expression is +// nil, returns nil. +func caveatInvert(ce *v1.CaveatExpression) *v1.CaveatExpression { + if ce == nil { + return nil + } + + return &v1.CaveatExpression{ + OperationOrCaveat: &v1.CaveatExpression_Operation{ + Operation: &v1.CaveatOperation{ + Op: v1.CaveatOperation_NOT, + Children: []*v1.CaveatExpression{ce}, + }, + }, + } } diff --git a/internal/util/subjectset.go b/internal/util/subjectset.go index 922e23fc00..df8fbbe4fd 100644 --- a/internal/util/subjectset.go +++ b/internal/util/subjectset.go @@ -15,16 +15,7 @@ type SubjectSet struct { // NewSubjectSet creates and returns a new subject set. func NewSubjectSet() SubjectSet { return SubjectSet{ - BaseSubjectSet: BaseSubjectSet[*v1.FoundSubject]{ - values: map[string]*v1.FoundSubject{}, - constructor: func(subjectID string, excludedSubjectIDs []string, sources ...*v1.FoundSubject) *v1.FoundSubject { - return &v1.FoundSubject{ - SubjectId: subjectID, - ExcludedSubjectIds: excludedSubjectIDs, - } - }, - combiner: nil, - }, + BaseSubjectSet: NewBaseSubjectSet[*v1.FoundSubject](subjectSetConstructor), } } @@ -39,3 +30,11 @@ func (ss SubjectSet) IntersectionDifference(other SubjectSet) { func (ss SubjectSet) UnionWithSet(other SubjectSet) { ss.BaseSubjectSet.UnionWithSet(other.BaseSubjectSet) } + +func subjectSetConstructor(subjectID string, caveatExpression *v1.CaveatExpression, excludedSubjects []*v1.FoundSubject, sources ...*v1.FoundSubject) *v1.FoundSubject { + return &v1.FoundSubject{ + SubjectId: subjectID, + CaveatExpression: caveatExpression, + ExcludedSubjects: excludedSubjects, + } +} diff --git a/internal/util/subjectset_test.go b/internal/util/subjectset_test.go index 917eb364f4..02fa9ef950 100644 --- a/internal/util/subjectset_test.go +++ b/internal/util/subjectset_test.go @@ -1,149 +1,287 @@ package util import ( + "fmt" + "math" "sort" "strings" "testing" "github.com/stretchr/testify/require" + "golang.org/x/exp/maps" + core "github.com/authzed/spicedb/pkg/proto/core/v1" v1 "github.com/authzed/spicedb/pkg/proto/dispatch/v1" "github.com/authzed/spicedb/pkg/tuple" ) +func caveat(name string) *core.ContextualizedCaveat { + return &core.ContextualizedCaveat{ + CaveatName: name, + } +} + +func caveatexpr(name string) *v1.CaveatExpression { + return &v1.CaveatExpression{ + OperationOrCaveat: &v1.CaveatExpression_Caveat{ + Caveat: caveat(name), + }, + } +} + func sub(subjectID string) *v1.FoundSubject { return &v1.FoundSubject{ SubjectId: subjectID, } } +func csub(subjectID string, expr *v1.CaveatExpression) *v1.FoundSubject { + return &v1.FoundSubject{ + SubjectId: subjectID, + CaveatExpression: expr, + } +} + +func cwc(expr *v1.CaveatExpression, exclusions ...*v1.FoundSubject) *v1.FoundSubject { + return &v1.FoundSubject{ + SubjectId: tuple.PublicWildcard, + ExcludedSubjects: exclusions, + CaveatExpression: expr, + } +} + func wc(exclusions ...string) *v1.FoundSubject { + excludedSubjects := make([]*v1.FoundSubject, 0, len(exclusions)) + for _, excludedID := range exclusions { + excludedSubjects = append(excludedSubjects, &v1.FoundSubject{ + SubjectId: excludedID, + }) + } + return &v1.FoundSubject{ - SubjectId: tuple.PublicWildcard, - ExcludedSubjectIds: exclusions, + SubjectId: tuple.PublicWildcard, + ExcludedSubjects: excludedSubjects, } } func TestSubjectSetAdd(t *testing.T) { tcs := []struct { - name string - existing []*v1.FoundSubject - toAdd *v1.FoundSubject - expectedResult bool - expectedSet []*v1.FoundSubject + name string + existing []*v1.FoundSubject + toAdd *v1.FoundSubject + expectedSet []*v1.FoundSubject }{ { "basic add", []*v1.FoundSubject{sub("foo"), sub("bar")}, sub("baz"), - true, []*v1.FoundSubject{sub("foo"), sub("bar"), sub("baz")}, }, { "basic repeated add", []*v1.FoundSubject{sub("foo"), sub("bar")}, sub("bar"), - false, []*v1.FoundSubject{sub("foo"), sub("bar")}, }, { "add of an empty wildcard", []*v1.FoundSubject{sub("foo"), sub("bar")}, wc(), - true, []*v1.FoundSubject{sub("foo"), sub("bar"), wc()}, }, { "add of a wildcard with exclusions", []*v1.FoundSubject{sub("foo"), sub("bar")}, wc("1", "2"), - true, []*v1.FoundSubject{sub("foo"), sub("bar"), wc("1", "2")}, }, { "add of a wildcard to a wildcard", []*v1.FoundSubject{sub("foo"), sub("bar"), wc()}, wc(), - false, []*v1.FoundSubject{sub("foo"), sub("bar"), wc()}, }, { "add of a wildcard with exclusions to a bare wildcard", []*v1.FoundSubject{sub("foo"), sub("bar"), wc()}, wc("1", "2"), - false, []*v1.FoundSubject{sub("foo"), sub("bar"), wc()}, }, { "add of a bare wildcard to a wildcard with exclusions", []*v1.FoundSubject{sub("foo"), sub("bar"), wc("1", "2")}, wc(), - false, []*v1.FoundSubject{sub("foo"), sub("bar"), wc()}, }, { "add of a wildcard with exclusions to a wildcard with exclusions", []*v1.FoundSubject{sub("foo"), sub("bar"), wc("1", "2")}, wc("2", "3"), - false, []*v1.FoundSubject{sub("foo"), sub("bar"), wc("2")}, }, { "add of a subject to a wildcard with exclusions that does not have that subject", []*v1.FoundSubject{wc("1", "2")}, sub("3"), - true, []*v1.FoundSubject{wc("1", "2"), sub("3")}, }, { "add of a subject to a wildcard with exclusions that has that subject", []*v1.FoundSubject{wc("1", "2")}, sub("2"), - true, + []*v1.FoundSubject{wc("1"), sub("2")}, + }, + { + "add of a subject to a wildcard with exclusions that has that subject", + []*v1.FoundSubject{sub("2")}, + wc("1", "2"), []*v1.FoundSubject{wc("1"), sub("2")}, }, { "add of a subject to a bare wildcard", []*v1.FoundSubject{wc()}, sub("1"), - true, []*v1.FoundSubject{wc(), sub("1")}, }, { "add of two wildcards", []*v1.FoundSubject{wc("1")}, wc("2"), - false, []*v1.FoundSubject{wc()}, }, { - "add of two wildcards with same restrictions", + "add of two wildcards with same restrictions", []*v1.FoundSubject{wc("1")}, wc("1", "2"), - false, []*v1.FoundSubject{wc("1")}, }, + + // Tests with caveats. + { + "basic add of caveated to non-caveated", + []*v1.FoundSubject{ + csub("foo", caveatexpr("testcaveat")), + sub("bar"), + }, + sub("foo"), + []*v1.FoundSubject{sub("bar"), sub("foo")}, + }, + { + "basic add of non-caveated to caveated", + []*v1.FoundSubject{ + sub("foo"), + sub("bar"), + }, + csub("foo", caveatexpr("testcaveat")), + []*v1.FoundSubject{sub("bar"), sub("foo")}, + }, + { + "basic add of caveated to caveated", + []*v1.FoundSubject{ + csub("foo", caveatexpr("testcaveat")), + sub("bar"), + }, + csub("foo", caveatexpr("anothercaveat")), + []*v1.FoundSubject{ + sub("bar"), + csub("foo", caveatOr(caveatexpr("testcaveat"), caveatexpr("anothercaveat"))), + }, + }, + { + "add of caveated wildcard to non-caveated", + []*v1.FoundSubject{ + cwc(caveatexpr("testcaveat")), + sub("bar"), + }, + wc(), + []*v1.FoundSubject{sub("bar"), wc()}, + }, + { + "add of caveated wildcard to caveated", + []*v1.FoundSubject{ + cwc(caveatexpr("testcaveat")), + sub("bar"), + }, + cwc(caveatexpr("anothercaveat")), + []*v1.FoundSubject{ + sub("bar"), + cwc(caveatOr(caveatexpr("testcaveat"), caveatexpr("anothercaveat"))), + }, + }, + { + "add of wildcard to a caveated sub", + []*v1.FoundSubject{ + csub("bar", caveatexpr("testcaveat")), + }, + wc(), + []*v1.FoundSubject{ + csub("bar", caveatexpr("testcaveat")), + wc(), + }, + }, + { + "add caveated sub to wildcard with non-matching caveated exclusion", + []*v1.FoundSubject{ + cwc(nil, csub("bar", caveatexpr("testcaveat"))), + }, + csub("foo", caveatexpr("anothercaveat")), + []*v1.FoundSubject{ + cwc(nil, csub("bar", caveatexpr("testcaveat"))), + csub("foo", caveatexpr("anothercaveat")), + }, + }, + { + "add caveated sub to wildcard with matching caveated exclusion", + []*v1.FoundSubject{ + cwc(nil, csub("foo", caveatexpr("testcaveat"))), + }, + csub("foo", caveatexpr("anothercaveat")), + []*v1.FoundSubject{ + // The caveat of the exclusion is now the combination of the caveats from the original + // wildcard and the concrete which was added, as the exclusion applies when the exclusion + // caveat is true and the concrete's caveat is false. + cwc(nil, csub("foo", + caveatAnd( + caveatexpr("testcaveat"), + caveatInvert(caveatexpr("anothercaveat")), + ), + )), + csub("foo", caveatexpr("anothercaveat")), + }, + }, + { + "add caveated sub to caveated wildcard with matching caveated exclusion", + []*v1.FoundSubject{ + cwc(caveatexpr("wildcardcaveat"), csub("foo", caveatexpr("testcaveat"))), + }, + csub("foo", caveatexpr("anothercaveat")), + []*v1.FoundSubject{ + // The caveat of the exclusion is now the combination of the caveats from the original + // wildcard and the concrete which was added, as the exclusion applies when the exclusion + // caveat is true and the concrete's caveat is false. + cwc(caveatexpr("wildcardcaveat"), csub("foo", + caveatAnd( + caveatexpr("testcaveat"), + caveatInvert(caveatexpr("anothercaveat")), + ), + )), + csub("foo", caveatexpr("anothercaveat")), + }, + }, } for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { existingSet := NewSubjectSet() for _, existing := range tc.existing { - require.True(t, existingSet.Add(existing)) + existingSet.Add(existing) } - require.Equal(t, tc.expectedResult, existingSet.Add(tc.toAdd)) + existingSet.Add(tc.toAdd) expectedSet := tc.expectedSet computedSet := existingSet.AsSlice() - - sort.Sort(sortByID(expectedSet)) - sort.Sort(sortByID(computedSet)) - - stableSortExclusions(expectedSet) - stableSortExclusions(computedSet) - - require.Equal(t, expectedSet, computedSet) + requireEquivalentSets(t, expectedSet, computedSet) }) } } @@ -189,7 +327,7 @@ func TestSubjectSetSubtract(t *testing.T) { "subtract from wildcard with existing exclusions", []*v1.FoundSubject{sub("bar"), wc("hiya")}, sub("bar"), - []*v1.FoundSubject{wc("bar", "hiya")}, + []*v1.FoundSubject{wc("hiya", "bar")}, }, { "subtract bare wildcard from set", @@ -227,109 +365,607 @@ func TestSubjectSetSubtract(t *testing.T) { wc("sarah", "tom"), []*v1.FoundSubject{sub("tom")}, }, + + // Tests with caveats. + { + "subtract non-caveated from caveated", + []*v1.FoundSubject{csub("foo", caveatexpr("somecaveat"))}, + sub("foo"), + []*v1.FoundSubject{}, + }, + { + "subtract caveated from non-caveated", + []*v1.FoundSubject{sub("foo")}, + csub("foo", caveatexpr("somecaveat")), + []*v1.FoundSubject{ + // The subject should only appear now when the caveat is false. + csub("foo", caveatInvert(caveatexpr("somecaveat"))), + }, + }, + { + "subtract caveated from caveated", + []*v1.FoundSubject{ + csub("foo", caveatexpr("startingcaveat")), + }, + csub("foo", caveatexpr("somecaveat")), + []*v1.FoundSubject{ + // The subject should only appear when its caveat is true, and the subtracted caveat + // is false. + csub("foo", caveatAnd(caveatexpr("startingcaveat"), caveatInvert(caveatexpr("somecaveat")))), + }, + }, + { + "subtract caveated bare wildcard from non-caveated", + []*v1.FoundSubject{sub("foo")}, + cwc(caveatexpr("somecaveat")), + []*v1.FoundSubject{ + // The subject should only appear now when the caveat is false. + csub("foo", caveatInvert(caveatexpr("somecaveat"))), + }, + }, + { + "subtract bare wildcard from caveated", + []*v1.FoundSubject{csub("foo", caveatexpr("something"))}, + wc(), + []*v1.FoundSubject{}, + }, + { + "subtract caveated bare wildcard from caveated", + []*v1.FoundSubject{csub("foo", caveatexpr("something"))}, + cwc(caveatexpr("somecaveat")), + []*v1.FoundSubject{ + // The subject should only appear now when its caveat is true and the wildcard's caveat + // is false. + csub("foo", caveatAnd(caveatexpr("something"), caveatInvert(caveatexpr("somecaveat")))), + }, + }, + { + "subtract wildcard with caveated exception from non-caveated", + []*v1.FoundSubject{sub("foo")}, + cwc(nil, csub("foo", caveatexpr("somecaveat"))), + []*v1.FoundSubject{ + // The subject should only appear now when somecaveat is true, making the exclusion present + // on the wildcard, and thus preventing the wildcard from removing the concrete subject. + csub("foo", caveatexpr("somecaveat")), + }, + }, + { + "subtract wildcard with caveated exception from caveated", + []*v1.FoundSubject{csub("foo", caveatexpr("somecaveat"))}, + cwc(nil, csub("foo", caveatexpr("anothercaveat"))), + []*v1.FoundSubject{ + // The subject should only appear now when both caveats are true. + csub("foo", caveatAnd(caveatexpr("somecaveat"), caveatexpr("anothercaveat"))), + }, + }, + { + "subtract caveated wildcard with caveated exception from caveated", + []*v1.FoundSubject{csub("foo", caveatexpr("somecaveat"))}, + cwc(caveatexpr("wildcardcaveat"), csub("foo", caveatexpr("anothercaveat"))), + []*v1.FoundSubject{ + // The subject should appear when: + // somecaveat && (!wildcardcaveat || anothercaveat) + csub("foo", caveatAnd(caveatexpr("somecaveat"), caveatOr(caveatInvert(caveatexpr("wildcardcaveat")), caveatexpr("anothercaveat")))), + }, + }, + { + "subtract caveated concrete from non-caveated wildcard", + []*v1.FoundSubject{wc()}, + csub("foo", caveatexpr("somecaveat")), + []*v1.FoundSubject{ + // The subject should be a caveated exception on the wildcard. + cwc(nil, csub("foo", caveatexpr("somecaveat"))), + }, + }, + { + "subtract caveated concrete from caveated wildcard", + []*v1.FoundSubject{cwc(caveatexpr("wildcardcaveat"))}, + csub("foo", caveatexpr("somecaveat")), + []*v1.FoundSubject{ + // The subject should be a caveated exception on the wildcard. + cwc(caveatexpr("wildcardcaveat"), csub("foo", caveatexpr("somecaveat"))), + }, + }, + { + "subtract caveated concrete from wildcard with exclusion", + []*v1.FoundSubject{ + wc("foo"), + }, + csub("foo", caveatexpr("somecaveat")), + []*v1.FoundSubject{ + // No change, as `foo` is always removed. + wc("foo"), + }, + }, + { + "subtract caveated concrete from caveated wildcard with exclusion", + []*v1.FoundSubject{ + cwc(caveatexpr("wildcardcaveat"), sub("foo")), + }, + csub("foo", caveatexpr("somecaveat")), + []*v1.FoundSubject{ + // No change, as `foo` is always removed. + cwc(caveatexpr("wildcardcaveat"), sub("foo")), + }, + }, + { + "subtract caveated concrete from caveated wildcard with caveated exclusion", + []*v1.FoundSubject{ + cwc(caveatexpr("wildcardcaveat"), csub("foo", caveatexpr("exclusioncaveat"))), + }, + csub("foo", caveatexpr("somecaveat")), + []*v1.FoundSubject{ + // The subject should be excluded now if *either* caveat is true. + cwc(caveatexpr("wildcardcaveat"), csub("foo", caveatOr(caveatexpr("exclusioncaveat"), caveatexpr("somecaveat")))), + }, + }, + { + "subtract non-caveated bare wildcard from caveated wildcard", + []*v1.FoundSubject{ + cwc(caveatexpr("wildcardcaveat")), + }, + wc(), + []*v1.FoundSubject{}, + }, + { + "subtract caveated bare wildcard from non-caveated wildcard", + []*v1.FoundSubject{ + wc(), + }, + cwc(caveatexpr("wildcardcaveat")), + []*v1.FoundSubject{ + // The new wildcard is caveated on the inversion of the caveat. + cwc(caveatInvert(caveatexpr("wildcardcaveat"))), + }, + }, + { + "subtract non-caveated bare wildcard from caveated wildcard with exclusions", + []*v1.FoundSubject{ + cwc(caveatexpr("wildcardcaveat"), sub("foo")), + }, + wc(), + []*v1.FoundSubject{}, + }, + { + "subtract caveated bare wildcard from non-caveated wildcard with exclusions", + []*v1.FoundSubject{ + wc("foo"), + }, + cwc(caveatexpr("wildcardcaveat")), + []*v1.FoundSubject{ + // The new wildcard is caveated on the inversion of the caveat. + cwc(caveatInvert(caveatexpr("wildcardcaveat")), sub("foo")), + }, + }, + { + "subtract caveated wildcard with caveated exclusion from caveated wildcard with caveated exclusion", + []*v1.FoundSubject{ + cwc(caveatexpr("wildcardcaveat1"), csub("foo", caveatexpr("exclusion1"))), + }, + cwc(caveatexpr("wildcardcaveat2"), csub("foo", caveatexpr("exclusion2"))), + []*v1.FoundSubject{ + cwc( + // The wildcard itself only appears if caveat1 is true and caveat2 is *false*. + caveatAnd(caveatexpr("wildcardcaveat1"), caveatInvert(caveatexpr("wildcardcaveat2"))), + + // The exclusion still only applies if exclusion1 is true, because if it is false, + // it doesn't matter that the subject was excluded from the subtraction. + csub("foo", caveatexpr("exclusion1")), + ), + + // A concrete of foo is produced when all of these are true: + // 1) the first wildcard is present + // 2) the second wildcard is present + // 3) the exclusion on the first wildcard is false, meaning the concrete is present within + // the wildcard + // 4) the exclusion within the second is true, meaning that the concrete was not present + // + // thus causing the expression to become `{*} - {* - {foo}}`, and therefore producing + // `foo` as a concrete. + csub("foo", + caveatAnd( + caveatAnd( + caveatAnd( + caveatexpr("wildcardcaveat1"), + caveatexpr("wildcardcaveat2"), + ), + caveatInvert(caveatexpr("exclusion1")), + ), + caveatexpr("exclusion2"), + ), + ), + }, + }, + { + "subtract caveated wildcard with caveated exclusion from caveated wildcard with different caveated exclusion", + []*v1.FoundSubject{ + cwc(caveatexpr("wildcardcaveat1"), csub("foo", caveatexpr("exclusion1"))), + }, + cwc(caveatexpr("wildcardcaveat2"), csub("bar", caveatexpr("exclusion2"))), + []*v1.FoundSubject{ + cwc( + // The wildcard itself only appears if caveat1 is true and caveat2 is *false*. + caveatAnd(caveatexpr("wildcardcaveat1"), caveatInvert(caveatexpr("wildcardcaveat2"))), + + // The foo exclusion remains the same. + csub("foo", caveatexpr("exclusion1")), + ), + + // Because `bar` is excluded in the *subtraction*, it can appear as a *concrete* subject + // in the scenario where the first wildcard is applied, the second wildcard is applied + // and its own exclusion is true. + // Therefore, bar must be *concretely* in the set if: + // wildcard1 && wildcard2 && exclusion2 + csub("bar", + caveatAnd( + caveatAnd( + caveatexpr("wildcardcaveat1"), + caveatexpr("wildcardcaveat2"), + ), + caveatexpr("exclusion2"), + ), + ), + }, + }, + { + "concretes with wildcard with partially matching exclusions subtracted", + []*v1.FoundSubject{ + sub("foo"), + sub("bar"), + }, + cwc(caveatexpr("caveat"), sub("foo")), + []*v1.FoundSubject{ + sub("foo"), + csub("bar", caveatInvert(caveatexpr("caveat"))), + }, + }, + { + "subtract caveated from caveated (same caveat)", + []*v1.FoundSubject{ + csub("foo", caveatexpr("somecaveat")), + }, + csub("foo", caveatexpr("somecaveat")), + []*v1.FoundSubject{ + // Since no expression simplification is occurring, subtracting a caveated concrete + // subject from another with the *same* caveat, results in an expression that &&'s + // together the expression and its inversion. In practice, this will simplify down to + // nothing, but that happens at a different layer. + csub("foo", caveatAnd(caveatexpr("somecaveat"), caveatInvert(caveatexpr("somecaveat")))), + }, + }, } for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { existingSet := NewSubjectSet() for _, existing := range tc.existing { - require.True(t, existingSet.Add(existing)) + existingSet.Add(existing) } existingSet.Subtract(tc.toSubtract) expectedSet := tc.expectedSet computedSet := existingSet.AsSlice() - - sort.Sort(sortByID(expectedSet)) - sort.Sort(sortByID(computedSet)) - - stableSortExclusions(expectedSet) - stableSortExclusions(computedSet) - - require.Equal(t, expectedSet, computedSet) + requireEquivalentSets(t, expectedSet, computedSet) }) } } func TestSubjectSetIntersection(t *testing.T) { tcs := []struct { - name string - existing []*v1.FoundSubject - toIntersect []*v1.FoundSubject - expectedSet []*v1.FoundSubject + name string + existing []*v1.FoundSubject + toIntersect []*v1.FoundSubject + expectedSet []*v1.FoundSubject + expectedInvertedSet []*v1.FoundSubject }{ { "basic intersection, full overlap", []*v1.FoundSubject{sub("foo"), sub("bar")}, []*v1.FoundSubject{sub("foo"), sub("bar")}, []*v1.FoundSubject{sub("foo"), sub("bar")}, + nil, }, { "basic intersection, partial overlap", []*v1.FoundSubject{sub("foo"), sub("bar")}, []*v1.FoundSubject{sub("foo")}, []*v1.FoundSubject{sub("foo")}, + nil, }, { "basic intersection, no overlap", []*v1.FoundSubject{sub("foo"), sub("bar")}, []*v1.FoundSubject{sub("baz")}, []*v1.FoundSubject{}, + nil, }, { "intersection between bare wildcard and concrete", []*v1.FoundSubject{sub("foo")}, []*v1.FoundSubject{wc()}, []*v1.FoundSubject{sub("foo")}, + nil, }, { "intersection between wildcard with exclusions and concrete", []*v1.FoundSubject{sub("foo")}, []*v1.FoundSubject{wc("tom")}, []*v1.FoundSubject{sub("foo")}, + nil, }, { "intersection between wildcard with matching exclusions and concrete", []*v1.FoundSubject{sub("foo")}, []*v1.FoundSubject{wc("foo")}, []*v1.FoundSubject{}, + nil, }, { "intersection between bare wildcards", []*v1.FoundSubject{wc()}, []*v1.FoundSubject{wc()}, []*v1.FoundSubject{wc()}, + nil, }, { "intersection between bare wildcard and one with exclusions", []*v1.FoundSubject{wc()}, []*v1.FoundSubject{wc("1", "2")}, []*v1.FoundSubject{wc("1", "2")}, + nil, }, { "intersection between wildcards", []*v1.FoundSubject{wc("2", "3")}, []*v1.FoundSubject{wc("1", "2")}, []*v1.FoundSubject{wc("1", "2", "3")}, + nil, }, { "intersection wildcard with exclusions and concrete", []*v1.FoundSubject{wc("2", "3")}, []*v1.FoundSubject{sub("4")}, []*v1.FoundSubject{sub("4")}, + nil, }, { "intersection wildcard with matching exclusions and concrete", []*v1.FoundSubject{wc("2", "3", "4")}, []*v1.FoundSubject{sub("4")}, []*v1.FoundSubject{}, + nil, }, { "intersection of wildcards and two concrete types", []*v1.FoundSubject{wc(), sub("1")}, []*v1.FoundSubject{wc(), sub("2")}, []*v1.FoundSubject{wc(), sub("1"), sub("2")}, + nil, + }, + + // Tests with caveats. + { + "intersection of non-caveated concrete and caveated concrete", + []*v1.FoundSubject{sub("1")}, + []*v1.FoundSubject{csub("1", caveatexpr("caveat"))}, + + // Resulting subject is caveated on the caveat. + []*v1.FoundSubject{csub("1", caveatexpr("caveat"))}, + nil, + }, + { + "intersection of caveated concrete and non-caveated concrete", + []*v1.FoundSubject{csub("1", caveatexpr("caveat"))}, + []*v1.FoundSubject{sub("1")}, + + // Resulting subject is caveated on the caveat. + []*v1.FoundSubject{csub("1", caveatexpr("caveat"))}, + nil, + }, + { + "intersection of caveated concrete and caveated concrete", + []*v1.FoundSubject{csub("1", caveatexpr("caveat1"))}, + []*v1.FoundSubject{csub("1", caveatexpr("caveat2"))}, + + // Resulting subject is caveated both caveats. + []*v1.FoundSubject{csub("1", caveatAnd(caveatexpr("caveat1"), caveatexpr("caveat2")))}, + + // Inverted result has the caveats reversed in the `&&` expression. + []*v1.FoundSubject{csub("1", caveatAnd(caveatexpr("caveat2"), caveatexpr("caveat1")))}, + }, + { + "intersection of caveated concrete and non-caveated bare wildcard", + []*v1.FoundSubject{csub("1", caveatexpr("caveat1"))}, + []*v1.FoundSubject{wc()}, + + // Resulting subject is caveated and concrete. + []*v1.FoundSubject{csub("1", caveatexpr("caveat1"))}, + nil, + }, + { + "intersection of non-caveated bare wildcard and caveated concrete", + []*v1.FoundSubject{wc()}, + []*v1.FoundSubject{csub("1", caveatexpr("caveat1"))}, + + // Resulting subject is caveated and concrete. + []*v1.FoundSubject{csub("1", caveatexpr("caveat1"))}, + nil, + }, + { + "intersection of caveated concrete and caveated bare wildcard", + []*v1.FoundSubject{csub("1", caveatexpr("caveat1"))}, + []*v1.FoundSubject{cwc(caveatexpr("caveat2"))}, + + // Resulting subject is caveated from both and concrete. + []*v1.FoundSubject{csub("1", caveatAnd(caveatexpr("caveat1"), caveatexpr("caveat2")))}, + nil, + }, + { + "intersection of caveated bare wildcard and caveated concrete", + []*v1.FoundSubject{cwc(caveatexpr("caveat2"))}, + []*v1.FoundSubject{csub("1", caveatexpr("caveat1"))}, + + // Resulting subject is caveated from both and concrete. + []*v1.FoundSubject{csub("1", caveatAnd(caveatexpr("caveat1"), caveatexpr("caveat2")))}, + nil, + }, + { + "intersection of caveated concrete and non-caveated wildcard with non-matching exclusion", + []*v1.FoundSubject{wc("2")}, + []*v1.FoundSubject{csub("1", caveatexpr("caveat1"))}, + + // Resulting subject is caveated and concrete. + []*v1.FoundSubject{csub("1", caveatexpr("caveat1"))}, + nil, + }, + { + "intersection of caveated concrete and non-caveated wildcard with matching exclusion", + []*v1.FoundSubject{wc("1")}, + []*v1.FoundSubject{csub("1", caveatexpr("caveat1"))}, + + // Empty since the subject was in the exclusion set. + []*v1.FoundSubject{}, + nil, + }, + { + "intersection of caveated concrete and caveated wildcard with non-matching exclusion", + []*v1.FoundSubject{cwc(caveatexpr("wcaveat"), sub("2"))}, + []*v1.FoundSubject{csub("1", caveatexpr("caveat1"))}, + + // Since the wildcard is caveated and has a non-matching exclusion, the caveat is added to + // the subject. + []*v1.FoundSubject{csub("1", caveatAnd(caveatexpr("caveat1"), caveatexpr("wcaveat")))}, + nil, + }, + { + "intersection of caveated concrete and caveated wildcard with matching exclusion", + []*v1.FoundSubject{cwc(caveatexpr("wcaveat"), sub("1"))}, + []*v1.FoundSubject{csub("1", caveatexpr("caveat1"))}, + + // Since the wildcard is caveated but has a matching exclusion, the result is empty. + []*v1.FoundSubject{}, + nil, + }, + { + "intersection of caveated concrete and caveated wildcard with matching caveated exclusion", + []*v1.FoundSubject{cwc(caveatexpr("wcaveat"), csub("1", caveatexpr("ecaveat")))}, + []*v1.FoundSubject{csub("1", caveatexpr("caveat1"))}, + + []*v1.FoundSubject{ + // The concrete is included if its own caveat is true, the wildcard's caveat is true + // and the exclusion's caveat is false. + csub("1", + caveatAnd( + caveatexpr("caveat1"), + caveatAnd( + caveatexpr("wcaveat"), + caveatInvert(caveatexpr("ecaveat")), + ), + ), + ), + }, + nil, + }, + { + "intersection of caveated concrete and wildcard", + []*v1.FoundSubject{csub("1", caveatexpr("first"))}, + []*v1.FoundSubject{wc()}, + + []*v1.FoundSubject{ + csub("1", + caveatexpr("first"), + ), + }, + nil, + }, + { + "intersection of caveated concrete and wildcards", + []*v1.FoundSubject{wc(), csub("1", caveatexpr("first"))}, + []*v1.FoundSubject{wc(), csub("1", caveatexpr("second"))}, + + []*v1.FoundSubject{ + // Wildcard is included because it is in both sets. + wc(), + + // The subject is caveated to appear if either first or second is true, or both are true. + // This is because of the interaction with the wildcards in each set: + // + // - If first is true and second is false, we get {*, 1} ∩ {*} => {*, 1} + // - If first is false and second is true, we get {*} ∩ {*, 1} => {*, 1} + // - If both are true, then the subject appears, but that is just a remnant of the join + // on the concrete subject itself (since the set does not simplify expressions) + csub("1", + caveatOr( + caveatOr( + caveatAnd( + caveatexpr("first"), + caveatexpr("second"), + ), + caveatexpr("first"), + ), + caveatexpr("second"), + ), + ), + }, + []*v1.FoundSubject{ + wc(), + csub("1", + caveatOr( + caveatOr( + caveatAnd( + caveatexpr("second"), + caveatexpr("first"), + ), + caveatexpr("second"), + ), + caveatexpr("first"), + ), + ), + }, + }, + { + "intersection of caveated concrete and caveated wildcard", + []*v1.FoundSubject{csub("1", caveatexpr("first"))}, + []*v1.FoundSubject{ + cwc(caveatexpr("wcaveat")), + csub("1", caveatexpr("second")), + }, + []*v1.FoundSubject{ + csub("1", + caveatOr( + caveatAnd( + caveatexpr("first"), + caveatexpr("second"), + ), + caveatAnd( + caveatexpr("first"), + caveatexpr("wcaveat"), + ), + ), + ), + }, + []*v1.FoundSubject{ + csub("1", + caveatOr( + caveatAnd( + caveatexpr("second"), + caveatexpr("first"), + ), + caveatAnd( + caveatexpr("first"), + caveatexpr("wcaveat"), + ), + ), + ), + }, + }, + { + "intersection of caveated concrete and wildcard with wildcard", + []*v1.FoundSubject{csub("1", caveatexpr("caveat1")), wc()}, + []*v1.FoundSubject{wc()}, + []*v1.FoundSubject{wc(), csub("1", caveatexpr("caveat1"))}, + nil, }, } @@ -337,38 +973,2024 @@ func TestSubjectSetIntersection(t *testing.T) { t.Run(tc.name, func(t *testing.T) { existingSet := NewSubjectSet() for _, existing := range tc.existing { - require.True(t, existingSet.Add(existing)) + existingSet.Add(existing) } toIntersect := NewSubjectSet() for _, toAdd := range tc.toIntersect { - require.True(t, toIntersect.Add(toAdd)) + toIntersect.Add(toAdd) } existingSet.IntersectionDifference(toIntersect) expectedSet := tc.expectedSet computedSet := existingSet.AsSlice() + requireEquivalentSets(t, expectedSet, computedSet) - sort.Sort(sortByID(expectedSet)) - sort.Sort(sortByID(computedSet)) + // Run the intersection inverted, which should always result in the same results. + t.Run("inverted", func(t *testing.T) { + existingSet := NewSubjectSet() + for _, existing := range tc.existing { + existingSet.Add(existing) + } - stableSortExclusions(expectedSet) - stableSortExclusions(computedSet) + toIntersect := NewSubjectSet() + for _, toAdd := range tc.toIntersect { + toIntersect.Add(toAdd) + } - require.Equal(t, expectedSet, computedSet) + toIntersect.IntersectionDifference(existingSet) + + // The inverted set is necessary for some caveated sets, because their expressions may + // have references in reversed locations. + expectedSet := tc.expectedSet + if tc.expectedInvertedSet != nil { + expectedSet = tc.expectedInvertedSet + } + + computedSet := toIntersect.AsSlice() + requireEquivalentSets(t, expectedSet, computedSet) + }) }) } } -type sortByID []*v1.FoundSubject +func TestMultipleOperations(t *testing.T) { + tcs := []struct { + name string + runOps func(set SubjectSet) + expectedSet []*v1.FoundSubject + }{ + { + "basic adds", + func(set SubjectSet) { + set.Add(sub("1")) + set.Add(sub("2")) + set.Add(sub("3")) -func (a sortByID) Len() int { return len(a) } -func (a sortByID) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a sortByID) Less(i, j int) bool { return strings.Compare(a[i].SubjectId, a[j].SubjectId) < 0 } + set.Add(sub("1")) + set.Add(sub("2")) + set.Add(sub("3")) + }, + []*v1.FoundSubject{sub("1"), sub("2"), sub("3")}, + }, + { + "add and remove", + func(set SubjectSet) { + set.Add(sub("1")) + set.Add(sub("2")) + set.Add(sub("3")) + + set.Subtract(sub("1")) + }, + []*v1.FoundSubject{sub("2"), sub("3")}, + }, + { + "add and intersect", + func(set SubjectSet) { + set.Add(sub("1")) + set.Add(sub("2")) + set.Add(sub("3")) + + other := NewSubjectSet() + other.Add(sub("2")) + + set.IntersectionDifference(other) + }, + []*v1.FoundSubject{sub("2")}, + }, + { + "caveated adds", + func(set SubjectSet) { + set.Add(sub("1")) + set.Add(sub("2")) + set.Add(sub("3")) + + set.Add(csub("1", caveatexpr("first"))) + set.Add(csub("1", caveatexpr("second"))) + }, + []*v1.FoundSubject{sub("1"), sub("2"), sub("3")}, + }, + { + "all caveated adds", + func(set SubjectSet) { + set.Add(csub("1", caveatexpr("first"))) + set.Add(csub("1", caveatexpr("second"))) + }, + []*v1.FoundSubject{csub("1", caveatOr(caveatexpr("first"), caveatexpr("second")))}, + }, + { + "caveated adds and caveated sub", + func(set SubjectSet) { + set.Add(csub("1", caveatexpr("first"))) + set.Add(csub("1", caveatexpr("second"))) + set.Subtract(csub("1", caveatexpr("third"))) + }, + []*v1.FoundSubject{ + csub("1", + caveatAnd( + caveatOr(caveatexpr("first"), caveatexpr("second")), + caveatInvert(caveatexpr("third")), + ), + ), + }, + }, + { + "caveated adds, sub and add", + func(set SubjectSet) { + set.Add(csub("1", caveatexpr("first"))) + set.Add(csub("1", caveatexpr("second"))) + set.Subtract(csub("1", caveatexpr("third"))) + set.Add(csub("1", caveatexpr("fourth"))) + }, + []*v1.FoundSubject{ + csub("1", + caveatOr( + caveatAnd( + caveatOr(caveatexpr("first"), caveatexpr("second")), + caveatInvert(caveatexpr("third")), + ), + caveatexpr("fourth"), + ), + ), + }, + }, + { + "caveated adds, sub and concrete add", + func(set SubjectSet) { + set.Add(csub("1", caveatexpr("first"))) + set.Add(csub("1", caveatexpr("second"))) + set.Subtract(csub("1", caveatexpr("third"))) + set.Add(sub("1")) + }, + []*v1.FoundSubject{ + sub("1"), + }, + }, + { + "add concrete, add wildcard, sub concrete", + func(set SubjectSet) { + set.Add(sub("1")) + set.Add(wc()) + set.Subtract(sub("1")) + set.Subtract(sub("1")) + }, + []*v1.FoundSubject{wc("1")}, + }, + { + "add concrete, add wildcard, sub concrete, add concrete", + func(set SubjectSet) { + set.Add(sub("1")) + set.Add(wc()) + set.Subtract(sub("1")) + set.Add(sub("1")) + }, + []*v1.FoundSubject{wc(), sub("1")}, + }, + { + "caveated concrete subtracted from wildcard and then concrete added back", + func(set SubjectSet) { + set.Add(sub("1")) + set.Add(wc()) + set.Subtract(csub("1", caveatexpr("first"))) + set.Add(sub("1")) + }, + []*v1.FoundSubject{wc(), sub("1")}, + }, + { + "concrete subtracted from wildcard and then caveated added back", + func(set SubjectSet) { + set.Add(sub("1")) + set.Add(wc()) + set.Subtract(sub("1")) + set.Add(csub("1", caveatexpr("first"))) + }, + []*v1.FoundSubject{ + cwc(nil, csub("1", caveatInvert(caveatexpr("first")))), + csub("1", caveatexpr("first")), + }, + }, + { + "multiple concrete operations", + func(set SubjectSet) { + // Start with two sets of concretes and a wildcard. + // Example: permission view = viewer + editor + // ^ {*} ^ {1,2,3,4} + set.Add(sub("1")) + set.Add(sub("2")) + set.Add(sub("3")) + set.Add(sub("4")) + set.Add(wc()) + + // Subtract out banned users. + // Example: permission view_but_not_banned = view - banned + // ^ {3,6,7} + set.Subtract(sub("3")) + set.Subtract(sub("6")) + set.Subtract(sub("7")) + + // Intersect with another set. + // Example: permission result = view_but_not_banned & another_set + // ^ {1,3,*} + other := NewSubjectSet() + other.Add(sub("1")) + other.Add(sub("3")) + other.Add(wc()) + + set.IntersectionDifference(other) + + // Remaining + // `1` from `view` and `another_set` + // `2` from `view` and via * in `another_set` + // `4` from `view` and via * in `another_set` + // `*` with the `banned`` exclusions + }, + []*v1.FoundSubject{ + sub("1"), + sub("2"), + sub("4"), + wc("3", "6", "7"), + }, + }, + { + "multiple operations with caveats", + func(set SubjectSet) { + // Start with two sets of concretes and a wildcard. + // Example: permission view = viewer + editor + // ^ {1} ^ {2,3,4} + set.Add(csub("1", caveatexpr("first"))) + set.Add(sub("2")) + set.Add(sub("3")) + set.Add(sub("4")) + + // Subtract out banned users. + // Example: permission view_but_not_banned = view - banned + // ^ {3,6,7} + set.Subtract(csub("3", caveatexpr("banned"))) + set.Subtract(sub("6")) + set.Subtract(sub("7")) + + // Intersect with another set. + // Example: permission result = view_but_not_banned & another_set + // ^ {1,3,*} + other := NewSubjectSet() + other.Add(sub("1")) + other.Add(csub("3", caveatexpr("second"))) + + set.IntersectionDifference(other) + }, + []*v1.FoundSubject{ + csub("1", caveatexpr("first")), + csub("3", caveatAnd( + caveatInvert(caveatexpr("banned")), + caveatexpr("second"), + )), + }, + }, + { + "multiple operations with caveats and wildcards", + func(set SubjectSet) { + // Start with two sets of concretes and a wildcard. + // Example: permission view = viewer + editor + // ^ {*} ^ {1,2,3,4} + set.Add(csub("1", caveatexpr("first"))) + set.Add(sub("2")) + set.Add(sub("3")) + set.Add(sub("4")) + set.Add(wc()) + + // Subtract out banned users. + // Example: permission view_but_not_banned = view - banned + // ^ {3,6,7} + set.Subtract(csub("3", caveatexpr("banned"))) + set.Subtract(sub("6")) + set.Subtract(sub("7")) + + // Intersect with another set. + // Example: permission result = view_but_not_banned & another_set + // ^ {1,3,*} + other := NewSubjectSet() + other.Add(sub("1")) + other.Add(csub("3", caveatexpr("second"))) + other.Add(wc()) + + set.IntersectionDifference(other) + }, + []*v1.FoundSubject{ + // `1` is included without caveat because it is non-caveated in other and there is a + // wildcard in the original set. + sub("1"), + + // `3` inclusion expression: + // ({*, 3} - {3[banned]}) ∩ ({*, 3[second]}) + // therefore: + // `3` is included if banned is false (otherwise it gets removed in the first set). + // + // NOTE: the remaining expressions are cruft generated to cover other cases, but because + // there is no expression simplification, it is not collapsing due to just `!banned` + csub("3", + caveatOr( + caveatOr( + caveatAnd( + caveatInvert(caveatexpr("banned")), + caveatexpr("second"), + ), + caveatInvert(caveatexpr("banned")), + ), + caveatAnd( + caveatexpr("second"), + caveatInvert(caveatexpr("banned")), + ), + ), + ), + sub("2"), + sub("4"), + cwc(nil, csub("3", caveatexpr("banned")), sub("6"), sub("7")), + }, + }, + { + "subtraction followed by intersection without wildcard", + func(set SubjectSet) { + set.Add(sub("3")) + set.Add(wc()) + + set.Subtract(csub("3", caveatexpr("banned"))) -func stableSortExclusions(fss []*v1.FoundSubject) { - for _, fs := range fss { - sort.Strings(fs.ExcludedSubjectIds) + other := NewSubjectSet() + other.Add(csub("3", caveatexpr("second"))) + + set.IntersectionDifference(other) + }, + []*v1.FoundSubject{ + // `3` inclusion expression: + // ({*, 3} - {3[banned]}) ∩ ({3[second]}) + // therefore: + // `3` is included if banned is false and second is true. + csub("3", + caveatOr( + caveatAnd( + caveatInvert(caveatexpr("banned")), + caveatexpr("second"), + ), + caveatAnd( + caveatexpr("second"), + caveatInvert(caveatexpr("banned")), + ), + ), + ), + }, + }, } -} + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + set := NewSubjectSet() + tc.runOps(set) + + expectedSet := tc.expectedSet + computedSet := set.AsSlice() + requireEquivalentSets(t, expectedSet, computedSet) + }) + } +} + +func TestSubtractAll(t *testing.T) { + tcs := []struct { + name string + startingSubjects []*v1.FoundSubject + toSubtract []*v1.FoundSubject + expected []*v1.FoundSubject + }{ + { + "basic mult-subtraction", + []*v1.FoundSubject{ + sub("1"), sub("2"), sub("3"), + }, + []*v1.FoundSubject{sub("1"), sub("3")}, + []*v1.FoundSubject{sub("2")}, + }, + { + "wildcad subtraction", + []*v1.FoundSubject{ + sub("1"), sub("2"), sub("3"), + }, + []*v1.FoundSubject{wc()}, + []*v1.FoundSubject{}, + }, + { + "wildcad with exclusions subtraction", + []*v1.FoundSubject{ + sub("1"), sub("2"), sub("3"), + }, + []*v1.FoundSubject{wc("1"), sub("3")}, + []*v1.FoundSubject{sub("1")}, + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + set := NewSubjectSet() + + for _, starting := range tc.startingSubjects { + set.Add(starting) + } + + toRemove := NewSubjectSet() + for _, toSubtract := range tc.toSubtract { + toRemove.Add(toSubtract) + } + + set.SubtractAll(toRemove) + + expectedSet := tc.expected + computedSet := set.AsSlice() + requireEquivalentSets(t, expectedSet, computedSet) + }) + } +} + +func TestSubjectSetClone(t *testing.T) { + ss := NewSubjectSet() + require.True(t, ss.IsEmpty()) + + ss.Add(sub("first")) + ss.Add(sub("second")) + ss.Add(csub("third", caveatexpr("somecaveat"))) + ss.Add(wc("one", "two")) + require.False(t, ss.IsEmpty()) + + existingSubjects := ss.AsSlice() + + // Clone the set. + cloned := ss.Clone() + require.False(t, cloned.IsEmpty()) + + clonedSubjects := cloned.AsSlice() + requireEquivalentSets(t, existingSubjects, clonedSubjects) + + // Modify the existing set and ensure the cloned is not changed. + ss.Subtract(sub("first")) + ss.Subtract(wc()) + + clonedSubjects = cloned.AsSlice() + updatedExistingSubjects := ss.AsSlice() + + requireEquivalentSets(t, existingSubjects, clonedSubjects) + require.NotEqual(t, len(updatedExistingSubjects), len(clonedSubjects)) +} + +func TestSubjectSetGet(t *testing.T) { + ss := NewSubjectSet() + require.True(t, ss.IsEmpty()) + + ss.Add(sub("first")) + ss.Add(sub("second")) + ss.Add(csub("third", caveatexpr("somecaveat"))) + ss.Add(wc("one", "two")) + require.False(t, ss.IsEmpty()) + + found, ok := ss.Get("first") + require.True(t, ok) + require.Equal(t, "first", found.SubjectId) + require.Nil(t, found.CaveatExpression) + + found, ok = ss.Get("second") + require.True(t, ok) + require.Equal(t, "second", found.SubjectId) + require.Nil(t, found.CaveatExpression) + + found, ok = ss.Get("third") + require.True(t, ok) + require.Equal(t, "third", found.SubjectId) + require.NotNil(t, found.CaveatExpression) + + _, ok = ss.Get("fourth") + require.False(t, ok) + + found, ok = ss.Get("*") + require.True(t, ok) + require.Equal(t, "*", found.SubjectId) + require.Nil(t, found.CaveatExpression) + require.Equal(t, 2, len(found.ExcludedSubjects)) +} + +var testSets = [][]*v1.FoundSubject{ + {sub("foo"), sub("bar")}, + {sub("foo")}, + {sub("baz")}, + {wc()}, + {wc("tom")}, + {wc("1", "2")}, + {wc("1", "2", "3")}, + {wc("2", "3")}, + {sub("1")}, + {wc(), sub("1"), sub("2")}, + {wc(), sub("2")}, + {wc(), sub("1")}, + {csub("1", caveatexpr("caveat"))}, + {csub("1", caveatexpr("caveat2"))}, + {cwc(caveatexpr("caveat2"))}, + {cwc(caveatexpr("wcaveat"), sub("1"))}, +} + +func TestUnionCommutativity(t *testing.T) { + for _, pair := range allSubsets(testSets, 2) { + t.Run(fmt.Sprintf("%v", pair), func(t *testing.T) { + left1, left2 := NewSubjectSet(), NewSubjectSet() + for _, l := range pair[0] { + left1.Add(l) + left2.Add(l) + } + right1, right2 := NewSubjectSet(), NewSubjectSet() + for _, r := range pair[1] { + right1.Add(r) + right2.Add(r) + } + // left union right + left1.UnionWithSet(right1) + // right union left + right2.UnionWithSet(left2) + + mergedLeft := left1.AsSlice() + mergedRight := right2.AsSlice() + requireEquivalentSets(t, mergedLeft, mergedRight) + }) + } +} + +func TestUnionAssociativity(t *testing.T) { + for _, triple := range allSubsets(testSets, 3) { + t.Run(fmt.Sprintf("%s U %s U %s", formatSubjects(triple[0]), formatSubjects(triple[1]), formatSubjects(triple[2])), func(t *testing.T) { + // A U (B U C) == (A U B) U C + + A1, A2 := NewSubjectSet(), NewSubjectSet() + for _, l := range triple[0] { + A1.Add(l) + A2.Add(l) + } + B1, B2 := NewSubjectSet(), NewSubjectSet() + for _, l := range triple[1] { + B1.Add(l) + B2.Add(l) + } + C1, C2 := NewSubjectSet(), NewSubjectSet() + for _, l := range triple[2] { + C1.Add(l) + C2.Add(l) + } + + // A U (B U C) + B1.UnionWithSet(C1) + A1.UnionWithSet(B1) + + // (A U B) U C + A2.UnionWithSet(B2) + A2.UnionWithSet(C2) + + mergedLeft := A1.AsSlice() + mergedRight := A2.AsSlice() + requireEquivalentSets(t, mergedLeft, mergedRight) + }) + } +} + +func TestIntersectionCommutativity(t *testing.T) { + for _, pair := range allSubsets(testSets, 2) { + t.Run(fmt.Sprintf("%v", pair), func(t *testing.T) { + left1, left2 := NewSubjectSet(), NewSubjectSet() + for _, l := range pair[0] { + left1.Add(l) + left2.Add(l) + } + right1, right2 := NewSubjectSet(), NewSubjectSet() + for _, r := range pair[1] { + right1.Add(r) + right2.Add(r) + } + // left intersect right + left1.IntersectionDifference(right1) + // right intersects left + right2.IntersectionDifference(left2) + + mergedLeft := left1.AsSlice() + mergedRight := right2.AsSlice() + requireEquivalentSets(t, mergedLeft, mergedRight) + }) + } +} + +func TestIntersectionAssociativity(t *testing.T) { + for _, triple := range allSubsets(testSets, 3) { + t.Run(fmt.Sprintf("%s ∩ %s ∩ %s", formatSubjects(triple[0]), formatSubjects(triple[1]), formatSubjects(triple[2])), func(t *testing.T) { + // A ∩ (B ∩ C) == (A ∩ B) ∩ C + + A1, A2 := NewSubjectSet(), NewSubjectSet() + for _, l := range triple[0] { + A1.Add(l) + A2.Add(l) + } + B1, B2 := NewSubjectSet(), NewSubjectSet() + for _, l := range triple[1] { + B1.Add(l) + B2.Add(l) + } + C1, C2 := NewSubjectSet(), NewSubjectSet() + for _, l := range triple[2] { + C1.Add(l) + C2.Add(l) + } + + // A ∩ (B ∩ C) + B1.IntersectionDifference(C1) + A1.IntersectionDifference(B1) + + // (A ∩ B) ∩ C + A2.IntersectionDifference(B2) + A2.IntersectionDifference(C2) + + mergedLeft := A1.AsSlice() + mergedRight := A2.AsSlice() + requireEquivalentSets(t, mergedLeft, mergedRight) + }) + } +} + +func TestIdempotentUnion(t *testing.T) { + for _, set := range testSets { + t.Run(fmt.Sprintf("%v", set), func(t *testing.T) { + // A U A == A + A1, A2 := NewSubjectSet(), NewSubjectSet() + for _, l := range set { + A1.Add(l) + A2.Add(l) + } + A1.UnionWithSet(A2) + + mergedLeft := A1.AsSlice() + mergedRight := A2.AsSlice() + requireEquivalentSets(t, mergedLeft, mergedRight) + }) + } +} + +func TestIdempotentIntersection(t *testing.T) { + for _, set := range testSets { + t.Run(fmt.Sprintf("%v", set), func(t *testing.T) { + // A ∩ A == A + A1, A2 := NewSubjectSet(), NewSubjectSet() + for _, l := range set { + A1.Add(l) + A2.Add(l) + } + A1.IntersectionDifference(A2) + + mergedLeft := A1.AsSlice() + mergedRight := A2.AsSlice() + requireEquivalentSets(t, mergedLeft, mergedRight) + }) + } +} + +func TestUnionWildcardWithWildcard(t *testing.T) { + tcs := []struct { + existing *v1.FoundSubject + toUnion *v1.FoundSubject + expected *v1.FoundSubject + expectedInverse *v1.FoundSubject + }{ + { + nil, + wc(), + + // nil U {*} => {*} + wc(), + wc(), + }, + { + wc(), + wc(), + + // {*} U {*} => {*} + wc(), + wc(), + }, + { + wc("1"), + wc(), + + // {* - {1}} U {*} => {*} + wc(), + wc(), + }, + { + wc("1"), + wc("1"), + + // {* - {1}} U {* - {1}} => {* - {1}} + wc("1"), + wc("1"), + }, + { + wc("1", "2"), + wc("1"), + + // {* - {1, 2}} U {* - {1}} => {* - {1}} + wc("1"), + wc("1"), + }, + { + cwc(caveatexpr("first")), + wc(), + + // {*[first]} U {*} => {*} + wc(), + wc(), + }, + { + cwc(caveatexpr("first")), + cwc(caveatexpr("second")), + + // {*[first]} U {*[second]} => {*[first || second]} + cwc(caveatOr(caveatexpr("first"), caveatexpr("second"))), + cwc(caveatOr(caveatexpr("second"), caveatexpr("first"))), + }, + { + wc("1"), + cwc(nil, csub("1", caveatexpr("first"))), + + // Expected + // The subject is only excluded if the caveat is true + cwc(nil, csub("1", caveatexpr("first"))), + cwc(nil, csub("1", caveatexpr("first"))), + }, + { + cwc(nil, csub("1", caveatexpr("second"))), + cwc(nil, csub("1", caveatexpr("first"))), + + // Expected + // The subject is excluded if both its caveats are true + cwc(nil, + csub("1", + caveatAnd( + caveatexpr("second"), + caveatexpr("first"), + ), + ), + ), + cwc(nil, + csub("1", + caveatAnd( + caveatexpr("first"), + caveatexpr("second"), + ), + ), + ), + }, + } + + for _, tc := range tcs { + t.Run(fmt.Sprintf("%s U %s", format(tc.existing), format(tc.toUnion)), func(t *testing.T) { + existing := wrap(tc.existing) + produced := unionWildcardWithWildcard[*v1.FoundSubject](existing, tc.toUnion, subjectSetConstructor) + requireExpectedSubject(t, tc.expected, produced) + + toUnion := wrap(tc.toUnion) + produced2 := unionWildcardWithWildcard[*v1.FoundSubject](toUnion, tc.existing, subjectSetConstructor) + requireExpectedSubject(t, tc.expectedInverse, produced2) + }) + } +} + +func TestUnionWildcardWithConcrete(t *testing.T) { + tcs := []struct { + existing *v1.FoundSubject + toUnion *v1.FoundSubject + expected *v1.FoundSubject + }{ + { + nil, + sub("1"), + + // nil U {1} => nil + nil, + }, + { + wc(), + sub("1"), + + // {*} U {1} => {*} + wc(), + }, + { + wc("1"), + sub("1"), + + // {* - {1}} U {1} => {*} + wc(), + }, + { + wc("1", "2"), + sub("1"), + + // {* - {1, 2}} U {1} => {* - {2}} + wc("2"), + }, + { + cwc(nil, csub("1", caveatexpr("first"))), + sub("1"), + + // {* - {1[first]}} U {1} => {*} + wc(), + }, + { + cwc(nil, csub("2", caveatexpr("first"))), + sub("1"), + + // {* - {2[first]}} U {1} => {* - {2[first]}} + cwc(nil, csub("2", caveatexpr("first"))), + }, + { + cwc(nil, + csub("1", caveatexpr("first")), + csub("2", caveatexpr("second")), + ), + sub("1"), + + // {* - {1[first], 2[second]}} U {1} => {* - {2[second]}} + cwc(nil, csub("2", caveatexpr("second"))), + }, + { + cwc(nil, + csub("1", caveatexpr("first")), + csub("2", caveatexpr("second")), + ), + csub("1", caveatexpr("third")), + + // {* - {1[first], 2[second]}} U {1[third]} => {* - {1[first && !third], 2[second]}} + cwc(nil, + csub("1", + caveatAnd( + caveatexpr("first"), + caveatInvert(caveatexpr("third")), + ), + ), + csub("2", caveatexpr("second")), + ), + }, + { + cwc(caveatexpr("first")), + sub("1"), + + // {*}[first] U {1} => {*}[first] + cwc(caveatexpr("first")), + }, + { + cwc(caveatexpr("wcaveat"), + csub("1", caveatexpr("first")), + csub("2", caveatexpr("second")), + ), + csub("1", caveatexpr("third")), + + // {* - {1[first], 2[second]}}[wcaveat] U {1[third]} => {* - {1[first && !third], 2[second]}}[wcaveat] + cwc(caveatexpr("wcaveat"), + csub("1", + caveatAnd( + caveatexpr("first"), + caveatInvert(caveatexpr("third")), + ), + ), + csub("2", caveatexpr("second")), + ), + }, + } + + for _, tc := range tcs { + t.Run(fmt.Sprintf("%s U %s", format(tc.existing), format(tc.toUnion)), func(t *testing.T) { + existing := wrap(tc.existing) + produced := unionWildcardWithConcrete[*v1.FoundSubject](existing, tc.toUnion, subjectSetConstructor) + requireExpectedSubject(t, tc.expected, produced) + }) + } +} + +func TestUnionConcreteWithConcrete(t *testing.T) { + tcs := []struct { + existing *v1.FoundSubject + toUnion *v1.FoundSubject + expected *v1.FoundSubject + expectedInverted *v1.FoundSubject + }{ + { + nil, + nil, + + // nil U nil => nil + nil, + nil, + }, + { + sub("1"), + nil, + + // {1} U nil => {1} + sub("1"), + sub("1"), + }, + { + sub("1"), + sub("1"), + + // {1} U {1} => {1} + sub("1"), + sub("1"), + }, + { + csub("1", caveatexpr("first")), + sub("1"), + + // {1}[first] U {1} => {1} + sub("1"), + sub("1"), + }, + { + csub("1", caveatexpr("first")), + csub("1", caveatexpr("second")), + + // {1}[first] U {1}[second] => {1}[first || second] + csub("1", caveatOr(caveatexpr("first"), caveatexpr("second"))), + csub("1", caveatOr(caveatexpr("second"), caveatexpr("first"))), + }, + } + + for _, tc := range tcs { + t.Run(fmt.Sprintf("%s U %s", format(tc.existing), format(tc.toUnion)), func(t *testing.T) { + existing := wrap(tc.existing) + toUnion := wrap(tc.toUnion) + + produced := unionConcreteWithConcrete[*v1.FoundSubject](existing, toUnion, subjectSetConstructor) + requireExpectedSubject(t, tc.expected, produced) + + produced2 := unionConcreteWithConcrete[*v1.FoundSubject](toUnion, existing, subjectSetConstructor) + requireExpectedSubject(t, tc.expectedInverted, produced2) + }) + } +} + +func TestSubtractWildcardFromWildcard(t *testing.T) { + tcs := []struct { + existing *v1.FoundSubject + toSubtract *v1.FoundSubject + expected *v1.FoundSubject + expectedConcretes []*v1.FoundSubject + }{ + { + nil, + wc(), + + // nil - {*} => nil + nil, + nil, + }, + { + wc(), + wc(), + + // {*} - {*} => nil + nil, + nil, + }, + { + wc("1", "2"), + wc(), + + // {* - {1, 2}} - {*} => nil + nil, + nil, + }, + { + wc("1", "2"), + wc("2", "3"), + + // {* - {1, 2}} - {* - {2, 3}} => {3} + nil, + []*v1.FoundSubject{sub("3")}, + }, + { + wc(), + wc("1", "2"), + + // {*} - {* - {1, 2}} => {1, 2} + nil, + []*v1.FoundSubject{sub("1"), sub("2")}, + }, + { + cwc(caveatexpr("first")), + wc(), + + // {*}[first] - {*} => nil + nil, + nil, + }, + { + wc(), + cwc(caveatexpr("first")), + + // {*} - {*}[first] => {*}[!first] + cwc(caveatInvert(caveatexpr("first"))), + []*v1.FoundSubject{}, + }, + { + wc(), + cwc(nil, csub("1", caveatexpr("first"))), + + // {*} - {* - {1}[first]} => {1}[first] + nil, + []*v1.FoundSubject{csub("1", caveatexpr("first"))}, + }, + { + cwc(nil, csub("1", caveatexpr("first"))), + cwc(nil, csub("1", caveatexpr("second"))), + + // {* - {1}[first]} - {* - {1}[second]} => {1}[!first && second] + nil, + []*v1.FoundSubject{ + csub("1", + caveatAnd( + caveatInvert(caveatexpr("first")), + caveatexpr("second"), + ), + ), + }, + }, + { + cwc(caveatexpr("wcaveat1"), csub("1", caveatexpr("first"))), + cwc(caveatexpr("wcaveat2"), csub("1", caveatexpr("second"))), + + // {* - {1}[first]}[wcaveat1] - + // {* - {1}[second]}[wcaveat2] => + // + // The wildcard itself exists if its caveat is true and the caveat on the second wildcard + // is false: + // {* - {1}[first]}[wcaveat1 && !wcaveat2] + // + // The concrete is only produced when the first wildcard is present, the exclusion is + // not, the second wildcard is present, and its exclusion is true: + // {1}[wcaveat1 && !first && wcaveat2 && second] + cwc( + caveatAnd( + caveatexpr("wcaveat1"), + caveatInvert(caveatexpr("wcaveat2")), + ), + + // Note that the exclusion does not rely on the second caveat, because if the first caveat + // is true, then the value is excluded regardless of the second caveat's value. + csub("1", caveatexpr("first")), + ), + []*v1.FoundSubject{ + csub("1", + caveatAnd( + caveatAnd( + caveatAnd( + caveatexpr("wcaveat1"), + caveatexpr("wcaveat2"), + ), + caveatInvert(caveatexpr("first")), + ), + caveatexpr("second"), + ), + ), + }, + }, + } + + for _, tc := range tcs { + t.Run(fmt.Sprintf("%s - %s", format(tc.existing), format(tc.toSubtract)), func(t *testing.T) { + existing := wrap(tc.existing) + + produced, concrete := subtractWildcardFromWildcard[*v1.FoundSubject](existing, tc.toSubtract, subjectSetConstructor) + requireExpectedSubject(t, tc.expected, produced) + requireEquivalentSets(t, tc.expectedConcretes, concrete) + }) + } +} + +func TestSubtractWildcardFromConcrete(t *testing.T) { + tcs := []struct { + existing *v1.FoundSubject + toSubtract *v1.FoundSubject + expected *v1.FoundSubject + }{ + { + sub("1"), + wc(), + + // {1} - {*} => nil + nil, + }, + { + sub("1"), + wc("2"), + + // {1} - {* - {2}} => nil + nil, + }, + { + sub("1"), + wc("1", "2", "3"), + + // {1} - {* - {1, 2, 3}} => {1} + sub("1"), + }, + { + csub("1", caveatexpr("first")), + wc(), + + // {1}[first] - {*} => nil + nil, + }, + { + csub("1", caveatexpr("first")), + cwc(caveatexpr("second")), + + // {1}[first] - {*}[second] => {1[first && !second]} + csub("1", + caveatAnd( + caveatexpr("first"), + caveatInvert(caveatexpr("second")), + ), + ), + }, + { + sub("1"), + cwc(nil, csub("1", caveatexpr("first"))), + + // {1} - {* - {1[first]}} => {1}[first] + csub("1", + caveatexpr("first"), + ), + }, + { + csub("1", caveatexpr("previous")), + cwc(nil, csub("1", caveatexpr("exclusion"))), + + // {1}[previous] - {* - {1[exclusion]}} => {1}[previous && exclusion] + csub("1", + caveatAnd( + caveatexpr("previous"), + caveatexpr("exclusion"), + ), + ), + }, + { + csub("1", caveatexpr("previous")), + cwc(caveatexpr("wcaveat"), csub("1", caveatexpr("exclusion"))), + + // {1}[previous] - {* - {1[exclusion]}}[wcaveat] => {1}[previous && (!wcaveat || exclusion)]] + csub("1", + caveatAnd( + caveatexpr("previous"), + caveatOr( + caveatInvert(caveatexpr("wcaveat")), + caveatexpr("exclusion"), + ), + ), + ), + }, + { + csub("1", caveatexpr("previous")), + cwc(caveatexpr("wcaveat"), csub("2", caveatexpr("exclusion"))), + + // {1}[previous] - {* - {2[exclusion]}}[wcaveat] => {1}[previous && !wcaveat)]] + csub("1", + caveatAnd( + caveatexpr("previous"), + caveatInvert(caveatexpr("wcaveat")), + ), + ), + }, + } + + for _, tc := range tcs { + t.Run(fmt.Sprintf("%v - %v", format(tc.existing), format(tc.toSubtract)), func(t *testing.T) { + produced := subtractWildcardFromConcrete[*v1.FoundSubject](tc.existing, tc.toSubtract, subjectSetConstructor) + requireExpectedSubject(t, tc.expected, produced) + }) + } +} + +func TestSubtractConcreteFromConcrete(t *testing.T) { + tcs := []struct { + existing *v1.FoundSubject + toSubtract *v1.FoundSubject + expected *v1.FoundSubject + }{ + { + sub("1"), + sub("1"), + + // {1} - {1} => nil + nil, + }, + { + csub("1", caveatexpr("first")), + sub("1"), + + // {1[first]} - {1} => nil + nil, + }, + { + sub("1"), + csub("1", caveatexpr("first")), + + // {1} - {1[first]} => {1[!first]} + csub("1", caveatInvert(caveatexpr("first"))), + }, + { + csub("1", caveatexpr("first")), + csub("1", caveatexpr("second")), + + // {1[first]} - {1[second]} => {1[first && !second]} + csub("1", + caveatAnd( + caveatexpr("first"), + caveatInvert(caveatexpr("second")), + ), + ), + }, + } + + for _, tc := range tcs { + t.Run(fmt.Sprintf("%s - %s", format(tc.existing), format(tc.toSubtract)), func(t *testing.T) { + produced := subtractConcreteFromConcrete[*v1.FoundSubject](tc.existing, tc.toSubtract, subjectSetConstructor) + requireExpectedSubject(t, tc.expected, produced) + }) + } +} + +func TestSubtractConcreteFromWildcard(t *testing.T) { + tcs := []struct { + existing *v1.FoundSubject + toSubtract *v1.FoundSubject + expected *v1.FoundSubject + }{ + { + wc(), + sub("1"), + + // {*} - {1} => {* - {1}} + wc("1"), + }, + { + wc("1"), + sub("1"), + + // {* - {1}} - {1} => {* - {1}} + wc("1"), + }, + { + wc("1", "2"), + sub("1"), + + // {* - {1, 2}} - {1} => {* - {1, 2}} + wc("1", "2"), + }, + { + cwc(caveatexpr("wcaveat"), sub("1"), sub("2")), + sub("1"), + + // {* - {1, 2}}[wcaveat] - {1} => {* - {1, 2}}[wcaveat] + cwc(caveatexpr("wcaveat"), sub("1"), sub("2")), + }, + { + cwc(caveatexpr("wcaveat"), csub("1", caveatexpr("first")), sub("2")), + sub("1"), + + // {* - {1[first], 2}}[wcaveat] - {1} => {* - {1, 2}}[wcaveat] + cwc(caveatexpr("wcaveat"), sub("1"), sub("2")), + }, + { + cwc(caveatexpr("wcaveat"), sub("1"), sub("2")), + csub("1", caveatexpr("second")), + + // {* - {1, 2}}[wcaveat] - {1}[first] => {* - {1, 2}}[wcaveat] + cwc(caveatexpr("wcaveat"), sub("1"), sub("2")), + }, + { + cwc(caveatexpr("wcaveat"), csub("1", caveatexpr("first")), sub("2")), + csub("1", caveatexpr("second")), + + // {* - {1[first], 2}}[wcaveat] - {1}[second] => {* - {1[first || second], 2}}[wcaveat] + cwc( + caveatexpr("wcaveat"), + csub("1", + caveatOr( + caveatexpr("first"), + caveatexpr("second"), + ), + ), + sub("2")), + }, + } + + for _, tc := range tcs { + t.Run(fmt.Sprintf("%s - %s", format(tc.existing), format(tc.toSubtract)), func(t *testing.T) { + produced := subtractConcreteFromWildcard[*v1.FoundSubject](tc.existing, tc.toSubtract, subjectSetConstructor) + requireExpectedSubject(t, tc.expected, produced) + }) + } +} + +func TestIntersectConcreteWithConcrete(t *testing.T) { + tcs := []struct { + first *v1.FoundSubject + second *v1.FoundSubject + expected *v1.FoundSubject + }{ + { + sub("1"), + nil, + + // {1} ∩ {} => nil + nil, + }, + { + sub("1"), + sub("1"), + + // {1} ∩ {1} => {1} + sub("1"), + }, + { + csub("1", caveatexpr("first")), + sub("1"), + + // {1[first]} ∩ {1} => {1[first]} + csub("1", caveatexpr("first")), + }, + { + sub("1"), + csub("1", caveatexpr("first")), + + // {1} ∩ {1[first]} => {1[first]} + csub("1", caveatexpr("first")), + }, + { + csub("1", caveatexpr("first")), + csub("1", caveatexpr("second")), + + // {1[first]} ∩ {1[second]} => {1[first && second]} + csub("1", + caveatAnd( + caveatexpr("first"), + caveatexpr("second"), + ), + ), + }, + } + + for _, tc := range tcs { + t.Run(fmt.Sprintf("%s ∩ %s", format(tc.first), format(tc.second)), func(t *testing.T) { + second := wrap(tc.second) + + produced := intersectConcreteWithConcrete[*v1.FoundSubject](tc.first, second, subjectSetConstructor) + requireExpectedSubject(t, tc.expected, produced) + }) + } +} + +func TestIntersectWildcardWithWildcard(t *testing.T) { + tcs := []struct { + first *v1.FoundSubject + second *v1.FoundSubject + + expected *v1.FoundSubject + expectedInverted *v1.FoundSubject + }{ + { + nil, + nil, + + // nil ∩ nil => nil + nil, + nil, + }, + { + wc(), + nil, + + // {*} ∩ nil => nil + nil, + nil, + }, + { + wc(), + wc(), + + // {*} ∩ {*} => {*} + wc(), + wc(), + }, + { + wc("1"), + wc(), + + // {* - {1}} ∩ {*} => {* - {1}} + wc("1"), + wc("1"), + }, + { + wc("1", "2"), + wc("2", "3"), + + // {* - {1,2}} ∩ {* - {2,3}} => {* - {1,2,3}} + wc("1", "2", "3"), + wc("1", "2", "3"), + }, + { + cwc(caveatexpr("first")), + cwc(caveatexpr("second")), + + // {*}[first] ∩ {*}[second] => {*}[first && second] + cwc( + caveatAnd( + caveatexpr("first"), + caveatexpr("second"), + ), + ), + cwc( + caveatAnd( + caveatexpr("second"), + caveatexpr("first"), + ), + ), + }, + { + cwc( + caveatexpr("first"), + csub("1", caveatexpr("ex1cav")), + csub("2", caveatexpr("ex2cav")), + ), + cwc( + caveatexpr("second"), + csub("2", caveatexpr("ex3cav")), + csub("3", caveatexpr("ex4cav")), + ), + + // {* - {1[ex1cav], 2[ex2cav]}}[first] ∩ {* - {2[ex3cav], 3[ex4cav]}}[second] => {* - {1[ex1cav], 2[ex2cav || ex3cav], 3[ex4cav]}}[first && second] + cwc( + caveatAnd( + caveatexpr("first"), + caveatexpr("second"), + ), + csub("1", caveatexpr("ex1cav")), + csub("2", caveatOr(caveatexpr("ex2cav"), caveatexpr("ex3cav"))), + csub("3", caveatexpr("ex4cav")), + ), + cwc( + caveatAnd( + caveatexpr("second"), + caveatexpr("first"), + ), + csub("1", caveatexpr("ex1cav")), + csub("2", caveatOr(caveatexpr("ex3cav"), caveatexpr("ex2cav"))), + csub("3", caveatexpr("ex4cav")), + ), + }, + } + + for _, tc := range tcs { + t.Run(fmt.Sprintf("%s ∩ %s", format(tc.first), format(tc.second)), func(t *testing.T) { + first := wrap(tc.first) + second := wrap(tc.second) + + produced := intersectWildcardWithWildcard[*v1.FoundSubject](first, second, subjectSetConstructor) + requireExpectedSubject(t, tc.expected, produced) + + produced2 := intersectWildcardWithWildcard[*v1.FoundSubject](second, first, subjectSetConstructor) + requireExpectedSubject(t, tc.expectedInverted, produced2) + }) + } +} + +func TestIntersectConcreteWithWildcard(t *testing.T) { + tcs := []struct { + concrete *v1.FoundSubject + wildcard *v1.FoundSubject + + expected *v1.FoundSubject + }{ + { + sub("1"), + nil, + + // 1 ∩ nil => nil + nil, + }, + { + sub("1"), + wc(), + + // 1 ∩ {*} => {1} + sub("1"), + }, + { + sub("1"), + wc("1"), + + // 1 ∩ {* - {1}} => nil + nil, + }, + { + sub("1"), + wc("2"), + + // 1 ∩ {* - {2}} => {1} + sub("1"), + }, + { + sub("1"), + cwc(caveatexpr("wcaveat")), + + // 1 ∩ {*}[wcaveat] => {1}[wcaveat] + csub("1", caveatexpr("wcaveat")), + }, + { + sub("42"), + cwc(nil, csub("42", caveatexpr("first"))), + + // 42 ∩ {* - 42[first]} => {42}[!first] + csub("42", + caveatInvert(caveatexpr("first")), + ), + }, + { + sub("1"), + cwc(caveatexpr("wcaveat"), csub("1", caveatexpr("first"))), + + // 1 ∩ {* - 1[first]}[wcaveat] => {1}[wcaveat && !first] + csub("1", + caveatAnd( + caveatexpr("wcaveat"), + caveatInvert(caveatexpr("first")), + ), + ), + }, + { + csub("1", caveatexpr("first")), + cwc(nil, csub("1", caveatexpr("second"))), + + // 1[first] ∩ {* - 1[second]} => {1}[first && !second] + csub("1", + caveatAnd( + caveatexpr("first"), + caveatInvert(caveatexpr("second")), + ), + ), + }, + { + csub("1", caveatexpr("first")), + cwc(caveatexpr("wcaveat"), csub("1", caveatexpr("second"))), + + // 1[first] ∩ {* - 1[second]}[wcaveat] => {1}[first && !second && wcaveat] + csub("1", + caveatAnd( + caveatexpr("first"), + caveatAnd( + caveatexpr("wcaveat"), + caveatInvert(caveatexpr("second")), + ), + ), + ), + }, + { + csub("1", caveatexpr("first")), + cwc( + caveatexpr("wcaveat"), + csub("1", caveatexpr("second")), + csub("2", caveatexpr("third")), + ), + + // 1[first] ∩ {* - {1[second], 2[third]}}[wcaveat] => {1}[first && !second && wcaveat] + csub("1", + caveatAnd( + caveatexpr("first"), + caveatAnd( + caveatexpr("wcaveat"), + caveatInvert(caveatexpr("second")), + ), + ), + ), + }, + } + + for _, tc := range tcs { + t.Run(fmt.Sprintf("%s ∩ %s", format(tc.concrete), format(tc.wildcard)), func(t *testing.T) { + wildcard := wrap(tc.wildcard) + + produced := intersectConcreteWithWildcard[*v1.FoundSubject](tc.concrete, wildcard, subjectSetConstructor) + requireExpectedSubject(t, tc.expected, produced) + }) + } +} + +func TestCompareSubjects(t *testing.T) { + tcs := []struct { + first *v1.FoundSubject + second *v1.FoundSubject + expectedEquivalent bool + }{ + { + sub("1"), + sub("1"), + true, + }, + { + wc(), + wc(), + true, + }, + { + wc("1"), + wc("1"), + true, + }, + { + wc("1", "2"), + wc("2", "1"), + true, + }, + { + wc("1", "2", "3"), + wc("2", "1"), + false, + }, + { + sub("1"), + sub("2"), + false, + }, + { + csub("1", caveatexpr("first")), + csub("1", caveatexpr("first")), + true, + }, + { + csub("1", caveatexpr("first")), + csub("1", caveatexpr("second")), + false, + }, + { + cwc(caveatexpr("first")), + cwc(caveatexpr("first")), + true, + }, + { + cwc(caveatexpr("first")), + cwc(caveatexpr("second")), + false, + }, + { + cwc(caveatexpr("first"), csub("1", caveatexpr("c1"))), + cwc(caveatexpr("first"), csub("1", caveatexpr("c1"))), + true, + }, + { + cwc(caveatexpr("first"), csub("1", caveatexpr("c1"))), + cwc(caveatexpr("first"), csub("1", caveatexpr("c2"))), + false, + }, + { + cwc( + caveatAnd( + caveatexpr("first"), + caveatexpr("second"), + ), + ), + cwc( + caveatAnd( + caveatexpr("second"), + caveatexpr("first"), + ), + ), + true, + }, + { + cwc( + caveatAnd( + caveatexpr("first"), + caveatexpr("second"), + ), + ), + cwc( + caveatOr( + caveatexpr("second"), + caveatexpr("first"), + ), + ), + false, + }, + { + cwc( + caveatAnd( + caveatexpr("first"), + caveatexpr("first"), + ), + ), + cwc( + caveatexpr("first"), + ), + true, + }, + } + + for _, tc := range tcs { + t.Run(fmt.Sprintf("%s vs %s", format(tc.first), format(tc.second)), func(t *testing.T) { + err := checkEquivalentSubjects(tc.first, tc.second) + if tc.expectedEquivalent { + require.NoError(t, err) + } else { + require.NotNil(t, err) + } + + err = checkEquivalentSets([]*v1.FoundSubject{tc.first}, []*v1.FoundSubject{tc.second}) + if tc.expectedEquivalent { + require.NoError(t, err) + } else { + require.NotNil(t, err) + } + }) + } +} + +// formatSubjects formats the given slice of subjects in a human-readable string. +func formatSubjects(subs []*v1.FoundSubject) string { + formatted := make([]string, 0, len(subs)) + for _, sub := range subs { + formatted = append(formatted, format(sub)) + } + return strings.Join(formatted, ",") +} + +// format formats the given subject (which can be nil) into a human-readable string. +func format(sub *v1.FoundSubject) string { + if sub == nil { + return "[nil]" + } + + if sub.GetSubjectId() == tuple.PublicWildcard { + exclusions := make([]string, 0, len(sub.GetExcludedSubjects())) + for _, excludedSubject := range sub.GetExcludedSubjects() { + exclusions = append(exclusions, format(excludedSubject)) + } + + exclusionsStr := "" + if len(exclusions) > 0 { + exclusionsStr = fmt.Sprintf("- {%s}", strings.Join(exclusions, ",")) + } + + if sub.GetCaveatExpression() != nil { + return fmt.Sprintf("{*%s}[%s]", exclusionsStr, formatCaveatExpr(sub.GetCaveatExpression())) + } + + return fmt.Sprintf("{*%s}", exclusionsStr) + } + + if sub.GetCaveatExpression() != nil { + return fmt.Sprintf("%s[%s]", sub.GetSubjectId(), formatCaveatExpr(sub.GetCaveatExpression())) + } + + return sub.GetSubjectId() +} + +// formatCaveatExpr formats a caveat expression (which can be nil) into a human readable string. +func formatCaveatExpr(expr *v1.CaveatExpression) string { + if expr == nil { + return "[nil]" + } + + if expr.GetCaveat() != nil { + return expr.GetCaveat().CaveatName + } + + switch expr.GetOperation().Op { + case v1.CaveatOperation_AND: + return fmt.Sprintf("(%s) && (%s)", + formatCaveatExpr(expr.GetOperation().GetChildren()[0]), + formatCaveatExpr(expr.GetOperation().GetChildren()[1]), + ) + + case v1.CaveatOperation_OR: + return fmt.Sprintf("(%s) || (%s)", + formatCaveatExpr(expr.GetOperation().GetChildren()[0]), + formatCaveatExpr(expr.GetOperation().GetChildren()[1]), + ) + + case v1.CaveatOperation_NOT: + return fmt.Sprintf("!(%s)", + formatCaveatExpr(expr.GetOperation().GetChildren()[0]), + ) + + default: + panic("unknown op") + } +} + +// wrap wraps the given subject into a pointer to it, unless nil, in which case this method returns +// nil. +func wrap(sub *v1.FoundSubject) **v1.FoundSubject { + if sub == nil { + return nil + } + + return &sub +} + +// requireEquivalentSets requires that the given sets of subjects are equivalent. +func requireEquivalentSets(t *testing.T, expected []*v1.FoundSubject, found []*v1.FoundSubject) { + err := checkEquivalentSets(expected, found) + require.NoError(t, err, "found different subject sets: %v", err) +} + +// requireExpectedSubject requires that the given expected and produced subjects match. +func requireExpectedSubject(t *testing.T, expected *v1.FoundSubject, produced **v1.FoundSubject) { + if expected == nil { + require.Nil(t, produced) + } else { + require.NotNil(t, produced) + + found := *produced + err := checkEquivalentSubjects(expected, found) + require.NoError(t, err, "found different subjects: %v", err) + } +} + +// checkEquivalentSets checks if the sets of subjects are equivalent and returns an error if they are not. +func checkEquivalentSets(expected []*v1.FoundSubject, found []*v1.FoundSubject) error { + if len(expected) != len(found) { + return fmt.Errorf("found mismatch in number of elements:\n\texpected: %s\n\tfound: %s", formatSubjects(expected), formatSubjects(found)) + } + + sort.Sort(sortByID(expected)) + sort.Sort(sortByID(found)) + + for index := range expected { + err := checkEquivalentSubjects(expected[index], found[index]) + if err != nil { + return fmt.Errorf("found mismatch for subject #%d: %w", index, err) + } + } + + return nil +} + +// checkEquivalentSubjects checks if the given subjects are equivalent and returns an error if they are not. +func checkEquivalentSubjects(expected *v1.FoundSubject, found *v1.FoundSubject) error { + if expected.SubjectId != found.SubjectId { + return fmt.Errorf("expected subject %s, found %s", expected.SubjectId, found.SubjectId) + } + + err := checkEquivalentSets(expected.ExcludedSubjects, found.ExcludedSubjects) + if err != nil { + return fmt.Errorf("difference in exclusions: %w", err) + } + + return checkEquivalentCaveatExprs(expected.CaveatExpression, found.CaveatExpression) +} + +// checkEquivalentCaveatExprs checks if the given caveat expressions are equivalent and returns an error if they are not. +func checkEquivalentCaveatExprs(expected *v1.CaveatExpression, found *v1.CaveatExpression) error { + if expected == nil { + if found != nil { + return fmt.Errorf("found non-nil caveat expression `%s` where expected nil", formatCaveatExpr(found)) + } + return nil + } + + if found == nil { + if expected != nil { + return fmt.Errorf("expected non-nil caveat expression `%s` where found nil", formatCaveatExpr(expected)) + } + return nil + } + + // Caveat expressions that the subjectset generates can be different in structure but *logically* equivalent, + // so we compare by building a boolean table for each referenced caveat name and then checking all combinations + // of boolean inputs to ensure the expressions produce the same output. Note that while this isn't the most + // efficient means of comparison, it is logically correct. + referencedNamesSet := NewSet[string]() + collectReferencedNames(expected, referencedNamesSet) + collectReferencedNames(found, referencedNamesSet) + + referencedNames := referencedNamesSet.AsSlice() + for _, values := range combinatorialValues(referencedNames) { + expectedResult := executeCaveatExprForTesting(expected, values) + foundResult := executeCaveatExprForTesting(found, values) + if expectedResult != foundResult { + return fmt.Errorf("found difference between caveats for values:\n\tvalues: %v\n\texpected caveat: %s\n\tfound caveat:%s", values, formatCaveatExpr(expected), formatCaveatExpr(found)) + } + } + return nil +} + +// executeCaveatExprForTesting "executes" the given caveat expression for testing. DO NOT USE OUTSIDE OF TESTING. +// This method *ignores* caveat context and treats each caveat as just its name. +func executeCaveatExprForTesting(expr *v1.CaveatExpression, values map[string]bool) bool { + if expr.GetCaveat() != nil { + return values[expr.GetCaveat().CaveatName] + } + + switch expr.GetOperation().Op { + case v1.CaveatOperation_AND: + if len(expr.GetOperation().Children) != 2 { + panic("found invalid child count for AND") + } + return executeCaveatExprForTesting(expr.GetOperation().Children[0], values) && executeCaveatExprForTesting(expr.GetOperation().Children[1], values) + + case v1.CaveatOperation_OR: + if len(expr.GetOperation().Children) != 2 { + panic("found invalid child count for OR") + } + return executeCaveatExprForTesting(expr.GetOperation().Children[0], values) || executeCaveatExprForTesting(expr.GetOperation().Children[1], values) + + case v1.CaveatOperation_NOT: + if len(expr.GetOperation().Children) != 1 { + panic("found invalid child count for NOT") + } + return !executeCaveatExprForTesting(expr.GetOperation().Children[0], values) + + default: + panic("unknown op") + } +} + +// combinatorialValues returns the combinatorial set of values where each name is either true and false. +func combinatorialValues(names []string) []map[string]bool { + if len(names) == 0 { + return nil + } + + name := names[0] + childMaps := combinatorialValues(names[1:]) + + cmaps := make([]map[string]bool, 0, len(childMaps)*2) + if len(childMaps) == 0 { + for _, v := range []bool{true, false} { + cloned := map[string]bool{} + cloned[name] = v + cmaps = append(cmaps, cloned) + } + } else { + for _, childMap := range childMaps { + for _, v := range []bool{true, false} { + cloned := maps.Clone(childMap) + cloned[name] = v + cmaps = append(cmaps, cloned) + } + } + } + + return cmaps +} + +// collectReferencedNames collects all referenced caveat names into the given set. +func collectReferencedNames(expr *v1.CaveatExpression, nameSet *Set[string]) { + if expr.GetCaveat() != nil { + nameSet.Add(expr.GetCaveat().CaveatName) + return + } + + for _, child := range expr.GetOperation().GetChildren() { + collectReferencedNames(child, nameSet) + } +} + +// allSubsets returns a list of all subsets of length n +// it counts in binary and "activates" input funcs that match 1s in the binary representation +// it doesn't check for overflow so don't go crazy +func allSubsets[T any](objs []T, n int) [][]T { + maxInt := uint(math.Exp2(float64(len(objs)))) - 1 + all := make([][]T, 0) + + for i := uint(0); i < maxInt; i++ { + set := make([]T, 0, n) + for digit := uint(0); digit < uint(len(objs)); digit++ { + mask := uint(1) << digit + if mask&i != 0 { + set = append(set, objs[digit]) + } + if len(set) > n { + break + } + } + if len(set) == n { + all = append(all, set) + } + } + return all +} + +type sortByID []*v1.FoundSubject + +func (a sortByID) Len() int { return len(a) } +func (a sortByID) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a sortByID) Less(i, j int) bool { return strings.Compare(a[i].SubjectId, a[j].SubjectId) < 0 } diff --git a/pkg/proto/core/v1/core_vtproto.pb.go b/pkg/proto/core/v1/core_vtproto.pb.go index d281e2aa10..1a4cc9119a 100644 --- a/pkg/proto/core/v1/core_vtproto.pb.go +++ b/pkg/proto/core/v1/core_vtproto.pb.go @@ -810,6 +810,982 @@ func (m *SourcePosition) CloneGenericVT() proto.Message { return m.CloneVT() } +func (this *RelationTuple) EqualVT(that *RelationTuple) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if !this.ResourceAndRelation.EqualVT(that.ResourceAndRelation) { + return false + } + if !this.Subject.EqualVT(that.Subject) { + return false + } + if !this.Caveat.EqualVT(that.Caveat) { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *ContextualizedCaveat) EqualVT(that *ContextualizedCaveat) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.CaveatName != that.CaveatName { + return false + } + if equal, ok := interface{}(this.Context).(interface{ EqualVT(*structpb.Struct) bool }); ok { + if !equal.EqualVT(that.Context) { + return false + } + } else if !proto.Equal(this.Context, that.Context) { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *CaveatDefinition) EqualVT(that *CaveatDefinition) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.Name != that.Name { + return false + } + if string(this.SerializedExpression) != string(that.SerializedExpression) { + return false + } + if len(this.ParameterTypes) != len(that.ParameterTypes) { + return false + } + for i, vx := range this.ParameterTypes { + vy, ok := that.ParameterTypes[i] + if !ok { + return false + } + if p, q := vx, vy; p != q { + if p == nil { + p = &CaveatTypeReference{} + } + if q == nil { + q = &CaveatTypeReference{} + } + if !p.EqualVT(q) { + return false + } + } + } + if !this.Metadata.EqualVT(that.Metadata) { + return false + } + if !this.SourcePosition.EqualVT(that.SourcePosition) { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *CaveatTypeReference) EqualVT(that *CaveatTypeReference) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.TypeName != that.TypeName { + return false + } + if len(this.ChildTypes) != len(that.ChildTypes) { + return false + } + for i, vx := range this.ChildTypes { + vy := that.ChildTypes[i] + if p, q := vx, vy; p != q { + if p == nil { + p = &CaveatTypeReference{} + } + if q == nil { + q = &CaveatTypeReference{} + } + if !p.EqualVT(q) { + return false + } + } + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *ObjectAndRelation) EqualVT(that *ObjectAndRelation) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.Namespace != that.Namespace { + return false + } + if this.ObjectId != that.ObjectId { + return false + } + if this.Relation != that.Relation { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *RelationReference) EqualVT(that *RelationReference) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.Namespace != that.Namespace { + return false + } + if this.Relation != that.Relation { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *Zookie) EqualVT(that *Zookie) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.Token != that.Token { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *RelationTupleUpdate) EqualVT(that *RelationTupleUpdate) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.Operation != that.Operation { + return false + } + if !this.Tuple.EqualVT(that.Tuple) { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *RelationTupleTreeNode) EqualVT(that *RelationTupleTreeNode) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.NodeType == nil && that.NodeType != nil { + return false + } else if this.NodeType != nil { + if that.NodeType == nil { + return false + } + if !this.NodeType.(interface { + EqualVT(isRelationTupleTreeNode_NodeType) bool + }).EqualVT(that.NodeType) { + return false + } + } + if !this.Expanded.EqualVT(that.Expanded) { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *RelationTupleTreeNode_IntermediateNode) EqualVT(thatIface isRelationTupleTreeNode_NodeType) bool { + that, ok := thatIface.(*RelationTupleTreeNode_IntermediateNode) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if p, q := this.IntermediateNode, that.IntermediateNode; p != q { + if p == nil { + p = &SetOperationUserset{} + } + if q == nil { + q = &SetOperationUserset{} + } + if !p.EqualVT(q) { + return false + } + } + return true +} + +func (this *RelationTupleTreeNode_LeafNode) EqualVT(thatIface isRelationTupleTreeNode_NodeType) bool { + that, ok := thatIface.(*RelationTupleTreeNode_LeafNode) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if p, q := this.LeafNode, that.LeafNode; p != q { + if p == nil { + p = &DirectSubjects{} + } + if q == nil { + q = &DirectSubjects{} + } + if !p.EqualVT(q) { + return false + } + } + return true +} + +func (this *SetOperationUserset) EqualVT(that *SetOperationUserset) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.Operation != that.Operation { + return false + } + if len(this.ChildNodes) != len(that.ChildNodes) { + return false + } + for i, vx := range this.ChildNodes { + vy := that.ChildNodes[i] + if p, q := vx, vy; p != q { + if p == nil { + p = &RelationTupleTreeNode{} + } + if q == nil { + q = &RelationTupleTreeNode{} + } + if !p.EqualVT(q) { + return false + } + } + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *DirectSubjects) EqualVT(that *DirectSubjects) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if len(this.Subjects) != len(that.Subjects) { + return false + } + for i, vx := range this.Subjects { + vy := that.Subjects[i] + if p, q := vx, vy; p != q { + if p == nil { + p = &ObjectAndRelation{} + } + if q == nil { + q = &ObjectAndRelation{} + } + if !p.EqualVT(q) { + return false + } + } + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *Metadata) EqualVT(that *Metadata) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if len(this.MetadataMessage) != len(that.MetadataMessage) { + return false + } + for i, vx := range this.MetadataMessage { + vy := that.MetadataMessage[i] + if p, q := vx, vy; p != q { + if p == nil { + p = &anypb.Any{} + } + if q == nil { + q = &anypb.Any{} + } + if equal, ok := interface{}(p).(interface{ EqualVT(*anypb.Any) bool }); ok { + if !equal.EqualVT(q) { + return false + } + } else if !proto.Equal(p, q) { + return false + } + } + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *NamespaceDefinition) EqualVT(that *NamespaceDefinition) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.Name != that.Name { + return false + } + if len(this.Relation) != len(that.Relation) { + return false + } + for i, vx := range this.Relation { + vy := that.Relation[i] + if p, q := vx, vy; p != q { + if p == nil { + p = &Relation{} + } + if q == nil { + q = &Relation{} + } + if !p.EqualVT(q) { + return false + } + } + } + if !this.Metadata.EqualVT(that.Metadata) { + return false + } + if !this.SourcePosition.EqualVT(that.SourcePosition) { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *Relation) EqualVT(that *Relation) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.Name != that.Name { + return false + } + if !this.UsersetRewrite.EqualVT(that.UsersetRewrite) { + return false + } + if !this.TypeInformation.EqualVT(that.TypeInformation) { + return false + } + if !this.Metadata.EqualVT(that.Metadata) { + return false + } + if !this.SourcePosition.EqualVT(that.SourcePosition) { + return false + } + if this.AliasingRelation != that.AliasingRelation { + return false + } + if this.CanonicalCacheKey != that.CanonicalCacheKey { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *ReachabilityGraph) EqualVT(that *ReachabilityGraph) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if len(this.EntrypointsBySubjectType) != len(that.EntrypointsBySubjectType) { + return false + } + for i, vx := range this.EntrypointsBySubjectType { + vy, ok := that.EntrypointsBySubjectType[i] + if !ok { + return false + } + if p, q := vx, vy; p != q { + if p == nil { + p = &ReachabilityEntrypoints{} + } + if q == nil { + q = &ReachabilityEntrypoints{} + } + if !p.EqualVT(q) { + return false + } + } + } + if len(this.EntrypointsBySubjectRelation) != len(that.EntrypointsBySubjectRelation) { + return false + } + for i, vx := range this.EntrypointsBySubjectRelation { + vy, ok := that.EntrypointsBySubjectRelation[i] + if !ok { + return false + } + if p, q := vx, vy; p != q { + if p == nil { + p = &ReachabilityEntrypoints{} + } + if q == nil { + q = &ReachabilityEntrypoints{} + } + if !p.EqualVT(q) { + return false + } + } + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *ReachabilityEntrypoints) EqualVT(that *ReachabilityEntrypoints) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if len(this.Entrypoints) != len(that.Entrypoints) { + return false + } + for i, vx := range this.Entrypoints { + vy := that.Entrypoints[i] + if p, q := vx, vy; p != q { + if p == nil { + p = &ReachabilityEntrypoint{} + } + if q == nil { + q = &ReachabilityEntrypoint{} + } + if !p.EqualVT(q) { + return false + } + } + } + if this.SubjectType != that.SubjectType { + return false + } + if !this.SubjectRelation.EqualVT(that.SubjectRelation) { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *ReachabilityEntrypoint) EqualVT(that *ReachabilityEntrypoint) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.Kind != that.Kind { + return false + } + if !this.TargetRelation.EqualVT(that.TargetRelation) { + return false + } + if this.ResultStatus != that.ResultStatus { + return false + } + if this.TuplesetRelation != that.TuplesetRelation { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *TypeInformation) EqualVT(that *TypeInformation) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if len(this.AllowedDirectRelations) != len(that.AllowedDirectRelations) { + return false + } + for i, vx := range this.AllowedDirectRelations { + vy := that.AllowedDirectRelations[i] + if p, q := vx, vy; p != q { + if p == nil { + p = &AllowedRelation{} + } + if q == nil { + q = &AllowedRelation{} + } + if !p.EqualVT(q) { + return false + } + } + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *AllowedRelation_PublicWildcard) EqualVT(that *AllowedRelation_PublicWildcard) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *AllowedRelation) EqualVT(that *AllowedRelation) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.RelationOrWildcard == nil && that.RelationOrWildcard != nil { + return false + } else if this.RelationOrWildcard != nil { + if that.RelationOrWildcard == nil { + return false + } + if !this.RelationOrWildcard.(interface { + EqualVT(isAllowedRelation_RelationOrWildcard) bool + }).EqualVT(that.RelationOrWildcard) { + return false + } + } + if this.Namespace != that.Namespace { + return false + } + if !this.SourcePosition.EqualVT(that.SourcePosition) { + return false + } + if !this.RequiredCaveat.EqualVT(that.RequiredCaveat) { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *AllowedRelation_Relation) EqualVT(thatIface isAllowedRelation_RelationOrWildcard) bool { + that, ok := thatIface.(*AllowedRelation_Relation) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if this.Relation != that.Relation { + return false + } + return true +} + +func (this *AllowedRelation_PublicWildcard_) EqualVT(thatIface isAllowedRelation_RelationOrWildcard) bool { + that, ok := thatIface.(*AllowedRelation_PublicWildcard_) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if p, q := this.PublicWildcard, that.PublicWildcard; p != q { + if p == nil { + p = &AllowedRelation_PublicWildcard{} + } + if q == nil { + q = &AllowedRelation_PublicWildcard{} + } + if !p.EqualVT(q) { + return false + } + } + return true +} + +func (this *AllowedCaveat) EqualVT(that *AllowedCaveat) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.CaveatName != that.CaveatName { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *UsersetRewrite) EqualVT(that *UsersetRewrite) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.RewriteOperation == nil && that.RewriteOperation != nil { + return false + } else if this.RewriteOperation != nil { + if that.RewriteOperation == nil { + return false + } + if !this.RewriteOperation.(interface { + EqualVT(isUsersetRewrite_RewriteOperation) bool + }).EqualVT(that.RewriteOperation) { + return false + } + } + if !this.SourcePosition.EqualVT(that.SourcePosition) { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *UsersetRewrite_Union) EqualVT(thatIface isUsersetRewrite_RewriteOperation) bool { + that, ok := thatIface.(*UsersetRewrite_Union) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if p, q := this.Union, that.Union; p != q { + if p == nil { + p = &SetOperation{} + } + if q == nil { + q = &SetOperation{} + } + if !p.EqualVT(q) { + return false + } + } + return true +} + +func (this *UsersetRewrite_Intersection) EqualVT(thatIface isUsersetRewrite_RewriteOperation) bool { + that, ok := thatIface.(*UsersetRewrite_Intersection) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if p, q := this.Intersection, that.Intersection; p != q { + if p == nil { + p = &SetOperation{} + } + if q == nil { + q = &SetOperation{} + } + if !p.EqualVT(q) { + return false + } + } + return true +} + +func (this *UsersetRewrite_Exclusion) EqualVT(thatIface isUsersetRewrite_RewriteOperation) bool { + that, ok := thatIface.(*UsersetRewrite_Exclusion) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if p, q := this.Exclusion, that.Exclusion; p != q { + if p == nil { + p = &SetOperation{} + } + if q == nil { + q = &SetOperation{} + } + if !p.EqualVT(q) { + return false + } + } + return true +} + +func (this *SetOperation_Child_This) EqualVT(that *SetOperation_Child_This) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *SetOperation_Child_Nil) EqualVT(that *SetOperation_Child_Nil) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *SetOperation_Child) EqualVT(that *SetOperation_Child) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.ChildType == nil && that.ChildType != nil { + return false + } else if this.ChildType != nil { + if that.ChildType == nil { + return false + } + if !this.ChildType.(interface { + EqualVT(isSetOperation_Child_ChildType) bool + }).EqualVT(that.ChildType) { + return false + } + } + if !this.SourcePosition.EqualVT(that.SourcePosition) { + return false + } + if len(this.OperationPath) != len(that.OperationPath) { + return false + } + for i, vx := range this.OperationPath { + vy := that.OperationPath[i] + if vx != vy { + return false + } + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *SetOperation_Child_XThis) EqualVT(thatIface isSetOperation_Child_ChildType) bool { + that, ok := thatIface.(*SetOperation_Child_XThis) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if p, q := this.XThis, that.XThis; p != q { + if p == nil { + p = &SetOperation_Child_This{} + } + if q == nil { + q = &SetOperation_Child_This{} + } + if !p.EqualVT(q) { + return false + } + } + return true +} + +func (this *SetOperation_Child_ComputedUserset) EqualVT(thatIface isSetOperation_Child_ChildType) bool { + that, ok := thatIface.(*SetOperation_Child_ComputedUserset) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if p, q := this.ComputedUserset, that.ComputedUserset; p != q { + if p == nil { + p = &ComputedUserset{} + } + if q == nil { + q = &ComputedUserset{} + } + if !p.EqualVT(q) { + return false + } + } + return true +} + +func (this *SetOperation_Child_TupleToUserset) EqualVT(thatIface isSetOperation_Child_ChildType) bool { + that, ok := thatIface.(*SetOperation_Child_TupleToUserset) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if p, q := this.TupleToUserset, that.TupleToUserset; p != q { + if p == nil { + p = &TupleToUserset{} + } + if q == nil { + q = &TupleToUserset{} + } + if !p.EqualVT(q) { + return false + } + } + return true +} + +func (this *SetOperation_Child_UsersetRewrite) EqualVT(thatIface isSetOperation_Child_ChildType) bool { + that, ok := thatIface.(*SetOperation_Child_UsersetRewrite) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if p, q := this.UsersetRewrite, that.UsersetRewrite; p != q { + if p == nil { + p = &UsersetRewrite{} + } + if q == nil { + q = &UsersetRewrite{} + } + if !p.EqualVT(q) { + return false + } + } + return true +} + +func (this *SetOperation_Child_XNil) EqualVT(thatIface isSetOperation_Child_ChildType) bool { + that, ok := thatIface.(*SetOperation_Child_XNil) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if p, q := this.XNil, that.XNil; p != q { + if p == nil { + p = &SetOperation_Child_Nil{} + } + if q == nil { + q = &SetOperation_Child_Nil{} + } + if !p.EqualVT(q) { + return false + } + } + return true +} + +func (this *SetOperation) EqualVT(that *SetOperation) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if len(this.Child) != len(that.Child) { + return false + } + for i, vx := range this.Child { + vy := that.Child[i] + if p, q := vx, vy; p != q { + if p == nil { + p = &SetOperation_Child{} + } + if q == nil { + q = &SetOperation_Child{} + } + if !p.EqualVT(q) { + return false + } + } + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *TupleToUserset_Tupleset) EqualVT(that *TupleToUserset_Tupleset) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.Relation != that.Relation { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *TupleToUserset) EqualVT(that *TupleToUserset) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if !this.Tupleset.EqualVT(that.Tupleset) { + return false + } + if !this.ComputedUserset.EqualVT(that.ComputedUserset) { + return false + } + if !this.SourcePosition.EqualVT(that.SourcePosition) { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *ComputedUserset) EqualVT(that *ComputedUserset) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.Object != that.Object { + return false + } + if this.Relation != that.Relation { + return false + } + if !this.SourcePosition.EqualVT(that.SourcePosition) { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *SourcePosition) EqualVT(that *SourcePosition) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.ZeroIndexedLineNumber != that.ZeroIndexedLineNumber { + return false + } + if this.ZeroIndexedColumnPosition != that.ZeroIndexedColumnPosition { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + func (m *RelationTuple) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil diff --git a/pkg/proto/developer/v1/developer_vtproto.pb.go b/pkg/proto/developer/v1/developer_vtproto.pb.go index faf1f49d26..dcd30c35ff 100644 --- a/pkg/proto/developer/v1/developer_vtproto.pb.go +++ b/pkg/proto/developer/v1/developer_vtproto.pb.go @@ -380,6 +380,367 @@ func (m *FormatSchemaResult) CloneGenericVT() proto.Message { return m.CloneVT() } +func (this *DeveloperRequest) EqualVT(that *DeveloperRequest) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if !this.Context.EqualVT(that.Context) { + return false + } + if len(this.Operations) != len(that.Operations) { + return false + } + for i, vx := range this.Operations { + vy := that.Operations[i] + if p, q := vx, vy; p != q { + if p == nil { + p = &Operation{} + } + if q == nil { + q = &Operation{} + } + if !p.EqualVT(q) { + return false + } + } + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *DeveloperResponse) EqualVT(that *DeveloperResponse) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.InternalError != that.InternalError { + return false + } + if !this.DeveloperErrors.EqualVT(that.DeveloperErrors) { + return false + } + if !this.OperationsResults.EqualVT(that.OperationsResults) { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *RequestContext) EqualVT(that *RequestContext) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.Schema != that.Schema { + return false + } + if len(this.Relationships) != len(that.Relationships) { + return false + } + for i, vx := range this.Relationships { + vy := that.Relationships[i] + if p, q := vx, vy; p != q { + if p == nil { + p = &v1.RelationTuple{} + } + if q == nil { + q = &v1.RelationTuple{} + } + if equal, ok := interface{}(p).(interface{ EqualVT(*v1.RelationTuple) bool }); ok { + if !equal.EqualVT(q) { + return false + } + } else if !proto.Equal(p, q) { + return false + } + } + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *Operation) EqualVT(that *Operation) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if !this.CheckParameters.EqualVT(that.CheckParameters) { + return false + } + if !this.AssertionsParameters.EqualVT(that.AssertionsParameters) { + return false + } + if !this.ValidationParameters.EqualVT(that.ValidationParameters) { + return false + } + if !this.FormatSchemaParameters.EqualVT(that.FormatSchemaParameters) { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *OperationsResults) EqualVT(that *OperationsResults) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if len(this.Results) != len(that.Results) { + return false + } + for i, vx := range this.Results { + vy, ok := that.Results[i] + if !ok { + return false + } + if p, q := vx, vy; p != q { + if p == nil { + p = &OperationResult{} + } + if q == nil { + q = &OperationResult{} + } + if !p.EqualVT(q) { + return false + } + } + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *OperationResult) EqualVT(that *OperationResult) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if !this.CheckResult.EqualVT(that.CheckResult) { + return false + } + if !this.AssertionsResult.EqualVT(that.AssertionsResult) { + return false + } + if !this.ValidationResult.EqualVT(that.ValidationResult) { + return false + } + if !this.FormatSchemaResult.EqualVT(that.FormatSchemaResult) { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *DeveloperError) EqualVT(that *DeveloperError) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.Message != that.Message { + return false + } + if this.Line != that.Line { + return false + } + if this.Column != that.Column { + return false + } + if this.Source != that.Source { + return false + } + if this.Kind != that.Kind { + return false + } + if len(this.Path) != len(that.Path) { + return false + } + for i, vx := range this.Path { + vy := that.Path[i] + if vx != vy { + return false + } + } + if this.Context != that.Context { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *DeveloperErrors) EqualVT(that *DeveloperErrors) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if len(this.InputErrors) != len(that.InputErrors) { + return false + } + for i, vx := range this.InputErrors { + vy := that.InputErrors[i] + if p, q := vx, vy; p != q { + if p == nil { + p = &DeveloperError{} + } + if q == nil { + q = &DeveloperError{} + } + if !p.EqualVT(q) { + return false + } + } + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *CheckOperationParameters) EqualVT(that *CheckOperationParameters) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if equal, ok := interface{}(this.Resource).(interface { + EqualVT(*v1.ObjectAndRelation) bool + }); ok { + if !equal.EqualVT(that.Resource) { + return false + } + } else if !proto.Equal(this.Resource, that.Resource) { + return false + } + if equal, ok := interface{}(this.Subject).(interface { + EqualVT(*v1.ObjectAndRelation) bool + }); ok { + if !equal.EqualVT(that.Subject) { + return false + } + } else if !proto.Equal(this.Subject, that.Subject) { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *CheckOperationsResult) EqualVT(that *CheckOperationsResult) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.Membership != that.Membership { + return false + } + if !this.CheckError.EqualVT(that.CheckError) { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *RunAssertionsParameters) EqualVT(that *RunAssertionsParameters) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.AssertionsYaml != that.AssertionsYaml { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *RunAssertionsResult) EqualVT(that *RunAssertionsResult) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if !this.InputError.EqualVT(that.InputError) { + return false + } + if len(this.ValidationErrors) != len(that.ValidationErrors) { + return false + } + for i, vx := range this.ValidationErrors { + vy := that.ValidationErrors[i] + if p, q := vx, vy; p != q { + if p == nil { + p = &DeveloperError{} + } + if q == nil { + q = &DeveloperError{} + } + if !p.EqualVT(q) { + return false + } + } + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *RunValidationParameters) EqualVT(that *RunValidationParameters) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.ValidationYaml != that.ValidationYaml { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *RunValidationResult) EqualVT(that *RunValidationResult) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if !this.InputError.EqualVT(that.InputError) { + return false + } + if this.UpdatedValidationYaml != that.UpdatedValidationYaml { + return false + } + if len(this.ValidationErrors) != len(that.ValidationErrors) { + return false + } + for i, vx := range this.ValidationErrors { + vy := that.ValidationErrors[i] + if p, q := vx, vy; p != q { + if p == nil { + p = &DeveloperError{} + } + if q == nil { + q = &DeveloperError{} + } + if !p.EqualVT(q) { + return false + } + } + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *FormatSchemaParameters) EqualVT(that *FormatSchemaParameters) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *FormatSchemaResult) EqualVT(that *FormatSchemaResult) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.FormattedSchema != that.FormattedSchema { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + func (m *DeveloperRequest) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil diff --git a/pkg/proto/dispatch/v1/dispatch.pb.go b/pkg/proto/dispatch/v1/dispatch.pb.go index 348ab2038f..011cb29a89 100644 --- a/pkg/proto/dispatch/v1/dispatch.pb.go +++ b/pkg/proto/dispatch/v1/dispatch.pb.go @@ -1230,8 +1230,9 @@ type FoundSubject struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - SubjectId string `protobuf:"bytes,1,opt,name=subject_id,json=subjectId,proto3" json:"subject_id,omitempty"` - ExcludedSubjectIds []string `protobuf:"bytes,2,rep,name=excluded_subject_ids,json=excludedSubjectIds,proto3" json:"excluded_subject_ids,omitempty"` + SubjectId string `protobuf:"bytes,1,opt,name=subject_id,json=subjectId,proto3" json:"subject_id,omitempty"` + CaveatExpression *CaveatExpression `protobuf:"bytes,2,opt,name=caveat_expression,json=caveatExpression,proto3" json:"caveat_expression,omitempty"` + ExcludedSubjects []*FoundSubject `protobuf:"bytes,3,rep,name=excluded_subjects,json=excludedSubjects,proto3" json:"excluded_subjects,omitempty"` } func (x *FoundSubject) Reset() { @@ -1273,9 +1274,16 @@ func (x *FoundSubject) GetSubjectId() string { return "" } -func (x *FoundSubject) GetExcludedSubjectIds() []string { +func (x *FoundSubject) GetCaveatExpression() *CaveatExpression { if x != nil { - return x.ExcludedSubjectIds + return x.CaveatExpression + } + return nil +} + +func (x *FoundSubject) GetExcludedSubjects() []*FoundSubject { + if x != nil { + return x.ExcludedSubjects } return nil } @@ -1814,126 +1822,132 @@ var file_dispatch_v1_dispatch_proto_rawDesc = []byte{ 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x22, 0x5f, 0x0a, 0x0c, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x64, 0x12, - 0x30, 0x0a, 0x14, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x5f, 0x73, 0x75, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x65, - 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x64, - 0x73, 0x22, 0x99, 0x01, 0x0a, 0x1e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, - 0x6f, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x0e, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x73, 0x75, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x64, - 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6f, 0x75, 0x6e, 0x64, - 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x0d, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x75, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x35, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, - 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, - 0x65, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x7d, 0x0a, - 0x0c, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x3b, 0x0a, - 0x0b, 0x61, 0x74, 0x5f, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x42, 0x1a, 0xfa, 0x42, 0x17, 0x72, 0x15, 0x32, 0x13, 0x5e, 0x5b, 0x30, 0x2d, 0x39, - 0x5d, 0x2b, 0x28, 0x5c, 0x2e, 0x5b, 0x30, 0x2d, 0x39, 0x5d, 0x2b, 0x29, 0x3f, 0x24, 0x52, 0x0a, - 0x61, 0x74, 0x52, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x30, 0x0a, 0x0f, 0x64, 0x65, - 0x70, 0x74, 0x68, 0x5f, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0d, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x2a, 0x02, 0x20, 0x00, 0x52, 0x0e, 0x64, 0x65, - 0x70, 0x74, 0x68, 0x52, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x22, 0xda, 0x01, 0x0a, - 0x0c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x25, 0x0a, - 0x0e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x65, 0x70, 0x74, 0x68, 0x5f, 0x72, 0x65, - 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x64, 0x65, - 0x70, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x63, - 0x61, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x13, 0x63, 0x61, 0x63, 0x68, - 0x65, 0x64, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x3c, 0x0a, 0x0a, 0x64, 0x65, 0x62, 0x75, 0x67, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, - 0x31, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x09, 0x64, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x4a, 0x04, 0x08, - 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x22, 0x46, 0x0a, 0x10, 0x44, 0x65, 0x62, - 0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x0a, - 0x05, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x64, - 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, - 0x44, 0x65, 0x62, 0x75, 0x67, 0x54, 0x72, 0x61, 0x63, 0x65, 0x52, 0x05, 0x63, 0x68, 0x65, 0x63, - 0x6b, 0x22, 0xf8, 0x03, 0x0a, 0x0f, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x44, 0x65, 0x62, 0x75, 0x67, - 0x54, 0x72, 0x61, 0x63, 0x65, 0x12, 0x3b, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, - 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x43, 0x68, 0x65, - 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x5f, 0x0a, 0x16, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x72, - 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x44, 0x65, 0x62, 0x75, 0x67, 0x54, 0x72, 0x61, 0x63, 0x65, - 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x14, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, - 0x79, 0x70, 0x65, 0x12, 0x43, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x03, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, - 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x44, 0x65, 0x62, 0x75, 0x67, 0x54, 0x72, 0x61, - 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, - 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x69, 0x73, 0x5f, 0x63, - 0x61, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x0e, 0x69, 0x73, 0x43, 0x61, 0x63, 0x68, 0x65, 0x64, 0x52, 0x65, 0x73, 0x75, - 0x6c, 0x74, 0x12, 0x3f, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x5f, 0x70, 0x72, 0x6f, 0x62, 0x6c, 0x65, - 0x6d, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, - 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x44, 0x65, 0x62, 0x75, - 0x67, 0x54, 0x72, 0x61, 0x63, 0x65, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x50, 0x72, 0x6f, 0x62, 0x6c, - 0x65, 0x6d, 0x73, 0x1a, 0x5c, 0x0a, 0x0c, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x36, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, - 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x22, 0x39, 0x0a, 0x0c, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, - 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0c, - 0x0a, 0x08, 0x52, 0x45, 0x4c, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, - 0x50, 0x45, 0x52, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x02, 0x32, 0xa0, 0x04, 0x0a, - 0x0f, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x12, 0x58, 0x0a, 0x0d, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x43, 0x68, 0x65, 0x63, - 0x6b, 0x12, 0x21, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, - 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, + 0x22, 0xc1, 0x01, 0x0a, 0x0c, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x64, + 0x12, 0x4a, 0x0a, 0x11, 0x63, 0x61, 0x76, 0x65, 0x61, 0x74, 0x5f, 0x65, 0x78, 0x70, 0x72, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x64, 0x69, + 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x76, 0x65, 0x61, 0x74, + 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x10, 0x63, 0x61, 0x76, 0x65, + 0x61, 0x74, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x11, + 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x5f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, + 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x52, 0x10, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x53, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x73, 0x22, 0x99, 0x01, 0x0a, 0x1e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, + 0x68, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x0e, 0x66, 0x6f, 0x75, 0x6e, 0x64, + 0x5f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x19, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6f, + 0x75, 0x6e, 0x64, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x0d, 0x66, 0x6f, 0x75, 0x6e, + 0x64, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x35, 0x0a, 0x08, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x64, 0x69, + 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x22, 0x7d, 0x0a, 0x0c, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, + 0x12, 0x3b, 0x0a, 0x0b, 0x61, 0x74, 0x5f, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1a, 0xfa, 0x42, 0x17, 0x72, 0x15, 0x32, 0x13, 0x5e, 0x5b, + 0x30, 0x2d, 0x39, 0x5d, 0x2b, 0x28, 0x5c, 0x2e, 0x5b, 0x30, 0x2d, 0x39, 0x5d, 0x2b, 0x29, 0x3f, + 0x24, 0x52, 0x0a, 0x61, 0x74, 0x52, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x30, 0x0a, + 0x0f, 0x64, 0x65, 0x70, 0x74, 0x68, 0x5f, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x2a, 0x02, 0x20, 0x00, 0x52, + 0x0e, 0x64, 0x65, 0x70, 0x74, 0x68, 0x52, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x22, + 0xda, 0x01, 0x0a, 0x0c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, + 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, + 0x63, 0x68, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x65, 0x70, 0x74, 0x68, + 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x0d, 0x64, 0x65, 0x70, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x32, + 0x0a, 0x15, 0x63, 0x61, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, + 0x68, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x13, 0x63, + 0x61, 0x63, 0x68, 0x65, 0x64, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x0a, 0x64, 0x65, 0x62, 0x75, 0x67, 0x5f, 0x69, 0x6e, 0x66, 0x6f, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, + 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x64, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, + 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x22, 0x46, 0x0a, 0x10, + 0x44, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x32, 0x0a, 0x05, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1c, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, + 0x65, 0x63, 0x6b, 0x44, 0x65, 0x62, 0x75, 0x67, 0x54, 0x72, 0x61, 0x63, 0x65, 0x52, 0x05, 0x63, + 0x68, 0x65, 0x63, 0x6b, 0x22, 0xf8, 0x03, 0x0a, 0x0f, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x44, 0x65, + 0x62, 0x75, 0x67, 0x54, 0x72, 0x61, 0x63, 0x65, 0x12, 0x3b, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x64, 0x69, 0x73, 0x70, + 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, + 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x5f, 0x0a, 0x16, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x44, 0x65, 0x62, 0x75, 0x67, 0x54, 0x72, + 0x61, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, + 0x52, 0x14, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x43, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, + 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x44, 0x65, 0x62, 0x75, 0x67, + 0x54, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x69, + 0x73, 0x5f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x69, 0x73, 0x43, 0x61, 0x63, 0x68, 0x65, 0x64, 0x52, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x3f, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x5f, 0x70, 0x72, 0x6f, + 0x62, 0x6c, 0x65, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x64, 0x69, + 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x44, + 0x65, 0x62, 0x75, 0x67, 0x54, 0x72, 0x61, 0x63, 0x65, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x50, 0x72, + 0x6f, 0x62, 0x6c, 0x65, 0x6d, 0x73, 0x1a, 0x5c, 0x0a, 0x0c, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x36, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, + 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x68, + 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x3a, 0x02, 0x38, 0x01, 0x22, 0x39, 0x0a, 0x0c, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, + 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x4c, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x01, 0x12, + 0x0e, 0x0a, 0x0a, 0x50, 0x45, 0x52, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x02, 0x32, + 0xa0, 0x04, 0x0a, 0x0f, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x12, 0x58, 0x0a, 0x0d, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x43, + 0x68, 0x65, 0x63, 0x6b, 0x12, 0x21, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, + 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x43, 0x68, + 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5b, 0x0a, + 0x0e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x12, + 0x22, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, + 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, + 0x31, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5b, 0x0a, 0x0e, 0x44, 0x69, - 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x12, 0x22, 0x2e, 0x64, + 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x12, 0x22, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, - 0x74, 0x63, 0x68, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, - 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5b, 0x0a, 0x0e, 0x44, 0x69, 0x73, 0x70, 0x61, - 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x12, 0x22, 0x2e, 0x64, 0x69, 0x73, 0x70, - 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, - 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, - 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x70, - 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x12, 0x81, 0x01, 0x0a, 0x1a, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, - 0x68, 0x52, 0x65, 0x61, 0x63, 0x68, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x73, 0x12, 0x2e, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, - 0x31, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x61, 0x63, 0x68, 0x61, - 0x62, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, - 0x31, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x61, 0x63, 0x68, 0x61, - 0x62, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x75, 0x0a, 0x16, 0x44, 0x69, 0x73, 0x70, - 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x73, 0x12, 0x2a, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, - 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, - 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, - 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, - 0x70, 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x42, - 0xaa, 0x01, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, - 0x2e, 0x76, 0x31, 0x42, 0x0d, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x50, 0x72, 0x6f, - 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x65, 0x64, 0x2f, 0x73, 0x70, 0x69, 0x63, 0x65, 0x64, 0x62, - 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x64, 0x69, 0x73, 0x70, 0x61, - 0x74, 0x63, 0x68, 0x2f, 0x76, 0x31, 0x3b, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x76, - 0x31, 0xa2, 0x02, 0x03, 0x44, 0x58, 0x58, 0xaa, 0x02, 0x0b, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, - 0x63, 0x68, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0b, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, - 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x17, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5c, 0x56, - 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0c, - 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x81, 0x01, 0x0a, 0x1a, 0x44, 0x69, 0x73, 0x70, + 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x61, 0x63, 0x68, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x2e, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, + 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x61, + 0x63, 0x68, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, + 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x61, + 0x63, 0x68, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x75, 0x0a, 0x16, 0x44, + 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x2a, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, + 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x6f, 0x6b, + 0x75, 0x70, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x2b, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, + 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x30, 0x01, 0x42, 0xaa, 0x01, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, + 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x42, 0x0d, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x65, 0x64, 0x2f, 0x73, 0x70, 0x69, 0x63, + 0x65, 0x64, 0x62, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x64, 0x69, + 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2f, 0x76, 0x31, 0x3b, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, + 0x63, 0x68, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x44, 0x58, 0x58, 0xaa, 0x02, 0x0b, 0x44, 0x69, 0x73, + 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0b, 0x44, 0x69, 0x73, 0x70, 0x61, + 0x74, 0x63, 0x68, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x17, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, + 0x68, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0xea, 0x02, 0x0c, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x3a, 0x3a, 0x56, 0x31, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -2019,31 +2033,33 @@ var file_dispatch_v1_dispatch_proto_depIdxs = []int32{ 22, // 31: dispatch.v1.DispatchLookupSubjectsRequest.metadata:type_name -> dispatch.v1.ResolverMeta 28, // 32: dispatch.v1.DispatchLookupSubjectsRequest.resource_relation:type_name -> core.v1.RelationReference 28, // 33: dispatch.v1.DispatchLookupSubjectsRequest.subject_relation:type_name -> core.v1.RelationReference - 20, // 34: dispatch.v1.DispatchLookupSubjectsResponse.found_subjects:type_name -> dispatch.v1.FoundSubject - 23, // 35: dispatch.v1.DispatchLookupSubjectsResponse.metadata:type_name -> dispatch.v1.ResponseMeta - 24, // 36: dispatch.v1.ResponseMeta.debug_info:type_name -> dispatch.v1.DebugInformation - 25, // 37: dispatch.v1.DebugInformation.check:type_name -> dispatch.v1.CheckDebugTrace - 7, // 38: dispatch.v1.CheckDebugTrace.request:type_name -> dispatch.v1.DispatchCheckRequest - 6, // 39: dispatch.v1.CheckDebugTrace.resource_relation_type:type_name -> dispatch.v1.CheckDebugTrace.RelationType - 27, // 40: dispatch.v1.CheckDebugTrace.results:type_name -> dispatch.v1.CheckDebugTrace.ResultsEntry - 25, // 41: dispatch.v1.CheckDebugTrace.sub_problems:type_name -> dispatch.v1.CheckDebugTrace - 9, // 42: dispatch.v1.DispatchCheckResponse.ResultsByResourceIdEntry.value:type_name -> dispatch.v1.ResourceCheckResult - 9, // 43: dispatch.v1.CheckDebugTrace.ResultsEntry.value:type_name -> dispatch.v1.ResourceCheckResult - 7, // 44: dispatch.v1.DispatchService.DispatchCheck:input_type -> dispatch.v1.DispatchCheckRequest - 12, // 45: dispatch.v1.DispatchService.DispatchExpand:input_type -> dispatch.v1.DispatchExpandRequest - 14, // 46: dispatch.v1.DispatchService.DispatchLookup:input_type -> dispatch.v1.DispatchLookupRequest - 16, // 47: dispatch.v1.DispatchService.DispatchReachableResources:input_type -> dispatch.v1.DispatchReachableResourcesRequest - 19, // 48: dispatch.v1.DispatchService.DispatchLookupSubjects:input_type -> dispatch.v1.DispatchLookupSubjectsRequest - 8, // 49: dispatch.v1.DispatchService.DispatchCheck:output_type -> dispatch.v1.DispatchCheckResponse - 13, // 50: dispatch.v1.DispatchService.DispatchExpand:output_type -> dispatch.v1.DispatchExpandResponse - 15, // 51: dispatch.v1.DispatchService.DispatchLookup:output_type -> dispatch.v1.DispatchLookupResponse - 18, // 52: dispatch.v1.DispatchService.DispatchReachableResources:output_type -> dispatch.v1.DispatchReachableResourcesResponse - 21, // 53: dispatch.v1.DispatchService.DispatchLookupSubjects:output_type -> dispatch.v1.DispatchLookupSubjectsResponse - 49, // [49:54] is the sub-list for method output_type - 44, // [44:49] is the sub-list for method input_type - 44, // [44:44] is the sub-list for extension type_name - 44, // [44:44] is the sub-list for extension extendee - 0, // [0:44] is the sub-list for field type_name + 10, // 34: dispatch.v1.FoundSubject.caveat_expression:type_name -> dispatch.v1.CaveatExpression + 20, // 35: dispatch.v1.FoundSubject.excluded_subjects:type_name -> dispatch.v1.FoundSubject + 20, // 36: dispatch.v1.DispatchLookupSubjectsResponse.found_subjects:type_name -> dispatch.v1.FoundSubject + 23, // 37: dispatch.v1.DispatchLookupSubjectsResponse.metadata:type_name -> dispatch.v1.ResponseMeta + 24, // 38: dispatch.v1.ResponseMeta.debug_info:type_name -> dispatch.v1.DebugInformation + 25, // 39: dispatch.v1.DebugInformation.check:type_name -> dispatch.v1.CheckDebugTrace + 7, // 40: dispatch.v1.CheckDebugTrace.request:type_name -> dispatch.v1.DispatchCheckRequest + 6, // 41: dispatch.v1.CheckDebugTrace.resource_relation_type:type_name -> dispatch.v1.CheckDebugTrace.RelationType + 27, // 42: dispatch.v1.CheckDebugTrace.results:type_name -> dispatch.v1.CheckDebugTrace.ResultsEntry + 25, // 43: dispatch.v1.CheckDebugTrace.sub_problems:type_name -> dispatch.v1.CheckDebugTrace + 9, // 44: dispatch.v1.DispatchCheckResponse.ResultsByResourceIdEntry.value:type_name -> dispatch.v1.ResourceCheckResult + 9, // 45: dispatch.v1.CheckDebugTrace.ResultsEntry.value:type_name -> dispatch.v1.ResourceCheckResult + 7, // 46: dispatch.v1.DispatchService.DispatchCheck:input_type -> dispatch.v1.DispatchCheckRequest + 12, // 47: dispatch.v1.DispatchService.DispatchExpand:input_type -> dispatch.v1.DispatchExpandRequest + 14, // 48: dispatch.v1.DispatchService.DispatchLookup:input_type -> dispatch.v1.DispatchLookupRequest + 16, // 49: dispatch.v1.DispatchService.DispatchReachableResources:input_type -> dispatch.v1.DispatchReachableResourcesRequest + 19, // 50: dispatch.v1.DispatchService.DispatchLookupSubjects:input_type -> dispatch.v1.DispatchLookupSubjectsRequest + 8, // 51: dispatch.v1.DispatchService.DispatchCheck:output_type -> dispatch.v1.DispatchCheckResponse + 13, // 52: dispatch.v1.DispatchService.DispatchExpand:output_type -> dispatch.v1.DispatchExpandResponse + 15, // 53: dispatch.v1.DispatchService.DispatchLookup:output_type -> dispatch.v1.DispatchLookupResponse + 18, // 54: dispatch.v1.DispatchService.DispatchReachableResources:output_type -> dispatch.v1.DispatchReachableResourcesResponse + 21, // 55: dispatch.v1.DispatchService.DispatchLookupSubjects:output_type -> dispatch.v1.DispatchLookupSubjectsResponse + 51, // [51:56] is the sub-list for method output_type + 46, // [46:51] is the sub-list for method input_type + 46, // [46:46] is the sub-list for extension type_name + 46, // [46:46] is the sub-list for extension extendee + 0, // [0:46] is the sub-list for field type_name } func init() { file_dispatch_v1_dispatch_proto_init() } diff --git a/pkg/proto/dispatch/v1/dispatch.pb.validate.go b/pkg/proto/dispatch/v1/dispatch.pb.validate.go index c456de8e55..c7e7615489 100644 --- a/pkg/proto/dispatch/v1/dispatch.pb.validate.go +++ b/pkg/proto/dispatch/v1/dispatch.pb.validate.go @@ -2415,6 +2415,69 @@ func (m *FoundSubject) validate(all bool) error { // no validation rules for SubjectId + if all { + switch v := interface{}(m.GetCaveatExpression()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, FoundSubjectValidationError{ + field: "CaveatExpression", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, FoundSubjectValidationError{ + field: "CaveatExpression", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetCaveatExpression()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return FoundSubjectValidationError{ + field: "CaveatExpression", + reason: "embedded message failed validation", + cause: err, + } + } + } + + for idx, item := range m.GetExcludedSubjects() { + _, _ = idx, item + + if all { + switch v := interface{}(item).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, FoundSubjectValidationError{ + field: fmt.Sprintf("ExcludedSubjects[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, FoundSubjectValidationError{ + field: fmt.Sprintf("ExcludedSubjects[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return FoundSubjectValidationError{ + field: fmt.Sprintf("ExcludedSubjects[%v]", idx), + reason: "embedded message failed validation", + cause: err, + } + } + } + + } + if len(errors) > 0 { return FoundSubjectMultiError(errors) } diff --git a/pkg/proto/dispatch/v1/dispatch_vtproto.pb.go b/pkg/proto/dispatch/v1/dispatch_vtproto.pb.go index 524d698d2b..d11259b906 100644 --- a/pkg/proto/dispatch/v1/dispatch_vtproto.pb.go +++ b/pkg/proto/dispatch/v1/dispatch_vtproto.pb.go @@ -440,12 +440,15 @@ func (m *FoundSubject) CloneVT() *FoundSubject { return (*FoundSubject)(nil) } r := &FoundSubject{ - SubjectId: m.SubjectId, + SubjectId: m.SubjectId, + CaveatExpression: m.CaveatExpression.CloneVT(), } - if rhs := m.ExcludedSubjectIds; rhs != nil { - tmpContainer := make([]string, len(rhs)) - copy(tmpContainer, rhs) - r.ExcludedSubjectIds = tmpContainer + if rhs := m.ExcludedSubjects; rhs != nil { + tmpContainer := make([]*FoundSubject, len(rhs)) + for k, v := range rhs { + tmpContainer[k] = v.CloneVT() + } + r.ExcludedSubjects = tmpContainer } if len(m.unknownFields) > 0 { r.unknownFields = make([]byte, len(m.unknownFields)) @@ -575,6 +578,653 @@ func (m *CheckDebugTrace) CloneGenericVT() proto.Message { return m.CloneVT() } +func (this *DispatchCheckRequest) EqualVT(that *DispatchCheckRequest) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if !this.Metadata.EqualVT(that.Metadata) { + return false + } + if equal, ok := interface{}(this.ResourceRelation).(interface { + EqualVT(*v1.RelationReference) bool + }); ok { + if !equal.EqualVT(that.ResourceRelation) { + return false + } + } else if !proto.Equal(this.ResourceRelation, that.ResourceRelation) { + return false + } + if len(this.ResourceIds) != len(that.ResourceIds) { + return false + } + for i, vx := range this.ResourceIds { + vy := that.ResourceIds[i] + if vx != vy { + return false + } + } + if equal, ok := interface{}(this.Subject).(interface { + EqualVT(*v1.ObjectAndRelation) bool + }); ok { + if !equal.EqualVT(that.Subject) { + return false + } + } else if !proto.Equal(this.Subject, that.Subject) { + return false + } + if this.ResultsSetting != that.ResultsSetting { + return false + } + if this.Debug != that.Debug { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *DispatchCheckResponse) EqualVT(that *DispatchCheckResponse) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if !this.Metadata.EqualVT(that.Metadata) { + return false + } + if len(this.ResultsByResourceId) != len(that.ResultsByResourceId) { + return false + } + for i, vx := range this.ResultsByResourceId { + vy, ok := that.ResultsByResourceId[i] + if !ok { + return false + } + if p, q := vx, vy; p != q { + if p == nil { + p = &ResourceCheckResult{} + } + if q == nil { + q = &ResourceCheckResult{} + } + if !p.EqualVT(q) { + return false + } + } + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *ResourceCheckResult) EqualVT(that *ResourceCheckResult) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.Membership != that.Membership { + return false + } + if !this.Expression.EqualVT(that.Expression) { + return false + } + if len(this.MissingExprFields) != len(that.MissingExprFields) { + return false + } + for i, vx := range this.MissingExprFields { + vy := that.MissingExprFields[i] + if vx != vy { + return false + } + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *CaveatExpression) EqualVT(that *CaveatExpression) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.OperationOrCaveat == nil && that.OperationOrCaveat != nil { + return false + } else if this.OperationOrCaveat != nil { + if that.OperationOrCaveat == nil { + return false + } + if !this.OperationOrCaveat.(interface { + EqualVT(isCaveatExpression_OperationOrCaveat) bool + }).EqualVT(that.OperationOrCaveat) { + return false + } + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *CaveatExpression_Operation) EqualVT(thatIface isCaveatExpression_OperationOrCaveat) bool { + that, ok := thatIface.(*CaveatExpression_Operation) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if p, q := this.Operation, that.Operation; p != q { + if p == nil { + p = &CaveatOperation{} + } + if q == nil { + q = &CaveatOperation{} + } + if !p.EqualVT(q) { + return false + } + } + return true +} + +func (this *CaveatExpression_Caveat) EqualVT(thatIface isCaveatExpression_OperationOrCaveat) bool { + that, ok := thatIface.(*CaveatExpression_Caveat) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if p, q := this.Caveat, that.Caveat; p != q { + if p == nil { + p = &v1.ContextualizedCaveat{} + } + if q == nil { + q = &v1.ContextualizedCaveat{} + } + if equal, ok := interface{}(p).(interface { + EqualVT(*v1.ContextualizedCaveat) bool + }); ok { + if !equal.EqualVT(q) { + return false + } + } else if !proto.Equal(p, q) { + return false + } + } + return true +} + +func (this *CaveatOperation) EqualVT(that *CaveatOperation) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.Op != that.Op { + return false + } + if len(this.Children) != len(that.Children) { + return false + } + for i, vx := range this.Children { + vy := that.Children[i] + if p, q := vx, vy; p != q { + if p == nil { + p = &CaveatExpression{} + } + if q == nil { + q = &CaveatExpression{} + } + if !p.EqualVT(q) { + return false + } + } + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *DispatchExpandRequest) EqualVT(that *DispatchExpandRequest) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if !this.Metadata.EqualVT(that.Metadata) { + return false + } + if equal, ok := interface{}(this.ResourceAndRelation).(interface { + EqualVT(*v1.ObjectAndRelation) bool + }); ok { + if !equal.EqualVT(that.ResourceAndRelation) { + return false + } + } else if !proto.Equal(this.ResourceAndRelation, that.ResourceAndRelation) { + return false + } + if this.ExpansionMode != that.ExpansionMode { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *DispatchExpandResponse) EqualVT(that *DispatchExpandResponse) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if !this.Metadata.EqualVT(that.Metadata) { + return false + } + if equal, ok := interface{}(this.TreeNode).(interface { + EqualVT(*v1.RelationTupleTreeNode) bool + }); ok { + if !equal.EqualVT(that.TreeNode) { + return false + } + } else if !proto.Equal(this.TreeNode, that.TreeNode) { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *DispatchLookupRequest) EqualVT(that *DispatchLookupRequest) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if !this.Metadata.EqualVT(that.Metadata) { + return false + } + if equal, ok := interface{}(this.ObjectRelation).(interface { + EqualVT(*v1.RelationReference) bool + }); ok { + if !equal.EqualVT(that.ObjectRelation) { + return false + } + } else if !proto.Equal(this.ObjectRelation, that.ObjectRelation) { + return false + } + if equal, ok := interface{}(this.Subject).(interface { + EqualVT(*v1.ObjectAndRelation) bool + }); ok { + if !equal.EqualVT(that.Subject) { + return false + } + } else if !proto.Equal(this.Subject, that.Subject) { + return false + } + if this.Limit != that.Limit { + return false + } + if len(this.DirectStack) != len(that.DirectStack) { + return false + } + for i, vx := range this.DirectStack { + vy := that.DirectStack[i] + if p, q := vx, vy; p != q { + if p == nil { + p = &v1.RelationReference{} + } + if q == nil { + q = &v1.RelationReference{} + } + if equal, ok := interface{}(p).(interface { + EqualVT(*v1.RelationReference) bool + }); ok { + if !equal.EqualVT(q) { + return false + } + } else if !proto.Equal(p, q) { + return false + } + } + } + if len(this.TtuStack) != len(that.TtuStack) { + return false + } + for i, vx := range this.TtuStack { + vy := that.TtuStack[i] + if p, q := vx, vy; p != q { + if p == nil { + p = &v1.RelationReference{} + } + if q == nil { + q = &v1.RelationReference{} + } + if equal, ok := interface{}(p).(interface { + EqualVT(*v1.RelationReference) bool + }); ok { + if !equal.EqualVT(q) { + return false + } + } else if !proto.Equal(p, q) { + return false + } + } + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *DispatchLookupResponse) EqualVT(that *DispatchLookupResponse) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if !this.Metadata.EqualVT(that.Metadata) { + return false + } + if len(this.ResolvedOnrs) != len(that.ResolvedOnrs) { + return false + } + for i, vx := range this.ResolvedOnrs { + vy := that.ResolvedOnrs[i] + if p, q := vx, vy; p != q { + if p == nil { + p = &v1.ObjectAndRelation{} + } + if q == nil { + q = &v1.ObjectAndRelation{} + } + if equal, ok := interface{}(p).(interface { + EqualVT(*v1.ObjectAndRelation) bool + }); ok { + if !equal.EqualVT(q) { + return false + } + } else if !proto.Equal(p, q) { + return false + } + } + } + if this.NextPageReference != that.NextPageReference { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *DispatchReachableResourcesRequest) EqualVT(that *DispatchReachableResourcesRequest) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if !this.Metadata.EqualVT(that.Metadata) { + return false + } + if equal, ok := interface{}(this.ResourceRelation).(interface { + EqualVT(*v1.RelationReference) bool + }); ok { + if !equal.EqualVT(that.ResourceRelation) { + return false + } + } else if !proto.Equal(this.ResourceRelation, that.ResourceRelation) { + return false + } + if equal, ok := interface{}(this.SubjectRelation).(interface { + EqualVT(*v1.RelationReference) bool + }); ok { + if !equal.EqualVT(that.SubjectRelation) { + return false + } + } else if !proto.Equal(this.SubjectRelation, that.SubjectRelation) { + return false + } + if len(this.SubjectIds) != len(that.SubjectIds) { + return false + } + for i, vx := range this.SubjectIds { + vy := that.SubjectIds[i] + if vx != vy { + return false + } + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *ReachableResource) EqualVT(that *ReachableResource) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if len(this.ResourceIds) != len(that.ResourceIds) { + return false + } + for i, vx := range this.ResourceIds { + vy := that.ResourceIds[i] + if vx != vy { + return false + } + } + if this.ResultStatus != that.ResultStatus { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *DispatchReachableResourcesResponse) EqualVT(that *DispatchReachableResourcesResponse) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if !this.Resource.EqualVT(that.Resource) { + return false + } + if !this.Metadata.EqualVT(that.Metadata) { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *DispatchLookupSubjectsRequest) EqualVT(that *DispatchLookupSubjectsRequest) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if !this.Metadata.EqualVT(that.Metadata) { + return false + } + if equal, ok := interface{}(this.ResourceRelation).(interface { + EqualVT(*v1.RelationReference) bool + }); ok { + if !equal.EqualVT(that.ResourceRelation) { + return false + } + } else if !proto.Equal(this.ResourceRelation, that.ResourceRelation) { + return false + } + if len(this.ResourceIds) != len(that.ResourceIds) { + return false + } + for i, vx := range this.ResourceIds { + vy := that.ResourceIds[i] + if vx != vy { + return false + } + } + if equal, ok := interface{}(this.SubjectRelation).(interface { + EqualVT(*v1.RelationReference) bool + }); ok { + if !equal.EqualVT(that.SubjectRelation) { + return false + } + } else if !proto.Equal(this.SubjectRelation, that.SubjectRelation) { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *FoundSubject) EqualVT(that *FoundSubject) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.SubjectId != that.SubjectId { + return false + } + if !this.CaveatExpression.EqualVT(that.CaveatExpression) { + return false + } + if len(this.ExcludedSubjects) != len(that.ExcludedSubjects) { + return false + } + for i, vx := range this.ExcludedSubjects { + vy := that.ExcludedSubjects[i] + if p, q := vx, vy; p != q { + if p == nil { + p = &FoundSubject{} + } + if q == nil { + q = &FoundSubject{} + } + if !p.EqualVT(q) { + return false + } + } + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *DispatchLookupSubjectsResponse) EqualVT(that *DispatchLookupSubjectsResponse) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if len(this.FoundSubjects) != len(that.FoundSubjects) { + return false + } + for i, vx := range this.FoundSubjects { + vy := that.FoundSubjects[i] + if p, q := vx, vy; p != q { + if p == nil { + p = &FoundSubject{} + } + if q == nil { + q = &FoundSubject{} + } + if !p.EqualVT(q) { + return false + } + } + } + if !this.Metadata.EqualVT(that.Metadata) { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *ResolverMeta) EqualVT(that *ResolverMeta) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.AtRevision != that.AtRevision { + return false + } + if this.DepthRemaining != that.DepthRemaining { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *ResponseMeta) EqualVT(that *ResponseMeta) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.DispatchCount != that.DispatchCount { + return false + } + if this.DepthRequired != that.DepthRequired { + return false + } + if this.CachedDispatchCount != that.CachedDispatchCount { + return false + } + if !this.DebugInfo.EqualVT(that.DebugInfo) { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *DebugInformation) EqualVT(that *DebugInformation) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if !this.Check.EqualVT(that.Check) { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *CheckDebugTrace) EqualVT(that *CheckDebugTrace) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if !this.Request.EqualVT(that.Request) { + return false + } + if this.ResourceRelationType != that.ResourceRelationType { + return false + } + if len(this.Results) != len(that.Results) { + return false + } + for i, vx := range this.Results { + vy, ok := that.Results[i] + if !ok { + return false + } + if p, q := vx, vy; p != q { + if p == nil { + p = &ResourceCheckResult{} + } + if q == nil { + q = &ResourceCheckResult{} + } + if !p.EqualVT(q) { + return false + } + } + } + if this.IsCachedResult != that.IsCachedResult { + return false + } + if len(this.SubProblems) != len(that.SubProblems) { + return false + } + for i, vx := range this.SubProblems { + vy := that.SubProblems[i] + if p, q := vx, vy; p != q { + if p == nil { + p = &CheckDebugTrace{} + } + if q == nil { + q = &CheckDebugTrace{} + } + if !p.EqualVT(q) { + return false + } + } + } + return string(this.unknownFields) == string(that.unknownFields) +} + func (m *DispatchCheckRequest) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil @@ -1616,14 +2266,27 @@ func (m *FoundSubject) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } - if len(m.ExcludedSubjectIds) > 0 { - for iNdEx := len(m.ExcludedSubjectIds) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.ExcludedSubjectIds[iNdEx]) - copy(dAtA[i:], m.ExcludedSubjectIds[iNdEx]) - i = encodeVarint(dAtA, i, uint64(len(m.ExcludedSubjectIds[iNdEx]))) + if len(m.ExcludedSubjects) > 0 { + for iNdEx := len(m.ExcludedSubjects) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.ExcludedSubjects[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarint(dAtA, i, uint64(size)) i-- - dAtA[i] = 0x12 + dAtA[i] = 0x1a + } + } + if m.CaveatExpression != nil { + size, err := m.CaveatExpression.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err } + i -= size + i = encodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x12 } if len(m.SubjectId) > 0 { i -= len(m.SubjectId) @@ -2366,9 +3029,13 @@ func (m *FoundSubject) SizeVT() (n int) { if l > 0 { n += 1 + l + sov(uint64(l)) } - if len(m.ExcludedSubjectIds) > 0 { - for _, s := range m.ExcludedSubjectIds { - l = len(s) + if m.CaveatExpression != nil { + l = m.CaveatExpression.SizeVT() + n += 1 + l + sov(uint64(l)) + } + if len(m.ExcludedSubjects) > 0 { + for _, e := range m.ExcludedSubjects { + l = e.SizeVT() n += 1 + l + sov(uint64(l)) } } @@ -4769,9 +5436,9 @@ func (m *FoundSubject) UnmarshalVT(dAtA []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ExcludedSubjectIds", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field CaveatExpression", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflow @@ -4781,23 +5448,61 @@ func (m *FoundSubject) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLength } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - m.ExcludedSubjectIds = append(m.ExcludedSubjectIds, string(dAtA[iNdEx:postIndex])) + if m.CaveatExpression == nil { + m.CaveatExpression = &CaveatExpression{} + } + if err := m.CaveatExpression.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExcludedSubjects", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ExcludedSubjects = append(m.ExcludedSubjects, &FoundSubject{}) + if err := m.ExcludedSubjects[len(m.ExcludedSubjects)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex default: iNdEx = preIndex diff --git a/pkg/proto/impl/v1/impl_vtproto.pb.go b/pkg/proto/impl/v1/impl_vtproto.pb.go index 7bc5702f51..b6e733f233 100644 --- a/pkg/proto/impl/v1/impl_vtproto.pb.go +++ b/pkg/proto/impl/v1/impl_vtproto.pb.go @@ -292,6 +292,319 @@ func (m *V1Alpha1Revision) CloneGenericVT() proto.Message { return m.CloneVT() } +func (this *DecodedCaveat) EqualVT(that *DecodedCaveat) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.KindOneof == nil && that.KindOneof != nil { + return false + } else if this.KindOneof != nil { + if that.KindOneof == nil { + return false + } + if !this.KindOneof.(interface { + EqualVT(isDecodedCaveat_KindOneof) bool + }).EqualVT(that.KindOneof) { + return false + } + } + if this.Name != that.Name { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *DecodedCaveat_Cel) EqualVT(thatIface isDecodedCaveat_KindOneof) bool { + that, ok := thatIface.(*DecodedCaveat_Cel) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if p, q := this.Cel, that.Cel; p != q { + if p == nil { + p = &v1alpha1.CheckedExpr{} + } + if q == nil { + q = &v1alpha1.CheckedExpr{} + } + if equal, ok := interface{}(p).(interface { + EqualVT(*v1alpha1.CheckedExpr) bool + }); ok { + if !equal.EqualVT(q) { + return false + } + } else if !proto.Equal(p, q) { + return false + } + } + return true +} + +func (this *DecodedZookie_V1Zookie) EqualVT(that *DecodedZookie_V1Zookie) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.Revision != that.Revision { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *DecodedZookie_V2Zookie) EqualVT(that *DecodedZookie_V2Zookie) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.Revision != that.Revision { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *DecodedZookie) EqualVT(that *DecodedZookie) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.VersionOneof == nil && that.VersionOneof != nil { + return false + } else if this.VersionOneof != nil { + if that.VersionOneof == nil { + return false + } + if !this.VersionOneof.(interface { + EqualVT(isDecodedZookie_VersionOneof) bool + }).EqualVT(that.VersionOneof) { + return false + } + } + if this.Version != that.Version { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *DecodedZookie_V1) EqualVT(thatIface isDecodedZookie_VersionOneof) bool { + that, ok := thatIface.(*DecodedZookie_V1) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if p, q := this.V1, that.V1; p != q { + if p == nil { + p = &DecodedZookie_V1Zookie{} + } + if q == nil { + q = &DecodedZookie_V1Zookie{} + } + if !p.EqualVT(q) { + return false + } + } + return true +} + +func (this *DecodedZookie_V2) EqualVT(thatIface isDecodedZookie_VersionOneof) bool { + that, ok := thatIface.(*DecodedZookie_V2) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if p, q := this.V2, that.V2; p != q { + if p == nil { + p = &DecodedZookie_V2Zookie{} + } + if q == nil { + q = &DecodedZookie_V2Zookie{} + } + if !p.EqualVT(q) { + return false + } + } + return true +} + +func (this *DecodedZedToken_V1Zookie) EqualVT(that *DecodedZedToken_V1Zookie) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.Revision != that.Revision { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *DecodedZedToken_V1ZedToken) EqualVT(that *DecodedZedToken_V1ZedToken) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.Revision != that.Revision { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *DecodedZedToken) EqualVT(that *DecodedZedToken) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.VersionOneof == nil && that.VersionOneof != nil { + return false + } else if this.VersionOneof != nil { + if that.VersionOneof == nil { + return false + } + if !this.VersionOneof.(interface { + EqualVT(isDecodedZedToken_VersionOneof) bool + }).EqualVT(that.VersionOneof) { + return false + } + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *DecodedZedToken_DeprecatedV1Zookie) EqualVT(thatIface isDecodedZedToken_VersionOneof) bool { + that, ok := thatIface.(*DecodedZedToken_DeprecatedV1Zookie) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if p, q := this.DeprecatedV1Zookie, that.DeprecatedV1Zookie; p != q { + if p == nil { + p = &DecodedZedToken_V1Zookie{} + } + if q == nil { + q = &DecodedZedToken_V1Zookie{} + } + if !p.EqualVT(q) { + return false + } + } + return true +} + +func (this *DecodedZedToken_V1) EqualVT(thatIface isDecodedZedToken_VersionOneof) bool { + that, ok := thatIface.(*DecodedZedToken_V1) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if p, q := this.V1, that.V1; p != q { + if p == nil { + p = &DecodedZedToken_V1ZedToken{} + } + if q == nil { + q = &DecodedZedToken_V1ZedToken{} + } + if !p.EqualVT(q) { + return false + } + } + return true +} + +func (this *DocComment) EqualVT(that *DocComment) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.Comment != that.Comment { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *RelationMetadata) EqualVT(that *RelationMetadata) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.Kind != that.Kind { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *NamespaceAndRevision) EqualVT(that *NamespaceAndRevision) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if this.NamespaceName != that.NamespaceName { + return false + } + if this.Revision != that.Revision { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *V1Alpha1Revision) EqualVT(that *V1Alpha1Revision) bool { + if this == nil { + return that == nil + } else if that == nil { + return false + } + if len(this.NsRevisions) != len(that.NsRevisions) { + return false + } + for i, vx := range this.NsRevisions { + vy := that.NsRevisions[i] + if p, q := vx, vy; p != q { + if p == nil { + p = &NamespaceAndRevision{} + } + if q == nil { + q = &NamespaceAndRevision{} + } + if !p.EqualVT(q) { + return false + } + } + } + return string(this.unknownFields) == string(that.unknownFields) +} + func (m *DecodedCaveat) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil diff --git a/proto/internal/dispatch/v1/dispatch.proto b/proto/internal/dispatch/v1/dispatch.proto index 0f1246a294..d907d553e9 100644 --- a/proto/internal/dispatch/v1/dispatch.proto +++ b/proto/internal/dispatch/v1/dispatch.proto @@ -161,7 +161,8 @@ message DispatchLookupSubjectsRequest { message FoundSubject { string subject_id = 1; - repeated string excluded_subject_ids = 2; + CaveatExpression caveat_expression = 2; + repeated FoundSubject excluded_subjects = 3; } message DispatchLookupSubjectsResponse {