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  }