engines.*ExpandEngine.expandDirectAttribute   B
last analyzed

Complexity

Conditions 7

Size

Total Lines 62
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 40
nop 1
dl 0
loc 62
rs 7.52
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
package engines
2
3
import (
4
	"context"
5
	"errors"
6
7
	"google.golang.org/protobuf/types/known/anypb"
8
9
	"github.com/Permify/permify/internal/schema"
10
	"github.com/Permify/permify/internal/storage"
11
	storageContext "github.com/Permify/permify/internal/storage/context"
12
	"github.com/Permify/permify/pkg/database"
13
	base "github.com/Permify/permify/pkg/pb/base/v1"
14
	"github.com/Permify/permify/pkg/tuple"
15
)
16
17
// ExpandEngine - This comment is describing a type called ExpandEngine. The ExpandEngine type contains two fields: schemaReader,
18
// which is a storage.SchemaReader object, and relationshipReader, which is a storage.RelationshipReader object.
19
// The ExpandEngine type is used to expand permission scopes based on a given user ID and a set of permission requirements.
20
type ExpandEngine struct {
21
	// schemaReader is responsible for reading schema information
22
	schemaReader storage.SchemaReader
23
	// relationshipReader is responsible for reading relationship information
24
	dataReader storage.DataReader
25
}
26
27
// NewExpandEngine - This function creates a new instance of ExpandEngine by taking a SchemaReader and a RelationshipReader as
28
// parameters and returning a pointer to the created instance. The SchemaReader is used to read schema definitions, while the
29
// RelationshipReader is used to read relationship definitions.
30
func NewExpandEngine(sr storage.SchemaReader, rr storage.DataReader) *ExpandEngine {
31
	return &ExpandEngine{
32
		schemaReader: sr,
33
		dataReader:   rr,
34
	}
35
}
36
37
// Expand - This is the Run function of the ExpandEngine type, which takes a context, a PermissionExpandRequest,
38
// and returns a PermissionExpandResponse and an error.
39
// The function begins by starting a new OpenTelemetry span, with the name "permissions.expand.execute".
40
// It then checks if a snap token and schema version are included in the request. If not, it retrieves the head
41
// snapshot and head schema version, respectively, from the appropriate repository.
42
//
43
// Finally, the function calls the expand function of the ExpandEngine type with the context, PermissionExpandRequest,
44
// and false value, and returns the resulting PermissionExpandResponse and error. If there is an error, the span records
45
// the error and sets the status to indicate an error.
46
func (engine *ExpandEngine) Expand(ctx context.Context, request *base.PermissionExpandRequest) (response *base.PermissionExpandResponse, err error) {
47
	resp := engine.expand(ctx, request)
48
	if resp.Err != nil {
49
		return nil, resp.Err
50
	}
51
	return resp.Response, resp.Err
52
}
53
54
// ExpandResponse is a struct that contains the response and error returned from
55
// the expand function in the ExpandEngine. It is used to return the response and
56
// error together as a single object.
57
type ExpandResponse struct {
58
	Response *base.PermissionExpandResponse
59
	Err      error
60
}
61
62
// ExpandFunction represents a function that expands the schema and relationships
63
// of a request and sends the response through the provided channel.
64
type ExpandFunction func(ctx context.Context, expandChain chan<- ExpandResponse)
65
66
// ExpandCombiner represents a function that combines the results of multiple
67
// ExpandFunction calls into a single ExpandResponse.
68
type ExpandCombiner func(ctx context.Context, entity *base.Entity, permission string, arguments []*base.Argument, functions []ExpandFunction) ExpandResponse
69
70
// 'expand' is a method of ExpandEngine which takes a context and a PermissionExpandRequest,
71
// and returns an ExpandResponse. This function is the main entry point for expanding permissions.
72
func (engine *ExpandEngine) expand(ctx context.Context, request *base.PermissionExpandRequest) ExpandResponse {
73
	var fn ExpandFunction // Declare an ExpandFunction variable.
74
75
	// Read entity definition based on the entity type in the request.
76
	en, _, err := engine.schemaReader.ReadEntityDefinition(ctx, request.GetTenantId(), request.GetEntity().GetType(), request.GetMetadata().GetSchemaVersion())
77
	if err != nil {
78
		// If an error occurred while reading entity definition, return an ExpandResponse with the error.
79
		return ExpandResponse{Err: err}
80
	}
81
82
	var tor base.EntityDefinition_Reference
83
	// Get the type of reference by name in the entity definition.
84
	tor, _ = schema.GetTypeOfReferenceByNameInEntityDefinition(en, request.GetPermission())
85
86
	// Depending on the type of reference, execute different branches of code.
87
	switch tor {
88
	case base.EntityDefinition_REFERENCE_PERMISSION:
89
		// If the reference is a permission, get the permission by name from the entity definition.
90
		permission, err := schema.GetPermissionByNameInEntityDefinition(en, request.GetPermission())
91
		if err != nil {
92
			// If an error occurred while getting the permission, return an ExpandResponse with the error.
93
			return ExpandResponse{Err: err}
94
		}
95
96
		// Get the child of the permission.
97
		child := permission.GetChild()
98
		// If the child has a rewrite rule, use the 'expandRewrite' method.
99
		if child.GetRewrite() != nil {
100
			fn = engine.expandRewrite(ctx, request, child.GetRewrite())
101
		} else {
102
			// If the child doesn't have a rewrite rule, use the 'expandLeaf' method.
103
			fn = engine.expandLeaf(request, child.GetLeaf())
104
		}
105
	case base.EntityDefinition_REFERENCE_ATTRIBUTE:
106
		// If the reference is an attribute, use the 'expandDirectAttribute' method.
107
		fn = engine.expandDirectAttribute(request)
108
	case base.EntityDefinition_REFERENCE_RELATION:
109
		// If the reference is a relation, use the 'expandDirectRelation' method.
110
		fn = engine.expandDirectRelation(request)
111
	default:
112
		// If the reference is neither permission, attribute, nor relation, use the 'expandCall' method.
113
		fn = engine.expandDirectCall(request)
114
	}
115
116
	if fn == nil {
117
		// If no expand function was set, return an ExpandResponse with an error.
118
		return ExpandResponse{Err: errors.New(base.ErrorCode_ERROR_CODE_UNDEFINED_CHILD_KIND.String())}
119
	}
120
121
	// Execute the expand function with the root context.
122
	return expandRoot(ctx, fn)
123
}
124
125
// 'expandRewrite' is a method of ExpandEngine which takes a context, a PermissionExpandRequest,
126
// and a rewrite rule. It returns an ExpandFunction which represents the expansion of the rewrite rule.
127
func (engine *ExpandEngine) expandRewrite(ctx context.Context, request *base.PermissionExpandRequest, rewrite *base.Rewrite) ExpandFunction {
128
	// The rewrite rule can have different operations: UNION, INTERSECTION, EXCLUSION.
129
	// Depending on the operation type, it calls different methods.
130
	switch rewrite.GetRewriteOperation() {
131
	// If the operation is UNION, call the 'setChild' method with 'expandUnion' as the expand function.
132
	case *base.Rewrite_OPERATION_UNION.Enum():
133
		return engine.setChild(ctx, request, rewrite.GetChildren(), expandUnion)
134
135
	// If the operation is INTERSECTION, call the 'setChild' method with 'expandIntersection' as the expand function.
136
	case *base.Rewrite_OPERATION_INTERSECTION.Enum():
137
		return engine.setChild(ctx, request, rewrite.GetChildren(), expandIntersection)
138
139
	// If the operation is EXCLUSION, call the 'setChild' method with 'expandExclusion' as the expand function.
140
	case *base.Rewrite_OPERATION_EXCLUSION.Enum():
141
		return engine.setChild(ctx, request, rewrite.GetChildren(), expandExclusion)
142
143
	// If the operation is not any of the defined types, return an error.
144
	default:
145
		return expandFail(errors.New(base.ErrorCode_ERROR_CODE_UNDEFINED_CHILD_TYPE.String()))
146
	}
147
}
148
149
// 'expandLeaf' is a method of ExpandEngine which takes a PermissionExpandRequest and a leaf object.
150
// It returns an ExpandFunction, a function which performs the action associated with a given leaf type.
151
func (engine *ExpandEngine) expandLeaf(
152
	request *base.PermissionExpandRequest,
153
	leaf *base.Leaf,
154
) ExpandFunction {
155
	// The leaf object can have different types, each associated with a different expansion function.
156
	// Depending on the type of the leaf, different expansion functions are returned.
157
	switch op := leaf.GetType().(type) {
158
159
	// If the type of the leaf is TupleToUserSet, the method 'expandTupleToUserSet' is called.
160
	case *base.Leaf_TupleToUserSet:
161
		return engine.expandTupleToUserSet(request, op.TupleToUserSet)
162
163
	// If the type of the leaf is ComputedUserSet, the method 'expandComputedUserSet' is called.
164
	case *base.Leaf_ComputedUserSet:
165
		return engine.expandComputedUserSet(request, op.ComputedUserSet)
166
167
	// If the type of the leaf is ComputedAttribute, the method 'expandComputedAttribute' is called.
168
	case *base.Leaf_ComputedAttribute:
169
		return engine.expandComputedAttribute(request, op.ComputedAttribute)
170
171
	// If the type of the leaf is Call, the method 'expandCall' is called.
172
	case *base.Leaf_Call:
173
		return engine.expandCall(request, op.Call)
174
175
	// If the leaf type is none of the above, an error is returned.
176
	default:
177
		return expandFail(errors.New(base.ErrorCode_ERROR_CODE_UNDEFINED_CHILD_TYPE.String()))
178
	}
179
}
180
181
// 'setChild' is a method of the ExpandEngine struct, which aims to create and set an ExpandFunction
182
// for each child in the provided children array. These functions are derived from the type of child
183
// (either a rewrite or a leaf) and are passed to a provided 'combiner' function.
184
func (engine *ExpandEngine) setChild(
185
	ctx context.Context,
186
	request *base.PermissionExpandRequest,
187
	children []*base.Child,
188
	combiner ExpandCombiner,
189
) ExpandFunction {
190
	// Declare an array to hold ExpandFunctions.
191
	var functions []ExpandFunction
192
193
	// Iterate through each child in the provided array.
194
	for _, child := range children {
195
		// Based on the type of child, append the appropriate ExpandFunction to the functions array.
196
		switch child.GetType().(type) {
197
198
		// If the child is a 'rewrite' type, use the 'expandRewrite' function.
199
		case *base.Child_Rewrite:
200
			functions = append(functions, engine.expandRewrite(ctx, request, child.GetRewrite()))
201
202
		// If the child is a 'leaf' type, use the 'expandLeaf' function.
203
		case *base.Child_Leaf:
204
			functions = append(functions, engine.expandLeaf(request, child.GetLeaf()))
205
206
		// If the child type is not recognized, return an error.
207
		default:
208
			return expandFail(errors.New(base.ErrorCode_ERROR_CODE_UNDEFINED_CHILD_KIND.String()))
209
		}
210
	}
211
212
	// Return an ExpandFunction that will pass the prepared functions to the combiner when called.
213
	return func(ctx context.Context, resultChan chan<- ExpandResponse) {
214
		resultChan <- combiner(ctx, request.GetEntity(), request.GetPermission(), request.GetArguments(), functions)
215
	}
216
}
217
218
// expandDirectRelation Expands the target permission for direct subjects, i.e., those whose subject entity has the same type as the target
219
// entity type and whose subject relation is not an ellipsis. It queries the relationship store for relationships that
220
// match the target permission, and for each matching relationship, calls Expand on the corresponding subject entity
221
// and relation. If there are no matching relationships, it returns a leaf node of the expand tree containing the direct
222
// subjects. If there are matching relationships, it computes the union of the results of calling Expand on each matching
223
// relationship's subject entity and relation, and attaches the resulting expand nodes as children of a union node in
224
// the expand tree. Finally, it returns the top-level expand node.
225
func (engine *ExpandEngine) expandDirectRelation(request *base.PermissionExpandRequest) ExpandFunction {
226
	return func(ctx context.Context, expandChan chan<- ExpandResponse) {
227
		// Define a TupleFilter. This specifies which tuples we're interested in.
228
		// We want tuples that match the entity type and ID from the request, and have a specific relation.
229
		filter := &base.TupleFilter{
230
			Entity: &base.EntityFilter{
231
				Type: request.GetEntity().GetType(),
232
				Ids:  []string{request.GetEntity().GetId()},
233
			},
234
			Relation: request.GetPermission(),
235
		}
236
237
		// Use the filter to query for relationships in the given context.
238
		// NewContextualRelationships() creates a ContextualRelationships instance from tuples in the request.
239
		// QueryRelationships() then uses the filter to find and return matching relationships.
240
		cti, err := storageContext.NewContextualTuples(request.GetContext().GetTuples()...).QueryRelationships(filter, database.NewCursorPagination())
241
		if err != nil {
242
			// If an error occurred while querying, return a "denied" response and the error.
243
			expandChan <- expandFailResponse(err)
244
			return
245
		}
246
247
		// Query the relationships for the entity in the request.
248
		// TupleFilter helps in filtering out the relationships for a specific entity and a permission.
249
		var rit *database.TupleIterator
250
		rit, err = engine.dataReader.QueryRelationships(ctx, request.GetTenantId(), filter, request.GetMetadata().GetSnapToken(), database.NewCursorPagination())
251
		if err != nil {
252
			expandChan <- expandFailResponse(err)
253
			return
254
		}
255
256
		// Create a new UniqueTupleIterator from the two TupleIterators.
257
		// NewUniqueTupleIterator() ensures that the iterator only returns unique tuples.
258
		it := database.NewUniqueTupleIterator(rit, cti)
259
260
		foundedUserSets := database.NewSubjectCollection()
261
		foundedUsers := database.NewSubjectCollection()
262
263
		// it represents an iterator over some collection of subjects.
264
		for it.HasNext() {
265
			// Get the next tuple's subject.
266
			next, ok := it.GetNext()
267
			if !ok {
268
				break
269
			}
270
			subject := next.GetSubject()
271
272
			if tuple.IsDirectSubject(subject) || subject.GetRelation() == tuple.ELLIPSIS {
273
				foundedUsers.Add(subject)
274
			} else {
275
				foundedUserSets.Add(subject)
276
			}
277
		}
278
279
		// If there are no founded user sets, create and send an expand response.
280
		if len(foundedUserSets.GetSubjects()) == 0 {
281
			expandChan <- ExpandResponse{
282
				Response: &base.PermissionExpandResponse{
283
					Tree: &base.Expand{
284
						Entity:     request.GetEntity(),
285
						Permission: request.GetPermission(),
286
						Arguments:  request.GetArguments(),
287
						Node: &base.Expand_Leaf{
288
							Leaf: &base.ExpandLeaf{
289
								Type: &base.ExpandLeaf_Subjects{
290
									Subjects: &base.Subjects{
291
										Subjects: foundedUsers.GetSubjects(),
292
									},
293
								},
294
							},
295
						},
296
					},
297
				},
298
			}
299
			return
300
		}
301
302
		// Define a slice of ExpandFunction.
303
		var expandFunctions []ExpandFunction
304
305
		// Create an iterator for the foundedUserSets.
306
		si := foundedUserSets.CreateSubjectIterator()
307
308
		// Iterate over the foundedUserSets.
309
		for si.HasNext() {
310
			sub := si.GetNext()
311
			// For each subject, append a new function to the expandFunctions slice.
312
			expandFunctions = append(expandFunctions, func(ctx context.Context, resultChan chan<- ExpandResponse) {
313
				resultChan <- engine.expand(ctx, &base.PermissionExpandRequest{
314
					TenantId: request.GetTenantId(),
315
					Entity: &base.Entity{
316
						Type: sub.GetType(),
317
						Id:   sub.GetId(),
318
					},
319
					Permission: sub.GetRelation(),
320
					Metadata:   request.GetMetadata(),
321
					Context:    request.GetContext(),
322
				})
323
			})
324
		}
325
326
		// Use the expandUnion function to process the expandFunctions.
327
		result := expandUnion(ctx, request.GetEntity(), request.GetPermission(), request.GetArguments(), expandFunctions)
328
329
		// If an error occurred, send a failure response and return.
330
		if result.Err != nil {
331
			expandChan <- expandFailResponse(result.Err)
332
			return
333
		}
334
335
		// Get the Expand field from the response tree.
336
		expand := result.Response.GetTree().GetExpand()
337
338
		// Add a new child to the Expand field.
339
		expand.Children = append(expand.Children, &base.Expand{
340
			Entity:     request.GetEntity(),
341
			Permission: request.GetPermission(),
342
			Arguments:  request.GetArguments(),
343
			Node: &base.Expand_Leaf{
344
				Leaf: &base.ExpandLeaf{
345
					Type: &base.ExpandLeaf_Subjects{
346
						Subjects: &base.Subjects{
347
							Subjects: append(foundedUsers.GetSubjects(), foundedUserSets.GetSubjects()...),
348
						},
349
					},
350
				},
351
			},
352
		})
353
354
		expandChan <- result
355
	}
356
}
357
358
// expandTupleToUserSet is an ExpandFunction that retrieves relationships matching the given entity and relation filter,
359
// and expands each relationship into a set of users that have the corresponding tuple values. If the relationship subject
360
// contains an ellipsis (i.e. "..."), the function will recursively expand the computed user set for that entity. The
361
// exclusion parameter determines whether the resulting user set should be included or excluded from the final permission set.
362
// The function returns an ExpandFunction that sends the expanded user set to the provided channel.
363
//
364
// Parameters:
365
//   - ctx: context.Context for the request
366
//   - request: base.PermissionExpandRequest containing the request parameters
367
//   - ttu: base.TupleToUserSet containing the tuple filter and computed user set
368
//   - exclusion: bool indicating whether to exclude or include the resulting user set in the final permission set
369
//
370
// Returns:
371
//   - ExpandFunction that sends the expanded user set to the provided channel
372
func (engine *ExpandEngine) expandTupleToUserSet(
373
	request *base.PermissionExpandRequest,
374
	ttu *base.TupleToUserSet,
375
) ExpandFunction {
376
	return func(ctx context.Context, expandChan chan<- ExpandResponse) {
377
		filter := &base.TupleFilter{
378
			Entity: &base.EntityFilter{
379
				Type: request.GetEntity().GetType(),
380
				Ids:  []string{request.GetEntity().GetId()},
381
			},
382
			Relation: ttu.GetTupleSet().GetRelation(),
383
		}
384
385
		// Use the filter to query for relationships in the given context.
386
		// NewContextualRelationships() creates a ContextualRelationships instance from tuples in the request.
387
		// QueryRelationships() then uses the filter to find and return matching relationships.
388
		cti, err := storageContext.NewContextualTuples(request.GetContext().GetTuples()...).QueryRelationships(filter, database.NewCursorPagination())
389
		if err != nil {
390
			expandChan <- expandFailResponse(err)
391
			return
392
		}
393
394
		// Use the filter to query for relationships in the database.
395
		// relationshipReader.QueryRelationships() uses the filter to find and return matching relationships.
396
		rit, err := engine.dataReader.QueryRelationships(ctx, request.GetTenantId(), filter, request.GetMetadata().GetSnapToken(), database.NewCursorPagination())
397
		if err != nil {
398
			expandChan <- expandFailResponse(err)
399
			return
400
		}
401
402
		// Create a new UniqueTupleIterator from the two TupleIterators.
403
		// NewUniqueTupleIterator() ensures that the iterator only returns unique tuples.
404
		it := database.NewUniqueTupleIterator(rit, cti)
405
406
		var expandFunctions []ExpandFunction
407
		for it.HasNext() {
408
			// Get the next tuple's subject.
409
			next, ok := it.GetNext()
410
			if !ok {
411
				break
412
			}
413
			subject := next.GetSubject()
414
415
			expandFunctions = append(expandFunctions, engine.expandComputedUserSet(&base.PermissionExpandRequest{
416
				TenantId: request.GetTenantId(),
417
				Entity: &base.Entity{
418
					Type: subject.GetType(),
419
					Id:   subject.GetId(),
420
				},
421
				Permission: subject.GetRelation(),
422
				Metadata:   request.GetMetadata(),
423
				Context:    request.GetContext(),
424
			}, ttu.GetComputed()))
425
		}
426
427
		expandChan <- expandUnion(
428
			ctx,
429
			request.GetEntity(),
430
			ttu.GetTupleSet().GetRelation(),
431
			request.GetArguments(),
432
			expandFunctions,
433
		)
434
	}
435
}
436
437
// expandComputedUserSet is an ExpandFunction that expands the computed user set for the given entity and relation filter.
438
// The function first retrieves the set of tuples that match the filter, and then expands each tuple into a set of users based
439
// on the values in the computed user set. The exclusion parameter determines whether the resulting user set should be included
440
// or excluded from the final permission set. The function returns an ExpandFunction that sends the expanded user set to the
441
// provided channel.
442
//
443
// Parameters:
444
//   - ctx: context.Context for the request
445
//   - request: base.PermissionExpandRequest containing the request parameters
446
//   - cu: base.ComputedUserSet containing the computed user set to be expanded
447
//
448
// Returns:
449
//   - ExpandFunction that sends the expanded user set to the provided channel
450
func (engine *ExpandEngine) expandComputedUserSet(
451
	request *base.PermissionExpandRequest,
452
	cu *base.ComputedUserSet,
453
) ExpandFunction {
454
	return func(ctx context.Context, resultChan chan<- ExpandResponse) {
455
		resultChan <- engine.expand(ctx, &base.PermissionExpandRequest{
456
			TenantId: request.GetTenantId(),
457
			Entity: &base.Entity{
458
				Type: request.GetEntity().GetType(),
459
				Id:   request.GetEntity().GetId(),
460
			},
461
			Permission: cu.GetRelation(),
462
			Metadata:   request.GetMetadata(),
463
			Context:    request.GetContext(),
464
		})
465
	}
466
}
467
468
// The function 'expandDirectAttribute' is a method on the ExpandEngine struct.
469
// It returns an ExpandFunction which is a type alias for a function that takes a context and an expand response channel as parameters.
470
func (engine *ExpandEngine) expandDirectAttribute(
471
	request *base.PermissionExpandRequest, // the request object containing information necessary for the expansion
472
) ExpandFunction { // returns an ExpandFunction
473
	return func(ctx context.Context, expandChan chan<- ExpandResponse) { // defining the returned function
474
475
		var err error // variable to store any error occurred during the process
476
477
		// Defining a filter to get the attribute based on the entity type and ID and the permission requested.
478
		filter := &base.AttributeFilter{
479
			Entity: &base.EntityFilter{
480
				Type: request.GetEntity().GetType(),
481
				Ids:  []string{request.GetEntity().GetId()},
482
			},
483
			Attributes: []string{request.GetPermission()},
484
		}
485
486
		var val *base.Attribute // variable to hold the attribute value
487
488
		// Attempt to get the attribute using the defined filter.
489
		val, err = storageContext.NewContextualAttributes(request.GetContext().GetAttributes()...).QuerySingleAttribute(filter)
490
		// If there's an error in getting the attribute, send a failure response through the channel and return from the function.
491
		if err != nil {
492
			expandChan <- expandFailResponse(err)
493
			return
494
		}
495
496
		// If no attribute was found, attempt to query it directly from the data reader.
497
		if val == nil {
498
			val, err = engine.dataReader.QuerySingleAttribute(ctx, request.GetTenantId(), filter, request.GetMetadata().GetSnapToken())
499
			if err != nil {
500
				expandChan <- expandFailResponse(err)
501
				return
502
			}
503
		}
504
505
		// If the attribute is still nil, create a new attribute with a false value.
506
		if val == nil {
507
			val = &base.Attribute{
508
				Entity: &base.Entity{
509
					Type: request.GetEntity().GetType(),
510
					Id:   request.GetEntity().GetId(),
511
				},
512
				Attribute: request.GetPermission(),
513
			}
514
			val.Value, err = anypb.New(&base.BooleanValue{Data: false})
515
			if err != nil {
516
				expandChan <- expandFailResponse(err)
517
				return
518
			}
519
		}
520
521
		// Send an ExpandResponse containing the permission expansion response with the attribute value through the channel.
522
		expandChan <- ExpandResponse{
523
			Response: &base.PermissionExpandResponse{
524
				Tree: &base.Expand{
525
					Entity:     request.GetEntity(),
526
					Permission: request.GetPermission(),
527
					Arguments:  request.GetArguments(),
528
					Node: &base.Expand_Leaf{
529
						Leaf: &base.ExpandLeaf{
530
							Type: &base.ExpandLeaf_Value{
531
								Value: val.GetValue(),
532
							},
533
						},
534
					},
535
				},
536
			},
537
		}
538
	}
539
}
540
541
// expandCall returns an ExpandFunction for the given request and call.
542
// The returned function, when executed, sends the expanded permission result
543
// to the provided result channel.
544
func (engine *ExpandEngine) expandCall(
545
	request *base.PermissionExpandRequest,
546
	call *base.Call,
547
) ExpandFunction {
548
	return func(ctx context.Context, resultChan chan<- ExpandResponse) {
549
		resultChan <- engine.expand(ctx, &base.PermissionExpandRequest{
550
			TenantId: request.GetTenantId(),
551
			Entity: &base.Entity{
552
				Type: request.GetEntity().GetType(),
553
				Id:   request.GetEntity().GetId(),
554
			},
555
			Permission: call.GetRuleName(),
556
			Metadata:   request.GetMetadata(),
557
			Context:    request.GetContext(),
558
			Arguments:  call.GetArguments(),
559
		})
560
	}
561
}
562
563
// The function 'expandCall' is a method on the ExpandEngine struct.
564
// It takes a PermissionExpandRequest and a Call as parameters and returns an ExpandFunction.
565
func (engine *ExpandEngine) expandDirectCall(
566
	request *base.PermissionExpandRequest, // The request object containing information necessary for the expansion.
567
) ExpandFunction { // The function returns an ExpandFunction.
568
	return func(ctx context.Context, expandChan chan<- ExpandResponse) { // defining the returned function.
569
570
		var err error // variable to store any error occurred during the process.
571
572
		var ru *base.RuleDefinition // variable to hold the rule definition.
573
574
		// Read the rule definition based on the rule name in the call.
575
		ru, _, err = engine.schemaReader.ReadRuleDefinition(ctx, request.GetTenantId(), request.GetPermission(), request.GetMetadata().GetSchemaVersion())
576
		if err != nil {
577
			// If there's an error in reading the rule definition, send a failure response through the channel and return from the function.
578
			expandChan <- expandFailResponse(err)
579
			return
580
		}
581
582
		// Prepare the arguments map to be used in the CEL evaluation
583
		arguments := make(map[string]*anypb.Any)
584
585
		// Prepare a slice for attributes
586
		attributes := make([]string, 0)
587
588
		// For each argument in the call...
589
		for _, arg := range request.GetArguments() {
590
			switch actualArg := arg.Type.(type) { // Switch on the type of the argument.
591
			case *base.Argument_ComputedAttribute: // If the argument is a ComputedAttribute...
592
				attrName := actualArg.ComputedAttribute.GetName() // get the name of the attribute.
593
594
				// Get the empty value for the attribute type.
595
				emptyValue, err := getEmptyProtoValueForType(ru.GetArguments()[attrName])
596
				if err != nil {
597
					expandChan <- expandFailResponse(errors.New(base.ErrorCode_ERROR_CODE_TYPE_CONVERSATION.String()))
598
					return
599
				}
600
601
				// Set the empty value in the arguments map.
602
				arguments[attrName] = emptyValue
603
604
				// Append the attribute name to the attributes slice.
605
				attributes = append(attributes, attrName)
606
			default:
607
				// If the argument type is unknown, send a failure response and return from the function.
608
				expandChan <- expandFailResponse(errors.New(base.ErrorCode_ERROR_CODE_INTERNAL.String()))
609
				return
610
			}
611
		}
612
613
		// If there are any attributes to query...
614
		if len(attributes) > 0 {
615
			// Create an AttributeFilter for the attributes.
616
			filter := &base.AttributeFilter{
617
				Entity: &base.EntityFilter{
618
					Type: request.GetEntity().GetType(),
619
					Ids:  []string{request.GetEntity().GetId()},
620
				},
621
				Attributes: attributes,
622
			}
623
624
			// Query the attributes from the data reader.
625
			ait, err := engine.dataReader.QueryAttributes(ctx, request.GetTenantId(), filter, request.GetMetadata().GetSnapToken(), database.NewCursorPagination())
626
			if err != nil {
627
				expandChan <- expandFailResponse(err)
628
				return
629
			}
630
631
			// Query the attributes from the context.
632
			cta, err := storageContext.NewContextualAttributes(request.GetContext().GetAttributes()...).QueryAttributes(filter, database.NewCursorPagination())
633
			if err != nil {
634
				expandChan <- expandFailResponse(err)
635
				return
636
			}
637
638
			// Create an iterator for the unique attributes.
639
			it := database.NewUniqueAttributeIterator(ait, cta)
640
641
			// For each unique attribute...
642
			for it.HasNext() {
643
				// Get the next attribute and its value.
644
				next, ok := it.GetNext()
645
				if !ok {
646
					break
647
				}
648
				// Set the attribute's value in the arguments map.
649
				arguments[next.GetAttribute()] = next.GetValue()
650
			}
651
		}
652
653
		// Send an ExpandResponse containing the permission expansion response with the computed arguments through the channel.
654
		expandChan <- ExpandResponse{
655
			Response: &base.PermissionExpandResponse{
656
				Tree: &base.Expand{
657
					Entity:     request.GetEntity(),
658
					Permission: request.GetPermission(),
659
					Arguments:  request.GetArguments(),
660
					Node: &base.Expand_Leaf{
661
						Leaf: &base.ExpandLeaf{
662
							Type: &base.ExpandLeaf_Values{
663
								Values: &base.Values{
664
									Values: arguments,
665
								},
666
							},
667
						},
668
					},
669
				},
670
			},
671
		}
672
	}
673
}
674
675
// 'expandComputedAttribute' is a method on the ExpandEngine struct.
676
// It takes a PermissionExpandRequest and a ComputedAttribute as parameters and returns an ExpandFunction.
677
func (engine *ExpandEngine) expandComputedAttribute(
678
	request *base.PermissionExpandRequest, // The request object containing necessary information for the expansion.
679
	ca *base.ComputedAttribute, // The computed attribute object that has the name of the attribute to be computed.
680
) ExpandFunction { // The function returns an ExpandFunction.
681
	return func(ctx context.Context, resultChan chan<- ExpandResponse) { // defining the returned function.
682
683
		// The returned function sends the result of an expansion to the result channel.
684
		// The expansion is performed by calling the 'expand' method of the engine with a new PermissionExpandRequest.
685
		// The new request is constructed using the tenant ID, entity, computed attribute name, metadata, and context from the original request.
686
		resultChan <- engine.expand(ctx, &base.PermissionExpandRequest{
687
			TenantId: request.GetTenantId(), // Tenant ID from the original request.
688
			Entity: &base.Entity{
689
				Type: request.GetEntity().GetType(), // Entity type from the original request.
690
				Id:   request.GetEntity().GetId(),   // Entity ID from the original request.
691
			},
692
			Permission: ca.GetName(),          // The name of the computed attribute.
693
			Metadata:   request.GetMetadata(), // Metadata from the original request.
694
			Context:    request.GetContext(),  // Context from the original request.
695
		})
696
	}
697
}
698
699
// 'expandOperation' is a function that takes a context, an entity, permission string,
700
// a slice of arguments, slice of ExpandFunctions, and an operation of type base.ExpandTreeNode_Operation.
701
// It returns an ExpandResponse.
702
func expandOperation(
703
	ctx context.Context, // The context of this operation, which may carry deadlines, cancellation signals, etc.
704
	entity *base.Entity, // The entity on which the operation will be performed.
705
	permission string, // The permission string required for the operation.
706
	arguments []*base.Argument, // A slice of arguments required for the operation.
707
	functions []ExpandFunction, // A slice of functions that will be used to expand the operation.
708
	op base.ExpandTreeNode_Operation, // The operation to be performed.
709
) ExpandResponse { // The function returns an ExpandResponse.
710
711
	// Initialize an empty slice of base.Expand type.
712
	children := make([]*base.Expand, 0, len(functions))
713
714
	// If there are no functions, return an ExpandResponse with a base.PermissionExpandResponse.
715
	// This response includes an empty base.Expand with the entity, permission, arguments,
716
	// and an ExpandTreeNode with the given operation.
717
	if len(functions) == 0 {
718
		return ExpandResponse{
719
			Response: &base.PermissionExpandResponse{
720
				Tree: &base.Expand{
721
					Entity:     entity,
722
					Permission: permission,
723
					Arguments:  arguments,
724
					Node: &base.Expand_Expand{
725
						Expand: &base.ExpandTreeNode{
726
							Operation: op,
727
							Children:  children,
728
						},
729
					},
730
				},
731
			},
732
		}
733
	}
734
735
	// Create a new context that can be cancelled.
736
	c, cancel := context.WithCancel(ctx)
737
	// Defer the cancellation, so that it will be called when the function exits.
738
	defer func() {
739
		cancel()
740
	}()
741
742
	// Initialize an empty slice of channels which will receive ExpandResponses.
743
	results := make([]chan ExpandResponse, 0, len(functions))
744
	// For each function, create a channel and add it to the results slice.
745
	// Start a goroutine with the function and pass the cancelable context and the channel.
746
	for _, fn := range functions {
747
		fc := make(chan ExpandResponse, 1)
748
		results = append(results, fc)
749
		go fn(c, fc)
750
	}
751
752
	// For each result channel, wait for a response or for the context to be cancelled.
753
	for _, result := range results {
754
		select {
755
		case resp := <-result:
756
			// If the response contains an error, return an error response.
757
			if resp.Err != nil {
758
				return expandFailResponse(resp.Err)
759
			}
760
			// If the response does not contain an error, append the tree of the response to the children slice.
761
			children = append(children, resp.Response.GetTree())
762
		case <-ctx.Done():
763
			// If the context is cancelled, return an error response.
764
			return expandFailResponse(errors.New(base.ErrorCode_ERROR_CODE_CANCELLED.String()))
765
		}
766
	}
767
768
	// Return an ExpandResponse with a base.PermissionExpandResponse.
769
	// This response includes an base.Expand with the entity, permission, arguments,
770
	// and an ExpandTreeNode with the given operation and the children slice.
771
	return ExpandResponse{
772
		Response: &base.PermissionExpandResponse{
773
			Tree: &base.Expand{
774
				Entity:     entity,
775
				Permission: permission,
776
				Arguments:  arguments,
777
				Node: &base.Expand_Expand{
778
					Expand: &base.ExpandTreeNode{
779
						Operation: op,
780
						Children:  children,
781
					},
782
				},
783
			},
784
		},
785
	}
786
}
787
788
// expandRoot is a helper function that executes an ExpandFunction and returns the resulting ExpandResponse. The function
789
// creates a goroutine for the ExpandFunction to allow for cancellation and concurrent execution. If the ExpandFunction
790
// returns an error, the function returns an ExpandResponse with the error. If the context is cancelled before the
791
// ExpandFunction completes, the function returns an ExpandResponse with an error indicating that the operation was cancelled.
792
//
793
// Parameters:
794
//   - ctx: context.Context for the request
795
//   - fn: ExpandFunction to execute
796
//
797
// Returns:
798
//   - ExpandResponse containing the expanded user set or an error if the ExpandFunction failed
799
func expandRoot(ctx context.Context, fn ExpandFunction) ExpandResponse {
800
	res := make(chan ExpandResponse, 1)
801
	go fn(ctx, res)
802
803
	select {
804
	case result := <-res:
805
		if result.Err == nil {
806
			return result
807
		}
808
		return expandFailResponse(result.Err)
809
	case <-ctx.Done():
810
		return expandFailResponse(errors.New(base.ErrorCode_ERROR_CODE_CANCELLED.String()))
811
	}
812
}
813
814
// expandUnion is a helper function that executes multiple ExpandFunctions in parallel and returns an ExpandResponse containing
815
// the union of their expanded user sets. The function delegates to expandOperation with the UNION operation. If any of the
816
// ExpandFunctions return an error, the function returns an ExpandResponse with the error. If the context is cancelled before
817
// all ExpandFunctions complete, the function returns an ExpandResponse with an error indicating that the operation was cancelled.
818
//
819
// Parameters:
820
//   - ctx: context.Context for the request
821
//   - functions: slice of ExpandFunctions to execute in parallel
822
//
823
// Returns:
824
//   - ExpandResponse containing the union of the expanded user sets, or an error if any of the ExpandFunctions failed
825
func expandUnion(
826
	ctx context.Context,
827
	entity *base.Entity,
828
	permission string,
829
	arguments []*base.Argument,
830
	functions []ExpandFunction,
831
) ExpandResponse {
832
	return expandOperation(ctx, entity, permission, arguments, functions, base.ExpandTreeNode_OPERATION_UNION)
833
}
834
835
// expandIntersection is a helper function that executes multiple ExpandFunctions in parallel and returns an ExpandResponse
836
// containing the intersection of their expanded user sets. The function delegates to expandOperation with the INTERSECTION
837
// operation. If any of the ExpandFunctions return an error, the function returns an ExpandResponse with the error. If the
838
// context is cancelled before all ExpandFunctions complete, the function returns an ExpandResponse with an error indicating
839
// that the operation was cancelled.
840
//
841
// Parameters:
842
//   - ctx: context.Context for the request
843
//   - functions: slice of ExpandFunctions to execute in parallel
844
//
845
// Returns:
846
//   - ExpandResponse containing the intersection of the expanded user sets, or an error if any of the ExpandFunctions failed
847
func expandIntersection(
848
	ctx context.Context,
849
	entity *base.Entity,
850
	permission string,
851
	arguments []*base.Argument,
852
	functions []ExpandFunction,
853
) ExpandResponse {
854
	return expandOperation(ctx, entity, permission, arguments, functions, base.ExpandTreeNode_OPERATION_INTERSECTION)
855
}
856
857
// expandExclusion is a helper function that executes multiple ExpandFunctions in parallel and returns an ExpandResponse
858
// containing the expanded user set that results from the exclusion operation. The function delegates to expandOperation
859
// with the EXCLUSION operation. If any of the ExpandFunctions return an error, the function returns an ExpandResponse
860
// with the error. If the context is cancelled before all ExpandFunctions complete, the function returns an ExpandResponse
861
// with an error indicating that the operation was cancelled.
862
//
863
// Parameters:
864
//   - ctx: context.Context for the request
865
//   - target: EntityAndRelation containing the entity and its relation for which the exclusion is calculated
866
//   - functions: slice of ExpandFunctions to execute in parallel
867
//
868
// Returns:
869
//   - ExpandResponse containing the expanded user sets from the exclusion operation, or an error if any of the ExpandFunctions failed
870
func expandExclusion(
871
	ctx context.Context,
872
	entity *base.Entity,
873
	permission string,
874
	arguments []*base.Argument,
875
	functions []ExpandFunction,
876
) ExpandResponse {
877
	return expandOperation(ctx, entity, permission, arguments, functions, base.ExpandTreeNode_OPERATION_EXCLUSION)
878
}
879
880
// expandFail is a helper function that returns an ExpandFunction that immediately sends an ExpandResponse with the specified error
881
// to the provided channel. The resulting ExpandResponse contains an empty ExpandTreeNode and the specified error.
882
//
883
// Parameters:
884
//   - err: error to include in the resulting ExpandResponse
885
//
886
// Returns:
887
//   - ExpandFunction that sends an ExpandResponse with the specified error to the provided channel
888
func expandFail(err error) ExpandFunction {
889
	return func(ctx context.Context, expandChan chan<- ExpandResponse) {
890
		expandChan <- expandFailResponse(err)
891
	}
892
}
893
894
// expandFailResponse is a helper function that returns an ExpandResponse with the specified error and an empty ExpandTreeNode.
895
//
896
// Parameters:
897
//   - err: error to include in the resulting ExpandResponse
898
//
899
// Returns:
900
//   - ExpandResponse with the specified error and an empty ExpandTreeNode
901
func expandFailResponse(err error) ExpandResponse {
902
	return ExpandResponse{
903
		Response: &base.PermissionExpandResponse{
904
			Tree: &base.Expand{},
905
		},
906
		Err: err,
907
	}
908
}
909