github.com/openfga/openfga@v1.5.4-rc1/pkg/server/server.go (about) 1 // Package server contains the endpoint handlers. 2 package server 3 4 import ( 5 "context" 6 "errors" 7 "fmt" 8 "net/http" 9 "sort" 10 "strconv" 11 "time" 12 13 grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags" 14 openfgav1 "github.com/openfga/api/proto/openfga/v1" 15 "github.com/prometheus/client_golang/prometheus" 16 "github.com/prometheus/client_golang/prometheus/promauto" 17 "go.opentelemetry.io/otel" 18 "go.opentelemetry.io/otel/attribute" 19 "go.opentelemetry.io/otel/trace" 20 "go.uber.org/zap" 21 "google.golang.org/grpc/codes" 22 "google.golang.org/grpc/status" 23 24 "github.com/openfga/openfga/internal/build" 25 "github.com/openfga/openfga/internal/condition" 26 "github.com/openfga/openfga/internal/graph" 27 serverconfig "github.com/openfga/openfga/internal/server/config" 28 "github.com/openfga/openfga/internal/utils" 29 "github.com/openfga/openfga/internal/validation" 30 "github.com/openfga/openfga/pkg/encoder" 31 "github.com/openfga/openfga/pkg/gateway" 32 "github.com/openfga/openfga/pkg/logger" 33 httpmiddleware "github.com/openfga/openfga/pkg/middleware/http" 34 "github.com/openfga/openfga/pkg/middleware/validator" 35 "github.com/openfga/openfga/pkg/server/commands" 36 serverErrors "github.com/openfga/openfga/pkg/server/errors" 37 "github.com/openfga/openfga/pkg/storage" 38 "github.com/openfga/openfga/pkg/storage/storagewrappers" 39 "github.com/openfga/openfga/pkg/telemetry" 40 "github.com/openfga/openfga/pkg/tuple" 41 "github.com/openfga/openfga/pkg/typesystem" 42 ) 43 44 type ExperimentalFeatureFlag string 45 46 const ( 47 AuthorizationModelIDHeader = "Openfga-Authorization-Model-Id" 48 authorizationModelIDKey = "authorization_model_id" 49 ExperimentalEnableListUsers ExperimentalFeatureFlag = "enable-list-users" 50 ) 51 52 var tracer = otel.Tracer("openfga/pkg/server") 53 54 var ( 55 dispatchCountHistogramName = "dispatch_count" 56 57 dispatchCountHistogram = promauto.NewHistogramVec(prometheus.HistogramOpts{ 58 Namespace: build.ProjectName, 59 Name: dispatchCountHistogramName, 60 Help: "The number of dispatches required to resolve a query (e.g. Check).", 61 Buckets: []float64{1, 5, 20, 50, 100, 150, 225, 400, 500, 750, 1000}, 62 NativeHistogramBucketFactor: 1.1, 63 NativeHistogramMaxBucketNumber: 100, 64 NativeHistogramMinResetDuration: time.Hour, 65 }, []string{"grpc_service", "grpc_method"}) 66 67 datastoreQueryCountHistogramName = "datastore_query_count" 68 69 datastoreQueryCountHistogram = promauto.NewHistogramVec(prometheus.HistogramOpts{ 70 Namespace: build.ProjectName, 71 Name: datastoreQueryCountHistogramName, 72 Help: "The number of database queries required to resolve a query (e.g. Check, ListObjects or ListUsers).", 73 Buckets: []float64{1, 5, 20, 50, 100, 150, 225, 400, 500, 750, 1000}, 74 NativeHistogramBucketFactor: 1.1, 75 NativeHistogramMaxBucketNumber: 100, 76 NativeHistogramMinResetDuration: time.Hour, 77 }, []string{"grpc_service", "grpc_method"}) 78 79 requestDurationHistogramName = "request_duration_ms" 80 81 requestDurationHistogram = promauto.NewHistogramVec(prometheus.HistogramOpts{ 82 Namespace: build.ProjectName, 83 Name: requestDurationHistogramName, 84 Help: "The request duration (in ms) labeled by method and buckets of datastore query counts and number of dispatches. This allows for reporting percentiles based on the number of datastore queries and number of dispatches required to resolve the request.", 85 Buckets: []float64{1, 5, 10, 25, 50, 80, 100, 150, 200, 300, 1000, 2000, 5000}, 86 NativeHistogramBucketFactor: 1.1, 87 NativeHistogramMaxBucketNumber: 100, 88 NativeHistogramMinResetDuration: time.Hour, 89 }, []string{"grpc_service", "grpc_method", "datastore_query_count", "dispatch_count"}) 90 ) 91 92 // A Server implements the OpenFGA service backend as both 93 // a GRPC and HTTP server. 94 type Server struct { 95 openfgav1.UnimplementedOpenFGAServiceServer 96 97 logger logger.Logger 98 datastore storage.OpenFGADatastore 99 encoder encoder.Encoder 100 transport gateway.Transport 101 resolveNodeLimit uint32 102 resolveNodeBreadthLimit uint32 103 changelogHorizonOffset int 104 listObjectsDeadline time.Duration 105 listObjectsMaxResults uint32 106 listUsersDeadline time.Duration 107 listUsersMaxResults uint32 108 maxConcurrentReadsForListObjects uint32 109 maxConcurrentReadsForCheck uint32 110 maxConcurrentReadsForListUsers uint32 111 maxAuthorizationModelSizeInBytes int 112 experimentals []ExperimentalFeatureFlag 113 serviceName string 114 115 // NOTE don't use this directly, use function resolveTypesystem. See https://github.com/openfga/openfga/issues/1527 116 typesystemResolver typesystem.TypesystemResolverFunc 117 typesystemResolverStop func() 118 119 checkQueryCacheEnabled bool 120 checkQueryCacheLimit uint32 121 checkQueryCacheTTL time.Duration 122 cachedCheckResolver *graph.CachedCheckResolver 123 124 checkResolver graph.CheckResolver 125 126 requestDurationByQueryHistogramBuckets []uint 127 requestDurationByDispatchCountHistogramBuckets []uint 128 129 dispatchThrottlingCheckResolverEnabled bool 130 dispatchThrottlingCheckResolverFrequency time.Duration 131 dispatchThrottlingDefaultThreshold uint32 132 dispatchThrottlingMaxThreshold uint32 133 134 dispatchThrottlingCheckResolver *graph.DispatchThrottlingCheckResolver 135 } 136 137 type OpenFGAServiceV1Option func(s *Server) 138 139 // WithDatastore passes a datastore to the Server. 140 // You must call [storage.OpenFGADatastore.Close] on it after you have stopped using it. 141 func WithDatastore(ds storage.OpenFGADatastore) OpenFGAServiceV1Option { 142 return func(s *Server) { 143 s.datastore = ds 144 } 145 } 146 147 func WithLogger(l logger.Logger) OpenFGAServiceV1Option { 148 return func(s *Server) { 149 s.logger = l 150 } 151 } 152 153 func WithTokenEncoder(encoder encoder.Encoder) OpenFGAServiceV1Option { 154 return func(s *Server) { 155 s.encoder = encoder 156 } 157 } 158 159 // WithTransport sets the connection transport. 160 func WithTransport(t gateway.Transport) OpenFGAServiceV1Option { 161 return func(s *Server) { 162 s.transport = t 163 } 164 } 165 166 // WithResolveNodeLimit sets a limit on the number of recursive calls that one Check, ListObjects or ListUsers call will allow. 167 // Thinking of a request as a tree of evaluations, this option controls 168 // how many levels we will evaluate before throwing an error that the authorization model is too complex. 169 func WithResolveNodeLimit(limit uint32) OpenFGAServiceV1Option { 170 return func(s *Server) { 171 s.resolveNodeLimit = limit 172 } 173 } 174 175 // WithResolveNodeBreadthLimit sets a limit on the number of goroutines that can be created 176 // when evaluating a subtree of a Check, ListObjects or ListUsers call. 177 // Thinking of a Check request as a tree of evaluations, this option controls, 178 // on a given level of the tree, the maximum number of nodes that can be evaluated concurrently (the breadth). 179 // If your authorization models are very complex (e.g. one relation is a union of many relations, or one relation 180 // is deeply nested), or if you have lots of users for (object, relation) pairs, 181 // you should set this option to be a low number (e.g. 1000). 182 func WithResolveNodeBreadthLimit(limit uint32) OpenFGAServiceV1Option { 183 return func(s *Server) { 184 s.resolveNodeBreadthLimit = limit 185 } 186 } 187 188 // WithChangelogHorizonOffset sets an offset (in minutes) from the current time. 189 // Changes that occur after this offset will not be included in the response of ReadChanges API. 190 // If your datastore is eventually consistent or if you have a database with replication delay, we recommend setting this (e.g. 1 minute). 191 func WithChangelogHorizonOffset(offset int) OpenFGAServiceV1Option { 192 return func(s *Server) { 193 s.changelogHorizonOffset = offset 194 } 195 } 196 197 // WithListObjectsDeadline affect the ListObjects API only. 198 // It sets the maximum amount of time that the server will spend gathering results. 199 func WithListObjectsDeadline(deadline time.Duration) OpenFGAServiceV1Option { 200 return func(s *Server) { 201 s.listObjectsDeadline = deadline 202 } 203 } 204 205 // WithListObjectsMaxResults affects the ListObjects API only. 206 // It sets the maximum number of results that this API will return. 207 func WithListObjectsMaxResults(limit uint32) OpenFGAServiceV1Option { 208 return func(s *Server) { 209 s.listObjectsMaxResults = limit 210 } 211 } 212 213 // WithListUsersDeadline affect the ListUsers API only. 214 // It sets the maximum amount of time that the server will spend gathering results. 215 func WithListUsersDeadline(deadline time.Duration) OpenFGAServiceV1Option { 216 return func(s *Server) { 217 s.listUsersDeadline = deadline 218 } 219 } 220 221 // WithListUsersMaxResults affects the ListUsers API only. 222 // It sets the maximum number of results that this API will return. 223 // If it's zero, all results will be attempted to be returned. 224 func WithListUsersMaxResults(limit uint32) OpenFGAServiceV1Option { 225 return func(s *Server) { 226 s.listUsersMaxResults = limit 227 } 228 } 229 230 // WithMaxConcurrentReadsForListObjects sets a limit on the number of datastore reads that can be in flight for a given ListObjects call. 231 // This number should be set depending on the RPS expected for Check and ListObjects APIs, the number of OpenFGA replicas running, 232 // and the number of connections the datastore allows. 233 // E.g. If Datastore.MaxOpenConns = 100 and assuming that each ListObjects call takes 1 second and no traffic to Check API: 234 // - One OpenFGA replica and expected traffic of 100 RPS => set it to 1. 235 // - One OpenFGA replica and expected traffic of 1 RPS => set it to 100. 236 // - Two OpenFGA replicas and expected traffic of 1 RPS => set it to 50. 237 func WithMaxConcurrentReadsForListObjects(max uint32) OpenFGAServiceV1Option { 238 return func(s *Server) { 239 s.maxConcurrentReadsForListObjects = max 240 } 241 } 242 243 // WithMaxConcurrentReadsForCheck sets a limit on the number of datastore reads that can be in flight for a given Check call. 244 // This number should be set depending on the RPS expected for Check and ListObjects APIs, the number of OpenFGA replicas running, 245 // and the number of connections the datastore allows. 246 // E.g. If Datastore.MaxOpenConns = 100 and assuming that each Check call takes 1 second and no traffic to ListObjects API: 247 // - One OpenFGA replica and expected traffic of 100 RPS => set it to 1. 248 // - One OpenFGA replica and expected traffic of 1 RPS => set it to 100. 249 // - Two OpenFGA replicas and expected traffic of 1 RPS => set it to 50. 250 func WithMaxConcurrentReadsForCheck(max uint32) OpenFGAServiceV1Option { 251 return func(s *Server) { 252 s.maxConcurrentReadsForCheck = max 253 } 254 } 255 256 // WithMaxConcurrentReadsForListUsers sets a limit on the number of datastore reads that can be in flight for a given ListUsers call. 257 // This number should be set depending on the RPS expected for all query APIs, the number of OpenFGA replicas running, 258 // and the number of connections the datastore allows. 259 // E.g. If Datastore.MaxOpenConns = 100 and assuming that each ListUsers call takes 1 second and no traffic to other query APIs: 260 // - One OpenFGA replica and expected traffic of 100 RPS => set it to 1. 261 // - One OpenFGA replica and expected traffic of 1 RPS => set it to 100. 262 // - Two OpenFGA replicas and expected traffic of 1 RPS => set it to 50. 263 func WithMaxConcurrentReadsForListUsers(max uint32) OpenFGAServiceV1Option { 264 return func(s *Server) { 265 s.maxConcurrentReadsForListUsers = max 266 } 267 } 268 269 func WithExperimentals(experimentals ...ExperimentalFeatureFlag) OpenFGAServiceV1Option { 270 return func(s *Server) { 271 s.experimentals = experimentals 272 } 273 } 274 275 // WithCheckQueryCacheEnabled enables caching of Check results for the Check and List objects APIs. 276 // This cache is shared for all requests. 277 // See also WithCheckQueryCacheLimit and WithCheckQueryCacheTTL. 278 func WithCheckQueryCacheEnabled(enabled bool) OpenFGAServiceV1Option { 279 return func(s *Server) { 280 s.checkQueryCacheEnabled = enabled 281 } 282 } 283 284 // WithCheckQueryCacheLimit sets the cache size limit (in items) 285 // Needs WithCheckQueryCacheEnabled set to true. 286 func WithCheckQueryCacheLimit(limit uint32) OpenFGAServiceV1Option { 287 return func(s *Server) { 288 s.checkQueryCacheLimit = limit 289 } 290 } 291 292 // WithCheckQueryCacheTTL sets the TTL of cached checks and list objects partial results 293 // Needs WithCheckQueryCacheEnabled set to true. 294 func WithCheckQueryCacheTTL(ttl time.Duration) OpenFGAServiceV1Option { 295 return func(s *Server) { 296 s.checkQueryCacheTTL = ttl 297 } 298 } 299 300 // WithRequestDurationByQueryHistogramBuckets sets the buckets used in labelling the requestDurationByQueryAndDispatchHistogram. 301 func WithRequestDurationByQueryHistogramBuckets(buckets []uint) OpenFGAServiceV1Option { 302 return func(s *Server) { 303 sort.Slice(buckets, func(i, j int) bool { return buckets[i] < buckets[j] }) 304 s.requestDurationByQueryHistogramBuckets = buckets 305 } 306 } 307 308 // WithRequestDurationByDispatchCountHistogramBuckets sets the buckets used in labelling the requestDurationByQueryAndDispatchHistogram. 309 func WithRequestDurationByDispatchCountHistogramBuckets(buckets []uint) OpenFGAServiceV1Option { 310 return func(s *Server) { 311 sort.Slice(buckets, func(i, j int) bool { return buckets[i] < buckets[j] }) 312 s.requestDurationByDispatchCountHistogramBuckets = buckets 313 } 314 } 315 316 func WithMaxAuthorizationModelSizeInBytes(size int) OpenFGAServiceV1Option { 317 return func(s *Server) { 318 s.maxAuthorizationModelSizeInBytes = size 319 } 320 } 321 322 // WithDispatchThrottlingCheckResolverEnabled sets whether dispatch throttling is enabled. 323 // Enabling this feature will prioritize dispatched requests requiring less than the configured dispatch 324 // threshold over requests whose dispatch count exceeds the configured threshold. 325 func WithDispatchThrottlingCheckResolverEnabled(enabled bool) OpenFGAServiceV1Option { 326 return func(s *Server) { 327 s.dispatchThrottlingCheckResolverEnabled = enabled 328 } 329 } 330 331 // WithDispatchThrottlingCheckResolverFrequency defines how frequent dispatch throttling will be evaluated. 332 // Frequency controls how frequently throttled dispatch requests are evaluated to determine whether 333 // it can be processed. 334 // This value should not be too small (i.e., in the ns ranges) as i) there are limitation in timer resolution 335 // and ii) very small value will result in a higher frequency of processing dispatches, 336 // which diminishes the value of the throttling. 337 func WithDispatchThrottlingCheckResolverFrequency(frequency time.Duration) OpenFGAServiceV1Option { 338 return func(s *Server) { 339 s.dispatchThrottlingCheckResolverFrequency = frequency 340 } 341 } 342 343 // WithDispatchThrottlingCheckResolverThreshold define the number of dispatches to be throttled. 344 // In addition, it will update dispatchThrottlingMaxThreshold if required. 345 func WithDispatchThrottlingCheckResolverThreshold(defaultThreshold uint32) OpenFGAServiceV1Option { 346 return func(s *Server) { 347 s.dispatchThrottlingDefaultThreshold = defaultThreshold 348 } 349 } 350 351 // WithDispatchThrottlingCheckResolverMaxThreshold define the maximum threshold values allowed 352 // It will ensure dispatchThrottlingMaxThreshold will never be smaller than threshold. 353 func WithDispatchThrottlingCheckResolverMaxThreshold(maxThreshold uint32) OpenFGAServiceV1Option { 354 return func(s *Server) { 355 s.dispatchThrottlingMaxThreshold = maxThreshold 356 } 357 } 358 359 // MustNewServerWithOpts see NewServerWithOpts. 360 func MustNewServerWithOpts(opts ...OpenFGAServiceV1Option) *Server { 361 s, err := NewServerWithOpts(opts...) 362 if err != nil { 363 panic(fmt.Errorf("failed to construct the OpenFGA server: %w", err)) 364 } 365 366 return s 367 } 368 369 func (s *Server) IsExperimentallyEnabled(flag ExperimentalFeatureFlag) bool { 370 for _, e := range s.experimentals { 371 if e == flag { 372 return true 373 } 374 } 375 return false 376 } 377 378 // NewServerWithOpts returns a new server. 379 // You must call Close on it after you are done using it. 380 func NewServerWithOpts(opts ...OpenFGAServiceV1Option) (*Server, error) { 381 s := &Server{ 382 logger: logger.NewNoopLogger(), 383 encoder: encoder.NewBase64Encoder(), 384 transport: gateway.NewNoopTransport(), 385 changelogHorizonOffset: serverconfig.DefaultChangelogHorizonOffset, 386 resolveNodeLimit: serverconfig.DefaultResolveNodeLimit, 387 resolveNodeBreadthLimit: serverconfig.DefaultResolveNodeBreadthLimit, 388 listObjectsDeadline: serverconfig.DefaultListObjectsDeadline, 389 listObjectsMaxResults: serverconfig.DefaultListObjectsMaxResults, 390 listUsersDeadline: serverconfig.DefaultListUsersDeadline, 391 listUsersMaxResults: serverconfig.DefaultListUsersMaxResults, 392 maxConcurrentReadsForCheck: serverconfig.DefaultMaxConcurrentReadsForCheck, 393 maxConcurrentReadsForListObjects: serverconfig.DefaultMaxConcurrentReadsForListObjects, 394 maxConcurrentReadsForListUsers: serverconfig.DefaultMaxConcurrentReadsForListUsers, 395 maxAuthorizationModelSizeInBytes: serverconfig.DefaultMaxAuthorizationModelSizeInBytes, 396 experimentals: make([]ExperimentalFeatureFlag, 0, 10), 397 398 checkQueryCacheEnabled: serverconfig.DefaultCheckQueryCacheEnable, 399 checkQueryCacheLimit: serverconfig.DefaultCheckQueryCacheLimit, 400 checkQueryCacheTTL: serverconfig.DefaultCheckQueryCacheTTL, 401 checkResolver: nil, 402 403 requestDurationByQueryHistogramBuckets: []uint{50, 200}, 404 requestDurationByDispatchCountHistogramBuckets: []uint{50, 200}, 405 serviceName: openfgav1.OpenFGAService_ServiceDesc.ServiceName, 406 407 dispatchThrottlingCheckResolverEnabled: serverconfig.DefaultDispatchThrottlingEnabled, 408 dispatchThrottlingCheckResolverFrequency: serverconfig.DefaultDispatchThrottlingFrequency, 409 dispatchThrottlingDefaultThreshold: serverconfig.DefaultDispatchThrottlingDefaultThreshold, 410 } 411 412 for _, opt := range opts { 413 opt(s) 414 } 415 416 cycleDetectionCheckResolver := graph.NewCycleDetectionCheckResolver() 417 s.checkResolver = cycleDetectionCheckResolver 418 419 localChecker := graph.NewLocalChecker( 420 graph.WithResolveNodeBreadthLimit(s.resolveNodeBreadthLimit), 421 ) 422 423 cycleDetectionCheckResolver.SetDelegate(localChecker) 424 localChecker.SetDelegate(cycleDetectionCheckResolver) 425 426 if s.dispatchThrottlingCheckResolverEnabled { 427 dispatchThrottlingConfig := graph.DispatchThrottlingCheckResolverConfig{ 428 Frequency: s.dispatchThrottlingCheckResolverFrequency, 429 DefaultThreshold: s.dispatchThrottlingDefaultThreshold, 430 MaxThreshold: s.dispatchThrottlingMaxThreshold, 431 } 432 433 if s.dispatchThrottlingMaxThreshold != 0 && s.dispatchThrottlingDefaultThreshold > s.dispatchThrottlingMaxThreshold { 434 return nil, fmt.Errorf("default dispatch throttling threshold must be equal or smaller than max dispatch threshold") 435 } 436 437 s.logger.Info("Enabling dispatch throttling", 438 zap.Duration("Frequency", s.dispatchThrottlingCheckResolverFrequency), 439 zap.Uint32("DefaultThreshold", s.dispatchThrottlingDefaultThreshold), 440 zap.Uint32("MaxThreshold", s.dispatchThrottlingMaxThreshold), 441 ) 442 443 dispatchThrottlingCheckResolver := graph.NewDispatchThrottlingCheckResolver(dispatchThrottlingConfig) 444 dispatchThrottlingCheckResolver.SetDelegate(localChecker) 445 s.dispatchThrottlingCheckResolver = dispatchThrottlingCheckResolver 446 447 cycleDetectionCheckResolver.SetDelegate(dispatchThrottlingCheckResolver) 448 } 449 450 if s.checkQueryCacheEnabled { 451 s.logger.Info("Check query cache is enabled and may lead to stale query results up to the configured query cache TTL", 452 zap.Duration("CheckQueryCacheTTL", s.checkQueryCacheTTL), 453 zap.Uint32("CheckQueryCacheLimit", s.checkQueryCacheLimit)) 454 455 cachedCheckResolver := graph.NewCachedCheckResolver( 456 graph.WithMaxCacheSize(int64(s.checkQueryCacheLimit)), 457 graph.WithLogger(s.logger), 458 graph.WithCacheTTL(s.checkQueryCacheTTL), 459 ) 460 s.cachedCheckResolver = cachedCheckResolver 461 462 cachedCheckResolver.SetDelegate(localChecker) 463 if s.dispatchThrottlingCheckResolver != nil { 464 s.dispatchThrottlingCheckResolver.SetDelegate(cachedCheckResolver) 465 } else { 466 cycleDetectionCheckResolver.SetDelegate(cachedCheckResolver) 467 } 468 } 469 470 if s.datastore == nil { 471 return nil, fmt.Errorf("a datastore option must be provided") 472 } 473 474 if len(s.requestDurationByQueryHistogramBuckets) == 0 { 475 return nil, fmt.Errorf("request duration datastore count buckets must not be empty") 476 } 477 478 if len(s.requestDurationByDispatchCountHistogramBuckets) == 0 { 479 return nil, fmt.Errorf("request duration by dispatch count buckets must not be empty") 480 } 481 482 s.typesystemResolver, s.typesystemResolverStop = typesystem.MemoizedTypesystemResolverFunc(s.datastore) 483 484 return s, nil 485 } 486 487 // Close releases the server resources. 488 func (s *Server) Close() { 489 if s.dispatchThrottlingCheckResolver != nil { 490 s.dispatchThrottlingCheckResolver.Close() 491 } 492 493 if s.cachedCheckResolver != nil { 494 s.cachedCheckResolver.Close() 495 } 496 497 if s.checkResolver != nil { 498 s.checkResolver.Close() 499 } 500 501 s.typesystemResolverStop() 502 } 503 504 func (s *Server) ListObjects(ctx context.Context, req *openfgav1.ListObjectsRequest) (*openfgav1.ListObjectsResponse, error) { 505 start := time.Now() 506 507 targetObjectType := req.GetType() 508 509 ctx, span := tracer.Start(ctx, "ListObjects", trace.WithAttributes( 510 attribute.String("object_type", targetObjectType), 511 attribute.String("relation", req.GetRelation()), 512 attribute.String("user", req.GetUser()), 513 )) 514 defer span.End() 515 516 if !validator.RequestIsValidatedFromContext(ctx) { 517 if err := req.Validate(); err != nil { 518 return nil, status.Error(codes.InvalidArgument, err.Error()) 519 } 520 } 521 522 const methodName = "listobjects" 523 524 ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{ 525 Service: s.serviceName, 526 Method: methodName, 527 }) 528 529 storeID := req.GetStoreId() 530 531 typesys, err := s.resolveTypesystem(ctx, storeID, req.GetAuthorizationModelId()) 532 if err != nil { 533 return nil, err 534 } 535 536 q, err := commands.NewListObjectsQuery( 537 s.datastore, 538 s.checkResolver, 539 commands.WithLogger(s.logger), 540 commands.WithListObjectsDeadline(s.listObjectsDeadline), 541 commands.WithListObjectsMaxResults(s.listObjectsMaxResults), 542 commands.WithResolveNodeLimit(s.resolveNodeLimit), 543 commands.WithResolveNodeBreadthLimit(s.resolveNodeBreadthLimit), 544 commands.WithMaxConcurrentReads(s.maxConcurrentReadsForListObjects), 545 ) 546 if err != nil { 547 return nil, serverErrors.NewInternalError("", err) 548 } 549 550 result, err := q.Execute( 551 typesystem.ContextWithTypesystem(ctx, typesys), 552 &openfgav1.ListObjectsRequest{ 553 StoreId: storeID, 554 ContextualTuples: req.GetContextualTuples(), 555 AuthorizationModelId: typesys.GetAuthorizationModelID(), // the resolved model id 556 Type: targetObjectType, 557 Relation: req.GetRelation(), 558 User: req.GetUser(), 559 Context: req.GetContext(), 560 }, 561 ) 562 if err != nil { 563 telemetry.TraceError(span, err) 564 if errors.Is(err, condition.ErrEvaluationFailed) { 565 return nil, serverErrors.ValidationError(err) 566 } 567 568 return nil, err 569 } 570 datastoreQueryCount := float64(*result.ResolutionMetadata.DatastoreQueryCount) 571 572 grpc_ctxtags.Extract(ctx).Set(datastoreQueryCountHistogramName, datastoreQueryCount) 573 span.SetAttributes(attribute.Float64(datastoreQueryCountHistogramName, datastoreQueryCount)) 574 datastoreQueryCountHistogram.WithLabelValues( 575 s.serviceName, 576 methodName, 577 ).Observe(datastoreQueryCount) 578 579 dispatchCount := float64(*result.ResolutionMetadata.DispatchCount) 580 581 grpc_ctxtags.Extract(ctx).Set(dispatchCountHistogramName, dispatchCount) 582 span.SetAttributes(attribute.Float64(dispatchCountHistogramName, dispatchCount)) 583 dispatchCountHistogram.WithLabelValues( 584 s.serviceName, 585 methodName, 586 ).Observe(dispatchCount) 587 588 requestDurationHistogram.WithLabelValues( 589 s.serviceName, 590 methodName, 591 utils.Bucketize(uint(*result.ResolutionMetadata.DatastoreQueryCount), s.requestDurationByQueryHistogramBuckets), 592 utils.Bucketize(uint(*result.ResolutionMetadata.DispatchCount), s.requestDurationByDispatchCountHistogramBuckets), 593 ).Observe(float64(time.Since(start).Milliseconds())) 594 595 return &openfgav1.ListObjectsResponse{ 596 Objects: result.Objects, 597 }, nil 598 } 599 600 func (s *Server) StreamedListObjects(req *openfgav1.StreamedListObjectsRequest, srv openfgav1.OpenFGAService_StreamedListObjectsServer) error { 601 start := time.Now() 602 603 ctx := srv.Context() 604 ctx, span := tracer.Start(ctx, "StreamedListObjects", trace.WithAttributes( 605 attribute.String("object_type", req.GetType()), 606 attribute.String("relation", req.GetRelation()), 607 attribute.String("user", req.GetUser()), 608 )) 609 defer span.End() 610 611 if !validator.RequestIsValidatedFromContext(ctx) { 612 if err := req.Validate(); err != nil { 613 return status.Error(codes.InvalidArgument, err.Error()) 614 } 615 } 616 617 const methodName = "streamedlistobjects" 618 619 ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{ 620 Service: s.serviceName, 621 Method: methodName, 622 }) 623 624 storeID := req.GetStoreId() 625 626 typesys, err := s.resolveTypesystem(ctx, storeID, req.GetAuthorizationModelId()) 627 if err != nil { 628 return err 629 } 630 631 q, err := commands.NewListObjectsQuery( 632 s.datastore, 633 s.checkResolver, 634 commands.WithLogger(s.logger), 635 commands.WithListObjectsDeadline(s.listObjectsDeadline), 636 commands.WithListObjectsMaxResults(s.listObjectsMaxResults), 637 commands.WithResolveNodeLimit(s.resolveNodeLimit), 638 commands.WithResolveNodeBreadthLimit(s.resolveNodeBreadthLimit), 639 commands.WithMaxConcurrentReads(s.maxConcurrentReadsForListObjects), 640 ) 641 if err != nil { 642 return serverErrors.NewInternalError("", err) 643 } 644 645 req.AuthorizationModelId = typesys.GetAuthorizationModelID() // the resolved model id 646 647 resolutionMetadata, err := q.ExecuteStreamed( 648 typesystem.ContextWithTypesystem(ctx, typesys), 649 req, 650 srv, 651 ) 652 if err != nil { 653 telemetry.TraceError(span, err) 654 return err 655 } 656 datastoreQueryCount := float64(*resolutionMetadata.DatastoreQueryCount) 657 658 grpc_ctxtags.Extract(ctx).Set(datastoreQueryCountHistogramName, datastoreQueryCount) 659 span.SetAttributes(attribute.Float64(datastoreQueryCountHistogramName, datastoreQueryCount)) 660 datastoreQueryCountHistogram.WithLabelValues( 661 s.serviceName, 662 methodName, 663 ).Observe(datastoreQueryCount) 664 665 dispatchCount := float64(*resolutionMetadata.DispatchCount) 666 667 grpc_ctxtags.Extract(ctx).Set(dispatchCountHistogramName, dispatchCount) 668 span.SetAttributes(attribute.Float64(dispatchCountHistogramName, dispatchCount)) 669 dispatchCountHistogram.WithLabelValues( 670 s.serviceName, 671 methodName, 672 ).Observe(dispatchCount) 673 674 requestDurationHistogram.WithLabelValues( 675 s.serviceName, 676 methodName, 677 utils.Bucketize(uint(*resolutionMetadata.DatastoreQueryCount), s.requestDurationByQueryHistogramBuckets), 678 utils.Bucketize(uint(*resolutionMetadata.DispatchCount), s.requestDurationByDispatchCountHistogramBuckets), 679 ).Observe(float64(time.Since(start).Milliseconds())) 680 681 return nil 682 } 683 684 func (s *Server) Read(ctx context.Context, req *openfgav1.ReadRequest) (*openfgav1.ReadResponse, error) { 685 tk := req.GetTupleKey() 686 ctx, span := tracer.Start(ctx, "Read", trace.WithAttributes( 687 attribute.KeyValue{Key: "object", Value: attribute.StringValue(tk.GetObject())}, 688 attribute.KeyValue{Key: "relation", Value: attribute.StringValue(tk.GetRelation())}, 689 attribute.KeyValue{Key: "user", Value: attribute.StringValue(tk.GetUser())}, 690 )) 691 defer span.End() 692 693 if !validator.RequestIsValidatedFromContext(ctx) { 694 if err := req.Validate(); err != nil { 695 return nil, status.Error(codes.InvalidArgument, err.Error()) 696 } 697 } 698 699 ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{ 700 Service: s.serviceName, 701 Method: "Read", 702 }) 703 704 q := commands.NewReadQuery(s.datastore, 705 commands.WithReadQueryLogger(s.logger), 706 commands.WithReadQueryEncoder(s.encoder), 707 ) 708 return q.Execute(ctx, &openfgav1.ReadRequest{ 709 StoreId: req.GetStoreId(), 710 TupleKey: tk, 711 PageSize: req.GetPageSize(), 712 ContinuationToken: req.GetContinuationToken(), 713 }) 714 } 715 716 func (s *Server) Write(ctx context.Context, req *openfgav1.WriteRequest) (*openfgav1.WriteResponse, error) { 717 ctx, span := tracer.Start(ctx, "Write") 718 defer span.End() 719 720 if !validator.RequestIsValidatedFromContext(ctx) { 721 if err := req.Validate(); err != nil { 722 return nil, status.Error(codes.InvalidArgument, err.Error()) 723 } 724 } 725 726 ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{ 727 Service: s.serviceName, 728 Method: "Write", 729 }) 730 731 storeID := req.GetStoreId() 732 733 typesys, err := s.resolveTypesystem(ctx, storeID, req.GetAuthorizationModelId()) 734 if err != nil { 735 return nil, err 736 } 737 738 cmd := commands.NewWriteCommand( 739 s.datastore, 740 commands.WithWriteCmdLogger(s.logger), 741 ) 742 return cmd.Execute(ctx, &openfgav1.WriteRequest{ 743 StoreId: storeID, 744 AuthorizationModelId: typesys.GetAuthorizationModelID(), // the resolved model id 745 Writes: req.GetWrites(), 746 Deletes: req.GetDeletes(), 747 }) 748 } 749 750 func (s *Server) Check(ctx context.Context, req *openfgav1.CheckRequest) (*openfgav1.CheckResponse, error) { 751 start := time.Now() 752 753 tk := req.GetTupleKey() 754 ctx, span := tracer.Start(ctx, "Check", trace.WithAttributes( 755 attribute.KeyValue{Key: "store_id", Value: attribute.StringValue(req.GetStoreId())}, 756 attribute.KeyValue{Key: "object", Value: attribute.StringValue(tk.GetObject())}, 757 attribute.KeyValue{Key: "relation", Value: attribute.StringValue(tk.GetRelation())}, 758 attribute.KeyValue{Key: "user", Value: attribute.StringValue(tk.GetUser())}, 759 )) 760 defer span.End() 761 762 if !validator.RequestIsValidatedFromContext(ctx) { 763 if err := req.Validate(); err != nil { 764 return nil, status.Error(codes.InvalidArgument, err.Error()) 765 } 766 } 767 768 ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{ 769 Service: s.serviceName, 770 Method: "Check", 771 }) 772 773 storeID := req.GetStoreId() 774 775 typesys, err := s.resolveTypesystem(ctx, storeID, req.GetAuthorizationModelId()) 776 if err != nil { 777 return nil, err 778 } 779 780 if err := validation.ValidateUserObjectRelation(typesys, tuple.ConvertCheckRequestTupleKeyToTupleKey(tk)); err != nil { 781 return nil, serverErrors.ValidationError(err) 782 } 783 784 for _, ctxTuple := range req.GetContextualTuples().GetTupleKeys() { 785 if err := validation.ValidateTuple(typesys, ctxTuple); err != nil { 786 return nil, serverErrors.HandleTupleValidateError(err) 787 } 788 } 789 790 ctx = typesystem.ContextWithTypesystem(ctx, typesys) 791 ctx = storage.ContextWithRelationshipTupleReader(ctx, 792 storagewrappers.NewBoundedConcurrencyTupleReader( 793 storagewrappers.NewCombinedTupleReader( 794 s.datastore, 795 req.GetContextualTuples().GetTupleKeys(), 796 ), 797 s.maxConcurrentReadsForCheck, 798 ), 799 ) 800 801 checkRequestMetadata := graph.NewCheckRequestMetadata(s.resolveNodeLimit) 802 803 resolveCheckRequest := graph.ResolveCheckRequest{ 804 StoreID: req.GetStoreId(), 805 AuthorizationModelID: typesys.GetAuthorizationModelID(), // the resolved model id 806 TupleKey: tuple.ConvertCheckRequestTupleKeyToTupleKey(req.GetTupleKey()), 807 ContextualTuples: req.GetContextualTuples().GetTupleKeys(), 808 Context: req.GetContext(), 809 RequestMetadata: checkRequestMetadata, 810 } 811 812 resp, err := s.checkResolver.ResolveCheck(ctx, &resolveCheckRequest) 813 if err != nil { 814 telemetry.TraceError(span, err) 815 if errors.Is(err, graph.ErrResolutionDepthExceeded) { 816 return nil, serverErrors.AuthorizationModelResolutionTooComplex 817 } 818 819 if errors.Is(err, condition.ErrEvaluationFailed) { 820 return nil, serverErrors.ValidationError(err) 821 } 822 823 if errors.Is(err, context.DeadlineExceeded) && resolveCheckRequest.GetRequestMetadata().WasThrottled.Load() { 824 return nil, serverErrors.ThrottledTimeout 825 } 826 827 return nil, serverErrors.HandleError("", err) 828 } 829 830 queryCount := float64(resp.GetResolutionMetadata().DatastoreQueryCount) 831 const methodName = "check" 832 833 grpc_ctxtags.Extract(ctx).Set(datastoreQueryCountHistogramName, queryCount) 834 span.SetAttributes(attribute.Float64(datastoreQueryCountHistogramName, queryCount)) 835 datastoreQueryCountHistogram.WithLabelValues( 836 s.serviceName, 837 methodName, 838 ).Observe(queryCount) 839 840 rawDispatchCount := checkRequestMetadata.DispatchCounter.Load() 841 dispatchCount := float64(rawDispatchCount) 842 843 grpc_ctxtags.Extract(ctx).Set(dispatchCountHistogramName, dispatchCount) 844 span.SetAttributes(attribute.Float64(dispatchCountHistogramName, dispatchCount)) 845 dispatchCountHistogram.WithLabelValues( 846 s.serviceName, 847 methodName, 848 ).Observe(dispatchCount) 849 850 res := &openfgav1.CheckResponse{ 851 Allowed: resp.Allowed, 852 } 853 854 span.SetAttributes(attribute.KeyValue{Key: "allowed", Value: attribute.BoolValue(res.GetAllowed())}) 855 856 requestDurationHistogram.WithLabelValues( 857 s.serviceName, 858 methodName, 859 utils.Bucketize(uint(resp.GetResolutionMetadata().DatastoreQueryCount), s.requestDurationByQueryHistogramBuckets), 860 utils.Bucketize(uint(rawDispatchCount), s.requestDurationByDispatchCountHistogramBuckets), 861 ).Observe(float64(time.Since(start).Milliseconds())) 862 863 return res, nil 864 } 865 866 func (s *Server) Expand(ctx context.Context, req *openfgav1.ExpandRequest) (*openfgav1.ExpandResponse, error) { 867 tk := req.GetTupleKey() 868 ctx, span := tracer.Start(ctx, "Expand", trace.WithAttributes( 869 attribute.KeyValue{Key: "object", Value: attribute.StringValue(tk.GetObject())}, 870 attribute.KeyValue{Key: "relation", Value: attribute.StringValue(tk.GetRelation())}, 871 )) 872 defer span.End() 873 874 if !validator.RequestIsValidatedFromContext(ctx) { 875 if err := req.Validate(); err != nil { 876 return nil, status.Error(codes.InvalidArgument, err.Error()) 877 } 878 } 879 880 ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{ 881 Service: s.serviceName, 882 Method: "Expand", 883 }) 884 885 storeID := req.GetStoreId() 886 887 typesys, err := s.resolveTypesystem(ctx, storeID, req.GetAuthorizationModelId()) 888 if err != nil { 889 return nil, err 890 } 891 892 q := commands.NewExpandQuery(s.datastore, commands.WithExpandQueryLogger(s.logger)) 893 return q.Execute(ctx, &openfgav1.ExpandRequest{ 894 StoreId: storeID, 895 AuthorizationModelId: typesys.GetAuthorizationModelID(), // the resolved model id 896 TupleKey: tk, 897 }) 898 } 899 900 func (s *Server) ReadAuthorizationModel(ctx context.Context, req *openfgav1.ReadAuthorizationModelRequest) (*openfgav1.ReadAuthorizationModelResponse, error) { 901 ctx, span := tracer.Start(ctx, "ReadAuthorizationModel", trace.WithAttributes( 902 attribute.KeyValue{Key: authorizationModelIDKey, Value: attribute.StringValue(req.GetId())}, 903 )) 904 defer span.End() 905 906 if !validator.RequestIsValidatedFromContext(ctx) { 907 if err := req.Validate(); err != nil { 908 return nil, status.Error(codes.InvalidArgument, err.Error()) 909 } 910 } 911 912 ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{ 913 Service: s.serviceName, 914 Method: "ReadAuthorizationModels", 915 }) 916 917 q := commands.NewReadAuthorizationModelQuery(s.datastore, commands.WithReadAuthModelQueryLogger(s.logger)) 918 return q.Execute(ctx, req) 919 } 920 921 func (s *Server) WriteAuthorizationModel(ctx context.Context, req *openfgav1.WriteAuthorizationModelRequest) (*openfgav1.WriteAuthorizationModelResponse, error) { 922 ctx, span := tracer.Start(ctx, "WriteAuthorizationModel") 923 defer span.End() 924 925 if !validator.RequestIsValidatedFromContext(ctx) { 926 if err := req.Validate(); err != nil { 927 return nil, status.Error(codes.InvalidArgument, err.Error()) 928 } 929 } 930 931 ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{ 932 Service: s.serviceName, 933 Method: "WriteAuthorizationModel", 934 }) 935 936 c := commands.NewWriteAuthorizationModelCommand(s.datastore, 937 commands.WithWriteAuthModelLogger(s.logger), 938 commands.WithWriteAuthModelMaxSizeInBytes(s.maxAuthorizationModelSizeInBytes), 939 ) 940 res, err := c.Execute(ctx, req) 941 if err != nil { 942 return nil, err 943 } 944 945 s.transport.SetHeader(ctx, httpmiddleware.XHttpCode, strconv.Itoa(http.StatusCreated)) 946 947 return res, nil 948 } 949 950 func (s *Server) ReadAuthorizationModels(ctx context.Context, req *openfgav1.ReadAuthorizationModelsRequest) (*openfgav1.ReadAuthorizationModelsResponse, error) { 951 ctx, span := tracer.Start(ctx, "ReadAuthorizationModels") 952 defer span.End() 953 954 if !validator.RequestIsValidatedFromContext(ctx) { 955 if err := req.Validate(); err != nil { 956 return nil, status.Error(codes.InvalidArgument, err.Error()) 957 } 958 } 959 960 ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{ 961 Service: s.serviceName, 962 Method: "ReadAuthorizationModels", 963 }) 964 965 c := commands.NewReadAuthorizationModelsQuery(s.datastore, 966 commands.WithReadAuthModelsQueryLogger(s.logger), 967 commands.WithReadAuthModelsQueryEncoder(s.encoder), 968 ) 969 return c.Execute(ctx, req) 970 } 971 972 func (s *Server) WriteAssertions(ctx context.Context, req *openfgav1.WriteAssertionsRequest) (*openfgav1.WriteAssertionsResponse, error) { 973 ctx, span := tracer.Start(ctx, "WriteAssertions") 974 defer span.End() 975 976 if !validator.RequestIsValidatedFromContext(ctx) { 977 if err := req.Validate(); err != nil { 978 return nil, status.Error(codes.InvalidArgument, err.Error()) 979 } 980 } 981 982 ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{ 983 Service: s.serviceName, 984 Method: "WriteAssertions", 985 }) 986 987 storeID := req.GetStoreId() 988 989 typesys, err := s.resolveTypesystem(ctx, storeID, req.GetAuthorizationModelId()) 990 if err != nil { 991 return nil, err 992 } 993 994 c := commands.NewWriteAssertionsCommand(s.datastore, commands.WithWriteAssertCmdLogger(s.logger)) 995 res, err := c.Execute(ctx, &openfgav1.WriteAssertionsRequest{ 996 StoreId: storeID, 997 AuthorizationModelId: typesys.GetAuthorizationModelID(), // the resolved model id 998 Assertions: req.GetAssertions(), 999 }) 1000 if err != nil { 1001 return nil, err 1002 } 1003 1004 s.transport.SetHeader(ctx, httpmiddleware.XHttpCode, strconv.Itoa(http.StatusNoContent)) 1005 1006 return res, nil 1007 } 1008 1009 func (s *Server) ReadAssertions(ctx context.Context, req *openfgav1.ReadAssertionsRequest) (*openfgav1.ReadAssertionsResponse, error) { 1010 ctx, span := tracer.Start(ctx, "ReadAssertions") 1011 defer span.End() 1012 1013 if !validator.RequestIsValidatedFromContext(ctx) { 1014 if err := req.Validate(); err != nil { 1015 return nil, status.Error(codes.InvalidArgument, err.Error()) 1016 } 1017 } 1018 1019 ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{ 1020 Service: s.serviceName, 1021 Method: "ReadAssertions", 1022 }) 1023 1024 typesys, err := s.resolveTypesystem(ctx, req.GetStoreId(), req.GetAuthorizationModelId()) 1025 if err != nil { 1026 return nil, err 1027 } 1028 1029 q := commands.NewReadAssertionsQuery(s.datastore, commands.WithReadAssertionsQueryLogger(s.logger)) 1030 return q.Execute(ctx, req.GetStoreId(), typesys.GetAuthorizationModelID()) 1031 } 1032 1033 func (s *Server) ReadChanges(ctx context.Context, req *openfgav1.ReadChangesRequest) (*openfgav1.ReadChangesResponse, error) { 1034 ctx, span := tracer.Start(ctx, "ReadChangesQuery", trace.WithAttributes( 1035 attribute.KeyValue{Key: "type", Value: attribute.StringValue(req.GetType())}, 1036 )) 1037 defer span.End() 1038 1039 if !validator.RequestIsValidatedFromContext(ctx) { 1040 if err := req.Validate(); err != nil { 1041 return nil, status.Error(codes.InvalidArgument, err.Error()) 1042 } 1043 } 1044 1045 ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{ 1046 Service: s.serviceName, 1047 Method: "ReadChanges", 1048 }) 1049 1050 q := commands.NewReadChangesQuery(s.datastore, 1051 commands.WithReadChangesQueryLogger(s.logger), 1052 commands.WithReadChangesQueryEncoder(s.encoder), 1053 commands.WithReadChangeQueryHorizonOffset(s.changelogHorizonOffset), 1054 ) 1055 return q.Execute(ctx, req) 1056 } 1057 1058 func (s *Server) CreateStore(ctx context.Context, req *openfgav1.CreateStoreRequest) (*openfgav1.CreateStoreResponse, error) { 1059 ctx, span := tracer.Start(ctx, "CreateStore") 1060 defer span.End() 1061 1062 if !validator.RequestIsValidatedFromContext(ctx) { 1063 if err := req.Validate(); err != nil { 1064 return nil, status.Error(codes.InvalidArgument, err.Error()) 1065 } 1066 } 1067 1068 ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{ 1069 Service: s.serviceName, 1070 Method: "CreateStore", 1071 }) 1072 1073 c := commands.NewCreateStoreCommand(s.datastore, commands.WithCreateStoreCmdLogger(s.logger)) 1074 res, err := c.Execute(ctx, req) 1075 if err != nil { 1076 return nil, err 1077 } 1078 1079 s.transport.SetHeader(ctx, httpmiddleware.XHttpCode, strconv.Itoa(http.StatusCreated)) 1080 1081 return res, nil 1082 } 1083 1084 func (s *Server) DeleteStore(ctx context.Context, req *openfgav1.DeleteStoreRequest) (*openfgav1.DeleteStoreResponse, error) { 1085 ctx, span := tracer.Start(ctx, "DeleteStore") 1086 defer span.End() 1087 1088 if !validator.RequestIsValidatedFromContext(ctx) { 1089 if err := req.Validate(); err != nil { 1090 return nil, status.Error(codes.InvalidArgument, err.Error()) 1091 } 1092 } 1093 1094 ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{ 1095 Service: s.serviceName, 1096 Method: "DeleteStore", 1097 }) 1098 1099 cmd := commands.NewDeleteStoreCommand(s.datastore, commands.WithDeleteStoreCmdLogger(s.logger)) 1100 res, err := cmd.Execute(ctx, req) 1101 if err != nil { 1102 return nil, err 1103 } 1104 1105 s.transport.SetHeader(ctx, httpmiddleware.XHttpCode, strconv.Itoa(http.StatusNoContent)) 1106 1107 return res, nil 1108 } 1109 1110 func (s *Server) GetStore(ctx context.Context, req *openfgav1.GetStoreRequest) (*openfgav1.GetStoreResponse, error) { 1111 ctx, span := tracer.Start(ctx, "GetStore") 1112 defer span.End() 1113 1114 if !validator.RequestIsValidatedFromContext(ctx) { 1115 if err := req.Validate(); err != nil { 1116 return nil, status.Error(codes.InvalidArgument, err.Error()) 1117 } 1118 } 1119 1120 ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{ 1121 Service: s.serviceName, 1122 Method: "GetStore", 1123 }) 1124 1125 q := commands.NewGetStoreQuery(s.datastore, commands.WithGetStoreQueryLogger(s.logger)) 1126 return q.Execute(ctx, req) 1127 } 1128 1129 func (s *Server) ListStores(ctx context.Context, req *openfgav1.ListStoresRequest) (*openfgav1.ListStoresResponse, error) { 1130 ctx, span := tracer.Start(ctx, "ListStores") 1131 defer span.End() 1132 1133 if !validator.RequestIsValidatedFromContext(ctx) { 1134 if err := req.Validate(); err != nil { 1135 return nil, status.Error(codes.InvalidArgument, err.Error()) 1136 } 1137 } 1138 1139 ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{ 1140 Service: s.serviceName, 1141 Method: "ListStores", 1142 }) 1143 1144 q := commands.NewListStoresQuery(s.datastore, 1145 commands.WithListStoresQueryLogger(s.logger), 1146 commands.WithListStoresQueryEncoder(s.encoder), 1147 ) 1148 return q.Execute(ctx, req) 1149 } 1150 1151 // IsReady reports whether the datastore is ready. Please see the implementation of [[storage.OpenFGADatastore.IsReady]] 1152 // for your datastore. 1153 func (s *Server) IsReady(ctx context.Context) (bool, error) { 1154 // for now we only depend on the datastore being ready, but in the future 1155 // server readiness may also depend on other criteria in addition to the 1156 // datastore being ready. 1157 1158 status, err := s.datastore.IsReady(ctx) 1159 if err != nil { 1160 return false, err 1161 } 1162 1163 if status.IsReady { 1164 return true, nil 1165 } 1166 1167 s.logger.WarnWithContext(ctx, "datastore is not ready", zap.Any("status", status.Message)) 1168 return false, nil 1169 } 1170 1171 // resolveTypesystem resolves the underlying TypeSystem given the storeID and modelID and 1172 // it sets some response metadata based on the model resolution. 1173 func (s *Server) resolveTypesystem(ctx context.Context, storeID, modelID string) (*typesystem.TypeSystem, error) { 1174 ctx, span := tracer.Start(ctx, "resolveTypesystem") 1175 defer span.End() 1176 1177 typesys, err := s.typesystemResolver(ctx, storeID, modelID) 1178 if err != nil { 1179 telemetry.TraceError(span, err) 1180 if errors.Is(err, typesystem.ErrModelNotFound) { 1181 if modelID == "" { 1182 return nil, serverErrors.LatestAuthorizationModelNotFound(storeID) 1183 } 1184 1185 return nil, serverErrors.AuthorizationModelNotFound(modelID) 1186 } 1187 1188 if errors.Is(err, typesystem.ErrInvalidModel) { 1189 return nil, serverErrors.ValidationError(err) 1190 } 1191 1192 return nil, serverErrors.HandleError("", err) 1193 } 1194 1195 resolvedModelID := typesys.GetAuthorizationModelID() 1196 1197 span.SetAttributes(attribute.KeyValue{Key: authorizationModelIDKey, Value: attribute.StringValue(resolvedModelID)}) 1198 grpc_ctxtags.Extract(ctx).Set(authorizationModelIDKey, resolvedModelID) 1199 s.transport.SetHeader(ctx, AuthorizationModelIDHeader, resolvedModelID) 1200 1201 return typesys, nil 1202 }