Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support wildcard in check requests #1

Closed
wants to merge 11 commits into from
Closed
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.16
require (
github.com/Masterminds/squirrel v1.5.2
github.com/alecthomas/units v0.0.0-20210927113745-59d0afb8317a
github.com/authzed/authzed-go v0.3.1-0.20211130221323-9d59da6e55da
github.com/authzed/authzed-go v0.3.1-0.20211210231744-4126c5fdd0a5
github.com/authzed/grpcutil v0.0.0-20211020204402-aba1876830e6
github.com/aws/aws-sdk-go v1.42.16
github.com/benbjohnson/clock v1.3.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/authzed/authzed-go v0.3.1-0.20211130221323-9d59da6e55da h1:dcjIyi2Om9CDAmEKiwQZsF7bV0x4PXuJeoN11BbbXXI=
github.com/authzed/authzed-go v0.3.1-0.20211130221323-9d59da6e55da/go.mod h1:bsUniBRroq4l5WZMYLO+T9osQa/P2qMwZ+Af8zoJK8Y=
github.com/authzed/authzed-go v0.3.1-0.20211210231744-4126c5fdd0a5 h1:avwpP+RWe7YaA6IisYzpfejb42/dDzhb7S4pwF4tmWs=
github.com/authzed/authzed-go v0.3.1-0.20211210231744-4126c5fdd0a5/go.mod h1:bsUniBRroq4l5WZMYLO+T9osQa/P2qMwZ+Af8zoJK8Y=
github.com/authzed/grpcutil v0.0.0-20210913124023-cad23ae5a9e8/go.mod h1:HwO/KbRU3fWXEYHE96kvXnwxzi97tkXD1hfi5UaZ71Y=
github.com/authzed/grpcutil v0.0.0-20211020204402-aba1876830e6 h1:izP/rEris51ZmomXb5J0ShyJKqsxTfVKDRnJz0QGbgg=
github.com/authzed/grpcutil v0.0.0-20211020204402-aba1876830e6/go.mod h1:rqjY3zyK/YP7NID9+B2BdIRRkvnK+cdf9/qya/zaFZE=
Expand Down
28 changes: 28 additions & 0 deletions internal/datastore/common/validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package common

import (
"fmt"

v1 "github.com/authzed/authzed-go/proto/authzed/api/v1"

"github.com/authzed/spicedb/pkg/tuple"
)

// ValidateUpdatesToWrite performs basic validation on relationship updates going into datastores.
func ValidateUpdatesToWrite(updates []*v1.RelationshipUpdate) error {
for _, update := range updates {
err := update.HandwrittenValidate()
if err != nil {
return err
}

if update.Relationship.Subject.Object.ObjectId == tuple.PublicWildcard && update.Relationship.Subject.OptionalRelation != "" {
return fmt.Errorf(
"Attempt to write a wildcard relationship (`%s`) with a non-empty relation. Please report this bug",
tuple.RelString(update.Relationship),
)
}
}

return nil
}
4 changes: 4 additions & 0 deletions internal/datastore/crdb/tuple.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ func (cds *crdbDatastore) WriteTuples(ctx context.Context, preconditions []*v1.P
defer span.End()
var nowRevision datastore.Revision

if err := common.ValidateUpdatesToWrite(mutations); err != nil {
return datastore.NoRevision, fmt.Errorf(errUnableToWriteTuples, err)
}

if err := cds.execute(ctx, cds.conn, pgx.TxOptions{}, func(tx pgx.Tx) error {
keySet := newKeySet()
if err := cds.checkPreconditions(ctx, tx, keySet, preconditions); err != nil {
Expand Down
5 changes: 5 additions & 0 deletions internal/datastore/memdb/tuple.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/jzelinskie/stringz"

"github.com/authzed/spicedb/internal/datastore"
"github.com/authzed/spicedb/internal/datastore/common"
)

const (
Expand Down Expand Up @@ -59,6 +60,10 @@ func (mds *memdbDatastore) WriteTuples(ctx context.Context, preconditions []*v1.
return datastore.NoRevision, fmt.Errorf(errUnableToWriteTuples, err)
}

if err := common.ValidateUpdatesToWrite(mutations); err != nil {
return datastore.NoRevision, fmt.Errorf(errUnableToWriteTuples, err)
}

newChangelogID, err := mds.write(ctx, txn, mutations)
if err != nil {
return datastore.NoRevision, fmt.Errorf(errUnableToWriteTuples, err)
Expand Down
4 changes: 4 additions & 0 deletions internal/datastore/postgres/tuple.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ func (pgd *pgDatastore) WriteTuples(ctx context.Context, preconditions []*v1.Pre
ctx, span := tracer.Start(datastore.SeparateContextWithTracing(ctx), "WriteTuples")
defer span.End()

if err := common.ValidateUpdatesToWrite(mutations); err != nil {
return datastore.NoRevision, fmt.Errorf(errUnableToWriteTuples, err)
}

tx, err := pgd.dbpool.Begin(ctx)
if err != nil {
return datastore.NoRevision, fmt.Errorf(errUnableToWriteTuples, err)
Expand Down
6 changes: 3 additions & 3 deletions internal/datastore/test/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ import (

var (
testNamespace = ns.Namespace("test/test",
ns.Relation("editor", nil, ns.RelationReference(testUserNS.Name, "...")),
ns.Relation("editor", nil, ns.AllowedRelation(testUserNS.Name, "...")),
)

updatedNamespace = ns.Namespace(testNamespace.Name,
ns.Relation("reader", nil, ns.RelationReference(testUserNS.Name, "...")),
ns.Relation("editor", nil, ns.RelationReference(testUserNS.Name, "...")),
ns.Relation("reader", nil, ns.AllowedRelation(testUserNS.Name, "...")),
ns.Relation("editor", nil, ns.AllowedRelation(testUserNS.Name, "...")),
)
)

Expand Down
6 changes: 5 additions & 1 deletion internal/graph/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ func onrEqual(lhs, rhs *v0.ObjectAndRelation) bool {
return lhs.ObjectId == rhs.ObjectId && lhs.Relation == rhs.Relation && lhs.Namespace == rhs.Namespace
}

func onrEqualOrPublic(tuple, target *v0.ObjectAndRelation) bool {
return onrEqual(tuple, target) || (target.Namespace == tuple.Namespace && tuple.ObjectId == "*")
}

// ValidatedCheckRequest represents a request after it has been validated and parsed for internal
// consumption.
type ValidatedCheckRequest struct {
Expand Down Expand Up @@ -82,7 +86,7 @@ func (cc *ConcurrentChecker) checkDirect(ctx context.Context, req ValidatedCheck
var requestsToDispatch []ReduceableCheckFunc
for tpl := it.Next(); tpl != nil; tpl = it.Next() {
tplUserset := tpl.User.GetUserset()
if onrEqual(tplUserset, req.Subject) {
if onrEqualOrPublic(tplUserset, req.Subject) {
resultChan <- checkResult(v1.DispatchCheckResponse_MEMBER, emptyMetadata)
return
}
Expand Down
14 changes: 7 additions & 7 deletions internal/graph/lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,9 @@ func (cl *ConcurrentLookup) lookupDirect(ctx context.Context, req ValidatedLooku
})
}

// Dispatch to any allowed direct relation types that don't match the target ONR, collect
// Dispatch to any allowed subject relation types that don't match the target ONR, collect
// the found object IDs, and then search for those.
allowedDirect, err := typeSystem.AllowedDirectRelations(req.ObjectRelation.Relation)
allowedDirect, err := typeSystem.AllowedSubjectRelations(req.ObjectRelation.Relation)
if err != nil {
return returnResult(lookupResultError(req, err, emptyMetadata))
}
Expand All @@ -212,19 +212,19 @@ func (cl *ConcurrentLookup) lookupDirect(ctx context.Context, req ValidatedLooku

var excludedDirect []*v0.RelationReference
for _, allowedDirectType := range allowedDirect {
if allowedDirectType.Relation == Ellipsis {
if allowedDirectType.GetRelation() == Ellipsis {
continue
}

if allowedDirectType.Namespace == req.ObjectRelation.Namespace &&
allowedDirectType.Relation == req.ObjectRelation.Relation {
allowedDirectType.GetRelation() == req.ObjectRelation.Relation {
continue
}

// Prevent recursive inferred lookups, which can cause an infinite loop.
rr := &v0.RelationReference{
Namespace: allowedDirectType.Namespace,
Relation: allowedDirectType.Relation,
Relation: allowedDirectType.GetRelation(),
}
if checkStackContains(directStack, rr) {
excludedDirect = append(excludedDirect, rr)
Expand All @@ -241,7 +241,7 @@ func (cl *ConcurrentLookup) lookupDirect(ctx context.Context, req ValidatedLooku
Subject: req.Subject,
ObjectRelation: &v0.RelationReference{
Namespace: allowedDirectType.Namespace,
Relation: allowedDirectType.Relation,
Relation: allowedDirectType.GetRelation(),
},
Limit: noLimit, // Since this is an inferred lookup, we can't limit.
Metadata: decrementDepth(req.Metadata),
Expand Down Expand Up @@ -350,7 +350,7 @@ func (cl *ConcurrentLookup) processTupleToUserset(ctx context.Context, req Valid
return returnResult(result)
}

tuplesetDirectRelations, err := typeSystem.AllowedDirectRelations(ttu.Tupleset.Relation)
tuplesetDirectRelations, err := typeSystem.AllowedSubjectRelations(ttu.Tupleset.Relation)
if err != nil {
return returnResult(lookupResultError(req, err, emptyMetadata))
}
Expand Down
54 changes: 54 additions & 0 deletions internal/middleware/handwrittenvalidation/handwrittenvalidation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package handwrittenvalidation

import (
"context"

"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

type handwrittenValidator interface {
HandwrittenValidate() error
}

// UnaryServerInterceptor returns a new unary server interceptor that runs the handwritten validation
// on the incoming request, if any.
func UnaryServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
validator, ok := req.(handwrittenValidator)
if ok {
err := validator.HandwrittenValidate()
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "%s", err)
}
}

return handler(ctx, req)
}

// StreamServerInterceptor returns a new stream server interceptor that runs the handwritten validation
// on the incoming request messages, if any.
func StreamServerInterceptor(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
wrapper := &recvWrapper{stream}
return handler(srv, wrapper)
}

type recvWrapper struct {
grpc.ServerStream
}

func (s *recvWrapper) RecvMsg(m interface{}) error {
if err := s.ServerStream.RecvMsg(m); err != nil {
return err
}

validator, ok := m.(handwrittenValidator)
if ok {
err := validator.HandwrittenValidate()
if err != nil {
return err
}
}

return nil
}
99 changes: 73 additions & 26 deletions internal/namespace/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ const (
// RelationDirectTypeRemoved indicates that an allowed direct relation type has been removed from
// the relation.
RelationDirectTypeRemoved DeltaType = "relation-direct-type-removed"

// RelationDirectWildcardTypeAdded indicates that an allowed relation wildcard type has been added to
// the relation.
RelationDirectWildcardTypeAdded DeltaType = "relation-direct-type-added"

// RelationDirectWildcardTypeRemoved indicates that an allowed relation wildcard type has been removed from
// the relation.
RelationDirectWildcardTypeRemoved DeltaType = "relation-direct-type-removed"
)

// NamespaceDiff holds the diff between two namespaces.
Expand All @@ -60,6 +68,9 @@ type Delta struct {

// DirectType is the direct relation type added or removed, if any.
DirectType *v0.RelationReference

// WildcardType is the wildcard type added or removed, if any.
WildcardType string
}

// DiffNamespaces performs a diff between two namespace definitions. One or both of the definitions
Expand Down Expand Up @@ -167,41 +178,77 @@ func DiffNamespaces(existing *v0.NamespaceDefinition, updated *v0.NamespaceDefin
updatedAllowedRels := tuple.NewONRSet()

for _, existingAllowed := range existingTypeInfo.AllowedDirectRelations {
existingAllowedRels.Add(&v0.ObjectAndRelation{
Namespace: existingAllowed.Namespace,
Relation: existingAllowed.Relation,
ObjectId: "",
})
if existingAllowed.GetRelation() != "" {
existingAllowedRels.Add(&v0.ObjectAndRelation{
Namespace: existingAllowed.Namespace,
Relation: existingAllowed.GetRelation(),
ObjectId: "",
})
}

if existingAllowed.GetPublicWildcard() != nil {
existingAllowedRels.Add(&v0.ObjectAndRelation{
Namespace: existingAllowed.Namespace,
Relation: "-",
ObjectId: tuple.PublicWildcard,
})
}
}

for _, updatedAllowed := range updatedTypeInfo.AllowedDirectRelations {
updatedAllowedRels.Add(&v0.ObjectAndRelation{
Namespace: updatedAllowed.Namespace,
Relation: updatedAllowed.Relation,
ObjectId: "",
})
if updatedAllowed.GetRelation() != "" {
updatedAllowedRels.Add(&v0.ObjectAndRelation{
Namespace: updatedAllowed.Namespace,
Relation: updatedAllowed.GetRelation(),
ObjectId: "",
})
}

if updatedAllowed.GetPublicWildcard() != nil {
updatedAllowedRels.Add(&v0.ObjectAndRelation{
Namespace: updatedAllowed.Namespace,
Relation: "-",
ObjectId: tuple.PublicWildcard,
})
}
}

for _, removed := range existingAllowedRels.Subtract(updatedAllowedRels).AsSlice() {
deltas = append(deltas, Delta{
Type: RelationDirectTypeRemoved,
RelationName: shared,
DirectType: &v0.RelationReference{
Namespace: removed.Namespace,
Relation: removed.Relation,
},
})
if removed.ObjectId == tuple.PublicWildcard {
deltas = append(deltas, Delta{
Type: RelationDirectWildcardTypeRemoved,
RelationName: shared,
WildcardType: removed.Namespace,
})
} else {
deltas = append(deltas, Delta{
Type: RelationDirectTypeRemoved,
RelationName: shared,
DirectType: &v0.RelationReference{
Namespace: removed.Namespace,
Relation: removed.Relation,
},
})
}
}

for _, added := range updatedAllowedRels.Subtract(existingAllowedRels).AsSlice() {
deltas = append(deltas, Delta{
Type: RelationDirectTypeAdded,
RelationName: shared,
DirectType: &v0.RelationReference{
Namespace: added.Namespace,
Relation: added.Relation,
},
})
if added.ObjectId == tuple.PublicWildcard {
deltas = append(deltas, Delta{
Type: RelationDirectWildcardTypeAdded,
RelationName: shared,
WildcardType: added.Namespace,
})
} else {
deltas = append(deltas, Delta{
Type: RelationDirectTypeAdded,
RelationName: shared,
DirectType: &v0.RelationReference{
Namespace: added.Namespace,
Relation: added.Relation,
},
})
}
}
}

Expand Down
Loading
Loading