Passed
Push — master ( 64abf6...0f1b55 )
by Tolga
01:31 queued 15s
created

engines.*ExpandEngine.expandComputedUserSet   A

Complexity

Conditions 2

Size

Total Lines 14
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 13
nop 2
dl 0
loc 14
rs 9.75
c 0
b 0
f 0
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
491
		// If there's an error in getting the attribute, send a failure response through the channel and return from the function.
492
		if err != nil {
493
			expandChan <- expandFailResponse(err)
494
			return
495
		}
496
497
		// If no attribute was found, attempt to query it directly from the data reader.
498
		if val == nil {
499
			val, err = engine.dataReader.QuerySingleAttribute(ctx, request.GetTenantId(), filter, request.GetMetadata().GetSnapToken())
500
			if err != nil {
501
				expandChan <- expandFailResponse(err)
502
				return
503
			}
504
		}
505
506
		// If the attribute is still nil, create a new attribute with a false value.
507
		if val == nil {
508
			val = &base.Attribute{
509
				Entity: &base.Entity{
510
					Type: request.GetEntity().GetType(),
511
					Id:   request.GetEntity().GetId(),
512
				},
513
				Attribute: request.GetPermission(),
514
			}
515
			val.Value, err = anypb.New(&base.BooleanValue{Data: false})
516
			if err != nil {
517
				expandChan <- expandFailResponse(err)
518
				return
519
			}
520
		}
521
522
		// Send an ExpandResponse containing the permission expansion response with the attribute value through the channel.
523
		expandChan <- ExpandResponse{
524
			Response: &base.PermissionExpandResponse{
525
				Tree: &base.Expand{
526
					Entity:     request.GetEntity(),
527
					Permission: request.GetPermission(),
528
					Arguments:  request.GetArguments(),
529
					Node: &base.Expand_Leaf{
530
						Leaf: &base.ExpandLeaf{
531
							Type: &base.ExpandLeaf_Value{
532
								Value: val.GetValue(),
533
							},
534
						},
535
					},
536
				},
537
			},
538
		}
539
	}
