github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/services/v1/relationships.go (about)

     1  package v1
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	v1 "github.com/authzed/authzed-go/proto/authzed/api/v1"
     9  	grpcvalidate "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/validator"
    10  	"github.com/jzelinskie/stringz"
    11  	"github.com/prometheus/client_golang/prometheus"
    12  	"github.com/prometheus/client_golang/prometheus/promauto"
    13  	"go.opentelemetry.io/otel/trace"
    14  	"google.golang.org/protobuf/proto"
    15  
    16  	"github.com/authzed/spicedb/internal/dispatch"
    17  	"github.com/authzed/spicedb/internal/middleware"
    18  	datastoremw "github.com/authzed/spicedb/internal/middleware/datastore"
    19  	"github.com/authzed/spicedb/internal/middleware/handwrittenvalidation"
    20  	"github.com/authzed/spicedb/internal/middleware/streamtimeout"
    21  	"github.com/authzed/spicedb/internal/middleware/usagemetrics"
    22  	"github.com/authzed/spicedb/internal/namespace"
    23  	"github.com/authzed/spicedb/internal/relationships"
    24  	"github.com/authzed/spicedb/internal/services/shared"
    25  	"github.com/authzed/spicedb/pkg/cursor"
    26  	"github.com/authzed/spicedb/pkg/datastore"
    27  	"github.com/authzed/spicedb/pkg/datastore/options"
    28  	"github.com/authzed/spicedb/pkg/datastore/pagination"
    29  	"github.com/authzed/spicedb/pkg/genutil"
    30  	"github.com/authzed/spicedb/pkg/genutil/mapz"
    31  	"github.com/authzed/spicedb/pkg/middleware/consistency"
    32  	dispatchv1 "github.com/authzed/spicedb/pkg/proto/dispatch/v1"
    33  	"github.com/authzed/spicedb/pkg/tuple"
    34  	"github.com/authzed/spicedb/pkg/zedtoken"
    35  )
    36  
    37  var writeUpdateCounter = promauto.NewHistogramVec(prometheus.HistogramOpts{
    38  	Namespace: "spicedb",
    39  	Subsystem: "v1",
    40  	Name:      "write_relationships_updates",
    41  	Help:      "The update counts for the WriteRelationships calls",
    42  	Buckets:   []float64{0, 1, 2, 5, 10, 15, 25, 50, 100, 250, 500, 1000},
    43  }, []string{"kind"})
    44  
    45  // PermissionsServerConfig is configuration for the permissions server.
    46  type PermissionsServerConfig struct {
    47  	// MaxUpdatesPerWrite holds the maximum number of updates allowed per
    48  	// WriteRelationships call.
    49  	MaxUpdatesPerWrite uint16
    50  
    51  	// MaxPreconditionsCount holds the maximum number of preconditions allowed
    52  	// on a WriteRelationships or DeleteRelationships call.
    53  	MaxPreconditionsCount uint16
    54  
    55  	// MaximumAPIDepth is the default/starting depth remaining for API calls made
    56  	// to the permissions server.
    57  	MaximumAPIDepth uint32
    58  
    59  	// StreamingAPITimeout is the timeout for streaming APIs when no response has been
    60  	// recently received.
    61  	StreamingAPITimeout time.Duration
    62  
    63  	// MaxCaveatContextSize defines the maximum length of the request caveat context in bytes
    64  	MaxCaveatContextSize int
    65  
    66  	// MaxRelationshipContextSize defines the maximum length of a relationship's context in bytes
    67  	MaxRelationshipContextSize int
    68  
    69  	// MaxDatastoreReadPageSize defines the maximum number of relationships loaded from the
    70  	// datastore in one query.
    71  	MaxDatastoreReadPageSize uint64
    72  
    73  	// MaxCheckBulkConcurrency defines the maximum number of concurrent checks that can be
    74  	// made in a single CheckBulkPermissions call.
    75  	MaxCheckBulkConcurrency uint16
    76  
    77  	// MaxReadRelationshipsLimit defines the maximum number of relationships that can be read
    78  	// in a single ReadRelationships call.
    79  	MaxReadRelationshipsLimit uint32
    80  
    81  	// MaxDeleteRelationshipsLimit defines the maximum number of relationships that can be deleted
    82  	// in a single DeleteRelationships call.
    83  	MaxDeleteRelationshipsLimit uint32
    84  
    85  	// MaxLookupResourcesLimit defines the maximum number of resources that can be looked up in a
    86  	// single LookupResources call.
    87  	MaxLookupResourcesLimit uint32
    88  
    89  	// MaxBulkExportRelationshipsLimit defines the maximum number of relationships that can be
    90  	// exported in a single BulkExportRelationships call.
    91  	MaxBulkExportRelationshipsLimit uint32
    92  }
    93  
    94  // NewPermissionsServer creates a PermissionsServiceServer instance.
    95  func NewPermissionsServer(
    96  	dispatch dispatch.Dispatcher,
    97  	config PermissionsServerConfig,
    98  ) v1.PermissionsServiceServer {
    99  	configWithDefaults := PermissionsServerConfig{
   100  		MaxPreconditionsCount:           defaultIfZero(config.MaxPreconditionsCount, 1000),
   101  		MaxUpdatesPerWrite:              defaultIfZero(config.MaxUpdatesPerWrite, 1000),
   102  		MaximumAPIDepth:                 defaultIfZero(config.MaximumAPIDepth, 50),
   103  		StreamingAPITimeout:             defaultIfZero(config.StreamingAPITimeout, 30*time.Second),
   104  		MaxCaveatContextSize:            defaultIfZero(config.MaxCaveatContextSize, 4096),
   105  		MaxRelationshipContextSize:      defaultIfZero(config.MaxRelationshipContextSize, 25_000),
   106  		MaxDatastoreReadPageSize:        defaultIfZero(config.MaxDatastoreReadPageSize, 1_000),
   107  		MaxReadRelationshipsLimit:       defaultIfZero(config.MaxReadRelationshipsLimit, 1_000),
   108  		MaxDeleteRelationshipsLimit:     defaultIfZero(config.MaxDeleteRelationshipsLimit, 1_000),
   109  		MaxLookupResourcesLimit:         defaultIfZero(config.MaxLookupResourcesLimit, 1_000),
   110  		MaxBulkExportRelationshipsLimit: defaultIfZero(config.MaxBulkExportRelationshipsLimit, 100_000),
   111  	}
   112  
   113  	return &permissionServer{
   114  		dispatch: dispatch,
   115  		config:   configWithDefaults,
   116  		WithServiceSpecificInterceptors: shared.WithServiceSpecificInterceptors{
   117  			Unary: middleware.ChainUnaryServer(
   118  				grpcvalidate.UnaryServerInterceptor(),
   119  				handwrittenvalidation.UnaryServerInterceptor,
   120  				usagemetrics.UnaryServerInterceptor(),
   121  			),
   122  			Stream: middleware.ChainStreamServer(
   123  				grpcvalidate.StreamServerInterceptor(),
   124  				handwrittenvalidation.StreamServerInterceptor,
   125  				usagemetrics.StreamServerInterceptor(),
   126  				streamtimeout.MustStreamServerInterceptor(configWithDefaults.StreamingAPITimeout),
   127  			),
   128  		},
   129  		bulkChecker: &bulkChecker{
   130  			maxAPIDepth:          configWithDefaults.MaximumAPIDepth,
   131  			maxCaveatContextSize: configWithDefaults.MaxCaveatContextSize,
   132  			maxConcurrency:       configWithDefaults.MaxCheckBulkConcurrency,
   133  			dispatch:             dispatch,
   134  		},
   135  	}
   136  }
   137  
   138  type permissionServer struct {
   139  	v1.UnimplementedPermissionsServiceServer
   140  	shared.WithServiceSpecificInterceptors
   141  
   142  	dispatch dispatch.Dispatcher
   143  	config   PermissionsServerConfig
   144  
   145  	bulkChecker *bulkChecker
   146  }
   147  
   148  func (ps *permissionServer) ReadRelationships(req *v1.ReadRelationshipsRequest, resp v1.PermissionsService_ReadRelationshipsServer) error {
   149  	if req.OptionalLimit > 0 && req.OptionalLimit > ps.config.MaxReadRelationshipsLimit {
   150  		return ps.rewriteError(resp.Context(), NewExceedsMaximumLimitErr(uint64(req.OptionalLimit), uint64(ps.config.MaxReadRelationshipsLimit)))
   151  	}
   152  
   153  	ctx := resp.Context()
   154  	atRevision, revisionReadAt, err := consistency.RevisionFromContext(ctx)
   155  	if err != nil {
   156  		return ps.rewriteError(ctx, err)
   157  	}
   158  
   159  	ds := datastoremw.MustFromContext(ctx).SnapshotReader(atRevision)
   160  
   161  	if err := validateRelationshipsFilter(ctx, req.RelationshipFilter, ds); err != nil {
   162  		return ps.rewriteError(ctx, err)
   163  	}
   164  
   165  	usagemetrics.SetInContext(ctx, &dispatchv1.ResponseMeta{
   166  		DispatchCount: 1,
   167  	})
   168  
   169  	limit := 0
   170  	var startCursor options.Cursor
   171  
   172  	rrRequestHash, err := computeReadRelationshipsRequestHash(req)
   173  	if err != nil {
   174  		return ps.rewriteError(ctx, err)
   175  	}
   176  
   177  	if req.OptionalCursor != nil {
   178  		decodedCursor, err := cursor.DecodeToDispatchCursor(req.OptionalCursor, rrRequestHash)
   179  		if err != nil {
   180  			return ps.rewriteError(ctx, err)
   181  		}
   182  
   183  		if len(decodedCursor.Sections) != 1 {
   184  			return ps.rewriteError(ctx, NewInvalidCursorErr("did not find expected resume relationship"))
   185  		}
   186  
   187  		parsed := tuple.Parse(decodedCursor.Sections[0])
   188  		if parsed == nil {
   189  			return ps.rewriteError(ctx, NewInvalidCursorErr("could not parse resume relationship"))
   190  		}
   191  
   192  		startCursor = options.Cursor(parsed)
   193  	}
   194  
   195  	pageSize := ps.config.MaxDatastoreReadPageSize
   196  	if req.OptionalLimit > 0 {
   197  		limit = int(req.OptionalLimit)
   198  		if uint64(limit) < pageSize {
   199  			pageSize = uint64(limit)
   200  		}
   201  	}
   202  
   203  	dsFilter, err := datastore.RelationshipsFilterFromPublicFilter(req.RelationshipFilter)
   204  	if err != nil {
   205  		return ps.rewriteError(ctx, fmt.Errorf("error filtering: %w", err))
   206  	}
   207  
   208  	tupleIterator, err := pagination.NewPaginatedIterator(
   209  		ctx,
   210  		ds,
   211  		dsFilter,
   212  		pageSize,
   213  		options.ByResource,
   214  		startCursor,
   215  	)
   216  	if err != nil {
   217  		return ps.rewriteError(ctx, err)
   218  	}
   219  	defer tupleIterator.Close()
   220  
   221  	response := &v1.ReadRelationshipsResponse{
   222  		ReadAt: revisionReadAt,
   223  	}
   224  	targetRel := tuple.NewRelationship()
   225  	targetCaveat := &v1.ContextualizedCaveat{}
   226  	returnedCount := 0
   227  
   228  	dispatchCursor := &dispatchv1.Cursor{
   229  		DispatchVersion: 1,
   230  		Sections:        []string{""},
   231  	}
   232  
   233  	for tpl := tupleIterator.Next(); tpl != nil; tpl = tupleIterator.Next() {
   234  		if limit > 0 && returnedCount >= limit {
   235  			break
   236  		}
   237  
   238  		if tupleIterator.Err() != nil {
   239  			return ps.rewriteError(ctx, fmt.Errorf("error when reading tuples: %w", tupleIterator.Err()))
   240  		}
   241  
   242  		dispatchCursor.Sections[0] = tuple.StringWithoutCaveat(tpl)
   243  		encodedCursor, err := cursor.EncodeFromDispatchCursor(dispatchCursor, rrRequestHash, atRevision)
   244  		if err != nil {
   245  			return ps.rewriteError(ctx, err)
   246  		}
   247  
   248  		tuple.MustToRelationshipMutating(tpl, targetRel, targetCaveat)
   249  		response.Relationship = targetRel
   250  		response.AfterResultCursor = encodedCursor
   251  		err = resp.Send(response)
   252  		if err != nil {
   253  			return ps.rewriteError(ctx, fmt.Errorf("error when streaming tuple: %w", err))
   254  		}
   255  		returnedCount++
   256  	}
   257  
   258  	if tupleIterator.Err() != nil {
   259  		return ps.rewriteError(ctx, fmt.Errorf("error when reading tuples: %w", tupleIterator.Err()))
   260  	}
   261  
   262  	tupleIterator.Close()
   263  	return nil
   264  }
   265  
   266  func (ps *permissionServer) WriteRelationships(ctx context.Context, req *v1.WriteRelationshipsRequest) (*v1.WriteRelationshipsResponse, error) {
   267  	ds := datastoremw.MustFromContext(ctx)
   268  
   269  	span := trace.SpanFromContext(ctx)
   270  	span.AddEvent("validating mutations")
   271  	// Ensure that the updates and preconditions are not over the configured limits.
   272  	if len(req.Updates) > int(ps.config.MaxUpdatesPerWrite) {
   273  		return nil, ps.rewriteError(
   274  			ctx,
   275  			NewExceedsMaximumUpdatesErr(uint64(len(req.Updates)), uint64(ps.config.MaxUpdatesPerWrite)),
   276  		)
   277  	}
   278  
   279  	if len(req.OptionalPreconditions) > int(ps.config.MaxPreconditionsCount) {
   280  		return nil, ps.rewriteError(
   281  			ctx,
   282  			NewExceedsMaximumPreconditionsErr(uint64(len(req.OptionalPreconditions)), uint64(ps.config.MaxPreconditionsCount)),
   283  		)
   284  	}
   285  
   286  	// Check for duplicate updates and create the set of caveat names to load.
   287  	updateRelationshipSet := mapz.NewSet[string]()
   288  	for _, update := range req.Updates {
   289  		tupleStr := tuple.StringRelationshipWithoutCaveat(update.Relationship)
   290  		if !updateRelationshipSet.Add(tupleStr) {
   291  			return nil, ps.rewriteError(
   292  				ctx,
   293  				NewDuplicateRelationshipErr(update),
   294  			)
   295  		}
   296  		if proto.Size(update.Relationship.OptionalCaveat) > ps.config.MaxRelationshipContextSize {
   297  			return nil, ps.rewriteError(
   298  				ctx,
   299  				NewMaxRelationshipContextError(update, ps.config.MaxRelationshipContextSize),
   300  			)
   301  		}
   302  	}
   303  
   304  	// Execute the write operation(s).
   305  	span.AddEvent("read write transaction")
   306  	tupleUpdates := tuple.UpdateFromRelationshipUpdates(req.Updates)
   307  	revision, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   308  		span.AddEvent("preconditions")
   309  
   310  		// Validate the preconditions.
   311  		for _, precond := range req.OptionalPreconditions {
   312  			if err := validatePrecondition(ctx, precond, rwt); err != nil {
   313  				return err
   314  			}
   315  		}
   316  
   317  		// Validate the updates.
   318  		span.AddEvent("validate updates")
   319  		err := relationships.ValidateRelationshipUpdates(ctx, rwt, tupleUpdates)
   320  		if err != nil {
   321  			return ps.rewriteError(ctx, err)
   322  		}
   323  
   324  		dispatchCount, err := genutil.EnsureUInt32(len(req.OptionalPreconditions) + 1)
   325  		if err != nil {
   326  			return ps.rewriteError(ctx, err)
   327  		}
   328  
   329  		usagemetrics.SetInContext(ctx, &dispatchv1.ResponseMeta{
   330  			// One request per precondition and one request for the actual writes.
   331  			DispatchCount: dispatchCount,
   332  		})
   333  
   334  		span.AddEvent("preconditions")
   335  		if err := checkPreconditions(ctx, rwt, req.OptionalPreconditions); err != nil {
   336  			return err
   337  		}
   338  
   339  		span.AddEvent("write relationships")
   340  		return rwt.WriteRelationships(ctx, tupleUpdates)
   341  	})
   342  	if err != nil {
   343  		return nil, ps.rewriteError(ctx, err)
   344  	}
   345  
   346  	// Log a metric of the counts of the different kinds of update operations.
   347  	updateCountByOperation := make(map[v1.RelationshipUpdate_Operation]int, 0)
   348  	for _, update := range req.Updates {
   349  		updateCountByOperation[update.Operation]++
   350  	}
   351  
   352  	for kind, count := range updateCountByOperation {
   353  		writeUpdateCounter.WithLabelValues(v1.RelationshipUpdate_Operation_name[int32(kind)]).Observe(float64(count))
   354  	}
   355  
   356  	return &v1.WriteRelationshipsResponse{
   357  		WrittenAt: zedtoken.MustNewFromRevision(revision),
   358  	}, nil
   359  }
   360  
   361  func (ps *permissionServer) DeleteRelationships(ctx context.Context, req *v1.DeleteRelationshipsRequest) (*v1.DeleteRelationshipsResponse, error) {
   362  	if len(req.OptionalPreconditions) > int(ps.config.MaxPreconditionsCount) {
   363  		return nil, ps.rewriteError(
   364  			ctx,
   365  			NewExceedsMaximumPreconditionsErr(uint64(len(req.OptionalPreconditions)), uint64(ps.config.MaxPreconditionsCount)),
   366  		)
   367  	}
   368  
   369  	if req.OptionalLimit > 0 && req.OptionalLimit > ps.config.MaxDeleteRelationshipsLimit {
   370  		return nil, ps.rewriteError(ctx, NewExceedsMaximumLimitErr(uint64(req.OptionalLimit), uint64(ps.config.MaxDeleteRelationshipsLimit)))
   371  	}
   372  
   373  	ds := datastoremw.MustFromContext(ctx)
   374  	deletionProgress := v1.DeleteRelationshipsResponse_DELETION_PROGRESS_COMPLETE
   375  
   376  	revision, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   377  		if err := validateRelationshipsFilter(ctx, req.RelationshipFilter, rwt); err != nil {
   378  			return err
   379  		}
   380  
   381  		dispatchCount, err := genutil.EnsureUInt32(len(req.OptionalPreconditions) + 1)
   382  		if err != nil {
   383  			return ps.rewriteError(ctx, err)
   384  		}
   385  
   386  		usagemetrics.SetInContext(ctx, &dispatchv1.ResponseMeta{
   387  			// One request per precondition and one request for the actual delete.
   388  			DispatchCount: dispatchCount,
   389  		})
   390  
   391  		for _, precond := range req.OptionalPreconditions {
   392  			if err := validatePrecondition(ctx, precond, rwt); err != nil {
   393  				return err
   394  			}
   395  		}
   396  
   397  		if err := checkPreconditions(ctx, rwt, req.OptionalPreconditions); err != nil {
   398  			return err
   399  		}
   400  
   401  		// If a limit was specified but partial deletion is not allowed, we need to check if the
   402  		// number of relationships to be deleted exceeds the limit.
   403  		if req.OptionalLimit > 0 && !req.OptionalAllowPartialDeletions {
   404  			limit := uint64(req.OptionalLimit)
   405  			limitPlusOne := limit + 1
   406  			filter, err := datastore.RelationshipsFilterFromPublicFilter(req.RelationshipFilter)
   407  			if err != nil {
   408  				return ps.rewriteError(ctx, err)
   409  			}
   410  
   411  			iter, err := rwt.QueryRelationships(ctx, filter, options.WithLimit(&limitPlusOne))
   412  			if err != nil {
   413  				return ps.rewriteError(ctx, err)
   414  			}
   415  			defer iter.Close()
   416  
   417  			counter := 0
   418  			for tpl := iter.Next(); tpl != nil; tpl = iter.Next() {
   419  				if iter.Err() != nil {
   420  					return ps.rewriteError(ctx, err)
   421  				}
   422  
   423  				if counter == int(limit) {
   424  					return ps.rewriteError(ctx, NewCouldNotTransactionallyDeleteErr(req.RelationshipFilter, req.OptionalLimit))
   425  				}
   426  
   427  				counter++
   428  			}
   429  			iter.Close()
   430  		}
   431  
   432  		// Delete with the specified limit.
   433  		if req.OptionalLimit > 0 {
   434  			deleteLimit := uint64(req.OptionalLimit)
   435  			reachedLimit, err := rwt.DeleteRelationships(ctx, req.RelationshipFilter, options.WithDeleteLimit(&deleteLimit))
   436  			if err != nil {
   437  				return err
   438  			}
   439  
   440  			if reachedLimit {
   441  				deletionProgress = v1.DeleteRelationshipsResponse_DELETION_PROGRESS_PARTIAL
   442  			}
   443  
   444  			return nil
   445  		}
   446  
   447  		// Otherwise, kick off an unlimited deletion.
   448  		_, err = rwt.DeleteRelationships(ctx, req.RelationshipFilter)
   449  		return err
   450  	})
   451  	if err != nil {
   452  		return nil, ps.rewriteError(ctx, err)
   453  	}
   454  
   455  	return &v1.DeleteRelationshipsResponse{
   456  		DeletedAt:        zedtoken.MustNewFromRevision(revision),
   457  		DeletionProgress: deletionProgress,
   458  	}, nil
   459  }
   460  
   461  var emptyPrecondition = &v1.Precondition{}
   462  
   463  func validatePrecondition(ctx context.Context, precond *v1.Precondition, reader datastore.Reader) error {
   464  	if precond.EqualVT(emptyPrecondition) || precond.Filter == nil {
   465  		return NewEmptyPreconditionErr()
   466  	}
   467  
   468  	return validateRelationshipsFilter(ctx, precond.Filter, reader)
   469  }
   470  
   471  func checkFilterComponent(ctx context.Context, objectType, optionalRelation string, ds datastore.Reader) error {
   472  	if objectType == "" {
   473  		return nil
   474  	}
   475  
   476  	relationToTest := stringz.DefaultEmpty(optionalRelation, datastore.Ellipsis)
   477  	allowEllipsis := optionalRelation == ""
   478  	return namespace.CheckNamespaceAndRelation(ctx, objectType, relationToTest, allowEllipsis, ds)
   479  }
   480  
   481  func validateRelationshipsFilter(ctx context.Context, filter *v1.RelationshipFilter, ds datastore.Reader) error {
   482  	// ResourceType is optional, so only check the relation if it is specified.
   483  	if filter.ResourceType != "" {
   484  		if err := checkFilterComponent(ctx, filter.ResourceType, filter.OptionalRelation, ds); err != nil {
   485  			return err
   486  		}
   487  	}
   488  
   489  	// SubjectFilter is optional, so only check if it is specified.
   490  	if subjectFilter := filter.OptionalSubjectFilter; subjectFilter != nil {
   491  		subjectRelation := ""
   492  		if subjectFilter.OptionalRelation != nil {
   493  			subjectRelation = subjectFilter.OptionalRelation.Relation
   494  		}
   495  		if err := checkFilterComponent(ctx, subjectFilter.SubjectType, subjectRelation, ds); err != nil {
   496  			return err
   497  		}
   498  	}
   499  
   500  	// Ensure the resource ID and the resource ID prefix are not set at the same time.
   501  	if filter.OptionalResourceId != "" && filter.OptionalResourceIdPrefix != "" {
   502  		return NewInvalidFilterErr("resource_id and resource_id_prefix cannot be set at the same time", filter.String())
   503  	}
   504  
   505  	// Ensure that at least one field is set.
   506  	if filter.ResourceType == "" &&
   507  		filter.OptionalResourceId == "" &&
   508  		filter.OptionalResourceIdPrefix == "" &&
   509  		filter.OptionalRelation == "" &&
   510  		filter.OptionalSubjectFilter == nil {
   511  		return NewInvalidFilterErr("at least one field must be set", filter.String())
   512  	}
   513  
   514  	return nil
   515  }