internal/engines/utils.go   F
last analyzed

Size/Duplication

Total Lines 400
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
cc 74
eloc 207
dl 0
loc 400
rs 2.48
c 0
b 0
f 0

16 Methods

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