Passed
Pull Request — master (#1695)
by Tolga
03:53
created

engines.SubjectFilterConcurrencyLimit   A

Complexity

Conditions 2

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
package engines
2
3
import (
4
	"errors"
5
	"fmt"
6
	"sort"
7
	"strings"
8
	"sync"
9
10
	"google.golang.org/protobuf/types/known/anypb"
11
12
	"github.com/Permify/permify/internal/schema"
13
	"github.com/Permify/permify/pkg/attribute"
14
	base "github.com/Permify/permify/pkg/pb/base/v1"
15
	"github.com/Permify/permify/pkg/tuple"
16
)
17
18
const (
19
	_defaultConcurrencyLimit = 100
20
)
21
22
// CheckOption - a functional option type for configuring the CheckEngine.
23
type CheckOption func(engine *CheckEngine)
24
25
// CheckConcurrencyLimit - a functional option that sets the concurrency limit for the CheckEngine.
26
func CheckConcurrencyLimit(limit int) CheckOption {
27
	return func(c *CheckEngine) {
28
		c.concurrencyLimit = limit
29
	}
30
}
31
32
type LookupOption func(engine *LookupEngine)
33
34
func LookupConcurrencyLimit(limit int) LookupOption {
35
	return func(c *LookupEngine) {
36
		c.concurrencyLimit = limit
37
	}
38
}
39
40
// SubjectFilterOption - a functional option type for configuring the LookupSubjectEngine.
41
type SubjectFilterOption func(engine *SubjectFilter)
42
43
// SubjectFilterConcurrencyLimit - a functional option that sets the concurrency limit for the LookupSubjectEngine.
44
func SubjectFilterConcurrencyLimit(limit int) SubjectFilterOption {
45
	return func(c *SubjectFilter) {
46
		c.concurrencyLimit = limit
47
	}
48
}
49
50
// SubjectPermissionOption - a functional option type for configuring the SubjectPermissionEngine.
51
type SubjectPermissionOption func(engine *SubjectPermissionEngine)
52
53
// SubjectPermissionConcurrencyLimit - a functional option that sets the concurrency limit for the SubjectPermissionEngine.
54
func SubjectPermissionConcurrencyLimit(limit int) SubjectPermissionOption {
55
	return func(c *SubjectPermissionEngine) {
56
		c.concurrencyLimit = limit
57
	}
58
}
59
60
// joinResponseMetas - a helper function that merges multiple PermissionCheckResponseMetadata structs into one.
61
func joinResponseMetas(meta ...*base.PermissionCheckResponseMetadata) *base.PermissionCheckResponseMetadata {
62
	response := &base.PermissionCheckResponseMetadata{}
63
	for _, m := range meta {
64
		response.CheckCount += m.CheckCount
65
	}
66
	return response
67
}
68
69
// SubjectPermissionResponse - a struct that holds a SubjectPermissionResponse and an error for a single subject permission check result.
70
type SubjectPermissionResponse struct {
71
	permission string
72
	result     base.CheckResult
73
	err        error
74
}
75
76
// CheckResponse - a struct that holds a PermissionCheckResponse and an error for a single check function.
77
type CheckResponse struct {
78
	resp *base.PermissionCheckResponse
79
	err  error
80
}
81
82
// VisitsMap - a thread-safe map of ENR records.
83
type VisitsMap struct {
84
	er        sync.Map
85
	ea        sync.Map
86
	published sync.Map
87
}
88
89
func (s *VisitsMap) AddER(entity *base.Entity, relation string) bool {
90
	key := tuple.EntityAndRelationToString(entity, relation)
91
	_, existed := s.er.LoadOrStore(key, struct{}{})
92
	return !existed
93
}
94
95
func (s *VisitsMap) AddEA(entityType, attribute string) bool {
96
	key := fmt.Sprintf("%s$%s", entityType, attribute)
97
	_, existed := s.er.LoadOrStore(key, struct{}{})
98
	return !existed
99
}
100
101
func (s *VisitsMap) AddPublished(entity *base.Entity) bool {
102
	key := tuple.EntityToString(entity)
103
	_, existed := s.published.LoadOrStore(key, struct{}{})
104
	return !existed
105
}
106
107
type IdResponse struct {
108
	Ids         []string
109
	ExcludedIds []string // Holds the list of excluded IDs.
110
}
111
112
// SubjectFilterResponse -
113
type SubjectFilterResponse struct {
114
	resp IdResponse
115
	err  error
116
}
117
118
// getDuplicates is a function that accepts a slice of strings and returns a slice of duplicated strings in the input slice
119
func getDuplicates(s []string) []string {
120
	// "seen" will keep track of all strings we have encountered in the slice
121
	seen := make(map[string]bool)
122
123
	// "duplicates" will keep track of all strings that are duplicated in the slice
124
	duplicates := make(map[string]bool)
125
126
	// Iterate over every string in the input slice
127
	for _, str := range s {
128
		// If we have seen the string before, then it is a duplicate.
129
		// So, we add it to our duplicates map.
130
		if _, value := seen[str]; value {
131
			duplicates[str] = true
132
		} else {
133
			// If we haven't seen the string before, add it to the seen map.
134
			seen[str] = true
135
		}
136
	}
137
138
	// "duplicatesSlice" will eventually hold our results: all strings that are duplicated in the input slice
139
	duplicatesSlice := make([]string, 0, len(duplicates))
140
141
	// Now, duplicates map contains all the duplicated strings.
142
	// We iterate over it and add each string to our result slice.
143
	for str := range duplicates {
144
		duplicatesSlice = append(duplicatesSlice, str)
145
	}
146
147
	// Return the slice that contains all the duplicated strings
148
	return duplicatesSlice
149
}
150
151
// getEmptyValueForType is a helper function that takes a string representation of a type
152
// and returns an "empty" value for that type.
153
// An empty value is a value that is generally considered a default or initial state for a variable of a given type.
154
// The purpose of this function is to be able to initialize a variable of a given type without knowing the type in advance.
155
// The function uses a switch statement to handle different possible type values and returns a corresponding empty value.
156
func getEmptyValueForType(typ base.AttributeType) interface{} {
157
	switch typ {
158
	case base.AttributeType_ATTRIBUTE_TYPE_STRING:
159
		// In the case of a string type, an empty string "" is considered the empty value.
160
		return ""
161
	case base.AttributeType_ATTRIBUTE_TYPE_STRING_ARRAY:
162
		// In the case of a string type, an empty string "" is considered the empty value.
163
		return []string{}
164
	case base.AttributeType_ATTRIBUTE_TYPE_INTEGER:
165
		// In the case of an integer type, zero (0) is considered the empty value.
166
		return 0
167
	case base.AttributeType_ATTRIBUTE_TYPE_INTEGER_ARRAY:
168
		// In the case of an integer type, zero (0) is considered the empty value.
169
		return []int32{}
170
	case base.AttributeType_ATTRIBUTE_TYPE_DOUBLE:
171
		// In the case of a double (or floating point) type, zero (0.0) is considered the empty value.
172
		return 0.0
173
	case base.AttributeType_ATTRIBUTE_TYPE_DOUBLE_ARRAY:
174
		// In the case of a double (or floating point) type, zero (0.0) is considered the empty value.
175
		return []float64{}
176
	case base.AttributeType_ATTRIBUTE_TYPE_BOOLEAN:
177
		// In the case of a boolean type, false is considered the empty value.
178
		return false
179
	case base.AttributeType_ATTRIBUTE_TYPE_BOOLEAN_ARRAY:
180
		// In the case of a boolean type, false is considered the empty value.
181
		return []bool{}
182
	default:
183
		// For any other types that are not explicitly handled, the function returns nil.
184
		// This may need to be adjusted if there are other types that need specific empty values.
185
		return nil
186
	}
187
}
188
189
// getEmptyProtoValueForType returns an empty protobuf value of the specified type.
190
// It takes a base.AttributeType as input and generates an empty protobuf Any message
191
// containing a default value for that type.
192
func getEmptyProtoValueForType(typ base.AttributeType) (*anypb.Any, error) {
193
	switch typ {
194
	case base.AttributeType_ATTRIBUTE_TYPE_STRING:
195
		// Create an empty protobuf String message
196
		value, err := anypb.New(&base.StringValue{Data: ""})
197
		if err != nil {
198
			return nil, err
199
		}
200
		return value, nil
201
202
	case base.AttributeType_ATTRIBUTE_TYPE_STRING_ARRAY:
203
		// Create an empty protobuf StringArray message
204
		value, err := anypb.New(&base.StringArrayValue{Data: []string{}})
205
		if err != nil {
206
			return nil, err
207
		}
208
		return value, nil
209
210
	case base.AttributeType_ATTRIBUTE_TYPE_INTEGER:
211
		// Create an empty protobuf Integer message
212
		value, err := anypb.New(&base.IntegerValue{Data: 0})
213
		if err != nil {
214
			return nil, err
215
		}
216
		return value, nil
217
218
	case base.AttributeType_ATTRIBUTE_TYPE_INTEGER_ARRAY:
219
		// Create an empty protobuf IntegerArray message
220
		value, err := anypb.New(&base.IntegerArrayValue{Data: []int32{}})
221
		if err != nil {
222
			return nil, err
223
		}
224
		return value, nil
225
226
	case base.AttributeType_ATTRIBUTE_TYPE_DOUBLE:
227
		// Create an empty protobuf Double message
228
		value, err := anypb.New(&base.DoubleValue{Data: 0.0})
229
		if err != nil {
230
			return nil, err
231
		}
232
		return value, nil
233
234
	case base.AttributeType_ATTRIBUTE_TYPE_DOUBLE_ARRAY:
235
		// Create an empty protobuf DoubleArray message
236
		value, err := anypb.New(&base.DoubleArrayValue{Data: []float64{}})
237
		if err != nil {
238
			return nil, err
239
		}
240
		return value, nil
241
242
	case base.AttributeType_ATTRIBUTE_TYPE_BOOLEAN:
243
		// Create an empty protobuf Boolean message with a default value of false
244
		value, err := anypb.New(&base.BooleanValue{Data: false})
245
		if err != nil {
246
			return nil, err
247
		}
248
		return value, nil
249
250
	case base.AttributeType_ATTRIBUTE_TYPE_BOOLEAN_ARRAY:
251
		// Create an empty protobuf BooleanArray message
252
		value, err := anypb.New(&base.BooleanArrayValue{Data: []bool{}})
253
		if err != nil {
254
			return nil, err
255
		}
256
		return value, nil
257
258
	default:
259
		// Handle the case where the provided attribute type is unknown
260
		return nil, errors.New("unknown type")
261
	}
262
}
263
264
// ConvertToAnyPB is a function to convert various basic Go types into *anypb.Any.
265
// It supports conversion from bool, int, float64, and string.
266
// It uses a type switch to detect the type of the input value.
267
// If the type is unsupported or unknown, it returns an error.
268
func ConvertToAnyPB(value interface{}) (*anypb.Any, error) {
269
	// anyValue will store the converted value, err will store any error occurred during conversion.
270
	var anyValue *anypb.Any
271
	var err error
272
273
	// Use a type switch to handle different types of value.
274
	switch v := value.(type) {
275
	case bool:
276
		anyValue, err = anypb.New(&base.BooleanValue{Data: v})
277
	case []bool:
278
		anyValue, err = anypb.New(&base.BooleanArrayValue{Data: v})
279
	case int:
280
		anyValue, err = anypb.New(&base.IntegerValue{Data: int32(v)})
281
	case []int32:
282
		anyValue, err = anypb.New(&base.IntegerArrayValue{Data: v})
283
	case float64:
284
		anyValue, err = anypb.New(&base.DoubleValue{Data: v})
285
	case []float64:
286
		anyValue, err = anypb.New(&base.DoubleArrayValue{Data: v})
287
	case string:
288
		anyValue, err = anypb.New(&base.StringValue{Data: v})
289
	case []string:
290
		anyValue, err = anypb.New(&base.StringArrayValue{Data: v})
291
	default:
292
		// In case of an unsupported or unknown type, we return an error.
293
		return nil, errors.New("unknown type")
294
	}
295
296
	// If there was an error during the conversion, return the error.
297
	if err != nil {
298
		return nil, err
299
	}
300
301
	// If the conversion was successful, return the converted value.
302
	return anyValue, nil
303
}
304
305
// GenerateKey function takes a PermissionCheckRequest and generates a unique key
306
// Key format: check|{tenant_id}|{schema_version}|{snap_token}|{context}|{entity:id#permission(optional_arguments)@subject:id#optional_relation}
307
func GenerateKey(key *base.PermissionCheckRequest, isRelational bool) string {
308
	// Initialize the parts slice with the string "check"
309
	parts := []string{"check"}
310
311
	// If tenantId is not empty, append it to parts
312
	if tenantId := key.GetTenantId(); tenantId != "" {
313
		parts = append(parts, tenantId)
314
	}
315
316
	// If Metadata exists, extract schema version and snap token and append them to parts if they are not empty
317
	if meta := key.GetMetadata(); meta != nil {
318
		if version := meta.GetSchemaVersion(); version != "" {
319
			parts = append(parts, version)
320
		}
321
		if token := meta.GetSnapToken(); token != "" {
322
			parts = append(parts, token)
323
		}
324
	}
325
326
	// If Context exists, convert it to string and append it to parts
327
	if ctx := key.GetContext(); ctx != nil {
328
		parts = append(parts, ContextToString(ctx))
329
	}
330
331
	if isRelational {
332
		// Convert entity and relation to string with any optional arguments and append to parts
333
		entityRelationString := tuple.EntityAndRelationToString(key.GetEntity(), key.GetPermission())
334
335
		subjectString := tuple.SubjectToString(key.GetSubject())
336
337
		if entityRelationString != "" {
338
			parts = append(parts, fmt.Sprintf("%s@%s", entityRelationString, subjectString))
339
		}
340
	} else {
341
		parts = append(parts, attribute.EntityAndCallOrAttributeToString(
342
			key.GetEntity(),
343
			key.GetPermission(),
344
			key.GetArguments()...,
345
		))
346
	}
347
348
	// Join all parts with "|" delimiter to generate the final key
349
	return strings.Join(parts, "|")
350
}
351
352
// ContextToString function takes a Context object and converts it into a string
353
func ContextToString(context *base.Context) string {
354
	// Initialize an empty slice to store parts of the context
355
	var parts []string
356
357
	// For each Tuple in the Context, convert it to a string and append to parts
358
	for _, tup := range context.GetTuples() {
359
		parts = append(parts, tuple.ToString(tup)) // replace with your function
360
	}
361
362
	// For each Attribute in the Context, convert it to a string and append to parts
363
	for _, attr := range context.GetAttributes() {
364
		parts = append(parts, attribute.ToString(attr)) // replace with your function
365
	}
366
367
	// If Data exists in the Context, convert it to JSON string and append to parts
368
	if data := context.GetData(); data != nil {
369
		parts = append(parts, mapToString(data.AsMap()))
370
	}
371
372
	// Join all parts with "," delimiter to generate the final context string
373
	return strings.Join(parts, ",")
374
}
375
376
// mapToString function takes a map[string]interface{} and converts it into a string
377
func mapToString(m map[string]interface{}) string {
378
	var keys []string
379
	for key := range m {
380
		keys = append(keys, key)
381
	}
382
	sort.Strings(keys)
383
384
	var parts []string
385
	for _, key := range keys {
386
		value := m[key]
387
		parts = append(parts, fmt.Sprintf("%s:%v", key, value))
388
	}
389
	return strings.Join(parts, ",")
390
}
391
392
// IsRelational determines if a given permission corresponds to a relational attribute
393
// in the provided entity definition.
394
func IsRelational(en *base.EntityDefinition, permission string) bool {
395
	// Default to non-relational
396
	isRelational := false
397
398
	// Attempt to get the type of reference for the given permission in the entity definition
399
	tor, err := schema.GetTypeOfReferenceByNameInEntityDefinition(en, permission)
400
	if err == nil && tor != base.EntityDefinition_REFERENCE_ATTRIBUTE {
401
		// If the type of reference is anything other than REFERENCE_ATTRIBUTE,
402
		// treat it as a relational attribute
403
		isRelational = true
404
	}
405
406
	return isRelational
407
}
408