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 }