From 23fe8016fadbfba31b1d0594b793bdd710ec9101 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Fri, 21 Oct 2022 12:24:04 -0400 Subject: [PATCH] Update BaseSubjectSet to support caveat expressions This is the first (massive) step in supporting caveats in LookupSubjects, as this implements all the bookkeeping and tracking associated with each subject added to the subject set First part of #931 --- .golangci.yaml | 102 +- .../dispatch/graph/lookupsubjects_test.go | 2 +- internal/membership/foundsubject.go | 50 +- internal/membership/trackingsubjectset.go | 28 +- .../membership/trackingsubjectset_test.go | 17 +- internal/services/v1/permissions.go | 7 +- internal/util/basesubjectset.go | 893 ++++-- internal/util/subjectset.go | 19 +- internal/util/subjectset_test.go | 2454 ++++++++++++++++- pkg/proto/dispatch/v1/dispatch.pb.go | 306 +- pkg/proto/dispatch/v1/dispatch.pb.validate.go | 63 + pkg/proto/dispatch/v1/dispatch_vtproto.pb.go | 100 +- proto/internal/dispatch/v1/dispatch.proto | 3 +- 13 files changed, 3592 insertions(+), 452 deletions(-) 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/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..66abd4b9bb 100644 --- a/internal/util/basesubjectset.go +++ b/internal/util/basesubjectset.go @@ -3,210 +3,170 @@ package util import ( "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](), } } +// 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. -func (bss BaseSubjectSet[T]) Add(foundSubject T) bool { - existing, ok := bss.values[foundSubject.GetSubjectId()] - if !ok { - bss.values[foundSubject.GetSubjectId()] = foundSubject - } +// existing set of subjects and a set containing the single subject, but modifies the set +// *in place*. +func (bss BaseSubjectSet[T]) Add(foundSubject T) { + switch foundSubject.GetSubjectId() == tuple.PublicWildcard { + case true: + existing := bss.wildcard.getOrNil() + existing = unionWildcardWithWildcard(existing, foundSubject, bss.constructor) + bss.wildcard.setOrNil(existing) - 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) + for _, concrete := range bss.concrete { + existing = unionWildcardWithConcrete(existing, concrete, bss.constructor) } - } 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) + bss.wildcard.setOrNil(existing) + + case false: + var existingOrNil *T + if existing, ok := bss.concrete[foundSubject.GetSubjectId()]; ok { + existingOrNil = &existing } + bss.setConcrete(foundSubject.GetSubjectId(), unionConcreteWithConcrete(existingOrNil, &foundSubject, bss.constructor)) + + wildcard := bss.wildcard.getOrNil() + wildcard = unionWildcardWithConcrete(wildcard, foundSubject, bss.constructor) + bss.wildcard.setOrNil(wildcard) } +} - if bss.combiner != nil && ok { - bss.values[foundSubject.GetSubjectId()] = bss.combiner(bss.values[foundSubject.GetSubjectId()], foundSubject) +func (bss BaseSubjectSet[T]) setConcrete(subjectID string, subjectOrNil *T) { + if subjectOrNil == nil { + delete(bss.concrete, subjectID) + return } - return !ok + subject := *subjectOrNil + bss.concrete[subject.GetSubjectId()] = subject } // 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 - } - - if !exclusions.Has(existingSubjectID) { - delete(bss.values, existingSubjectID) - } +func (bss BaseSubjectSet[T]) Subtract(toRemove T) { + switch toRemove.GetSubjectId() == tuple.PublicWildcard { + case true: + for _, concrete := range bss.concrete { + bss.setConcrete(concrete.GetSubjectId(), subtractWildcardFromConcrete(concrete, toRemove, bss.constructor)) } - // 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) - } - } + 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) } - delete(bss.values, tuple.PublicWildcard) - return - } - - // Remove the subject itself from the set. - delete(bss.values, foundSubject.GetSubjectId()) + case false: + 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 sets, as well as with the wildcards. + updatedConcretes := make(map[string]T, len(bss.concrete)) + + for _, concreteSubject := range bss.concrete { + var otherConcreteOrNil *T + if otherConcrete, ok := other.concrete[concreteSubject.GetSubjectId()]; ok { + otherConcreteOrNil = &otherConcrete } - } - // 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) - } - - // 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. @@ -224,32 +184,651 @@ func (bss BaseSubjectSet[T]) UnionWithSet(other BaseSubjectSet[T]) { // 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()) + + // If the whole exclusion is not conditional, nothing more to do. + if exclusionConditional == nil { + return &existingConcrete + } + + // Otherwise, add the exclusion's conditional to the entry. + 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 + + case isExcluded && wildcardToIntersect.GetCaveatExpression() == nil: + // If the concrete is excluded and the wildcard conditional is empty, then the concrete is never found. + // Example: {user:tom} & {* - user:tom} => {} + return nil + } + + return nil +} + +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 + } + + 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 + } + + 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..e1da365013 100644 --- a/internal/util/subjectset_test.go +++ b/internal/util/subjectset_test.go @@ -1,138 +1,284 @@ package util import ( + "fmt" + "math" "sort" "strings" "testing" + "google.golang.org/protobuf/proto" + "github.com/stretchr/testify/require" + 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() @@ -143,7 +289,10 @@ func TestSubjectSetAdd(t *testing.T) { stableSortExclusions(expectedSet) stableSortExclusions(computedSet) - require.Equal(t, expectedSet, computedSet) + require.Equal(t, len(expectedSet), len(computedSet), "got: %v", computedSet) + for index := range expectedSet { + require.True(t, proto.Equal(expectedSet[index], computedSet[index]), "got mismatch: %v vs %v", expectedSet[index], computedSet[index]) + } }) } } @@ -189,7 +338,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,13 +376,281 @@ 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) @@ -247,89 +664,329 @@ func TestSubjectSetSubtract(t *testing.T) { stableSortExclusions(expectedSet) stableSortExclusions(computedSet) - require.Equal(t, expectedSet, computedSet) + require.Equal(t, len(expectedSet), len(computedSet), "got: %v", computedSet) + for index := range expectedSet { + require.True(t, proto.Equal(expectedSet[index], computedSet[index]), "got mismatch: expected `%v`, got `%v`", expectedSet[index], computedSet[index]) + } }) } } 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,12 +994,12 @@ 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) @@ -356,19 +1013,1734 @@ func TestSubjectSetIntersection(t *testing.T) { stableSortExclusions(expectedSet) stableSortExclusions(computedSet) - require.Equal(t, expectedSet, computedSet) + require.Equal(t, len(expectedSet), len(computedSet), "got: %v", computedSet) + for index := range expectedSet { + require.True(t, proto.Equal(expectedSet[index], computedSet[index]), "got mismatch: expected %v vs found %v", expectedSet[index], computedSet[index]) + } + + // 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) + } + + toIntersect := NewSubjectSet() + for _, toAdd := range tc.toIntersect { + toIntersect.Add(toAdd) + } + + 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() + + sort.Sort(sortByID(expectedSet)) + sort.Sort(sortByID(computedSet)) + + stableSortExclusions(expectedSet) + stableSortExclusions(computedSet) + + require.Equal(t, len(expectedSet), len(computedSet), "got: %v", computedSet) + for index := range expectedSet { + require.True(t, proto.Equal(expectedSet[index], computedSet[index]), "got inverted mismatch: expected %v vs found %v", expectedSet[index], computedSet[index]) + } + }) }) } } -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")) -func stableSortExclusions(fss []*v1.FoundSubject) { - for _, fs := range fss { - sort.Strings(fs.ExcludedSubjectIds) + 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"))) + + 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() + + sort.Sort(sortByID(expectedSet)) + sort.Sort(sortByID(computedSet)) + + stableSortExclusions(expectedSet) + stableSortExclusions(computedSet) + + require.Equal(t, len(expectedSet), len(computedSet), "got: %v", computedSet) + for index := range expectedSet { + require.True(t, proto.Equal(expectedSet[index], computedSet[index]), "got multiple op mismatch: expected %v vs found %v", expectedSet[index], computedSet[index]) + } + }) + } +} + +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() + + sort.Sort(sortByID(mergedLeft)) + sort.Sort(sortByID(mergedRight)) + stableSortExclusions(mergedLeft) + stableSortExclusions(mergedRight) + stableSortAndSimplifyCaveats(mergedLeft) + stableSortAndSimplifyCaveats(mergedRight) + + require.Equal(t, len(mergedLeft), len(mergedRight)) + for index := range mergedLeft { + require.True(t, proto.Equal(mergedLeft[index], mergedRight[index]), "pair: %v\nexpected %v\nfound %v", pair, mergedLeft[index], mergedRight[index]) + } + }) + } +} + +func TestUnionAssociativity(t *testing.T) { + for _, triple := range allSubsets(testSets, 3) { + t.Run(fmt.Sprintf("%v", triple), 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[0] { + B1.Add(l) + B2.Add(l) + } + C1, C2 := NewSubjectSet(), NewSubjectSet() + for _, l := range triple[0] { + 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() + + sort.Sort(sortByID(mergedLeft)) + sort.Sort(sortByID(mergedRight)) + stableSortExclusions(mergedLeft) + stableSortExclusions(mergedRight) + stableSortAndSimplifyCaveats(mergedLeft) + stableSortAndSimplifyCaveats(mergedRight) + + require.Equal(t, len(mergedLeft), len(mergedRight)) + for index := range mergedLeft { + require.True(t, proto.Equal(mergedLeft[index], mergedRight[index]), "triple: %v\nexpected %v\nfound %v", triple, mergedLeft[index], mergedRight[index]) + } + }) + } +} + +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() + + sort.Sort(sortByID(mergedLeft)) + sort.Sort(sortByID(mergedRight)) + stableSortExclusions(mergedLeft) + stableSortExclusions(mergedRight) + stableSortAndSimplifyCaveats(mergedLeft) + stableSortAndSimplifyCaveats(mergedRight) + + require.Equal(t, len(mergedLeft), len(mergedRight)) + for index := range mergedLeft { + require.True(t, proto.Equal(mergedLeft[index], mergedRight[index]), "pair: %v\nexpected %v\nfound %v", pair, mergedLeft[index], mergedRight[index]) + } + }) + } +} + +func TestIntersectionAssociativity(t *testing.T) { + for _, triple := range allSubsets(testSets, 3) { + t.Run(fmt.Sprintf("%v", triple), 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[0] { + B1.Add(l) + B2.Add(l) + } + C1, C2 := NewSubjectSet(), NewSubjectSet() + for _, l := range triple[0] { + 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() + + sort.Sort(sortByID(mergedLeft)) + sort.Sort(sortByID(mergedRight)) + stableSortExclusions(mergedLeft) + stableSortExclusions(mergedRight) + stableSortAndSimplifyCaveats(mergedLeft) + stableSortAndSimplifyCaveats(mergedRight) + + require.Equal(t, len(mergedLeft), len(mergedRight)) + for index := range mergedLeft { + require.True(t, proto.Equal(mergedLeft[index], mergedRight[index]), "triple: %v\nexpected %v\nfound %v", triple, mergedLeft[index], mergedRight[index]) + } + }) + } +} + +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() + + sort.Sort(sortByID(mergedLeft)) + sort.Sort(sortByID(mergedRight)) + stableSortExclusions(mergedLeft) + stableSortExclusions(mergedRight) + stableSortAndSimplifyCaveats(mergedLeft) + stableSortAndSimplifyCaveats(mergedRight) + + require.Equal(t, len(mergedLeft), len(mergedRight)) + for index := range mergedLeft { + require.True(t, proto.Equal(mergedLeft[index], mergedRight[index]), "set: %v\nexpected %v\nfound %v", set, mergedLeft[index], mergedRight[index]) + } + }) + } +} + +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() + + sort.Sort(sortByID(mergedLeft)) + sort.Sort(sortByID(mergedRight)) + stableSortExclusions(mergedLeft) + stableSortExclusions(mergedRight) + stableSortAndSimplifyCaveats(mergedLeft) + stableSortAndSimplifyCaveats(mergedRight) + + require.Equal(t, len(mergedLeft), len(mergedRight)) + for index := range mergedLeft { + require.True(t, proto.Equal(mergedLeft[index], mergedRight[index]), "set: %v\nexpected %v\nfound %v", set, mergedLeft[index], mergedRight[index]) + } + }) + } +} + +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) + + sort.Sort(sortByID(concrete)) + sort.Sort(sortByID(tc.expectedConcretes)) + + require.Equal(t, len(tc.expectedConcretes), len(concrete)) + for index := range tc.expectedConcretes { + require.True(t, proto.Equal(tc.expectedConcretes[index], concrete[index]), "mismatch on expected concrete %d: %v vs %v", index, tc.expectedConcretes[index], concrete[index]) + } + }) + } +} + +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 format(sub *v1.FoundSubject) string { + 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, sub.GetCaveatExpression().GetCaveat().CaveatName) + } + + return fmt.Sprintf("{*%s}", exclusionsStr) + } + + if sub.GetCaveatExpression() != nil { + return fmt.Sprintf("%s[%s]", sub.GetSubjectId(), sub.GetCaveatExpression().GetCaveat().CaveatName) + } + + return sub.GetSubjectId() +} + +func requireExpectedSubject(t *testing.T, expected *v1.FoundSubject, produced **v1.FoundSubject) { + if expected == nil { + require.Nil(t, produced) + } else { + require.NotNil(t, produced) + + sort.Sort(sortByID(expected.ExcludedSubjects)) + sort.Sort(sortByID((*produced).ExcludedSubjects)) + + require.True(t, proto.Equal(expected, *produced), "expected: %v, found %v", expected, *produced) + } +} + +func wrap(sub *v1.FoundSubject) **v1.FoundSubject { + if sub == nil { + return nil + } + + return &sub +} + +// 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 } + +func stableSortExclusions(fss []*v1.FoundSubject) { + for _, fs := range fss { + sort.Sort(sortByID(fs.ExcludedSubjects)) + } +} + +func stableSortAndSimplifyCaveats(fss []*v1.FoundSubject) { + for _, fs := range fss { + if fs.CaveatExpression == nil { + continue + } + + if fs.CaveatExpression.GetOperation() != nil { + childCaveats := []string{} + op := fs.CaveatExpression.GetOperation() + for _, child := range op.GetChildren() { + if child.GetCaveat() != nil { + childCaveats = append(childCaveats, child.GetCaveat().CaveatName) + } + } + + sort.Strings(childCaveats) + + if len(childCaveats) == len(fs.CaveatExpression.GetOperation().GetChildren()) { + updatedChildren := make([]*v1.CaveatExpression, 0, len(childCaveats)) + for _, caveatName := range childCaveats { + updatedChildren = append(updatedChildren, caveatexpr(caveatName)) + } + op.Children = updatedChildren + } + } + + fs.CaveatExpression = simplifyCaveatExprForTesting(fs.CaveatExpression) + } +} + +func simplifyCaveatExprForTesting(caveatExpr *v1.CaveatExpression) *v1.CaveatExpression { + if caveatExpr == nil || caveatExpr.GetCaveat() != nil { + return caveatExpr + } + + if caveatExpr.GetOperation() != nil { + childCaveatNames := NewSet[string]() + isSimplifiable := true + op := caveatExpr.GetOperation() + + simplifiedChildren := make([]*v1.CaveatExpression, 0, len(op.Children)) + for _, child := range op.GetChildren() { + if child.GetCaveat() == nil { + isSimplifiable = false + simplifiedChildren = append(simplifiedChildren, simplifyCaveatExprForTesting(child)) + continue + } + + childCaveatNames.Add(child.GetCaveat().CaveatName) + } + + if !isSimplifiable { + op.Children = simplifiedChildren + return caveatExpr + } + + // Same caveat. + if childCaveatNames.Len() == 1 { + return caveatexpr(childCaveatNames.AsSlice()[0]) + } + } + + return caveatExpr } 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..c73de02d17 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)) @@ -1616,14 +1619,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 +2382,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 +4789,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 +4801,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/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 {