-
Notifications
You must be signed in to change notification settings - Fork 285
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request from GHSA-7p8f-8hjm-wm92
Fix handling of wildcards in lookup dispatch
- Loading branch information
Showing
28 changed files
with
1,761 additions
and
657 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
package membership | ||
|
||
import ( | ||
"fmt" | ||
"sort" | ||
"strings" | ||
|
||
v0 "github.com/authzed/authzed-go/proto/authzed/api/v0" | ||
|
||
"github.com/authzed/spicedb/pkg/tuple" | ||
) | ||
|
||
// NewFoundSubject creates a new FoundSubject for a subject and a set of its resources. | ||
func NewFoundSubject(subject *v0.ObjectAndRelation, resources ...*v0.ObjectAndRelation) FoundSubject { | ||
return FoundSubject{subject, tuple.NewONRSet(), tuple.NewONRSet(resources...)} | ||
} | ||
|
||
// FoundSubject contains a single found subject and all the relationships in which that subject | ||
// is a member which were found via the ONRs expansion. | ||
type FoundSubject struct { | ||
// subject is the subject found. | ||
subject *v0.ObjectAndRelation | ||
|
||
// excludedSubjects are any subjects excluded. Only should be set if subject is a wildcard. | ||
excludedSubjects *tuple.ONRSet | ||
|
||
// relations are the relations under which the subject lives that informed the locating | ||
// of this subject for the root ONR. | ||
relationships *tuple.ONRSet | ||
} | ||
|
||
// Subject returns the Subject of the FoundSubject. | ||
func (fs FoundSubject) Subject() *v0.ObjectAndRelation { | ||
return fs.subject | ||
} | ||
|
||
// WildcardType returns the object type for the wildcard subject, if this is a wildcard subject. | ||
func (fs FoundSubject) WildcardType() (string, bool) { | ||
if fs.subject.ObjectId == tuple.PublicWildcard { | ||
return fs.subject.Namespace, true | ||
} | ||
|
||
return "", false | ||
} | ||
|
||
// ExcludedSubjectsFromWildcard returns those subjects excluded from the wildcard subject. | ||
// If not a wildcard subject, returns false. | ||
func (fs FoundSubject) ExcludedSubjectsFromWildcard() ([]*v0.ObjectAndRelation, bool) { | ||
if fs.subject.ObjectId == tuple.PublicWildcard { | ||
return fs.excludedSubjects.AsSlice(), true | ||
} | ||
|
||
return []*v0.ObjectAndRelation{}, false | ||
} | ||
|
||
// Relationships returns all the relationships in which the subject was found as per the expand. | ||
func (fs FoundSubject) Relationships() []*v0.ObjectAndRelation { | ||
return fs.relationships.AsSlice() | ||
} | ||
|
||
// ToValidationString returns the FoundSubject in a format that is consumable by the validationfile | ||
// package. | ||
func (fs FoundSubject) ToValidationString() string { | ||
onrString := tuple.StringONR(fs.Subject()) | ||
excluded, isWildcard := fs.ExcludedSubjectsFromWildcard() | ||
if isWildcard && len(excluded) > 0 { | ||
excludedONRStrings := make([]string, 0, len(excluded)) | ||
for _, excludedONR := range excluded { | ||
excludedONRStrings = append(excludedONRStrings, tuple.StringONR(excludedONR)) | ||
} | ||
|
||
sort.Strings(excludedONRStrings) | ||
return fmt.Sprintf("%s - {%s}", onrString, strings.Join(excludedONRStrings, ", ")) | ||
} | ||
|
||
return onrString | ||
} | ||
|
||
// union performs merging of two FoundSubject's with the same subject. | ||
func (fs FoundSubject) union(other FoundSubject) FoundSubject { | ||
if toKey(fs.subject) != toKey(other.subject) { | ||
panic("Got wrong found subject to union") | ||
} | ||
|
||
relationships := fs.relationships.Union(other.relationships) | ||
var excludedSubjects *tuple.ONRSet | ||
|
||
// If a wildcard, then union together excluded subjects. | ||
_, isWildcard := fs.WildcardType() | ||
if isWildcard { | ||
excludedSubjects = fs.excludedSubjects.Union(other.excludedSubjects) | ||
} | ||
|
||
return FoundSubject{ | ||
subject: fs.subject, | ||
excludedSubjects: excludedSubjects, | ||
relationships: relationships, | ||
} | ||
} | ||
|
||
// intersect performs intersection between two FoundSubject's with the same subject. | ||
func (fs FoundSubject) intersect(other FoundSubject) FoundSubject { | ||
if toKey(fs.subject) != toKey(other.subject) { | ||
panic("Got wrong found subject to intersect") | ||
} | ||
|
||
relationships := fs.relationships.Union(other.relationships) | ||
var excludedSubjects *tuple.ONRSet | ||
|
||
// If a wildcard, then union together excluded subjects. | ||
_, isWildcard := fs.WildcardType() | ||
if isWildcard { | ||
excludedSubjects = fs.excludedSubjects.Union(other.excludedSubjects) | ||
} | ||
|
||
return FoundSubject{ | ||
subject: fs.subject, | ||
excludedSubjects: excludedSubjects, | ||
relationships: relationships, | ||
} | ||
} | ||
|
||
// FoundSubjects contains the subjects found for a specific ONR. | ||
type FoundSubjects struct { | ||
// subjects is a map from the Subject ONR (as a string) to the FoundSubject information. | ||
subjects map[string]FoundSubject | ||
} | ||
|
||
// ListFound returns a slice of all the FoundSubject's. | ||
func (fs FoundSubjects) ListFound() []FoundSubject { | ||
found := []FoundSubject{} | ||
for _, sub := range fs.subjects { | ||
found = append(found, sub) | ||
} | ||
return found | ||
} | ||
|
||
// LookupSubject returns the FoundSubject for a matching subject, if any. | ||
func (fs FoundSubjects) LookupSubject(subject *v0.ObjectAndRelation) (FoundSubject, bool) { | ||
found, ok := fs.subjects[toKey(subject)] | ||
return found, ok | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package membership | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/authzed/spicedb/pkg/validationfile" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestToValidationString(t *testing.T) { | ||
testCases := []struct { | ||
name string | ||
fs FoundSubject | ||
expected string | ||
}{ | ||
{ | ||
"basic", | ||
fs("user", "user1", "..."), | ||
"user:user1", | ||
}, | ||
{ | ||
"with exclusion", | ||
fs("user", "*", "...", ONR("user", "user1", "...")), | ||
"user:* - {user:user1}", | ||
}, | ||
{ | ||
"with some exclusion", | ||
fs("user", "*", "...", | ||
ONR("user", "user1", "..."), | ||
ONR("user", "user2", "..."), | ||
ONR("user", "user3", "..."), | ||
ONR("user", "user4", "..."), | ||
ONR("user", "user5", "..."), | ||
), | ||
"user:* - {user:user1, user:user2, user:user3, user:user4, user:user5}", | ||
}, | ||
{ | ||
"with many exclusion", | ||
fs("user", "*", "...", | ||
ONR("user", "user1", "..."), | ||
ONR("user", "user2", "..."), | ||
ONR("user", "user3", "..."), | ||
ONR("user", "user4", "..."), | ||
ONR("user", "user5", "..."), | ||
ONR("user", "user6", "..."), | ||
), | ||
"user:* - {user:user1, user:user2, user:user3, user:user4, user:user5, user:user6}", | ||
}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
require := require.New(t) | ||
require.Equal(tc.expected, tc.fs.ToValidationString()) | ||
|
||
sub, err := validationfile.ValidationString(fmt.Sprintf("[%s]", tc.expected)).Subject() | ||
require.Nil(err) | ||
require.NotNil(sub) | ||
}) | ||
} | ||
} |
Oops, something went wrong.