Skip to content

Commit 97ff507

Browse files
committed
feat: add schema reader for define cache keys type
1 parent c94646b commit 97ff507

File tree

12 files changed

+197
-132
lines changed

12 files changed

+197
-132
lines changed

internal/engines/consistent/hashring.go

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99

1010
"github.com/Permify/permify/internal/engines/keys"
1111
"github.com/Permify/permify/internal/invoke"
12+
"github.com/Permify/permify/internal/schema"
13+
"github.com/Permify/permify/internal/storage"
1214
hash "github.com/Permify/permify/pkg/consistent"
1315
"github.com/Permify/permify/pkg/gossip"
1416
"github.com/Permify/permify/pkg/logger"
@@ -17,6 +19,8 @@ import (
1719

1820
// Hashring is a wrapper around the consistent hash implementation that
1921
type Hashring struct {
22+
// schemaReader is responsible for reading schema information
23+
schemaReader storage.SchemaReader
2024
checker invoke.Check
2125
gossip gossip.IGossip
2226
consistent hash.Consistent
@@ -27,14 +31,15 @@ type Hashring struct {
2731

2832
// NewCheckEngineWithHashring creates a new instance of EngineKeyManager by initializing an EngineKeys
2933
// struct with the provided cache.Cache instance.
30-
func NewCheckEngineWithHashring(checker invoke.Check, consistent *hash.ConsistentHash, g gossip.IGossip, port string, l *logger.Logger) (invoke.Check, error) {
34+
func NewCheckEngineWithHashring(checker invoke.Check, schemaReader storage.SchemaReader, consistent *hash.ConsistentHash, g gossip.IGossip, port string, l *logger.Logger) (invoke.Check, error) {
3135
// Return a new instance of EngineKeys with the provided cache
3236
ip, err := gossip.ExternalIP()
3337
if err != nil {
3438
return nil, err
3539
}
3640

3741
return &Hashring{
42+
schemaReader: schemaReader,
3843
checker: checker,
3944
localNodeAddress: ip + ":" + port,
4045
gossip: g,
@@ -44,8 +49,30 @@ func NewCheckEngineWithHashring(checker invoke.Check, consistent *hash.Consisten
4449
}
4550

4651
func (c *Hashring) Check(ctx context.Context, request *base.PermissionCheckRequest) (response *base.PermissionCheckResponse, err error) {
52+
// Retrieve entity definition
53+
var en *base.EntityDefinition
54+
en, _, err = c.schemaReader.ReadEntityDefinition(ctx, request.GetTenantId(), request.GetEntity().GetType(), request.GetMetadata().GetSchemaVersion())
55+
if err != nil {
56+
return &base.PermissionCheckResponse{
57+
Can: base.CheckResult_CHECK_RESULT_DENIED,
58+
Metadata: &base.PermissionCheckResponseMetadata{
59+
CheckCount: 0,
60+
},
61+
}, err
62+
}
63+
64+
isRelational := false
65+
66+
// Determine the type of the reference by name in the given entity definition.
67+
tor, err := schema.GetTypeOfReferenceByNameInEntityDefinition(en, request.GetPermission())
68+
if err == nil {
69+
if tor != base.EntityDefinition_REFERENCE_ATTRIBUTE {
70+
isRelational = true
71+
}
72+
}
73+
4774
// Generate a unique checkKey string based on the provided PermissionCheckRequest
48-
k := keys.GenerateKey(request)
75+
k := keys.GenerateKey(request, isRelational)
4976

5077
_, _, ok := c.consistent.Get(k)
5178
if !ok {

internal/engines/keys/keys.go

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"github.com/cespare/xxhash/v2"
1010

1111
"github.com/Permify/permify/internal/invoke"
12+
"github.com/Permify/permify/internal/schema"
13+
"github.com/Permify/permify/internal/storage"
1214
"github.com/Permify/permify/pkg/attribute"
1315
"github.com/Permify/permify/pkg/cache"
1416
"github.com/Permify/permify/pkg/logger"
@@ -18,25 +20,50 @@ import (
1820

1921
// CheckEngineWithKeys is a struct that holds an instance of a cache.Cache for managing engine keys.
2022
type CheckEngineWithKeys struct {
21-
checker invoke.Check
22-
cache cache.Cache
23-
l *logger.Logger
23+
// schemaReader is responsible for reading schema information
24+
schemaReader storage.SchemaReader
25+
checker invoke.Check
26+
cache cache.Cache
27+
l *logger.Logger
2428
}
2529

2630
// NewCheckEngineWithKeys creates a new instance of EngineKeyManager by initializing an EngineKeys
2731
// struct with the provided cache.Cache instance.
28-
func NewCheckEngineWithKeys(checker invoke.Check, cache cache.Cache, l *logger.Logger) invoke.Check {
32+
func NewCheckEngineWithKeys(checker invoke.Check, schemaReader storage.SchemaReader, cache cache.Cache, l *logger.Logger) invoke.Check {
2933
return &CheckEngineWithKeys{
30-
checker: checker,
31-
cache: cache,
32-
l: l,
34+
schemaReader: schemaReader,
35+
checker: checker,
36+
cache: cache,
37+
l: l,
3338
}
3439
}
3540

3641
// Check performs a permission check for a given request, using the cached results if available.
3742
func (c *CheckEngineWithKeys) Check(ctx context.Context, request *base.PermissionCheckRequest) (response *base.PermissionCheckResponse, err error) {
43+
// Retrieve entity definition
44+
var en *base.EntityDefinition
45+
en, _, err = c.schemaReader.ReadEntityDefinition(ctx, request.GetTenantId(), request.GetEntity().GetType(), request.GetMetadata().GetSchemaVersion())
46+
if err != nil {
47+
return &base.PermissionCheckResponse{
48+
Can: base.CheckResult_CHECK_RESULT_DENIED,
49+
Metadata: &base.PermissionCheckResponseMetadata{
50+
CheckCount: 0,
51+
},
52+
}, err
53+
}
54+
55+
isRelational := false
56+
57+
// Determine the type of the reference by name in the given entity definition.
58+
tor, err := schema.GetTypeOfReferenceByNameInEntityDefinition(en, request.GetPermission())
59+
if err == nil {
60+
if tor != base.EntityDefinition_REFERENCE_ATTRIBUTE {
61+
isRelational = true
62+
}
63+
}
64+
3865
// Try to get the cached result for the given request.
39-
res, found := c.getCheckKey(request)
66+
res, found := c.getCheckKey(request, isRelational)
4067

4168
// If a cached result is found, handle exclusion and return the result.
4269
if found {
@@ -63,7 +90,7 @@ func (c *CheckEngineWithKeys) Check(ctx context.Context, request *base.Permissio
6390
c.setCheckKey(request, &base.PermissionCheckResponse{
6491
Can: res.GetCan(),
6592
Metadata: &base.PermissionCheckResponseMetadata{},
66-
})
93+
}, isRelational)
6794

6895
// Return the result of the permission check.
6996
return res, err
@@ -72,7 +99,7 @@ func (c *CheckEngineWithKeys) Check(ctx context.Context, request *base.Permissio
7299
// GetCheckKey retrieves the value for the given key from the EngineKeys cache.
73100
// It returns the PermissionCheckResponse if the key is found, and a boolean value
74101
// indicating whether the key was found or not.
75-
func (c *CheckEngineWithKeys) getCheckKey(key *base.PermissionCheckRequest) (*base.PermissionCheckResponse, bool) {
102+
func (c *CheckEngineWithKeys) getCheckKey(key *base.PermissionCheckRequest, isRelational bool) (*base.PermissionCheckResponse, bool) {
76103
if key == nil {
77104
// If either the key or value is nil, return false
78105
return nil, false
@@ -82,7 +109,7 @@ func (c *CheckEngineWithKeys) getCheckKey(key *base.PermissionCheckRequest) (*ba
82109
h := xxhash.New()
83110

84111
// Write the checkKey string to the hash object
85-
_, err := h.Write([]byte(GenerateKey(key)))
112+
_, err := h.Write([]byte(GenerateKey(key, isRelational)))
86113
if err != nil {
87114
// If there's an error, return nil and false
88115
return nil, false
@@ -112,7 +139,7 @@ func (c *CheckEngineWithKeys) getCheckKey(key *base.PermissionCheckRequest) (*ba
112139
// setCheckKey is a function to set a check key in the cache of the CheckEngineWithKeys.
113140
// It takes a permission check request as a key, a permission check response as a value,
114141
// and returns a boolean value indicating if the operation was successful.
115-
func (c *CheckEngineWithKeys) setCheckKey(key *base.PermissionCheckRequest, value *base.PermissionCheckResponse) bool {
142+
func (c *CheckEngineWithKeys) setCheckKey(key *base.PermissionCheckRequest, value *base.PermissionCheckResponse, isRelational bool) bool {
116143
// If either the key or the value is nil, return false.
117144
if key == nil || value == nil {
118145
return false
@@ -123,7 +150,7 @@ func (c *CheckEngineWithKeys) setCheckKey(key *base.PermissionCheckRequest, valu
123150

124151
// Generate a key string from the permission check request and write it to the hash.
125152
// If there's an error while writing to the hash, return false.
126-
size, err := h.Write([]byte(GenerateKey(key)))
153+
size, err := h.Write([]byte(GenerateKey(key, isRelational)))
127154
if err != nil {
128155
return false
129156
}
@@ -133,18 +160,12 @@ func (c *CheckEngineWithKeys) setCheckKey(key *base.PermissionCheckRequest, valu
133160

134161
// Set the hashed key and the check result in the cache, using the size of the hashed key as an expiry.
135162
// The Set method should return true if the operation was successful, so return the result.
136-
return c.cache.Set(k, value.Can, int64(size))
163+
return c.cache.Set(k, value.GetCan(), int64(size))
137164
}
138165

139166
// GenerateKey function takes a PermissionCheckRequest and generates a unique key
140167
// Key format: check|{tenant_id}|{schema_version}|{snap_token}|{context}|{entity:id#permission(optional_arguments)@subject:id#optional_relation}
141-
func GenerateKey(key *base.PermissionCheckRequest) string {
142-
// Create a new EntityAndRelation object with the entity and permission from the key
143-
entityRelation := &base.EntityAndRelation{
144-
Entity: key.GetEntity(),
145-
Relation: key.GetPermission(),
146-
}
147-
168+
func GenerateKey(key *base.PermissionCheckRequest, isRelational bool) string {
148169
// Initialize the parts slice with the string "check"
149170
parts := []string{"check"}
150171

@@ -168,12 +189,22 @@ func GenerateKey(key *base.PermissionCheckRequest) string {
168189
parts = append(parts, ContextToString(ctx))
169190
}
170191

171-
// Convert entity and relation to string with any optional arguments and append to parts
172-
entityRelationString := tuple.EntityAndRelationToString(entityRelation, key.GetArguments()...)
173-
subjectString := tuple.SubjectToString(key.GetSubject())
192+
if isRelational {
193+
// Convert entity and relation to string with any optional arguments and append to parts
194+
entityRelationString := tuple.EntityAndRelationToString(key.GetEntity(), key.GetPermission())
195+
196+
subjectString := tuple.SubjectToString(key.GetSubject())
197+
198+
if entityRelationString != "" {
199+
parts = append(parts, fmt.Sprintf("%s@%s", entityRelationString, subjectString))
200+
}
174201

175-
if entityRelationString != "" {
176-
parts = append(parts, fmt.Sprintf("%s@%s", entityRelationString, subjectString))
202+
} else {
203+
parts = append(parts, attribute.EntityAndCallOrAttributeToString(
204+
key.GetEntity(),
205+
key.GetPermission(),
206+
key.GetArguments()...,
207+
))
177208
}
178209

179210
// Join all parts with "|" delimiter to generate the final key

internal/engines/keys/keys_test.go

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@ package keys
33
import (
44
"testing"
55

6+
"github.com/stretchr/testify/assert"
67
"google.golang.org/protobuf/types/known/anypb"
78
"google.golang.org/protobuf/types/known/structpb"
8-
"google.golang.org/protobuf/types/known/wrapperspb"
9-
10-
"github.com/stretchr/testify/assert"
119

1210
"github.com/Permify/permify/pkg/cache/ristretto"
1311
"github.com/Permify/permify/pkg/logger"
@@ -22,7 +20,7 @@ func TestEngineKeys_SetCheckKey(t *testing.T) {
2220
l := logger.New("debug")
2321

2422
// Initialize a new EngineKeys struct with a new cache.Cache instance
25-
engineKeys := CheckEngineWithKeys{nil, cache, l}
23+
engineKeys := CheckEngineWithKeys{nil, nil, cache, l}
2624

2725
// Create a new PermissionCheckRequest and PermissionCheckResponse
2826
checkReq := &base.PermissionCheckRequest{
@@ -51,15 +49,15 @@ func TestEngineKeys_SetCheckKey(t *testing.T) {
5149
}
5250

5351
// Set the value for the given key in the cache
54-
success := engineKeys.setCheckKey(checkReq, checkResp)
52+
success := engineKeys.setCheckKey(checkReq, checkResp, true)
5553

5654
cache.Wait()
5755

5856
// Check that the operation was successful
5957
assert.True(t, success)
6058

6159
// Retrieve the value for the given key from the cache
62-
resp, found := engineKeys.getCheckKey(checkReq)
60+
resp, found := engineKeys.getCheckKey(checkReq, true)
6361

6462
// Check that the key was found and the retrieved value is the same as the original value
6563
assert.True(t, found)
@@ -74,7 +72,7 @@ func TestEngineKeys_SetCheckKey_WithHashError(t *testing.T) {
7472
l := logger.New("debug")
7573

7674
// Initialize a new EngineKeys struct with a new cache.Cache instance
77-
engineKeys := CheckEngineWithKeys{nil, cache, l}
75+
engineKeys := CheckEngineWithKeys{nil, nil, cache, l}
7876

7977
// Create a new PermissionCheckRequest and PermissionCheckResponse
8078
checkReq := &base.PermissionCheckRequest{
@@ -103,15 +101,15 @@ func TestEngineKeys_SetCheckKey_WithHashError(t *testing.T) {
103101
}
104102

105103
// Force an error while writing the key to the hash object by passing a nil key
106-
success := engineKeys.setCheckKey(nil, checkResp)
104+
success := engineKeys.setCheckKey(nil, checkResp, true)
107105

108106
cache.Wait()
109107

110108
// Check that the operation was unsuccessful
111109
assert.False(t, success)
112110

113111
// Retrieve the value for the given key from the cache
114-
resp, found := engineKeys.getCheckKey(checkReq)
112+
resp, found := engineKeys.getCheckKey(checkReq, true)
115113

116114
// Check that the key was not found
117115
assert.False(t, found)
@@ -126,7 +124,7 @@ func TestEngineKeys_GetCheckKey_KeyNotFound(t *testing.T) {
126124
l := logger.New("debug")
127125

128126
// Initialize a new EngineKeys struct with a new cache.Cache instance
129-
engineKeys := CheckEngineWithKeys{nil, cache, l}
127+
engineKeys := CheckEngineWithKeys{nil, nil, cache, l}
130128

131129
// Create a new PermissionCheckRequest
132130
checkReq := &base.PermissionCheckRequest{
@@ -148,7 +146,7 @@ func TestEngineKeys_GetCheckKey_KeyNotFound(t *testing.T) {
148146
}
149147

150148
// Retrieve the value for a non-existent key from the cache
151-
resp, found := engineKeys.getCheckKey(checkReq)
149+
resp, found := engineKeys.getCheckKey(checkReq, true)
152150

153151
// Check that the key was not found
154152
assert.False(t, found)
@@ -163,7 +161,7 @@ func TestEngineKeys_SetAndGetMultipleKeys(t *testing.T) {
163161
l := logger.New("debug")
164162

165163
// Initialize a new EngineKeys struct with a new cache.Cache instance
166-
engineKeys := CheckEngineWithKeys{nil, cache, l}
164+
engineKeys := CheckEngineWithKeys{nil, nil, cache, l}
167165

168166
// Create some new PermissionCheckRequests and PermissionCheckResponses
169167
checkReq1 := &base.PermissionCheckRequest{
@@ -239,9 +237,9 @@ func TestEngineKeys_SetAndGetMultipleKeys(t *testing.T) {
239237
}
240238

241239
// Set the values for the given keys in the cache
242-
success1 := engineKeys.setCheckKey(checkReq1, checkResp1)
243-
success2 := engineKeys.setCheckKey(checkReq2, checkResp2)
244-
success3 := engineKeys.setCheckKey(checkReq3, checkResp3)
240+
success1 := engineKeys.setCheckKey(checkReq1, checkResp1, true)
241+
success2 := engineKeys.setCheckKey(checkReq2, checkResp2, true)
242+
success3 := engineKeys.setCheckKey(checkReq3, checkResp3, true)
245243

246244
cache.Wait()
247245

@@ -251,9 +249,9 @@ func TestEngineKeys_SetAndGetMultipleKeys(t *testing.T) {
251249
assert.True(t, success3)
252250

253251
// Retrieve the value for the given key from the cache
254-
resp1, found1 := engineKeys.getCheckKey(checkReq1)
255-
resp2, found2 := engineKeys.getCheckKey(checkReq2)
256-
resp3, found3 := engineKeys.getCheckKey(checkReq3)
252+
resp1, found1 := engineKeys.getCheckKey(checkReq1, true)
253+
resp2, found2 := engineKeys.getCheckKey(checkReq2, true)
254+
resp3, found3 := engineKeys.getCheckKey(checkReq3, true)
257255

258256
// Check that the key was not found
259257
assert.True(t, found1)
@@ -274,7 +272,7 @@ func TestEngineKeys_SetCheckKeyWithArguments(t *testing.T) {
274272
l := logger.New("debug")
275273

276274
// Initialize a new EngineKeys struct with a new cache.Cache instance
277-
engineKeys := CheckEngineWithKeys{nil, cache, l}
275+
engineKeys := CheckEngineWithKeys{nil, nil, cache, l}
278276

279277
// Create a new PermissionCheckRequest and PermissionCheckResponse
280278
checkReq := &base.PermissionCheckRequest{
@@ -319,23 +317,23 @@ func TestEngineKeys_SetCheckKeyWithArguments(t *testing.T) {
319317
}
320318

321319
// Set the value for the given key in the cache
322-
success := engineKeys.setCheckKey(checkReq, checkResp)
320+
success := engineKeys.setCheckKey(checkReq, checkResp, true)
323321

324322
cache.Wait()
325323

326324
// Check that the operation was successful
327325
assert.True(t, success)
328326

329327
// Retrieve the value for the given key from the cache
330-
resp, found := engineKeys.getCheckKey(checkReq)
328+
resp, found := engineKeys.getCheckKey(checkReq, true)
331329

332330
// Check that the key was found and the retrieved value is the same as the original value
333331
assert.True(t, found)
334332
assert.Equal(t, checkResp, resp)
335333
}
336334

337335
func TestEngineKeys_SetCheckKeyWithContext(t *testing.T) {
338-
value, err := anypb.New(wrapperspb.Bool(true))
336+
value, err := anypb.New(&base.BooleanValue{Data: true})
339337
if err != nil {
340338
}
341339

@@ -407,5 +405,5 @@ func TestEngineKeys_SetCheckKeyWithContext(t *testing.T) {
407405
},
408406
}
409407

410-
assert.Equal(t, "check|t1|test_version|test_snap_token|entity_type:entity_id#relation@subject_type:subject_id,entity_type:entity_id#is_public@boolean:true,day_of_a_week:saturday,day_of_a_year:35|test-entity:e1#test-rule(test_argument_1,test_argument_2)@user:u1", GenerateKey(checkReq))
408+
assert.Equal(t, "check|t1|test_version|test_snap_token|entity_type:entity_id#relation@subject_type:subject_id,entity_type:entity_id$is_public|boolean:true,day_of_a_week:saturday,day_of_a_year:35|test-entity:e1$test-rule(test_argument_1,test_argument_2)", GenerateKey(checkReq, false))
411409
}

0 commit comments

Comments
 (0)