github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/services/v1/permissions.go (about) 1 package v1 2 3 import ( 4 "context" 5 "fmt" 6 7 "github.com/authzed/authzed-go/pkg/requestmeta" 8 v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" 9 "github.com/jzelinskie/stringz" 10 "google.golang.org/grpc/codes" 11 "google.golang.org/grpc/metadata" 12 "google.golang.org/grpc/status" 13 "google.golang.org/protobuf/proto" 14 "google.golang.org/protobuf/types/known/structpb" 15 16 cexpr "github.com/authzed/spicedb/internal/caveats" 17 dispatchpkg "github.com/authzed/spicedb/internal/dispatch" 18 "github.com/authzed/spicedb/internal/graph" 19 "github.com/authzed/spicedb/internal/graph/computed" 20 datastoremw "github.com/authzed/spicedb/internal/middleware/datastore" 21 "github.com/authzed/spicedb/internal/middleware/usagemetrics" 22 "github.com/authzed/spicedb/internal/namespace" 23 "github.com/authzed/spicedb/internal/services/shared" 24 "github.com/authzed/spicedb/pkg/cursor" 25 "github.com/authzed/spicedb/pkg/datastore" 26 "github.com/authzed/spicedb/pkg/middleware/consistency" 27 core "github.com/authzed/spicedb/pkg/proto/core/v1" 28 dispatch "github.com/authzed/spicedb/pkg/proto/dispatch/v1" 29 impl "github.com/authzed/spicedb/pkg/proto/impl/v1" 30 "github.com/authzed/spicedb/pkg/tuple" 31 ) 32 33 func (ps *permissionServer) rewriteError(ctx context.Context, err error) error { 34 return shared.RewriteError(ctx, err, &shared.ConfigForErrors{ 35 MaximumAPIDepth: ps.config.MaximumAPIDepth, 36 }) 37 } 38 39 func (ps *permissionServer) CheckPermission(ctx context.Context, req *v1.CheckPermissionRequest) (*v1.CheckPermissionResponse, error) { 40 atRevision, checkedAt, err := consistency.RevisionFromContext(ctx) 41 if err != nil { 42 return nil, ps.rewriteError(ctx, err) 43 } 44 45 ds := datastoremw.MustFromContext(ctx).SnapshotReader(atRevision) 46 47 caveatContext, err := GetCaveatContext(ctx, req.Context, ps.config.MaxCaveatContextSize) 48 if err != nil { 49 return nil, ps.rewriteError(ctx, err) 50 } 51 52 if err := namespace.CheckNamespaceAndRelations(ctx, 53 []namespace.TypeAndRelationToCheck{ 54 { 55 NamespaceName: req.Resource.ObjectType, 56 RelationName: req.Permission, 57 AllowEllipsis: false, 58 }, 59 { 60 NamespaceName: req.Subject.Object.ObjectType, 61 RelationName: normalizeSubjectRelation(req.Subject), 62 AllowEllipsis: true, 63 }, 64 }, ds); err != nil { 65 return nil, ps.rewriteError(ctx, err) 66 } 67 68 debugOption := computed.NoDebugging 69 70 if md, ok := metadata.FromIncomingContext(ctx); ok { 71 _, isDebuggingEnabled := md[string(requestmeta.RequestDebugInformation)] 72 if isDebuggingEnabled { 73 debugOption = computed.BasicDebuggingEnabled 74 } 75 } 76 77 if req.WithTracing { 78 debugOption = computed.BasicDebuggingEnabled 79 } 80 81 cr, metadata, err := computed.ComputeCheck(ctx, ps.dispatch, 82 computed.CheckParameters{ 83 ResourceType: &core.RelationReference{ 84 Namespace: req.Resource.ObjectType, 85 Relation: req.Permission, 86 }, 87 Subject: &core.ObjectAndRelation{ 88 Namespace: req.Subject.Object.ObjectType, 89 ObjectId: req.Subject.Object.ObjectId, 90 Relation: normalizeSubjectRelation(req.Subject), 91 }, 92 CaveatContext: caveatContext, 93 AtRevision: atRevision, 94 MaximumDepth: ps.config.MaximumAPIDepth, 95 DebugOption: debugOption, 96 }, 97 req.Resource.ObjectId, 98 ) 99 usagemetrics.SetInContext(ctx, metadata) 100 101 var debugTrace *v1.DebugInformation 102 if debugOption != computed.NoDebugging && metadata.DebugInfo != nil { 103 // Convert the dispatch debug information into API debug information. 104 converted, cerr := ConvertCheckDispatchDebugInformation(ctx, caveatContext, metadata, ds) 105 if cerr != nil { 106 return nil, ps.rewriteError(ctx, cerr) 107 } 108 debugTrace = converted 109 } 110 111 if err != nil { 112 return nil, ps.rewriteError(ctx, err) 113 } 114 115 permissionship, partialCaveat := checkResultToAPITypes(cr) 116 117 return &v1.CheckPermissionResponse{ 118 CheckedAt: checkedAt, 119 Permissionship: permissionship, 120 PartialCaveatInfo: partialCaveat, 121 DebugTrace: debugTrace, 122 }, nil 123 } 124 125 func checkResultToAPITypes(cr *dispatch.ResourceCheckResult) (v1.CheckPermissionResponse_Permissionship, *v1.PartialCaveatInfo) { 126 var partialCaveat *v1.PartialCaveatInfo 127 permissionship := v1.CheckPermissionResponse_PERMISSIONSHIP_NO_PERMISSION 128 if cr.Membership == dispatch.ResourceCheckResult_MEMBER { 129 permissionship = v1.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION 130 } else if cr.Membership == dispatch.ResourceCheckResult_CAVEATED_MEMBER { 131 permissionship = v1.CheckPermissionResponse_PERMISSIONSHIP_CONDITIONAL_PERMISSION 132 partialCaveat = &v1.PartialCaveatInfo{ 133 MissingRequiredContext: cr.MissingExprFields, 134 } 135 } 136 return permissionship, partialCaveat 137 } 138 139 func (ps *permissionServer) CheckBulkPermissions(ctx context.Context, req *v1.CheckBulkPermissionsRequest) (*v1.CheckBulkPermissionsResponse, error) { 140 res, err := ps.bulkChecker.checkBulkPermissions(ctx, req) 141 if err != nil { 142 return nil, ps.rewriteError(ctx, err) 143 } 144 145 return res, nil 146 } 147 148 func pairItemFromCheckResult(checkResult *dispatch.ResourceCheckResult) *v1.CheckBulkPermissionsPair_Item { 149 permissionship, partialCaveat := checkResultToAPITypes(checkResult) 150 return &v1.CheckBulkPermissionsPair_Item{ 151 Item: &v1.CheckBulkPermissionsResponseItem{ 152 Permissionship: permissionship, 153 PartialCaveatInfo: partialCaveat, 154 }, 155 } 156 } 157 158 func requestItemFromResourceAndParameters(params *computed.CheckParameters, resourceID string) (*v1.CheckBulkPermissionsRequestItem, error) { 159 item := &v1.CheckBulkPermissionsRequestItem{ 160 Resource: &v1.ObjectReference{ 161 ObjectType: params.ResourceType.Namespace, 162 ObjectId: resourceID, 163 }, 164 Permission: params.ResourceType.Relation, 165 Subject: &v1.SubjectReference{ 166 Object: &v1.ObjectReference{ 167 ObjectType: params.Subject.Namespace, 168 ObjectId: params.Subject.ObjectId, 169 }, 170 OptionalRelation: denormalizeSubjectRelation(params.Subject.Relation), 171 }, 172 } 173 if len(params.CaveatContext) > 0 { 174 var err error 175 item.Context, err = structpb.NewStruct(params.CaveatContext) 176 if err != nil { 177 return nil, fmt.Errorf("caveat context wasn't properly validated: %w", err) 178 } 179 } 180 return item, nil 181 } 182 183 func (ps *permissionServer) ExpandPermissionTree(ctx context.Context, req *v1.ExpandPermissionTreeRequest) (*v1.ExpandPermissionTreeResponse, error) { 184 atRevision, expandedAt, err := consistency.RevisionFromContext(ctx) 185 if err != nil { 186 return nil, ps.rewriteError(ctx, err) 187 } 188 189 ds := datastoremw.MustFromContext(ctx).SnapshotReader(atRevision) 190 191 err = namespace.CheckNamespaceAndRelation(ctx, req.Resource.ObjectType, req.Permission, false, ds) 192 if err != nil { 193 return nil, ps.rewriteError(ctx, err) 194 } 195 196 bf, err := dispatch.NewTraversalBloomFilter(uint(ps.config.MaximumAPIDepth)) 197 if err != nil { 198 return nil, err 199 } 200 201 resp, err := ps.dispatch.DispatchExpand(ctx, &dispatch.DispatchExpandRequest{ 202 Metadata: &dispatch.ResolverMeta{ 203 AtRevision: atRevision.String(), 204 DepthRemaining: ps.config.MaximumAPIDepth, 205 TraversalBloom: bf, 206 }, 207 ResourceAndRelation: &core.ObjectAndRelation{ 208 Namespace: req.Resource.ObjectType, 209 ObjectId: req.Resource.ObjectId, 210 Relation: req.Permission, 211 }, 212 ExpansionMode: dispatch.DispatchExpandRequest_SHALLOW, 213 }) 214 usagemetrics.SetInContext(ctx, resp.Metadata) 215 if err != nil { 216 return nil, ps.rewriteError(ctx, err) 217 } 218 219 // TODO(jschorr): Change to either using shared interfaces for nodes, or switch the internal 220 // dispatched expand to return V1 node types. 221 return &v1.ExpandPermissionTreeResponse{ 222 TreeRoot: TranslateExpansionTree(resp.TreeNode), 223 ExpandedAt: expandedAt, 224 }, nil 225 } 226 227 // TranslateRelationshipTree translates a V1 PermissionRelationshipTree into a RelationTupleTreeNode. 228 func TranslateRelationshipTree(tree *v1.PermissionRelationshipTree) *core.RelationTupleTreeNode { 229 var expanded *core.ObjectAndRelation 230 if tree.ExpandedObject != nil { 231 expanded = &core.ObjectAndRelation{ 232 Namespace: tree.ExpandedObject.ObjectType, 233 ObjectId: tree.ExpandedObject.ObjectId, 234 Relation: tree.ExpandedRelation, 235 } 236 } 237 238 switch t := tree.TreeType.(type) { 239 case *v1.PermissionRelationshipTree_Intermediate: 240 var operation core.SetOperationUserset_Operation 241 switch t.Intermediate.Operation { 242 case v1.AlgebraicSubjectSet_OPERATION_EXCLUSION: 243 operation = core.SetOperationUserset_EXCLUSION 244 case v1.AlgebraicSubjectSet_OPERATION_INTERSECTION: 245 operation = core.SetOperationUserset_INTERSECTION 246 case v1.AlgebraicSubjectSet_OPERATION_UNION: 247 operation = core.SetOperationUserset_UNION 248 default: 249 panic("unknown set operation") 250 } 251 252 children := []*core.RelationTupleTreeNode{} 253 for _, child := range t.Intermediate.Children { 254 children = append(children, TranslateRelationshipTree(child)) 255 } 256 257 return &core.RelationTupleTreeNode{ 258 NodeType: &core.RelationTupleTreeNode_IntermediateNode{ 259 IntermediateNode: &core.SetOperationUserset{ 260 Operation: operation, 261 ChildNodes: children, 262 }, 263 }, 264 Expanded: expanded, 265 } 266 267 case *v1.PermissionRelationshipTree_Leaf: 268 var subjects []*core.DirectSubject 269 for _, subj := range t.Leaf.Subjects { 270 subjects = append(subjects, &core.DirectSubject{ 271 Subject: &core.ObjectAndRelation{ 272 Namespace: subj.Object.ObjectType, 273 ObjectId: subj.Object.ObjectId, 274 Relation: stringz.DefaultEmpty(subj.OptionalRelation, graph.Ellipsis), 275 }, 276 }) 277 } 278 279 return &core.RelationTupleTreeNode{ 280 NodeType: &core.RelationTupleTreeNode_LeafNode{ 281 LeafNode: &core.DirectSubjects{Subjects: subjects}, 282 }, 283 Expanded: expanded, 284 } 285 286 default: 287 panic("unknown type of expansion tree node") 288 } 289 } 290 291 func TranslateExpansionTree(node *core.RelationTupleTreeNode) *v1.PermissionRelationshipTree { 292 switch t := node.NodeType.(type) { 293 case *core.RelationTupleTreeNode_IntermediateNode: 294 var operation v1.AlgebraicSubjectSet_Operation 295 switch t.IntermediateNode.Operation { 296 case core.SetOperationUserset_EXCLUSION: 297 operation = v1.AlgebraicSubjectSet_OPERATION_EXCLUSION 298 case core.SetOperationUserset_INTERSECTION: 299 operation = v1.AlgebraicSubjectSet_OPERATION_INTERSECTION 300 case core.SetOperationUserset_UNION: 301 operation = v1.AlgebraicSubjectSet_OPERATION_UNION 302 default: 303 panic("unknown set operation") 304 } 305 306 var children []*v1.PermissionRelationshipTree 307 for _, child := range node.GetIntermediateNode().ChildNodes { 308 children = append(children, TranslateExpansionTree(child)) 309 } 310 311 var objRef *v1.ObjectReference 312 var objRel string 313 if node.Expanded != nil { 314 objRef = &v1.ObjectReference{ 315 ObjectType: node.Expanded.Namespace, 316 ObjectId: node.Expanded.ObjectId, 317 } 318 objRel = node.Expanded.Relation 319 } 320 321 return &v1.PermissionRelationshipTree{ 322 TreeType: &v1.PermissionRelationshipTree_Intermediate{ 323 Intermediate: &v1.AlgebraicSubjectSet{ 324 Operation: operation, 325 Children: children, 326 }, 327 }, 328 ExpandedObject: objRef, 329 ExpandedRelation: objRel, 330 } 331 332 case *core.RelationTupleTreeNode_LeafNode: 333 var subjects []*v1.SubjectReference 334 for _, found := range t.LeafNode.Subjects { 335 subjects = append(subjects, &v1.SubjectReference{ 336 Object: &v1.ObjectReference{ 337 ObjectType: found.Subject.Namespace, 338 ObjectId: found.Subject.ObjectId, 339 }, 340 OptionalRelation: denormalizeSubjectRelation(found.Subject.Relation), 341 }) 342 } 343 344 if node.Expanded == nil { 345 return &v1.PermissionRelationshipTree{ 346 TreeType: &v1.PermissionRelationshipTree_Leaf{ 347 Leaf: &v1.DirectSubjectSet{ 348 Subjects: subjects, 349 }, 350 }, 351 } 352 } 353 354 return &v1.PermissionRelationshipTree{ 355 TreeType: &v1.PermissionRelationshipTree_Leaf{ 356 Leaf: &v1.DirectSubjectSet{ 357 Subjects: subjects, 358 }, 359 }, 360 ExpandedObject: &v1.ObjectReference{ 361 ObjectType: node.Expanded.Namespace, 362 ObjectId: node.Expanded.ObjectId, 363 }, 364 ExpandedRelation: node.Expanded.Relation, 365 } 366 367 default: 368 panic("unknown type of expansion tree node") 369 } 370 } 371 372 func (ps *permissionServer) LookupResources(req *v1.LookupResourcesRequest, resp v1.PermissionsService_LookupResourcesServer) error { 373 if req.OptionalLimit > 0 && req.OptionalLimit > ps.config.MaxLookupResourcesLimit { 374 return ps.rewriteError(resp.Context(), NewExceedsMaximumLimitErr(uint64(req.OptionalLimit), uint64(ps.config.MaxLookupResourcesLimit))) 375 } 376 377 ctx := resp.Context() 378 379 atRevision, revisionReadAt, err := consistency.RevisionFromContext(ctx) 380 if err != nil { 381 return ps.rewriteError(ctx, err) 382 } 383 384 ds := datastoremw.MustFromContext(ctx).SnapshotReader(atRevision) 385 386 if err := namespace.CheckNamespaceAndRelations(ctx, 387 []namespace.TypeAndRelationToCheck{ 388 { 389 NamespaceName: req.ResourceObjectType, 390 RelationName: req.Permission, 391 AllowEllipsis: false, 392 }, 393 { 394 NamespaceName: req.Subject.Object.ObjectType, 395 RelationName: normalizeSubjectRelation(req.Subject), 396 AllowEllipsis: true, 397 }, 398 }, ds); err != nil { 399 return ps.rewriteError(ctx, err) 400 } 401 402 respMetadata := &dispatch.ResponseMeta{ 403 DispatchCount: 1, 404 CachedDispatchCount: 0, 405 DepthRequired: 1, 406 DebugInfo: nil, 407 } 408 usagemetrics.SetInContext(ctx, respMetadata) 409 410 var currentCursor *dispatch.Cursor 411 412 lrRequestHash, err := computeLRRequestHash(req) 413 if err != nil { 414 return ps.rewriteError(ctx, err) 415 } 416 417 if req.OptionalCursor != nil { 418 decodedCursor, err := cursor.DecodeToDispatchCursor(req.OptionalCursor, lrRequestHash) 419 if err != nil { 420 return ps.rewriteError(ctx, err) 421 } 422 currentCursor = decodedCursor 423 } 424 425 alreadyPublishedPermissionedResourceIds := map[string]struct{}{} 426 427 stream := dispatchpkg.NewHandlingDispatchStream(ctx, func(result *dispatch.DispatchLookupResourcesResponse) error { 428 found := result.ResolvedResource 429 430 dispatchpkg.AddResponseMetadata(respMetadata, result.Metadata) 431 currentCursor = result.AfterResponseCursor 432 433 var partial *v1.PartialCaveatInfo 434 permissionship := v1.LookupPermissionship_LOOKUP_PERMISSIONSHIP_HAS_PERMISSION 435 if found.Permissionship == dispatch.ResolvedResource_CONDITIONALLY_HAS_PERMISSION { 436 permissionship = v1.LookupPermissionship_LOOKUP_PERMISSIONSHIP_CONDITIONAL_PERMISSION 437 partial = &v1.PartialCaveatInfo{ 438 MissingRequiredContext: found.MissingRequiredContext, 439 } 440 } else if req.OptionalLimit == 0 { 441 if _, ok := alreadyPublishedPermissionedResourceIds[found.ResourceId]; ok { 442 // Skip publishing the duplicate. 443 return nil 444 } 445 446 alreadyPublishedPermissionedResourceIds[found.ResourceId] = struct{}{} 447 } 448 449 encodedCursor, err := cursor.EncodeFromDispatchCursor(result.AfterResponseCursor, lrRequestHash, atRevision) 450 if err != nil { 451 return ps.rewriteError(ctx, err) 452 } 453 454 err = resp.Send(&v1.LookupResourcesResponse{ 455 LookedUpAt: revisionReadAt, 456 ResourceObjectId: found.ResourceId, 457 Permissionship: permissionship, 458 PartialCaveatInfo: partial, 459 AfterResultCursor: encodedCursor, 460 }) 461 if err != nil { 462 return err 463 } 464 return nil 465 }) 466 467 bf, err := dispatch.NewTraversalBloomFilter(uint(ps.config.MaximumAPIDepth)) 468 if err != nil { 469 return err 470 } 471 472 err = ps.dispatch.DispatchLookupResources( 473 &dispatch.DispatchLookupResourcesRequest{ 474 Metadata: &dispatch.ResolverMeta{ 475 AtRevision: atRevision.String(), 476 DepthRemaining: ps.config.MaximumAPIDepth, 477 TraversalBloom: bf, 478 }, 479 ObjectRelation: &core.RelationReference{ 480 Namespace: req.ResourceObjectType, 481 Relation: req.Permission, 482 }, 483 Subject: &core.ObjectAndRelation{ 484 Namespace: req.Subject.Object.ObjectType, 485 ObjectId: req.Subject.Object.ObjectId, 486 Relation: normalizeSubjectRelation(req.Subject), 487 }, 488 Context: req.Context, 489 OptionalCursor: currentCursor, 490 OptionalLimit: req.OptionalLimit, 491 }, 492 stream) 493 if err != nil { 494 return ps.rewriteError(ctx, err) 495 } 496 497 return nil 498 } 499 500 func (ps *permissionServer) LookupSubjects(req *v1.LookupSubjectsRequest, resp v1.PermissionsService_LookupSubjectsServer) error { 501 ctx := resp.Context() 502 503 atRevision, revisionReadAt, err := consistency.RevisionFromContext(ctx) 504 if err != nil { 505 return ps.rewriteError(ctx, err) 506 } 507 508 ds := datastoremw.MustFromContext(ctx).SnapshotReader(atRevision) 509 510 caveatContext, err := GetCaveatContext(ctx, req.Context, ps.config.MaxCaveatContextSize) 511 if err != nil { 512 return ps.rewriteError(ctx, err) 513 } 514 515 if err := namespace.CheckNamespaceAndRelations(ctx, 516 []namespace.TypeAndRelationToCheck{ 517 { 518 NamespaceName: req.Resource.ObjectType, 519 RelationName: req.Permission, 520 AllowEllipsis: false, 521 }, 522 { 523 NamespaceName: req.SubjectObjectType, 524 RelationName: stringz.DefaultEmpty(req.OptionalSubjectRelation, tuple.Ellipsis), 525 AllowEllipsis: true, 526 }, 527 }, ds); err != nil { 528 return ps.rewriteError(ctx, err) 529 } 530 531 respMetadata := &dispatch.ResponseMeta{ 532 DispatchCount: 0, 533 CachedDispatchCount: 0, 534 DepthRequired: 0, 535 DebugInfo: nil, 536 } 537 usagemetrics.SetInContext(ctx, respMetadata) 538 539 var currentCursor *dispatch.Cursor 540 remainingConcreteLimit := 0 541 542 lsRequestHash, err := computeLSRequestHash(req) 543 if err != nil { 544 return ps.rewriteError(ctx, err) 545 } 546 547 if req.OptionalCursor != nil { 548 decodedCursor, err := cursor.DecodeToDispatchCursor(req.OptionalCursor, lsRequestHash) 549 if err != nil { 550 return ps.rewriteError(ctx, err) 551 } 552 currentCursor = decodedCursor 553 } 554 555 if req.OptionalConcreteLimit > 0 { 556 remainingConcreteLimit = int(req.OptionalConcreteLimit) 557 } 558 559 internalResponseCursor := &impl.DecodedCursor{ 560 VersionOneof: &impl.DecodedCursor_V1{ 561 V1: &impl.V1Cursor{ 562 Revision: atRevision.String(), 563 CallAndParametersHash: lsRequestHash, 564 }, 565 }, 566 } 567 568 ctxWithCancel, cancel := context.WithCancel(ctx) 569 defer cancel() 570 571 for { 572 countSubjectsFound := 0 573 574 stream := dispatchpkg.NewHandlingDispatchStream(ctxWithCancel, func(result *dispatch.DispatchLookupSubjectsResponse) error { 575 foundSubjects, ok := result.FoundSubjectsByResourceId[req.Resource.ObjectId] 576 if !ok { 577 return fmt.Errorf("missing resource ID in returned LS") 578 } 579 580 for _, foundSubject := range foundSubjects.FoundSubjects { 581 // Skip wildcards if requested they be skipped. 582 if req.WildcardOption == v1.LookupSubjectsRequest_WILDCARD_OPTION_EXCLUDE_WILDCARDS && foundSubject.SubjectId == tuple.PublicWildcard { 583 continue 584 } 585 586 excludedSubjectIDs := make([]string, 0, len(foundSubject.ExcludedSubjects)) 587 excludedSubjects := make([]*v1.ResolvedSubject, 0, len(foundSubject.ExcludedSubjects)) 588 for _, excludedSubject := range foundSubject.ExcludedSubjects { 589 excludedSubjectIDs = append(excludedSubjectIDs, excludedSubject.SubjectId) 590 591 resolvedExcludedSubject, err := foundSubjectToResolvedSubject(ctx, excludedSubject, caveatContext, ds) 592 if err != nil { 593 return fmt.Errorf("error when resolving excluded subject: %w", err) 594 } 595 596 if resolvedExcludedSubject == nil { 597 continue 598 } 599 600 excludedSubjects = append(excludedSubjects, resolvedExcludedSubject) 601 } 602 603 subject, err := foundSubjectToResolvedSubject(ctx, foundSubject, caveatContext, ds) 604 if err != nil { 605 return fmt.Errorf("error when resolving subject: %w", err) 606 } 607 if subject == nil { 608 continue 609 } 610 611 // NOTE: we need to recompute the cursor here because we get multiple results back from DispatchLookupSubjects 612 // in one message. 613 dispatchCursor, err := graph.CursorForFoundSubjectID(subject.SubjectObjectId, result.AfterResponseCursor) 614 if err != nil { 615 return err 616 } 617 618 // Update the existing internal cursor for encoding. 619 internalResponseCursor.GetV1().DispatchVersion = dispatchCursor.DispatchVersion 620 internalResponseCursor.GetV1().Sections = dispatchCursor.Sections 621 622 encodedCursor, err := cursor.Encode(internalResponseCursor) 623 if err != nil { 624 return err 625 } 626 627 currentCursor = dispatchCursor 628 629 if subject.SubjectObjectId != tuple.PublicWildcard { 630 countSubjectsFound++ 631 if req.OptionalConcreteLimit > 0 && remainingConcreteLimit <= 0 { 632 return nil 633 } 634 remainingConcreteLimit-- 635 } 636 637 err = resp.Send(&v1.LookupSubjectsResponse{ 638 Subject: subject, 639 ExcludedSubjects: excludedSubjects, 640 LookedUpAt: revisionReadAt, 641 SubjectObjectId: foundSubject.SubjectId, // Deprecated 642 ExcludedSubjectIds: excludedSubjectIDs, // Deprecated 643 Permissionship: subject.Permissionship, // Deprecated 644 PartialCaveatInfo: subject.PartialCaveatInfo, // Deprecated 645 AfterResultCursor: encodedCursor, 646 }) 647 if err != nil { 648 return err 649 } 650 } 651 652 dispatchpkg.AddResponseMetadata(respMetadata, result.Metadata) 653 return nil 654 }) 655 656 bf, err := dispatch.NewTraversalBloomFilter(uint(ps.config.MaximumAPIDepth)) 657 if err != nil { 658 return err 659 } 660 661 err = ps.dispatch.DispatchLookupSubjects( 662 &dispatch.DispatchLookupSubjectsRequest{ 663 Metadata: &dispatch.ResolverMeta{ 664 AtRevision: atRevision.String(), 665 DepthRemaining: ps.config.MaximumAPIDepth, 666 TraversalBloom: bf, 667 }, 668 ResourceRelation: &core.RelationReference{ 669 Namespace: req.Resource.ObjectType, 670 Relation: req.Permission, 671 }, 672 ResourceIds: []string{req.Resource.ObjectId}, 673 SubjectRelation: &core.RelationReference{ 674 Namespace: req.SubjectObjectType, 675 Relation: stringz.DefaultEmpty(req.OptionalSubjectRelation, tuple.Ellipsis), 676 }, 677 OptionalCursor: currentCursor, 678 OptionalLimit: req.OptionalConcreteLimit, 679 }, 680 stream) 681 if err != nil { 682 return ps.rewriteError(ctx, err) 683 } 684 685 // If no concrete limit was requested, then all results are streamed in a single call to match 686 // older behavior. 687 if req.OptionalConcreteLimit == 0 { 688 return nil 689 } 690 691 // If no subjects were found, then we're done. 692 if countSubjectsFound == 0 || remainingConcreteLimit <= 0 { 693 return nil 694 } 695 } 696 } 697 698 func foundSubjectToResolvedSubject(ctx context.Context, foundSubject *dispatch.FoundSubject, caveatContext map[string]any, ds datastore.CaveatReader) (*v1.ResolvedSubject, error) { 699 var partialCaveat *v1.PartialCaveatInfo 700 permissionship := v1.LookupPermissionship_LOOKUP_PERMISSIONSHIP_HAS_PERMISSION 701 if foundSubject.GetCaveatExpression() != nil { 702 permissionship = v1.LookupPermissionship_LOOKUP_PERMISSIONSHIP_CONDITIONAL_PERMISSION 703 704 cr, err := cexpr.RunCaveatExpression(ctx, foundSubject.GetCaveatExpression(), caveatContext, ds, cexpr.RunCaveatExpressionNoDebugging) 705 if err != nil { 706 return nil, err 707 } 708 709 if cr.Value() { 710 permissionship = v1.LookupPermissionship_LOOKUP_PERMISSIONSHIP_HAS_PERMISSION 711 } else if cr.IsPartial() { 712 missingFields, _ := cr.MissingVarNames() 713 partialCaveat = &v1.PartialCaveatInfo{ 714 MissingRequiredContext: missingFields, 715 } 716 } else { 717 // Skip this found subject. 718 return nil, nil 719 } 720 } 721 722 return &v1.ResolvedSubject{ 723 SubjectObjectId: foundSubject.SubjectId, 724 Permissionship: permissionship, 725 PartialCaveatInfo: partialCaveat, 726 }, nil 727 } 728 729 func normalizeSubjectRelation(sub *v1.SubjectReference) string { 730 if sub.OptionalRelation == "" { 731 return graph.Ellipsis 732 } 733 return sub.OptionalRelation 734 } 735 736 func denormalizeSubjectRelation(relation string) string { 737 if relation == graph.Ellipsis { 738 return "" 739 } 740 return relation 741 } 742 743 func GetCaveatContext(ctx context.Context, caveatCtx *structpb.Struct, maxCaveatContextSize int) (map[string]any, error) { 744 var caveatContext map[string]any 745 if caveatCtx != nil { 746 if size := proto.Size(caveatCtx); maxCaveatContextSize > 0 && size > maxCaveatContextSize { 747 return nil, shared.RewriteError( 748 ctx, 749 status.Errorf( 750 codes.InvalidArgument, 751 "request caveat context should have less than %d bytes but had %d", 752 maxCaveatContextSize, 753 size, 754 ), 755 nil, 756 ) 757 } 758 caveatContext = caveatCtx.AsMap() 759 } 760 return caveatContext, nil 761 }