From eb2e71571e0152e7c6216c6784e1f2d9e18eae27 Mon Sep 17 00:00:00 2001 From: Jake Moshenko Date: Thu, 9 Dec 2021 17:02:30 -0500 Subject: [PATCH] user revisions for loading namespaces in handlers --- internal/dashboard/dashboard.go | 9 +++- internal/services/consistency_test.go | 2 +- internal/services/shared/schema.go | 5 ++- internal/services/v0/acl.go | 59 +++++++++++++++------------ internal/services/v0/devcontext.go | 29 ++++++++----- internal/services/v0/namespace.go | 20 ++++++--- internal/services/v0/validation.go | 12 +++++- internal/services/v0/watch.go | 22 +++++----- internal/services/v1/permissions.go | 19 ++++++--- internal/services/v1/relationships.go | 31 ++++++++++---- internal/services/v1/schema.go | 17 ++++++-- internal/services/v1alpha1/schema.go | 26 ++++++++---- pkg/cmd/serve/serve.go | 7 +++- 13 files changed, 173 insertions(+), 85 deletions(-) diff --git a/internal/dashboard/dashboard.go b/internal/dashboard/dashboard.go index b003e794bd..58c7eed725 100644 --- a/internal/dashboard/dashboard.go +++ b/internal/dashboard/dashboard.go @@ -131,7 +131,14 @@ func NewHandler(grpcAddr string, grpcTLSEnabled bool, datastoreEngine string, ds userFound := false resourceFound := false - nsDefs, err := ds.ListNamespaces(r.Context()) + syncRevision, err := ds.SyncRevision(r.Context()) + if err != nil { + log.Ctx(r.Context()).Error().Err(err).Msg("Got error when computing datastore revision") + fmt.Fprintf(w, "Internal Error") + return + } + + nsDefs, err := ds.ListNamespaces(r.Context(), syncRevision) if err != nil { log.Ctx(r.Context()).Error().AnErr("datastore-error", err).Msg("Got error when trying to load namespaces") fmt.Fprintf(w, "Internal Error") diff --git a/internal/services/consistency_test.go b/internal/services/consistency_test.go index 780f2ab82f..978c5a637b 100644 --- a/internal/services/consistency_test.go +++ b/internal/services/consistency_test.go @@ -74,7 +74,7 @@ func TestConsistency(t *testing.T) { // Validate the type system for each namespace. for _, nsDef := range fullyResolved.NamespaceDefinitions { - _, ts, _, err := ns.ReadNamespaceAndTypes(context.Background(), nsDef.Name) + _, ts, err := ns.ReadNamespaceAndTypes(context.Background(), nsDef.Name, revision) lrequire.NoError(err) err = ts.Validate(context.Background()) diff --git a/internal/services/shared/schema.go b/internal/services/shared/schema.go index fa112a5e98..cd0885cdfc 100644 --- a/internal/services/shared/schema.go +++ b/internal/services/shared/schema.go @@ -5,6 +5,7 @@ import ( "errors" v0 "github.com/authzed/authzed-go/proto/authzed/api/v0" + "github.com/shopspring/decimal" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -42,12 +43,12 @@ func EnsureNoRelationshipsExist(ctx context.Context, ds datastore.Datastore, nam // SanityCheckExistingRelationships ensures that a namespace definition being written does not result // in relationships without associated defined schema object definitions and relations. -func SanityCheckExistingRelationships(ctx context.Context, ds datastore.Datastore, nsdef *v0.NamespaceDefinition) error { +func SanityCheckExistingRelationships(ctx context.Context, ds datastore.Datastore, nsdef *v0.NamespaceDefinition, revision decimal.Decimal) error { // Ensure that the updated namespace does not break the existing tuple data. // // NOTE: We use the datastore here to read the namespace, rather than the namespace manager, // to ensure there is no caching being used. - existing, _, err := ds.ReadNamespace(ctx, nsdef.Name) + existing, _, err := ds.ReadNamespace(ctx, nsdef.Name, revision) if err != nil && !errors.As(err, &datastore.ErrNamespaceNotFound{}) { return err } diff --git a/internal/services/v0/acl.go b/internal/services/v0/acl.go index aa488e175c..f090e94254 100644 --- a/internal/services/v0/acl.go +++ b/internal/services/v0/acl.go @@ -66,8 +66,13 @@ func NewACLServer(ds datastore.Datastore, nsm namespace.Manager, dispatch dispat } func (as *aclServer) Write(ctx context.Context, req *v0.WriteRequest) (*v0.WriteResponse, error) { + atRevision, err := as.ds.SyncRevision(ctx) + if err != nil { + return nil, rewriteACLError(ctx, err) + } + for _, mutation := range req.Updates { - err := validateTupleWrite(ctx, mutation.Tuple, as.nsm) + err := validateTupleWrite(ctx, mutation.Tuple, as.nsm, atRevision) if err != nil { return nil, rewriteACLError(ctx, err) } @@ -97,6 +102,24 @@ func (as *aclServer) Write(ctx context.Context, req *v0.WriteRequest) (*v0.Write } func (as *aclServer) Read(ctx context.Context, req *v0.ReadRequest) (*v0.ReadResponse, error) { + var atRevision decimal.Decimal + if req.AtRevision != nil { + // Read should attempt to use the exact revision requested + decoded, err := zookie.DecodeRevision(req.AtRevision) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "bad request revision: %s", err) + } + + atRevision = decoded + } else { + // No revision provided, we'll pick one + var err error + atRevision, err = as.ds.Revision(ctx) + if err != nil { + return nil, status.Errorf(codes.Internal, "unable to pick request revision: %s", err) + } + } + for _, tuplesetFilter := range req.Tuplesets { checkedRelation := false for _, filter := range tuplesetFilter.Filters { @@ -120,6 +143,7 @@ func (as *aclServer) Read(ctx context.Context, req *v0.ReadRequest) (*v0.ReadRes tuplesetFilter.Namespace, tuplesetFilter.Relation, false, // Disallow ellipsis + atRevision, ); err != nil { return nil, rewriteACLError(ctx, err) } @@ -146,30 +170,13 @@ func (as *aclServer) Read(ctx context.Context, req *v0.ReadRequest) (*v0.ReadRes tuplesetFilter.Namespace, datastore.Ellipsis, true, // Allow ellipsis + atRevision, ); err != nil { return nil, rewriteACLError(ctx, err) } } } - var atRevision decimal.Decimal - if req.AtRevision != nil { - // Read should attempt to use the exact revision requested - decoded, err := zookie.DecodeRevision(req.AtRevision) - if err != nil { - return nil, status.Errorf(codes.InvalidArgument, "bad request revision: %s", err) - } - - atRevision = decoded - } else { - // No revision provided, we'll pick one - var err error - atRevision, err = as.ds.Revision(ctx) - if err != nil { - return nil, status.Errorf(codes.Internal, "unable to pick request revision: %s", err) - } - } - err := as.ds.CheckRevision(ctx, atRevision) if err != nil { return nil, rewriteACLError(ctx, err) @@ -248,12 +255,12 @@ func (as *aclServer) commonCheck( start *v0.ObjectAndRelation, goal *v0.ObjectAndRelation, ) (*v0.CheckResponse, error) { - err := as.nsm.CheckNamespaceAndRelation(ctx, start.Namespace, start.Relation, false) + err := as.nsm.CheckNamespaceAndRelation(ctx, start.Namespace, start.Relation, false, atRevision) if err != nil { return nil, rewriteACLError(ctx, err) } - err = as.nsm.CheckNamespaceAndRelation(ctx, goal.Namespace, goal.Relation, true) + err = as.nsm.CheckNamespaceAndRelation(ctx, goal.Namespace, goal.Relation, true, atRevision) if err != nil { return nil, rewriteACLError(ctx, err) } @@ -290,12 +297,12 @@ func (as *aclServer) commonCheck( } func (as *aclServer) Expand(ctx context.Context, req *v0.ExpandRequest) (*v0.ExpandResponse, error) { - err := as.nsm.CheckNamespaceAndRelation(ctx, req.Userset.Namespace, req.Userset.Relation, false) + atRevision, err := as.pickBestRevision(ctx, req.AtRevision) if err != nil { return nil, rewriteACLError(ctx, err) } - atRevision, err := as.pickBestRevision(ctx, req.AtRevision) + err = as.nsm.CheckNamespaceAndRelation(ctx, req.Userset.Namespace, req.Userset.Relation, false, atRevision) if err != nil { return nil, rewriteACLError(ctx, err) } @@ -320,17 +327,17 @@ func (as *aclServer) Expand(ctx context.Context, req *v0.ExpandRequest) (*v0.Exp } func (as *aclServer) Lookup(ctx context.Context, req *v0.LookupRequest) (*v0.LookupResponse, error) { - err := as.nsm.CheckNamespaceAndRelation(ctx, req.User.Namespace, req.User.Relation, true) + atRevision, err := as.pickBestRevision(ctx, req.AtRevision) if err != nil { return nil, rewriteACLError(ctx, err) } - err = as.nsm.CheckNamespaceAndRelation(ctx, req.ObjectRelation.Namespace, req.ObjectRelation.Relation, false) + err = as.nsm.CheckNamespaceAndRelation(ctx, req.User.Namespace, req.User.Relation, true, atRevision) if err != nil { return nil, rewriteACLError(ctx, err) } - atRevision, err := as.pickBestRevision(ctx, req.AtRevision) + err = as.nsm.CheckNamespaceAndRelation(ctx, req.ObjectRelation.Namespace, req.ObjectRelation.Relation, false, atRevision) if err != nil { return nil, rewriteACLError(ctx, err) } diff --git a/internal/services/v0/devcontext.go b/internal/services/v0/devcontext.go index f72d2b4d37..8f7f696831 100644 --- a/internal/services/v0/devcontext.go +++ b/internal/services/v0/devcontext.go @@ -72,7 +72,9 @@ func newDevContext(ctx context.Context, requestContext *v0.RequestContext, ds da return &DevContext{Ctx: ctx, NamespaceManager: nsm, RequestErrors: []*v0.DeveloperError{devError}}, false, nil } - requestErrors, err := loadNamespaces(ctx, namespaces, nsm, ds) + var currentRevision decimal.Decimal + var requestErrors []*v0.DeveloperError + requestErrors, currentRevision, err = loadNamespaces(ctx, namespaces, nsm, ds) if err != nil { return &DevContext{Ctx: ctx, NamespaceManager: nsm}, false, err } @@ -82,7 +84,7 @@ func newDevContext(ctx context.Context, requestContext *v0.RequestContext, ds da } if len(requestContext.LegacyNsConfigs) > 0 { - requestErrors, err := loadNamespaces(ctx, requestContext.LegacyNsConfigs, nsm, ds) + requestErrors, currentRevision, err = loadNamespaces(ctx, requestContext.LegacyNsConfigs, nsm, ds) if err != nil { return &DevContext{Ctx: ctx, NamespaceManager: nsm}, false, err } @@ -92,7 +94,7 @@ func newDevContext(ctx context.Context, requestContext *v0.RequestContext, ds da } } - revision, requestErrors, err := loadTuples(ctx, requestContext.Relationships, nsm, ds) + revision, requestErrors, err := loadTuples(ctx, requestContext.Relationships, nsm, ds, currentRevision) if err != nil { return &DevContext{Ctx: ctx, NamespaceManager: nsm, Namespaces: namespaces}, false, err } @@ -162,7 +164,7 @@ func compile(schema string) ([]*v0.NamespaceDefinition, *v0.DeveloperError, erro return namespaces, nil, nil } -func loadTuples(ctx context.Context, tuples []*v0.RelationTuple, nsm namespace.Manager, ds datastore.Datastore) (decimal.Decimal, []*v0.DeveloperError, error) { +func loadTuples(ctx context.Context, tuples []*v0.RelationTuple, nsm namespace.Manager, ds datastore.Datastore, revision decimal.Decimal) (decimal.Decimal, []*v0.DeveloperError, error) { var errors []*v0.DeveloperError var updates []*v1.RelationshipUpdate for _, tpl := range tuples { @@ -177,7 +179,7 @@ func loadTuples(ctx context.Context, tuples []*v0.RelationTuple, nsm namespace.M continue } - err := validateTupleWrite(ctx, tpl, nsm) + err := validateTupleWrite(ctx, tpl, nsm, revision) if err != nil { verrs, wireErr := rewriteGraphError(ctx, v0.DeveloperError_RELATIONSHIP, 0, 0, tuple.String(tpl), err) if wireErr == nil { @@ -198,19 +200,26 @@ func loadTuples(ctx context.Context, tuples []*v0.RelationTuple, nsm namespace.M return revision, errors, err } -func loadNamespaces(ctx context.Context, namespaces []*v0.NamespaceDefinition, nsm namespace.Manager, ds datastore.Datastore) ([]*v0.DeveloperError, error) { +func loadNamespaces( + ctx context.Context, + namespaces []*v0.NamespaceDefinition, + nsm namespace.Manager, + ds datastore.Datastore, +) ([]*v0.DeveloperError, decimal.Decimal, error) { var errors []*v0.DeveloperError + var lastRevision decimal.Decimal for _, nsDef := range namespaces { ts, terr := namespace.BuildNamespaceTypeSystemForDefs(nsDef, namespaces) if terr != nil { - return errors, terr + return errors, lastRevision, terr } tverr := ts.Validate(ctx) if tverr == nil { - _, err := ds.WriteNamespace(ctx, nsDef) + var err error + lastRevision, err = ds.WriteNamespace(ctx, nsDef) if err != nil { - return errors, err + return errors, lastRevision, err } continue } @@ -223,5 +232,5 @@ func loadNamespaces(ctx context.Context, namespaces []*v0.NamespaceDefinition, n }) } - return errors, nil + return errors, lastRevision, nil } diff --git a/internal/services/v0/namespace.go b/internal/services/v0/namespace.go index 43cac1f411..04a32d66f1 100644 --- a/internal/services/v0/namespace.go +++ b/internal/services/v0/namespace.go @@ -45,9 +45,14 @@ func (nss *nsServer) WriteConfig(ctx context.Context, req *v0.WriteConfigRequest return nil, rewriteNamespaceError(ctx, err) } + readRevision, err := nss.ds.SyncRevision(ctx) + if err != nil { + return nil, rewriteNamespaceError(ctx, err) + } + for _, config := range req.Configs { // Validate the type system for the updated namespace. - ts, terr := namespace.BuildNamespaceTypeSystemWithFallback(config, nsm, req.Configs) + ts, terr := namespace.BuildNamespaceTypeSystemWithFallback(config, nsm, req.Configs, readRevision) if terr != nil { return nil, rewriteNamespaceError(ctx, terr) } @@ -61,7 +66,7 @@ func (nss *nsServer) WriteConfig(ctx context.Context, req *v0.WriteConfigRequest // // NOTE: We use the datastore here to read the namespace, rather than the namespace manager, // to ensure there is no caching being used. - existing, _, err := nss.ds.ReadNamespace(ctx, config.Name) + existing, _, err := nss.ds.ReadNamespace(ctx, config.Name, readRevision) if err != nil && !errors.As(err, &datastore.ErrNamespaceNotFound{}) { return nil, rewriteNamespaceError(ctx, err) } @@ -128,7 +133,12 @@ func (nss *nsServer) WriteConfig(ctx context.Context, req *v0.WriteConfigRequest } func (nss *nsServer) ReadConfig(ctx context.Context, req *v0.ReadConfigRequest) (*v0.ReadConfigResponse, error) { - found, version, err := nss.ds.ReadNamespace(ctx, req.Namespace) + readRevision, err := nss.ds.SyncRevision(ctx) + if err != nil { + return nil, rewriteNamespaceError(ctx, err) + } + + found, _, err := nss.ds.ReadNamespace(ctx, req.Namespace, readRevision) if err != nil { return nil, rewriteNamespaceError(ctx, err) } @@ -136,7 +146,7 @@ func (nss *nsServer) ReadConfig(ctx context.Context, req *v0.ReadConfigRequest) return &v0.ReadConfigResponse{ Namespace: req.Namespace, Config: found, - Revision: zookie.NewFromRevision(version), + Revision: zookie.NewFromRevision(readRevision), }, nil } @@ -149,7 +159,7 @@ func (nss *nsServer) DeleteConfigs(ctx context.Context, req *v0.DeleteConfigsReq // Ensure that all the specified namespaces can be deleted. for _, nsName := range req.Namespaces { // Ensure the namespace exists. - _, _, err := nss.ds.ReadNamespace(ctx, nsName) + _, _, err := nss.ds.ReadNamespace(ctx, nsName, syncRevision) if err != nil { return nil, rewriteNamespaceError(ctx, err) } diff --git a/internal/services/v0/validation.go b/internal/services/v0/validation.go index 474d52ebbf..a9c26cf0db 100644 --- a/internal/services/v0/validation.go +++ b/internal/services/v0/validation.go @@ -5,6 +5,7 @@ import ( "fmt" v0 "github.com/authzed/authzed-go/proto/authzed/api/v0" + "github.com/shopspring/decimal" "github.com/authzed/spicedb/internal/namespace" ) @@ -15,12 +16,18 @@ type invalidRelationError struct { onr *v0.ObjectAndRelation } -func validateTupleWrite(ctx context.Context, tpl *v0.RelationTuple, nsm namespace.Manager) error { +func validateTupleWrite( + ctx context.Context, + tpl *v0.RelationTuple, + nsm namespace.Manager, + revision decimal.Decimal, +) error { if err := nsm.CheckNamespaceAndRelation( ctx, tpl.ObjectAndRelation.Namespace, tpl.ObjectAndRelation.Relation, false, // Disallow ellipsis + revision, ); err != nil { return err } @@ -30,11 +37,12 @@ func validateTupleWrite(ctx context.Context, tpl *v0.RelationTuple, nsm namespac tpl.User.GetUserset().Namespace, tpl.User.GetUserset().Relation, true, // Allow Ellipsis + revision, ); err != nil { return err } - _, ts, _, err := nsm.ReadNamespaceAndTypes(ctx, tpl.ObjectAndRelation.Namespace) + _, ts, err := nsm.ReadNamespaceAndTypes(ctx, tpl.ObjectAndRelation.Namespace, revision) if err != nil { return err } diff --git a/internal/services/v0/watch.go b/internal/services/v0/watch.go index cfb56ffbcc..933ad1f916 100644 --- a/internal/services/v0/watch.go +++ b/internal/services/v0/watch.go @@ -35,17 +35,6 @@ func NewWatchServer(ds datastore.Datastore, nsm namespace.Manager) v0.WatchServi } func (ws *watchServer) Watch(req *v0.WatchRequest, stream v0.WatchService_WatchServer) error { - namespaceMap := make(map[string]struct{}) - for _, ns := range req.Namespaces { - err := ws.nsm.CheckNamespaceAndRelation(stream.Context(), ns, datastore.Ellipsis, true) - if err != nil { - return status.Errorf(codes.FailedPrecondition, "invalid namespace: %s", err) - } - - namespaceMap[ns] = struct{}{} - } - filter := namespaceFilter{namespaces: namespaceMap} - var afterRevision decimal.Decimal if req.StartRevision != nil && req.StartRevision.Token != "" { decodedRevision, err := zookie.DecodeRevision(req.StartRevision) @@ -62,6 +51,17 @@ func (ws *watchServer) Watch(req *v0.WatchRequest, stream v0.WatchService_WatchS } } + namespaceMap := make(map[string]struct{}) + for _, ns := range req.Namespaces { + err := ws.nsm.CheckNamespaceAndRelation(stream.Context(), ns, datastore.Ellipsis, true, afterRevision) + if err != nil { + return status.Errorf(codes.FailedPrecondition, "invalid namespace: %s", err) + } + + namespaceMap[ns] = struct{}{} + } + filter := namespaceFilter{namespaces: namespaceMap} + updates, errchan := ws.ds.Watch(stream.Context(), afterRevision) for { select { diff --git a/internal/services/v1/permissions.go b/internal/services/v1/permissions.go index b38abd1142..520fd30086 100644 --- a/internal/services/v1/permissions.go +++ b/internal/services/v1/permissions.go @@ -17,7 +17,7 @@ import ( func (ps *permissionServer) CheckPermission(ctx context.Context, req *v1.CheckPermissionRequest) (*v1.CheckPermissionResponse, error) { atRevision, checkedAt := consistency.MustRevisionFromContext(ctx) - err := ps.nsm.CheckNamespaceAndRelation(ctx, req.Resource.ObjectType, req.Permission, false) + err := ps.nsm.CheckNamespaceAndRelation(ctx, req.Resource.ObjectType, req.Permission, false, atRevision) if err != nil { return nil, rewritePermissionsError(ctx, err) } @@ -25,7 +25,9 @@ func (ps *permissionServer) CheckPermission(ctx context.Context, req *v1.CheckPe err = ps.nsm.CheckNamespaceAndRelation(ctx, req.Subject.Object.ObjectType, normalizeSubjectRelation(req.Subject), - true) + true, + atRevision, + ) if err != nil { return nil, rewritePermissionsError(ctx, err) } @@ -70,7 +72,7 @@ func (ps *permissionServer) CheckPermission(ctx context.Context, req *v1.CheckPe func (ps *permissionServer) ExpandPermissionTree(ctx context.Context, req *v1.ExpandPermissionTreeRequest) (*v1.ExpandPermissionTreeResponse, error) { atRevision, expandedAt := consistency.MustRevisionFromContext(ctx) - err := ps.nsm.CheckNamespaceAndRelation(ctx, req.Resource.ObjectType, req.Permission, false) + err := ps.nsm.CheckNamespaceAndRelation(ctx, req.Resource.ObjectType, req.Permission, false, atRevision) if err != nil { return nil, rewritePermissionsError(ctx, err) } @@ -252,13 +254,18 @@ func (ps *permissionServer) LookupResources(req *v1.LookupResourcesRequest, resp ctx := resp.Context() atRevision, revisionReadAt := consistency.MustRevisionFromContext(ctx) - err := ps.nsm.CheckNamespaceAndRelation(ctx, req.Subject.Object.ObjectType, - normalizeSubjectRelation(req.Subject), true) + err := ps.nsm.CheckNamespaceAndRelation( + ctx, + req.Subject.Object.ObjectType, + normalizeSubjectRelation(req.Subject), + true, + atRevision, + ) if err != nil { return rewritePermissionsError(ctx, err) } - err = ps.nsm.CheckNamespaceAndRelation(ctx, req.ResourceObjectType, req.Permission, false) + err = ps.nsm.CheckNamespaceAndRelation(ctx, req.ResourceObjectType, req.Permission, false, atRevision) if err != nil { return rewritePermissionsError(ctx, err) } diff --git a/internal/services/v1/relationships.go b/internal/services/v1/relationships.go index 1e99f2fedc..86d895aa90 100644 --- a/internal/services/v1/relationships.go +++ b/internal/services/v1/relationships.go @@ -9,6 +9,7 @@ import ( grpcvalidate "github.com/grpc-ecosystem/go-grpc-middleware/validator" "github.com/jzelinskie/stringz" "github.com/rs/zerolog/log" + "github.com/shopspring/decimal" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -61,14 +62,14 @@ type permissionServer struct { defaultDepth uint32 } -func (ps *permissionServer) checkFilterComponent(ctx context.Context, objectType, optionalRelation string) error { +func (ps *permissionServer) checkFilterComponent(ctx context.Context, objectType, optionalRelation string, revision decimal.Decimal) error { relationToTest := stringz.DefaultEmpty(optionalRelation, datastore.Ellipsis) allowEllipsis := optionalRelation == "" - return ps.nsm.CheckNamespaceAndRelation(ctx, objectType, relationToTest, allowEllipsis) + return ps.nsm.CheckNamespaceAndRelation(ctx, objectType, relationToTest, allowEllipsis, revision) } -func (ps *permissionServer) checkFilterNamespaces(ctx context.Context, filter *v1.RelationshipFilter) error { - if err := ps.checkFilterComponent(ctx, filter.ResourceType, filter.OptionalRelation); err != nil { +func (ps *permissionServer) checkFilterNamespaces(ctx context.Context, filter *v1.RelationshipFilter, revision decimal.Decimal) error { + if err := ps.checkFilterComponent(ctx, filter.ResourceType, filter.OptionalRelation, revision); err != nil { return err } @@ -77,7 +78,7 @@ func (ps *permissionServer) checkFilterNamespaces(ctx context.Context, filter *v if subjectFilter.OptionalRelation != nil { subjectRelation = subjectFilter.OptionalRelation.Relation } - if err := ps.checkFilterComponent(ctx, subjectFilter.SubjectType, subjectRelation); err != nil { + if err := ps.checkFilterComponent(ctx, subjectFilter.SubjectType, subjectRelation, revision); err != nil { return err } } @@ -90,7 +91,7 @@ func (ps *permissionServer) ReadRelationships(req *v1.ReadRelationshipsRequest, atRevision, revisionReadAt := consistency.MustRevisionFromContext(ctx) - if err := ps.checkFilterNamespaces(ctx, req.RelationshipFilter); err != nil { + if err := ps.checkFilterNamespaces(ctx, req.RelationshipFilter, atRevision); err != nil { return rewritePermissionsError(ctx, err) } @@ -146,8 +147,13 @@ func (ps *permissionServer) ReadRelationships(req *v1.ReadRelationshipsRequest, } func (ps *permissionServer) WriteRelationships(ctx context.Context, req *v1.WriteRelationshipsRequest) (*v1.WriteRelationshipsResponse, error) { + readRevision, err := ps.ds.SyncRevision(ctx) + if err != nil { + return nil, rewritePermissionsError(ctx, err) + } + for _, precond := range req.OptionalPreconditions { - if err := ps.checkFilterNamespaces(ctx, precond.Filter); err != nil { + if err := ps.checkFilterNamespaces(ctx, precond.Filter, readRevision); err != nil { return nil, rewritePermissionsError(ctx, err) } } @@ -158,6 +164,7 @@ func (ps *permissionServer) WriteRelationships(ctx context.Context, req *v1.Writ update.Relationship.Resource.ObjectType, update.Relationship.Relation, false, + readRevision, ); err != nil { return nil, rewritePermissionsError(ctx, err) } @@ -167,11 +174,12 @@ func (ps *permissionServer) WriteRelationships(ctx context.Context, req *v1.Writ update.Relationship.Subject.Object.ObjectType, stringz.DefaultEmpty(update.Relationship.Subject.OptionalRelation, datastore.Ellipsis), true, + readRevision, ); err != nil { return nil, rewritePermissionsError(ctx, err) } - _, ts, _, err := ps.nsm.ReadNamespaceAndTypes(ctx, update.Relationship.Resource.ObjectType) + _, ts, err := ps.nsm.ReadNamespaceAndTypes(ctx, update.Relationship.Resource.ObjectType, readRevision) if err != nil { return nil, err } @@ -214,7 +222,12 @@ func (ps *permissionServer) WriteRelationships(ctx context.Context, req *v1.Writ } func (ps *permissionServer) DeleteRelationships(ctx context.Context, req *v1.DeleteRelationshipsRequest) (*v1.DeleteRelationshipsResponse, error) { - if err := ps.checkFilterNamespaces(ctx, req.RelationshipFilter); err != nil { + readRevision, err := ps.ds.SyncRevision(ctx) + if err != nil { + return nil, rewritePermissionsError(ctx, err) + } + + if err := ps.checkFilterNamespaces(ctx, req.RelationshipFilter, readRevision); err != nil { return nil, rewritePermissionsError(ctx, err) } diff --git a/internal/services/v1/schema.go b/internal/services/v1/schema.go index 0bd64c47af..88844ec1db 100644 --- a/internal/services/v1/schema.go +++ b/internal/services/v1/schema.go @@ -40,7 +40,12 @@ type schemaServer struct { } func (ss *schemaServer) ReadSchema(ctx context.Context, in *v1.ReadSchemaRequest) (*v1.ReadSchemaResponse, error) { - nsDefs, err := ss.ds.ListNamespaces(ctx) + readRevision, err := ss.ds.SyncRevision(ctx) + if err != nil { + return nil, rewritePermissionsError(ctx, err) + } + + nsDefs, err := ss.ds.ListNamespaces(ctx, readRevision) if err != nil { return nil, rewriteSchemaError(ctx, err) } @@ -62,13 +67,19 @@ func (ss *schemaServer) ReadSchema(ctx context.Context, in *v1.ReadSchemaRequest func (ss *schemaServer) WriteSchema(ctx context.Context, in *v1.WriteSchemaRequest) (*v1.WriteSchemaResponse, error) { log.Ctx(ctx).Trace().Str("schema", in.GetSchema()).Msg("requested Schema to be written") + + readRevision, err := ss.ds.SyncRevision(ctx) + if err != nil { + return nil, rewritePermissionsError(ctx, err) + } + inputSchema := compiler.InputSchema{ Source: input.InputSource("schema"), SchemaString: in.GetSchema(), } // Build a map of existing definitions to determine those being removed, if any. - existingDefs, err := ss.ds.ListNamespaces(ctx) + existingDefs, err := ss.ds.ListNamespaces(ctx, readRevision) if err != nil { return nil, rewriteSchemaError(ctx, err) } @@ -98,7 +109,7 @@ func (ss *schemaServer) WriteSchema(ctx context.Context, in *v1.WriteSchemaReque return nil, rewriteSchemaError(ctx, err) } - if err := shared.SanityCheckExistingRelationships(ctx, ss.ds, nsdef); err != nil { + if err := shared.SanityCheckExistingRelationships(ctx, ss.ds, nsdef, readRevision); err != nil { return nil, rewriteSchemaError(ctx, err) } diff --git a/internal/services/v1alpha1/schema.go b/internal/services/v1alpha1/schema.go index a80765884c..49dca0c8c6 100644 --- a/internal/services/v1alpha1/schema.go +++ b/internal/services/v1alpha1/schema.go @@ -59,21 +59,26 @@ func NewSchemaServer(ds datastore.Datastore, prefixRequired PrefixRequiredOption } func (ss *schemaServiceServer) ReadSchema(ctx context.Context, in *v1alpha1.ReadSchemaRequest) (*v1alpha1.ReadSchemaResponse, error) { + syncRevision, err := ss.ds.SyncRevision(ctx) + if err != nil { + return nil, rewriteError(ctx, err) + } + var objectDefs []string - revisions := make(map[string]datastore.Revision, len(in.GetObjectDefinitionsNames())) + createdRevisions := make(map[string]datastore.Revision, len(in.GetObjectDefinitionsNames())) for _, objectDefName := range in.GetObjectDefinitionsNames() { - found, revision, err := ss.ds.ReadNamespace(ctx, objectDefName) + found, createdAt, err := ss.ds.ReadNamespace(ctx, objectDefName, syncRevision) if err != nil { return nil, rewriteError(ctx, err) } - revisions[objectDefName] = revision + createdRevisions[objectDefName] = createdAt objectDef, _ := generator.GenerateSource(found) objectDefs = append(objectDefs, objectDef) } - computedRevision, err := nspkg.ComputeV1Alpha1Revision(revisions) + computedRevision, err := nspkg.ComputeV1Alpha1Revision(createdRevisions) if err != nil { return nil, rewriteError(ctx, err) } @@ -107,10 +112,15 @@ func (ss *schemaServiceServer) WriteSchema(ctx context.Context, in *v1alpha1.Wri return nil, rewriteError(ctx, err) } + syncRevision, err := ss.ds.SyncRevision(ctx) + if err != nil { + return nil, rewriteError(ctx, err) + } + log.Ctx(ctx).Trace().Interface("namespace definitions", nsdefs).Msg("compiled namespace definitions") for _, nsdef := range nsdefs { - ts, err := namespace.BuildNamespaceTypeSystemWithFallback(nsdef, nsm, nsdefs) + ts, err := namespace.BuildNamespaceTypeSystemWithFallback(nsdef, nsm, nsdefs, syncRevision) if err != nil { return nil, rewriteError(ctx, err) } @@ -119,7 +129,7 @@ func (ss *schemaServiceServer) WriteSchema(ctx context.Context, in *v1alpha1.Wri return nil, rewriteError(ctx, err) } - if err := shared.SanityCheckExistingRelationships(ctx, ss.ds, nsdef); err != nil { + if err := shared.SanityCheckExistingRelationships(ctx, ss.ds, nsdef, syncRevision); err != nil { return nil, rewriteError(ctx, err) } } @@ -134,7 +144,7 @@ func (ss *schemaServiceServer) WriteSchema(ctx context.Context, in *v1alpha1.Wri } for nsName, existingRevision := range decoded { - _, revision, err := ss.ds.ReadNamespace(ctx, nsName) + _, createdAt, err := ss.ds.ReadNamespace(ctx, nsName, syncRevision) if err != nil { var nsNotFoundError sharederrors.UnknownNamespaceError if errors.As(err, &nsNotFoundError) { @@ -146,7 +156,7 @@ func (ss *schemaServiceServer) WriteSchema(ctx context.Context, in *v1alpha1.Wri return nil, rewriteError(ctx, err) } - if !revision.Equal(existingRevision) { + if !createdAt.Equal(existingRevision) { return nil, rewriteError(ctx, &writeSchemaPreconditionFailure{ errors.New("current schema differs from the revision specified"), }) diff --git a/pkg/cmd/serve/serve.go b/pkg/cmd/serve/serve.go index d670540c83..f5442212be 100644 --- a/pkg/cmd/serve/serve.go +++ b/pkg/cmd/serve/serve.go @@ -105,7 +105,12 @@ func serveRun(cmd *cobra.Command, args []string, datastoreOpts *cmdutil.Datastor bootstrapFilePaths := cobrautil.MustGetStringSlice(cmd, "datastore-bootstrap-files") if len(bootstrapFilePaths) > 0 { bootstrapOverwrite := cobrautil.MustGetBool(cmd, "datastore-bootstrap-overwrite") - nsDefs, err := ds.ListNamespaces(context.Background()) + revision, err := ds.SyncRevision(context.Background()) + if err != nil { + log.Fatal().Err(err).Msg("unable to determine datastore state before applying bootstrap data") + } + + nsDefs, err := ds.ListNamespaces(context.Background(), revision) if err != nil { log.Fatal().Err(err).Msg("unable to determine datastore state before applying bootstrap data") }