540
}
541
542
// expandCall returns an ExpandFunction for the given request and call.
543
// The returned function, when executed, sends the expanded permission result
544
// to the provided result channel.
545
func (engine *ExpandEngine) expandCall(
546
	request *base.PermissionExpandRequest,
547
	call *base.Call,
548
) ExpandFunction {
549
	return func(ctx context.Context, resultChan chan<- ExpandResponse) {
550
		resultChan <- engine.expand(ctx, &base.PermissionExpandRequest{
551
			TenantId: request.GetTenantId(),
552
			Entity: &base.Entity{
553
				Type: request.GetEntity().GetType(),
554
				Id:   request.GetEntity().GetId(),
555
			},
556
			Permission: call.GetRuleName(),
557
			Metadata:   request.GetMetadata(),
558
			Context:    request.GetContext(),
559
			Arguments:  call.GetArguments(),
560
		})
561
	}
562
}
563
564
// The function 'expandCall' is a method on the ExpandEngine struct.
565
// It takes a PermissionExpandRequest and a Call as parameters and returns an ExpandFunction.
566
func (engine *ExpandEngine) expandDirectCall(
567
	request *base.PermissionExpandRequest, // The request object containing information necessary for the expansion.
568
) ExpandFunction { // The function returns an ExpandFunction.
569
	return func(ctx context.Context, expandChan chan<- ExpandResponse) { // defining the returned function.
570
571
		var err error // variable to store any error occurred during the process.
572
573
		var ru *base.RuleDefinition // variable to hold the rule definition.
574
575
		// Read the rule definition based on the rule name in the call.
576
		ru, _, err = engine.schemaReader.ReadRuleDefinition(ctx, request.GetTenantId(), request.GetPermission(), request.GetMetadata().GetSchemaVersion())
577
		if err != nil {
578
			// If there's an error in reading the rule definition, send a failure response through the channel and return from the function.
579
			expandChan <- expandFailResponse(err)
580
			return
581
		}
582
583
		// Prepare the arguments map to be used in the CEL evaluation
584
		arguments := make(map[string]*anypb.Any)
585
586
		// Prepare a slice for attributes
587
		attributes := make([]string, 0)
588
589
		// For each argument in the call...
590
		for _, arg := range request.GetArguments() {
591
			switch actualArg := arg.Type.(type) { // Switch on the type of the argument.
592
			case *base.Argument_ComputedAttribute: // If the argument is a ComputedAttribute...
593
				attrName := actualArg.ComputedAttribute.GetName() // get the name of the attribute.
594
595
				// Get the empty value for the attribute type.
596
				emptyValue, err := getEmptyProtoValueForType(ru.GetArguments()[attrName])
597
				if err != nil {
598
					expandChan <- expandFailResponse(errors.New(base.ErrorCode_ERROR_CODE_TYPE_CONVERSATION.String()))
599
					return
600
				}
601
602
				// Set the empty value in the arguments map.
603
				arguments[attrName] = emptyValue
604
605
				// Append the attribute name to the attributes slice.
606
				attributes = append(attributes, attrName)
607
			default:
608
				// If the argument type is unknown, send a failure response and return from the function.
609
				expandChan <- expandFailResponse(errors.New(base.ErrorCode_ERROR_CODE_INTERNAL.String()))
610
				return
611
			}
612
		}
613
614
		// If there are any attributes to query...
615
		if len(attributes) > 0 {
616
			// Create an AttributeFilter for the attributes.
617
			filter := &base.AttributeFilter{
618
				Entity: &base.EntityFilter{
619
					Type: request.GetEntity().GetType(),
620
					Ids:  []string{request.GetEntity().GetId()},
621
				},
622
				Attributes: attributes,
623
			}
624
625
			// Query the attributes from the data reader.
626
			ait, err := engine.dataReader.QueryAttributes(ctx, request.GetTenantId(), filter, request.GetMetadata().GetSnapToken(), database.NewCursorPagination())
627
			if err != nil {
628
				expandChan <- expandFailResponse(err)
629
				return
630
			}
631
632
			// Query the attributes from the context.
633
			cta, err := storageContext.NewContextualAttributes(request.GetContext().GetAttributes()...).QueryAttributes(filter, database.NewCursorPagination())
634
			if err != nil {
635
				expandChan <- expandFailResponse(err)
636
				return
637
			}
638
639
			// Create an iterator for the unique attributes.
640
			it := database.NewUniqueAttributeIterator(ait, cta)
641
642
			// For each unique attribute...
643
			for it.HasNext() {
644
				// Get the next attribute and its value.
645
				next, ok := it.GetNext()
646
				if !ok {
647
					break
648
				}
649
				// Set the attribute's value in the arguments map.
650
				arguments[next.GetAttribute()] = next.GetValue()
651
			}
652
		}
653
654
		// Send an ExpandResponse containing the permission expansion response with the computed arguments through the channel.
655
		expandChan <- ExpandResponse{
656
			Response: &base.PermissionExpandResponse{
657
				Tree: &base.Expand{
658
					Entity:     request.GetEntity(),
659
					Permission: request.GetPermission(),
660
					Arguments:  request.GetArguments(),
661
					Node: &base.Expand_Leaf{
662
						Leaf: &base.ExpandLeaf{
663
							Type: &base.ExpandLeaf_Values{
664
								Values: &base.Values{
665
									Values: arguments,
666
								},
667
							},
668
						},
669
					},
670
				},
671
			},
672
		}
673
	}
674
}
675
676
// 'expandComputedAttribute' is a method on the ExpandEngine struct.
677
// It takes a PermissionExpandRequest and a ComputedAttribute as parameters and returns an ExpandFunction.
678
func (engine *ExpandEngine) expandComputedAttribute(
679
	request *base.PermissionExpandRequest, // The request object containing necessary information for the expansion.
680
	ca *base.ComputedAttribute, // The computed attribute object that has the name of the attribute to be computed.
681
) ExpandFunction { // The function returns an ExpandFunction.
682
	return func(ctx context.Context, resultChan chan<- ExpandResponse) { // defining the returned function.
683
684
		// The returned function sends the result of an expansion to the result channel.
685
		// The expansion is performed by calling the 'expand' method of the engine with a new PermissionExpandRequest.
686
		// The new request is constructed using the tenant ID, entity, computed attribute name, metadata, and context from the original request.
687
		resultChan <- engine.expand(ctx, &base.PermissionExpandRequest{
688
			TenantId: request.GetTenantId(), // Tenant ID from the original request.
689
			Entity: &base.Entity{
690
				Type: request.GetEntity().GetType(), // Entity type from the original request.
691
				Id:   request.GetEntity().GetId(),   // Entity ID from the original request.
692
			},
693
			Permission: ca.GetName(),          // The name of the computed attribute.
694
			Metadata:   request.GetMetadata(), // Metadata from the original request.
695
			Context:    request.GetContext(),  // Context from the original request.
696
		})
697
	}
698
}
699
700
// 'expandOperation' is a function that takes a context, an entity, permission string,
701
// a slice of arguments, slice of ExpandFunctions, and an operation of type base.ExpandTreeNode_Operation.
702
// It returns an ExpandResponse.
703
func expandOperation(
704
	ctx context.Context, // The context of this operation, which may carry deadlines, cancellation signals, etc.
705
	entity *base.Entity, // The entity on which the operation will be performed.
706
	permission string, // The permission string required for the operation.
707
	arguments []*base.Argument, // A slice of arguments required for the operation.
708
	functions []ExpandFunction, // A slice of functions that will be used to expand the operation.
709
	op base.ExpandTreeNode_Operation, // The operation to be performed.
710
) ExpandResponse { // The function returns an ExpandResponse.
711
712
	// Initialize an empty slice of base.Expand type.
713
	children := make([]*base.Expand, 0, len(functions))
714
715
	// If there are no functions, return an ExpandResponse with a base.PermissionExpandResponse.
716
	// This response includes an empty base.Expand with the entity, permission, arguments,
717
	// and an ExpandTreeNode with the given operation.
718
	if len(functions) == 0 {
719
		return ExpandResponse{
720
			Response: &base.PermissionExpandResponse{
721
				Tree: &base.Expand{
722
					Entity:     entity,
723
					Permission: permission,
724
					Arguments:  arguments,
725
					Node: &base.Expand_Expand{
726
						Expand: &base.ExpandTreeNode{
727
							Operation: op,
728
							Children:  children,
729
						},
730
					},
731
				},
732
			},
733
		}
734
	}
735
736
	// Create a new context that can be cancelled.
737
	c, cancel := context.WithCancel(ctx)
738
	// Defer the cancellation, so that it will be called when the function exits.
739
	defer func() {
740
		cancel()
741
	}()
742
743
	// Initialize an empty slice of channels which will receive ExpandResponses.
744
	results := make([]chan ExpandResponse, 0, len(functions))
745
	// For each function, create a channel and add it to the results slice.
746
	// Start a goroutine with the function and pass the cancelable context and the channel.
747
	for _, fn := range functions {
748
		fc := make(chan ExpandResponse, 1)
749
		results = append(results, fc)
750
		go fn(c, fc)
751
	}
752
753
	// For each result channel, wait for a response or for the context to be cancelled.
754
	for _, result := range results {
755
		select {
756
		case resp := <-result:
757
			// If the response contains an error, return an error response.
758
			if resp.Err != nil {
759
				return expandFailResponse(resp.Err)
760
			}
761
			// If the response does not contain an error, append the tree of the response to the children slice.
762
			children = append(children, resp.Response.GetTree())
763
		case <-ctx.Done():
764
			// If the context is cancelled, return an error response.
765
			return expandFailResponse(errors.New(base.ErrorCode_ERROR_CODE_CANCELLED.String()))
766
		}
767
	}
768
769
	// Return an ExpandResponse with a base.PermissionExpandResponse.
770
	// This response includes an base.Expand with the entity, permission, arguments,
771
	// and an ExpandTreeNode with the given operation and the children slice.
772
	return ExpandResponse{
773
		Response: &base.PermissionExpandResponse{
774
			Tree: &base.Expand{
775
				Entity:     entity,
776
				Permission: permission,
777
				Arguments:  arguments,
778
				Node: &base.Expand_Expand{
779
					Expand: &base.ExpandTreeNode{
780
						Operation: op,
781
						Children:  children,
782
					},
783
				},
784
			},
785
		},
786
	}
787
}
788
789
// expandRoot is a helper function that executes an ExpandFunction and returns the resulting ExpandResponse. The function
790
// creates a goroutine for the ExpandFunction to allow for cancellation and concurrent execution. If the ExpandFunction
791
// returns an error, the function returns an ExpandResponse with the error. If the context is cancelled before the
792
// ExpandFunction completes, the function returns an ExpandResponse with an error indicating that the operation was cancelled.
793
//
794
// Parameters:
795
//   - ctx: context.Context for the request
796
//   - fn: ExpandFunction to execute
797
//
798
// Returns:
799
//   - ExpandResponse containing the expanded user set or an error if the ExpandFunction failed
800
func expandRoot(ctx context.Context, fn ExpandFunction) ExpandResponse {
801
	res := make(chan ExpandResponse, 1)
802
	go fn(ctx, res)
803
804
	select {
805
	case result := <-res:
806
		if result.Err == nil {
807
			return result
808
		}
809
		return expandFailResponse(result.Err)
810
	case <-ctx.Done():
811
		return expandFailResponse(errors.New(base.ErrorCode_ERROR_CODE_CANCELLED.String()))
812
	}
813
}
814
815
// expandUnion is a helper function that executes multiple ExpandFunctions in parallel and returns an ExpandResponse containing
816
// the union of their expanded user sets. The function delegates to expandOperation with the UNION operation. If any of the
817
// ExpandFunctions return an error, the function returns an ExpandResponse with the error. If the context is cancelled before
818
// all ExpandFunctions complete, the function returns an ExpandResponse with an error indicating that the operation was cancelled.
819
//
820
// Parameters:
821
//   - ctx: context.Context for the request
822
//   - functions: slice of ExpandFunctions to execute in parallel
823
//
824
// Returns:
825
//   - ExpandResponse containing the union of the expanded user sets, or an error if any of the ExpandFunctions failed
826
func expandUnion(
827
	ctx context.Context,
828
	entity *base.Entity,
829
	permission string,
830
	arguments []*base.Argument,
831
	functions []ExpandFunction,
832
) ExpandResponse {
833
	return expandOperation(ctx, entity, permission, arguments, functions, base.ExpandTreeNode_OPERATION_UNION)
834
}
835
836
// expandIntersection is a helper function that executes multiple ExpandFunctions in parallel and returns an ExpandResponse
837
// containing the intersection of their expanded user sets. The function delegates to expandOperation with the INTERSECTION
838
// operation. If any of the ExpandFunctions return an error, the function returns an ExpandResponse with the error. If the
839
// context is cancelled before all ExpandFunctions complete, the function returns an ExpandResponse with an error indicating
840
// that the operation was cancelled.
841
//
842
// Parameters:
843
//   - ctx: context.Context for the request
844
//   - functions: slice of ExpandFunctions to execute in parallel
845
//
846
// Returns:
847
//   - ExpandResponse containing the intersection of the expanded user sets, or an error if any of the ExpandFunctions failed
848
func expandIntersection(
849
	ctx context.Context,
850
	entity *base.Entity,
851
	permission string,
852
	arguments []*base.Argument,
853
	functions []ExpandFunction,
854
) ExpandResponse {
855
	return expandOperation(ctx, entity, permission, arguments, functions, base.ExpandTreeNode_OPERATION_INTERSECTION)
856
}
857
858
// expandExclusion is a helper function that executes multiple ExpandFunctions in parallel and returns an ExpandResponse
859
// containing the expanded user set that results from the exclusion operation. The function delegates to expandOperation
860
// with the EXCLUSION operation. If any of the ExpandFunctions return an error, the function returns an ExpandResponse
861
// with the error. If the context is cancelled before all ExpandFunctions complete, the function returns an ExpandResponse
862
// with an error indicating that the operation was cancelled.
863
//
864
// Parameters:
865
//   - ctx: context.Context for the request
866
//   - target: EntityAndRelation containing the entity and its relation for which the exclusion is calculated
867
//   - functions: slice of ExpandFunctions to execute in parallel
868
//
869
// Returns:
870
//   - ExpandResponse containing the expanded user sets from the exclusion operation, or an error if any of the ExpandFunctions failed
871
func expandExclusion(
872
	ctx context.Context,
873
	entity *base.Entity,
874
	permission string,
875
	arguments []*base.Argument,
876
	functions []ExpandFunction,
877
) ExpandResponse {
878
	return expandOperation(ctx, entity, permission, arguments, functions, base.ExpandTreeNode_OPERATION_EXCLUSION)
879
}
880
881
// expandFail is a helper function that returns an ExpandFunction that immediately sends an ExpandResponse with the specified error
882
// to the provided channel. The resulting ExpandResponse contains an empty ExpandTreeNode and the specified error.
883
//
884
// Parameters:
885
//   - err: error to include in the resulting ExpandResponse
886
//
887
// Returns:
888
//   - ExpandFunction that sends an ExpandResponse with the specified error to the provided channel
889
func expandFail(err error) ExpandFunction {
890
	return func(ctx context.Context, expandChan chan<- ExpandResponse) {
891
		expandChan <- expandFailResponse(err)
892
	}
893
}
894
895
// expandFailResponse is a helper function that returns an ExpandResponse with the specified error and an empty ExpandTreeNode.
896
//
897
// Parameters:
898
//   - err: error to include in the resulting ExpandResponse
899
//
900
// Returns:
901
//   - ExpandResponse with the specified error and an empty ExpandTreeNode
902
func expandFailResponse(err error) ExpandResponse {
903
	return ExpandResponse{
904
		Response: &base.PermissionExpandResponse{
905
			Tree: &base.Expand{},
906
		},
907
		Err: err,
908
	}
909
}
910