Passed
Push — master ( c5e27d...3c7e9a )
by Tolga
02:44 queued 54s
created

internal/engines/cache/check.go   A

Size/Duplication

Total Lines 178
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 92
dl 0
loc 178
rs 10
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
A cache.*CheckEngineWithCache.Check 0 57 4
A cache.NewCheckEngineWithCache 0 11 1
A cache.*CheckEngineWithCache.setCheckKey 0 22 4
A cache.*CheckEngineWithCache.getCheckKey 0 35 4
1
package cache
2
3
import (
4
	"context"
5
	"encoding/hex"
6
	"time"
7
8
	"go.opentelemetry.io/otel"
9
	api "go.opentelemetry.io/otel/metric"
10
11
	"github.com/cespare/xxhash/v2"
12
13
	"github.com/Permify/permify/internal/engines"
14
	"github.com/Permify/permify/internal/invoke"
15
	"github.com/Permify/permify/internal/storage"
16
	"github.com/Permify/permify/pkg/cache"
17
	base "github.com/Permify/permify/pkg/pb/base/v1"
18
	"github.com/Permify/permify/pkg/telemetry"
19
)
20
21
var (
22
	tracer = otel.Tracer("check-cache")
23
	meter  = otel.Meter("check-cache")
24
)
25
26
// CheckEngineWithCache is a struct that holds an instance of a cache.Cache for managing engine cache.
27
type CheckEngineWithCache struct {
28
	// schemaReader is responsible for reading schema information
29
	schemaReader storage.SchemaReader
30
	checker      invoke.Check
31
	cache        cache.Cache
32
33
	// Metrics
34
	cacheCounter              api.Int64Counter
35
	cacheHitDurationHistogram api.Int64Histogram
36
}
37
38
// NewCheckEngineWithCache creates a new instance of EngineKeyManager by initializing an EngineKeys
39
// struct with the provided cache.Cache instance.
40
func NewCheckEngineWithCache(
41
	checker invoke.Check,
42
	schemaReader storage.SchemaReader,
43
	cache cache.Cache,
44
) invoke.Check {
45
	return &CheckEngineWithCache{
46
		schemaReader:              schemaReader,
47
		checker:                   checker,
48
		cache:                     cache,
49
		cacheCounter:              telemetry.NewCounter(meter, "cache_check_count", "Number of permission cached checks performed"),
50
		cacheHitDurationHistogram: telemetry.NewHistogram(meter, "cache_hit_duration", "microseconds", "Duration of cache hits in microseconds"),
51
	}
52
}
53
54
// Check performs a permission check for a given request, using the cached results if available.
55
func (c *CheckEngineWithCache) Check(ctx context.Context, request *base.PermissionCheckRequest) (response *base.PermissionCheckResponse, err error) {
56
	// Retrieve entity definition
57
	var en *base.EntityDefinition
58
	en, _, err = c.schemaReader.ReadEntityDefinition(ctx, request.GetTenantId(), request.GetEntity().GetType(), request.GetMetadata().GetSchemaVersion())
59
	if err != nil {
60
		return &base.PermissionCheckResponse{
61
			Can: base.CheckResult_CHECK_RESULT_DENIED,
62
			Metadata: &base.PermissionCheckResponseMetadata{
63
				CheckCount: 0,
64
			},
65
		}, err
66
	}
67
68
	isRelational := engines.IsRelational(en, request.GetPermission())
69
70
	// Try to get the cached result for the given request.
71
	res, found := c.getCheckKey(request, isRelational)
72
73
	// If a cached result is found, handle exclusion and return the result.
74
	if found {
75
		ctx, span := tracer.Start(ctx, "hit")
76
		defer span.End()
77
		start := time.Now()
78
79
		// Increase the check count in the metrics.
80
		c.cacheCounter.Add(ctx, 1)
81
82
		duration := time.Now().Sub(start)
83
		c.cacheHitDurationHistogram.Record(ctx, duration.Microseconds())
84
85
		// If the request doesn't have the exclusion flag set, return the cached result.
86
		return &base.PermissionCheckResponse{
87
			Can:      res.GetCan(),
88
			Metadata: &base.PermissionCheckResponseMetadata{},
89
		}, nil
90
	}
91
92
	// Perform the actual permission check using the provided request.
93
	cres, err := c.checker.Check(ctx, request)
94
	// Check if there's an error or the response is nil, and return the result.
95
	if err != nil {
96
		return &base.PermissionCheckResponse{
97
			Can: base.CheckResult_CHECK_RESULT_DENIED,
98
			Metadata: &base.PermissionCheckResponseMetadata{
99
				CheckCount: 0,
100
			},
101
		}, err
102
	}
103
104
	// Add to histogram the response
105
106
	c.setCheckKey(request, &base.PermissionCheckResponse{
107
		Can:      cres.GetCan(),
108
		Metadata: &base.PermissionCheckResponseMetadata{},
109
	}, isRelational)
110
	// Return the result of the permission check.
111
	return cres, err
112
}
113
114
// GetCheckKey retrieves the value for the given key from the EngineKeys cache.
115
// It returns the PermissionCheckResponse if the key is found, and a boolean value
116
// indicating whether the key was found or not.
117
func (c *CheckEngineWithCache) getCheckKey(key *base.PermissionCheckRequest, isRelational bool) (*base.PermissionCheckResponse, bool) {
118
	if key == nil {
119
		// If either the key or value is nil, return false
120
		return nil, false
121
	}
122
123
	// Initialize a new xxhash object
124
	h := xxhash.New()
125
126
	// Write the checkKey string to the hash object
127
	_, err := h.Write([]byte(engines.GenerateKey(key, isRelational)))
128
	if err != nil {
129
		// If there's an error, return nil and false
130
		return nil, false
131
	}
132
133
	// Generate the final cache key by encoding the hash object's sum as a hexadecimal string
134
	k := hex.EncodeToString(h.Sum(nil))
135
136
	// Get the value from the cache using the generated cache key
137
	resp, found := c.cache.Get(k)
138
139
	// If the key is found, return the value and true
140
	if found {
141
		// If permission is granted, return allowed response
142
		return &base.PermissionCheckResponse{
143
			Can: resp.(base.CheckResult),
144
			Metadata: &base.PermissionCheckResponseMetadata{
145
				CheckCount: 0,
146
			},
147
		}, true
148
	}
149
150
	// If the key is not found, return nil and false
151
	return nil, false
152
}
153
154
// setCheckKey is a function to set a check key in the cache of the CheckEngineWithKeys.
155
// It takes a permission check request as a key, a permission check response as a value,
156
// and returns a boolean value indicating if the operation was successful.
157
func (c *CheckEngineWithCache) setCheckKey(key *base.PermissionCheckRequest, value *base.PermissionCheckResponse, isRelational bool) bool {
158
	// If either the key or the value is nil, return false.
159
	if key == nil || value == nil {
160
		return false
161
	}
162
163
	// Create a new xxhash object for hashing.
164
	h := xxhash.New()
165
166
	// Generate a key string from the permission check request and write it to the hash.
167
	// If there's an error while writing to the hash, return false.
168
	size, err := h.Write([]byte(engines.GenerateKey(key, isRelational)))
169
	if err != nil {
170
		return false
171
	}
172
173
	// Compute the hash sum and encode it as a hexadecimal string.
174
	k := hex.EncodeToString(h.Sum(nil))
175
176
	// Set the hashed key and the check result in the cache, using the size of the hashed key as an expiry.
177
	// The Set method should return true if the operation was successful, so return the result.
178
	return c.cache.Set(k, value.GetCan(), int64(size))
179
}
180