From 97ff50788fd9b24b405085aa20ce8d53ea764442 Mon Sep 17 00:00:00 2001 From: Tolga Ozen Date: Sat, 16 Sep 2023 14:53:23 +0300 Subject: [PATCH] feat: add schema reader for define cache keys type --- internal/engines/consistent/hashring.go | 31 +++++++- internal/engines/keys/keys.go | 83 ++++++++++++++------- internal/engines/keys/keys_test.go | 44 ++++++----- internal/engines/schemaBasedEntityFilter.go | 2 +- internal/engines/utils.go | 12 +-- internal/engines/utils_test.go | 36 ++++----- internal/invoke/invoke.go | 4 +- pkg/attribute/attribute.go | 67 +++++++++++------ pkg/attribute/attribute_test.go | 6 +- pkg/cmd/serve.go | 16 ++-- pkg/dsl/utils/util.go | 8 +- pkg/tuple/tuple.go | 20 +---- 12 files changed, 197 insertions(+), 132 deletions(-) diff --git a/internal/engines/consistent/hashring.go b/internal/engines/consistent/hashring.go index 16c870d80..6c3a26eb2 100644 --- a/internal/engines/consistent/hashring.go +++ b/internal/engines/consistent/hashring.go @@ -9,6 +9,8 @@ import ( "github.com/Permify/permify/internal/engines/keys" "github.com/Permify/permify/internal/invoke" + "github.com/Permify/permify/internal/schema" + "github.com/Permify/permify/internal/storage" hash "github.com/Permify/permify/pkg/consistent" "github.com/Permify/permify/pkg/gossip" "github.com/Permify/permify/pkg/logger" @@ -17,6 +19,8 @@ import ( // Hashring is a wrapper around the consistent hash implementation that type Hashring struct { + // schemaReader is responsible for reading schema information + schemaReader storage.SchemaReader checker invoke.Check gossip gossip.IGossip consistent hash.Consistent @@ -27,7 +31,7 @@ type Hashring struct { // NewCheckEngineWithHashring creates a new instance of EngineKeyManager by initializing an EngineKeys // struct with the provided cache.Cache instance. -func NewCheckEngineWithHashring(checker invoke.Check, consistent *hash.ConsistentHash, g gossip.IGossip, port string, l *logger.Logger) (invoke.Check, error) { +func NewCheckEngineWithHashring(checker invoke.Check, schemaReader storage.SchemaReader, consistent *hash.ConsistentHash, g gossip.IGossip, port string, l *logger.Logger) (invoke.Check, error) { // Return a new instance of EngineKeys with the provided cache ip, err := gossip.ExternalIP() if err != nil { @@ -35,6 +39,7 @@ func NewCheckEngineWithHashring(checker invoke.Check, consistent *hash.Consisten } return &Hashring{ + schemaReader: schemaReader, checker: checker, localNodeAddress: ip + ":" + port, gossip: g, @@ -44,8 +49,30 @@ func NewCheckEngineWithHashring(checker invoke.Check, consistent *hash.Consisten } func (c *Hashring) Check(ctx context.Context, request *base.PermissionCheckRequest) (response *base.PermissionCheckResponse, err error) { + // Retrieve entity definition + var en *base.EntityDefinition + en, _, err = c.schemaReader.ReadEntityDefinition(ctx, request.GetTenantId(), request.GetEntity().GetType(), request.GetMetadata().GetSchemaVersion()) + if err != nil { + return &base.PermissionCheckResponse{ + Can: base.CheckResult_CHECK_RESULT_DENIED, + Metadata: &base.PermissionCheckResponseMetadata{ + CheckCount: 0, + }, + }, err + } + + isRelational := false + + // Determine the type of the reference by name in the given entity definition. + tor, err := schema.GetTypeOfReferenceByNameInEntityDefinition(en, request.GetPermission()) + if err == nil { + if tor != base.EntityDefinition_REFERENCE_ATTRIBUTE { + isRelational = true + } + } + // Generate a unique checkKey string based on the provided PermissionCheckRequest - k := keys.GenerateKey(request) + k := keys.GenerateKey(request, isRelational) _, _, ok := c.consistent.Get(k) if !ok { diff --git a/internal/engines/keys/keys.go b/internal/engines/keys/keys.go index 203cdd02c..7873f429e 100644 --- a/internal/engines/keys/keys.go +++ b/internal/engines/keys/keys.go @@ -9,6 +9,8 @@ import ( "github.com/cespare/xxhash/v2" "github.com/Permify/permify/internal/invoke" + "github.com/Permify/permify/internal/schema" + "github.com/Permify/permify/internal/storage" "github.com/Permify/permify/pkg/attribute" "github.com/Permify/permify/pkg/cache" "github.com/Permify/permify/pkg/logger" @@ -18,25 +20,50 @@ import ( // CheckEngineWithKeys is a struct that holds an instance of a cache.Cache for managing engine keys. type CheckEngineWithKeys struct { - checker invoke.Check - cache cache.Cache - l *logger.Logger + // schemaReader is responsible for reading schema information + schemaReader storage.SchemaReader + checker invoke.Check + cache cache.Cache + l *logger.Logger } // NewCheckEngineWithKeys creates a new instance of EngineKeyManager by initializing an EngineKeys // struct with the provided cache.Cache instance. -func NewCheckEngineWithKeys(checker invoke.Check, cache cache.Cache, l *logger.Logger) invoke.Check { +func NewCheckEngineWithKeys(checker invoke.Check, schemaReader storage.SchemaReader, cache cache.Cache, l *logger.Logger) invoke.Check { return &CheckEngineWithKeys{ - checker: checker, - cache: cache, - l: l, + schemaReader: schemaReader, + checker: checker, + cache: cache, + l: l, } } // Check performs a permission check for a given request, using the cached results if available. func (c *CheckEngineWithKeys) Check(ctx context.Context, request *base.PermissionCheckRequest) (response *base.PermissionCheckResponse, err error) { + // Retrieve entity definition + var en *base.EntityDefinition + en, _, err = c.schemaReader.ReadEntityDefinition(ctx, request.GetTenantId(), request.GetEntity().GetType(), request.GetMetadata().GetSchemaVersion()) + if err != nil { + return &base.PermissionCheckResponse{ + Can: base.CheckResult_CHECK_RESULT_DENIED, + Metadata: &base.PermissionCheckResponseMetadata{ + CheckCount: 0, + }, + }, err + } + + isRelational := false + + // Determine the type of the reference by name in the given entity definition. + tor, err := schema.GetTypeOfReferenceByNameInEntityDefinition(en, request.GetPermission()) + if err == nil { + if tor != base.EntityDefinition_REFERENCE_ATTRIBUTE { + isRelational = true + } + } + // Try to get the cached result for the given request. - res, found := c.getCheckKey(request) + res, found := c.getCheckKey(request, isRelational) // If a cached result is found, handle exclusion and return the result. if found { @@ -63,7 +90,7 @@ func (c *CheckEngineWithKeys) Check(ctx context.Context, request *base.Permissio c.setCheckKey(request, &base.PermissionCheckResponse{ Can: res.GetCan(), Metadata: &base.PermissionCheckResponseMetadata{}, - }) + }, isRelational) // Return the result of the permission check. return res, err @@ -72,7 +99,7 @@ func (c *CheckEngineWithKeys) Check(ctx context.Context, request *base.Permissio // GetCheckKey retrieves the value for the given key from the EngineKeys cache. // It returns the PermissionCheckResponse if the key is found, and a boolean value // indicating whether the key was found or not. -func (c *CheckEngineWithKeys) getCheckKey(key *base.PermissionCheckRequest) (*base.PermissionCheckResponse, bool) { +func (c *CheckEngineWithKeys) getCheckKey(key *base.PermissionCheckRequest, isRelational bool) (*base.PermissionCheckResponse, bool) { if key == nil { // If either the key or value is nil, return false return nil, false @@ -82,7 +109,7 @@ func (c *CheckEngineWithKeys) getCheckKey(key *base.PermissionCheckRequest) (*ba h := xxhash.New() // Write the checkKey string to the hash object - _, err := h.Write([]byte(GenerateKey(key))) + _, err := h.Write([]byte(GenerateKey(key, isRelational))) if err != nil { // If there's an error, return nil and false return nil, false @@ -112,7 +139,7 @@ func (c *CheckEngineWithKeys) getCheckKey(key *base.PermissionCheckRequest) (*ba // setCheckKey is a function to set a check key in the cache of the CheckEngineWithKeys. // It takes a permission check request as a key, a permission check response as a value, // and returns a boolean value indicating if the operation was successful. -func (c *CheckEngineWithKeys) setCheckKey(key *base.PermissionCheckRequest, value *base.PermissionCheckResponse) bool { +func (c *CheckEngineWithKeys) setCheckKey(key *base.PermissionCheckRequest, value *base.PermissionCheckResponse, isRelational bool) bool { // If either the key or the value is nil, return false. if key == nil || value == nil { return false @@ -123,7 +150,7 @@ func (c *CheckEngineWithKeys) setCheckKey(key *base.PermissionCheckRequest, valu // Generate a key string from the permission check request and write it to the hash. // If there's an error while writing to the hash, return false. - size, err := h.Write([]byte(GenerateKey(key))) + size, err := h.Write([]byte(GenerateKey(key, isRelational))) if err != nil { return false } @@ -133,18 +160,12 @@ func (c *CheckEngineWithKeys) setCheckKey(key *base.PermissionCheckRequest, valu // Set the hashed key and the check result in the cache, using the size of the hashed key as an expiry. // The Set method should return true if the operation was successful, so return the result. - return c.cache.Set(k, value.Can, int64(size)) + return c.cache.Set(k, value.GetCan(), int64(size)) } // GenerateKey function takes a PermissionCheckRequest and generates a unique key // Key format: check|{tenant_id}|{schema_version}|{snap_token}|{context}|{entity:id#permission(optional_arguments)@subject:id#optional_relation} -func GenerateKey(key *base.PermissionCheckRequest) string { - // Create a new EntityAndRelation object with the entity and permission from the key - entityRelation := &base.EntityAndRelation{ - Entity: key.GetEntity(), - Relation: key.GetPermission(), - } - +func GenerateKey(key *base.PermissionCheckRequest, isRelational bool) string { // Initialize the parts slice with the string "check" parts := []string{"check"} @@ -168,12 +189,22 @@ func GenerateKey(key *base.PermissionCheckRequest) string { parts = append(parts, ContextToString(ctx)) } - // Convert entity and relation to string with any optional arguments and append to parts - entityRelationString := tuple.EntityAndRelationToString(entityRelation, key.GetArguments()...) - subjectString := tuple.SubjectToString(key.GetSubject()) + if isRelational { + // Convert entity and relation to string with any optional arguments and append to parts + entityRelationString := tuple.EntityAndRelationToString(key.GetEntity(), key.GetPermission()) + + subjectString := tuple.SubjectToString(key.GetSubject()) + + if entityRelationString != "" { + parts = append(parts, fmt.Sprintf("%s@%s", entityRelationString, subjectString)) + } - if entityRelationString != "" { - parts = append(parts, fmt.Sprintf("%s@%s", entityRelationString, subjectString)) + } else { + parts = append(parts, attribute.EntityAndCallOrAttributeToString( + key.GetEntity(), + key.GetPermission(), + key.GetArguments()..., + )) } // Join all parts with "|" delimiter to generate the final key diff --git a/internal/engines/keys/keys_test.go b/internal/engines/keys/keys_test.go index 00b807732..cc685c23b 100644 --- a/internal/engines/keys/keys_test.go +++ b/internal/engines/keys/keys_test.go @@ -3,11 +3,9 @@ package keys import ( "testing" + "github.com/stretchr/testify/assert" "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/structpb" - "google.golang.org/protobuf/types/known/wrapperspb" - - "github.com/stretchr/testify/assert" "github.com/Permify/permify/pkg/cache/ristretto" "github.com/Permify/permify/pkg/logger" @@ -22,7 +20,7 @@ func TestEngineKeys_SetCheckKey(t *testing.T) { l := logger.New("debug") // Initialize a new EngineKeys struct with a new cache.Cache instance - engineKeys := CheckEngineWithKeys{nil, cache, l} + engineKeys := CheckEngineWithKeys{nil, nil, cache, l} // Create a new PermissionCheckRequest and PermissionCheckResponse checkReq := &base.PermissionCheckRequest{ @@ -51,7 +49,7 @@ func TestEngineKeys_SetCheckKey(t *testing.T) { } // Set the value for the given key in the cache - success := engineKeys.setCheckKey(checkReq, checkResp) + success := engineKeys.setCheckKey(checkReq, checkResp, true) cache.Wait() @@ -59,7 +57,7 @@ func TestEngineKeys_SetCheckKey(t *testing.T) { assert.True(t, success) // Retrieve the value for the given key from the cache - resp, found := engineKeys.getCheckKey(checkReq) + resp, found := engineKeys.getCheckKey(checkReq, true) // Check that the key was found and the retrieved value is the same as the original value assert.True(t, found) @@ -74,7 +72,7 @@ func TestEngineKeys_SetCheckKey_WithHashError(t *testing.T) { l := logger.New("debug") // Initialize a new EngineKeys struct with a new cache.Cache instance - engineKeys := CheckEngineWithKeys{nil, cache, l} + engineKeys := CheckEngineWithKeys{nil, nil, cache, l} // Create a new PermissionCheckRequest and PermissionCheckResponse checkReq := &base.PermissionCheckRequest{ @@ -103,7 +101,7 @@ func TestEngineKeys_SetCheckKey_WithHashError(t *testing.T) { } // Force an error while writing the key to the hash object by passing a nil key - success := engineKeys.setCheckKey(nil, checkResp) + success := engineKeys.setCheckKey(nil, checkResp, true) cache.Wait() @@ -111,7 +109,7 @@ func TestEngineKeys_SetCheckKey_WithHashError(t *testing.T) { assert.False(t, success) // Retrieve the value for the given key from the cache - resp, found := engineKeys.getCheckKey(checkReq) + resp, found := engineKeys.getCheckKey(checkReq, true) // Check that the key was not found assert.False(t, found) @@ -126,7 +124,7 @@ func TestEngineKeys_GetCheckKey_KeyNotFound(t *testing.T) { l := logger.New("debug") // Initialize a new EngineKeys struct with a new cache.Cache instance - engineKeys := CheckEngineWithKeys{nil, cache, l} + engineKeys := CheckEngineWithKeys{nil, nil, cache, l} // Create a new PermissionCheckRequest checkReq := &base.PermissionCheckRequest{ @@ -148,7 +146,7 @@ func TestEngineKeys_GetCheckKey_KeyNotFound(t *testing.T) { } // Retrieve the value for a non-existent key from the cache - resp, found := engineKeys.getCheckKey(checkReq) + resp, found := engineKeys.getCheckKey(checkReq, true) // Check that the key was not found assert.False(t, found) @@ -163,7 +161,7 @@ func TestEngineKeys_SetAndGetMultipleKeys(t *testing.T) { l := logger.New("debug") // Initialize a new EngineKeys struct with a new cache.Cache instance - engineKeys := CheckEngineWithKeys{nil, cache, l} + engineKeys := CheckEngineWithKeys{nil, nil, cache, l} // Create some new PermissionCheckRequests and PermissionCheckResponses checkReq1 := &base.PermissionCheckRequest{ @@ -239,9 +237,9 @@ func TestEngineKeys_SetAndGetMultipleKeys(t *testing.T) { } // Set the values for the given keys in the cache - success1 := engineKeys.setCheckKey(checkReq1, checkResp1) - success2 := engineKeys.setCheckKey(checkReq2, checkResp2) - success3 := engineKeys.setCheckKey(checkReq3, checkResp3) + success1 := engineKeys.setCheckKey(checkReq1, checkResp1, true) + success2 := engineKeys.setCheckKey(checkReq2, checkResp2, true) + success3 := engineKeys.setCheckKey(checkReq3, checkResp3, true) cache.Wait() @@ -251,9 +249,9 @@ func TestEngineKeys_SetAndGetMultipleKeys(t *testing.T) { assert.True(t, success3) // Retrieve the value for the given key from the cache - resp1, found1 := engineKeys.getCheckKey(checkReq1) - resp2, found2 := engineKeys.getCheckKey(checkReq2) - resp3, found3 := engineKeys.getCheckKey(checkReq3) + resp1, found1 := engineKeys.getCheckKey(checkReq1, true) + resp2, found2 := engineKeys.getCheckKey(checkReq2, true) + resp3, found3 := engineKeys.getCheckKey(checkReq3, true) // Check that the key was not found assert.True(t, found1) @@ -274,7 +272,7 @@ func TestEngineKeys_SetCheckKeyWithArguments(t *testing.T) { l := logger.New("debug") // Initialize a new EngineKeys struct with a new cache.Cache instance - engineKeys := CheckEngineWithKeys{nil, cache, l} + engineKeys := CheckEngineWithKeys{nil, nil, cache, l} // Create a new PermissionCheckRequest and PermissionCheckResponse checkReq := &base.PermissionCheckRequest{ @@ -319,7 +317,7 @@ func TestEngineKeys_SetCheckKeyWithArguments(t *testing.T) { } // Set the value for the given key in the cache - success := engineKeys.setCheckKey(checkReq, checkResp) + success := engineKeys.setCheckKey(checkReq, checkResp, true) cache.Wait() @@ -327,7 +325,7 @@ func TestEngineKeys_SetCheckKeyWithArguments(t *testing.T) { assert.True(t, success) // Retrieve the value for the given key from the cache - resp, found := engineKeys.getCheckKey(checkReq) + resp, found := engineKeys.getCheckKey(checkReq, true) // Check that the key was found and the retrieved value is the same as the original value assert.True(t, found) @@ -335,7 +333,7 @@ func TestEngineKeys_SetCheckKeyWithArguments(t *testing.T) { } func TestEngineKeys_SetCheckKeyWithContext(t *testing.T) { - value, err := anypb.New(wrapperspb.Bool(true)) + value, err := anypb.New(&base.BooleanValue{Data: true}) if err != nil { } @@ -407,5 +405,5 @@ func TestEngineKeys_SetCheckKeyWithContext(t *testing.T) { }, } - 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)) + 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)) } diff --git a/internal/engines/schemaBasedEntityFilter.go b/internal/engines/schemaBasedEntityFilter.go index e2535ce8d..4446c4786 100644 --- a/internal/engines/schemaBasedEntityFilter.go +++ b/internal/engines/schemaBasedEntityFilter.go @@ -250,7 +250,7 @@ func (engine *SchemaBasedEntityFilter) l( g *errgroup.Group, // An errgroup used for executing goroutines. publisher *BulkEntityPublisher, // A custom publisher that publishes results in bulk. ) error { // Returns an error if one occurs during execution. - if !visits.Add(found) { // If the entity and relation has already been visited. + if !visits.Add(found.GetEntity(), found.GetRelation()) { // If the entity and relation has already been visited. return nil } diff --git a/internal/engines/utils.go b/internal/engines/utils.go index b68957500..67c7752b1 100644 --- a/internal/engines/utils.go +++ b/internal/engines/utils.go @@ -79,8 +79,8 @@ type ERMap struct { value sync.Map } -func (s *ERMap) Add(onr *base.EntityAndRelation) bool { - key := tuple.EntityAndRelationToString(onr) +func (s *ERMap) Add(entity *base.Entity, relation string) bool { + key := tuple.EntityAndRelationToString(entity, relation) _, existed := s.value.LoadOrStore(key, struct{}{}) return !existed } @@ -217,7 +217,7 @@ func getEmptyProtoValueForType(typ base.AttributeType) (*anypb.Any, error) { case base.AttributeType_ATTRIBUTE_TYPE_BOOLEAN: // Create an empty protobuf Boolean message with a default value of false - value, err := anypb.New(&base.BoolValue{Data: false}) + value, err := anypb.New(&base.BooleanValue{Data: false}) if err != nil { return nil, err } @@ -225,7 +225,7 @@ func getEmptyProtoValueForType(typ base.AttributeType) (*anypb.Any, error) { case base.AttributeType_ATTRIBUTE_TYPE_BOOLEAN_ARRAY: // Create an empty protobuf BooleanArray message - value, err := anypb.New(&base.BoolArrayValue{Data: []bool{}}) + value, err := anypb.New(&base.BooleanArrayValue{Data: []bool{}}) if err != nil { return nil, err } @@ -249,9 +249,9 @@ func ConvertToAnyPB(value interface{}) (*anypb.Any, error) { // Use a type switch to handle different types of value. switch v := value.(type) { case bool: - anyValue, err = anypb.New(&base.BoolValue{Data: v}) + anyValue, err = anypb.New(&base.BooleanValue{Data: v}) case []bool: - anyValue, err = anypb.New(&base.BoolArrayValue{Data: v}) + anyValue, err = anypb.New(&base.BooleanArrayValue{Data: v}) case int: anyValue, err = anypb.New(&base.IntegerValue{Data: int32(v)}) case []int32: diff --git a/internal/engines/utils_test.go b/internal/engines/utils_test.go index d2d92f7aa..1392a6da6 100644 --- a/internal/engines/utils_test.go +++ b/internal/engines/utils_test.go @@ -22,7 +22,7 @@ var _ = Describe("utils", func() { { typ: base.AttributeType_ATTRIBUTE_TYPE_STRING, expectedAny: &anypb.Any{ - TypeUrl: "type.googleapis.com/base.v1.String", + TypeUrl: "type.googleapis.com/base.v1.StringValue", Value: []byte(""), }, expectedErr: nil, @@ -30,7 +30,7 @@ var _ = Describe("utils", func() { { typ: base.AttributeType_ATTRIBUTE_TYPE_STRING_ARRAY, expectedAny: &anypb.Any{ - TypeUrl: "type.googleapis.com/base.v1.StringArray", + TypeUrl: "type.googleapis.com/base.v1.StringArrayValue", Value: []byte(""), }, expectedErr: nil, @@ -38,7 +38,7 @@ var _ = Describe("utils", func() { { typ: base.AttributeType_ATTRIBUTE_TYPE_INTEGER, expectedAny: &anypb.Any{ - TypeUrl: "type.googleapis.com/base.v1.Integer", + TypeUrl: "type.googleapis.com/base.v1.IntegerValue", Value: []byte(""), }, expectedErr: nil, @@ -46,7 +46,7 @@ var _ = Describe("utils", func() { { typ: base.AttributeType_ATTRIBUTE_TYPE_INTEGER_ARRAY, expectedAny: &anypb.Any{ - TypeUrl: "type.googleapis.com/base.v1.IntegerArray", + TypeUrl: "type.googleapis.com/base.v1.IntegerArrayValue", Value: []byte(""), }, expectedErr: nil, @@ -54,7 +54,7 @@ var _ = Describe("utils", func() { { typ: base.AttributeType_ATTRIBUTE_TYPE_DOUBLE, expectedAny: &anypb.Any{ - TypeUrl: "type.googleapis.com/base.v1.Double", + TypeUrl: "type.googleapis.com/base.v1.DoubleValue", Value: []byte(""), }, expectedErr: nil, @@ -62,7 +62,7 @@ var _ = Describe("utils", func() { { typ: base.AttributeType_ATTRIBUTE_TYPE_DOUBLE_ARRAY, expectedAny: &anypb.Any{ - TypeUrl: "type.googleapis.com/base.v1.DoubleArray", + TypeUrl: "type.googleapis.com/base.v1.DoubleArrayValue", Value: []byte(""), }, expectedErr: nil, @@ -70,7 +70,7 @@ var _ = Describe("utils", func() { { typ: base.AttributeType_ATTRIBUTE_TYPE_BOOLEAN, expectedAny: &anypb.Any{ - TypeUrl: "type.googleapis.com/base.v1.Boolean", + TypeUrl: "type.googleapis.com/base.v1.BooleanValue", Value: []byte(""), }, expectedErr: nil, @@ -78,7 +78,7 @@ var _ = Describe("utils", func() { { typ: base.AttributeType_ATTRIBUTE_TYPE_BOOLEAN_ARRAY, expectedAny: &anypb.Any{ - TypeUrl: "type.googleapis.com/base.v1.BooleanArray", + TypeUrl: "type.googleapis.com/base.v1.BooleanArrayValue", Value: []byte(""), }, expectedErr: nil, @@ -125,14 +125,14 @@ var _ = Describe("utils", func() { value interface{} typeName string }{ - {true, "base.v1.Boolean"}, - {boolArray, "base.v1.BooleanArray"}, - {intValue, "base.v1.Integer"}, - {intArray, "base.v1.IntegerArray"}, - {floatValue, "base.v1.Double"}, - {floatArray, "base.v1.DoubleArray"}, - {stringValue, "base.v1.String"}, - {stringArray, "base.v1.StringArray"}, + {true, "base.v1.BooleanValue"}, + {boolArray, "base.v1.BooleanArrayValue"}, + {intValue, "base.v1.IntegerValue"}, + {intArray, "base.v1.IntegerArrayValue"}, + {floatValue, "base.v1.DoubleValue"}, + {floatArray, "base.v1.DoubleArrayValue"}, + {stringValue, "base.v1.StringValue"}, + {stringArray, "base.v1.StringArrayValue"}, } for _, testCase := range supportedTestCases { @@ -210,7 +210,7 @@ var _ = Describe("utils", func() { duplicates := getDuplicates(inputWithDuplicates) - Expect(reflect.DeepEqual(expectedDuplicates, duplicates)).Should(Equal(true)) + Expect(isSameArray(expectedDuplicates, duplicates)).Should(Equal(true)) // Test case with no duplicates inputWithoutDuplicates := []string{"apple", "banana", "cherry", "date"} @@ -218,7 +218,7 @@ var _ = Describe("utils", func() { noDuplicates := getDuplicates(inputWithoutDuplicates) - Expect(reflect.DeepEqual(expectedNoDuplicates, noDuplicates)).Should(Equal(true)) + Expect(isSameArray(expectedNoDuplicates, noDuplicates)).Should(Equal(true)) // Test case with an empty input slice emptyInput := []string{} diff --git a/internal/invoke/invoke.go b/internal/invoke/invoke.go index 3d008e7fe..b6e877201 100644 --- a/internal/invoke/invoke.go +++ b/internal/invoke/invoke.go @@ -132,7 +132,7 @@ func (invoker *DirectInvoker) Check(ctx context.Context, request *base.Permissio ctx, span := tracer.Start(ctx, "check", trace.WithAttributes( attribute.KeyValue{Key: "tenant_id", Value: attribute.StringValue(request.GetTenantId())}, attribute.KeyValue{Key: "entity", Value: attribute.StringValue(tuple.EntityToString(request.GetEntity()))}, - attribute.KeyValue{Key: "permission", Value: attribute.StringValue(tuple.RelationToString(request.GetPermission()))}, + attribute.KeyValue{Key: "permission", Value: attribute.StringValue(request.GetPermission())}, attribute.KeyValue{Key: "subject", Value: attribute.StringValue(tuple.SubjectToString(request.GetSubject()))}, )) defer span.End() @@ -208,7 +208,7 @@ func (invoker *DirectInvoker) Check(ctx context.Context, request *base.Permissio // Increase the check count in the metrics. invoker.checkCounter.Add(ctx, 1) - span.SetAttributes(attribute.KeyValue{Key: "can", Value: attribute.StringValue(response.Can.String())}) + span.SetAttributes(attribute.KeyValue{Key: "can", Value: attribute.StringValue(response.GetCan().String())}) return } diff --git a/pkg/attribute/attribute.go b/pkg/attribute/attribute.go index 945050882..342c7e982 100644 --- a/pkg/attribute/attribute.go +++ b/pkg/attribute/attribute.go @@ -14,7 +14,7 @@ import ( const ( ENTITY = "%s:%s" - ATTRIBUTE = "#%s" + ATTRIBUTE = "$%s" VALUE = "%s:%s" ) @@ -58,7 +58,7 @@ func Attribute(attribute string) (*base.Attribute, error) { if err != nil { return nil, fmt.Errorf("failed to parse boolean: %v", err) } - wrapped = &base.BoolValue{Data: boolVal} + wrapped = &base.BooleanValue{Data: boolVal} case "boolean[]": var ba []bool val := strings.Split(v[1], ",") @@ -69,7 +69,7 @@ func Attribute(attribute string) (*base.Attribute, error) { } ba = append(ba, boolVal) } - wrapped = &base.BoolArrayValue{Data: ba} + wrapped = &base.BooleanArrayValue{Data: ba} case "string": wrapped = &base.StringValue{Data: v[1]} case "string[]": @@ -154,23 +154,44 @@ func EntityToString(entity *base.Entity) string { return fmt.Sprintf(ENTITY, entity.GetType(), entity.GetId()) } +// EntityAndCallOrAttributeToString - +func EntityAndCallOrAttributeToString(entity *base.Entity, attributeOrCall string, arguments ...*base.Argument) string { + return EntityToString(entity) + fmt.Sprintf(ATTRIBUTE, CallOrAttributeToString(attributeOrCall, arguments...)) +} + +// CallOrAttributeToString - +func CallOrAttributeToString(attributeOrCall string, arguments ...*base.Argument) string { + if len(arguments) > 0 { + var args []string + for _, arg := range arguments { + if arg.GetComputedAttribute() != nil { + args = append(args, arg.GetComputedAttribute().GetName()) + } else { + args = append(args, "request."+arg.GetContextAttribute().GetName()) + } + } + return fmt.Sprintf("%s(%s)", attributeOrCall, strings.Join(args, ",")) + } + return attributeOrCall +} + func TypeUrlToString(url string) string { switch url { - case "type.googleapis.com/base.v1.String": + case "type.googleapis.com/base.v1.StringValue": return "string" - case "type.googleapis.com/base.v1.Boolean": + case "type.googleapis.com/base.v1.BooleanValue": return "boolean" - case "type.googleapis.com/base.v1.Integer": + case "type.googleapis.com/base.v1.IntegerValue": return "integer" - case "type.googleapis.com/base.v1.Double": + case "type.googleapis.com/base.v1.DoubleValue": return "double" - case "type.googleapis.com/base.v1.StringArray": + case "type.googleapis.com/base.v1.StringArrayValue": return "string[]" - case "type.googleapis.com/base.v1.BooleanArray": + case "type.googleapis.com/base.v1.BooleanArrayValue": return "boolean[]" - case "type.googleapis.com/base.v1.IntegerArray": + case "type.googleapis.com/base.v1.IntegerArrayValue": return "integer[]" - case "type.googleapis.com/base.v1.DoubleArray": + case "type.googleapis.com/base.v1.DoubleArrayValue": return "double[]" default: return "" @@ -183,14 +204,14 @@ func AnyToString(any *anypb.Any) string { // Convert the Any proto message into string based on its TypeUrl switch any.TypeUrl { - case "type.googleapis.com/base.v1.Boolean": - boolVal := &base.BoolValue{} + case "type.googleapis.com/base.v1.BooleanValue": + boolVal := &base.BooleanValue{} if err := any.UnmarshalTo(boolVal); err != nil { return "undefined" } str = strconv.FormatBool(boolVal.Data) - case "type.googleapis.com/base.v1.BooleanArray": - boolVal := &base.BoolArrayValue{} + case "type.googleapis.com/base.v1.BooleanArrayValue": + boolVal := &base.BooleanArrayValue{} if err := any.UnmarshalTo(boolVal); err != nil { return "undefined" } @@ -199,13 +220,13 @@ func AnyToString(any *anypb.Any) string { strs = append(strs, strconv.FormatBool(b)) } str = strings.Join(strs, ",") - case "type.googleapis.com/base.v1.String": + case "type.googleapis.com/base.v1.StringValue": stringVal := &base.StringValue{} if err := any.UnmarshalTo(stringVal); err != nil { return "undefined" } str = stringVal.Data - case "type.googleapis.com/base.v1.StringArray": + case "type.googleapis.com/base.v1.StringArrayValue": stringVal := &base.StringArrayValue{} if err := any.UnmarshalTo(stringVal); err != nil { return "undefined" @@ -215,13 +236,13 @@ func AnyToString(any *anypb.Any) string { strs = append(strs, v) } str = strings.Join(strs, ",") - case "type.googleapis.com/base.v1.Double": + case "type.googleapis.com/base.v1.DoubleValue": doubleVal := &base.DoubleValue{} if err := any.UnmarshalTo(doubleVal); err != nil { return "undefined" } str = strconv.FormatFloat(doubleVal.Data, 'f', -1, 64) - case "type.googleapis.com/base.v1.DoubleArray": + case "type.googleapis.com/base.v1.DoubleArrayValue": doubleVal := &base.DoubleArrayValue{} if err := any.UnmarshalTo(doubleVal); err != nil { return "undefined" @@ -231,13 +252,13 @@ func AnyToString(any *anypb.Any) string { strs = append(strs, strconv.FormatFloat(v, 'f', -1, 64)) } str = strings.Join(strs, ",") - case "type.googleapis.com/base.v1.Integer": + case "type.googleapis.com/base.v1.IntegerValue": intVal := &base.IntegerValue{} if err := any.UnmarshalTo(intVal); err != nil { return "undefined" } str = strconv.Itoa(int(intVal.Data)) - case "type.googleapis.com/base.v1.IntegerArray": + case "type.googleapis.com/base.v1.IntegerArrayValue": intVal := &base.IntegerArrayValue{} if err := any.UnmarshalTo(intVal); err != nil { return "undefined" @@ -305,9 +326,9 @@ func ValidateValue(any *anypb.Any, attributeType base.AttributeType) error { case base.AttributeType_ATTRIBUTE_TYPE_STRING_ARRAY: target = &base.StringArrayValue{} case base.AttributeType_ATTRIBUTE_TYPE_BOOLEAN: - target = &base.BoolValue{} + target = &base.BooleanValue{} case base.AttributeType_ATTRIBUTE_TYPE_BOOLEAN_ARRAY: - target = &base.BoolArrayValue{} + target = &base.BooleanArrayValue{} default: // If attributeType doesn't match any of the known types, return an error indicating invalid argument. return errors.New(base.ErrorCode_ERROR_CODE_INVALID_ARGUMENT.String()) diff --git a/pkg/attribute/attribute_test.go b/pkg/attribute/attribute_test.go index d349bbf7f..f05a2afcc 100644 --- a/pkg/attribute/attribute_test.go +++ b/pkg/attribute/attribute_test.go @@ -18,9 +18,9 @@ func TestAttribute(t *testing.T) { var _ = Describe("attribute", func() { Context("Attribute", func() { - isPublic, _ := anypb.New(&base.Boolean{Value: true}) - double, _ := anypb.New(&base.Double{Value: 100}) - integer, _ := anypb.New(&base.Integer{Value: 45}) + isPublic, _ := anypb.New(&base.BooleanValue{Data: true}) + double, _ := anypb.New(&base.DoubleValue{Data: 100}) + integer, _ := anypb.New(&base.IntegerValue{Data: 45}) It("ToString", func() { tests := []struct { diff --git a/pkg/cmd/serve.go b/pkg/cmd/serve.go index 94a2174dc..696c305ec 100644 --- a/pkg/cmd/serve.go +++ b/pkg/cmd/serve.go @@ -248,17 +248,17 @@ func serve() func(cmd *cobra.Command, args []string) error { // Initialize the engines using the key manager, schema reader, and relationship reader checkEngine := engines.NewCheckEngine(schemaReader, dataReader, engines.CheckConcurrencyLimit(cfg.Service.Permission.ConcurrencyLimit)) expandEngine := engines.NewExpandEngine(schemaReader, dataReader) - lookupEngine := engines.NewLookupEngine(checkEngine, schemaReader, schemaBaseEntityFilter, massEntityFilter, schemaBaseSubjectFilter, massSubjectFilter, engines.LookupConcurrencyLimit(cfg.Service.Permission.BulkLimit)) - subjectPermissionEngine := engines.NewSubjectPermission(checkEngine, schemaReader, engines.SubjectPermissionConcurrencyLimit(cfg.Service.Permission.ConcurrencyLimit)) - var check invoke.Check + var checker invoke.Check if cfg.Distributed.Enabled { - check, err = consistent.NewCheckEngineWithHashring( + checker, err = consistent.NewCheckEngineWithHashring( keys.NewCheckEngineWithKeys( checkEngine, + schemaReader, engineKeyCache, l, ), + schemaReader, consistencyChecker, gossipEngine, cfg.Server.GRPC.Port, @@ -268,17 +268,21 @@ func serve() func(cmd *cobra.Command, args []string) error { return err } } else { - check = keys.NewCheckEngineWithKeys( + checker = keys.NewCheckEngineWithKeys( checkEngine, + schemaReader, engineKeyCache, l, ) } + lookupEngine := engines.NewLookupEngine(checker, schemaReader, schemaBaseEntityFilter, massEntityFilter, schemaBaseSubjectFilter, massSubjectFilter, engines.LookupConcurrencyLimit(cfg.Service.Permission.BulkLimit)) + subjectPermissionEngine := engines.NewSubjectPermission(checker, schemaReader, engines.SubjectPermissionConcurrencyLimit(cfg.Service.Permission.ConcurrencyLimit)) + invoker := invoke.NewDirectInvoker( schemaReader, dataReader, - check, + checker, expandEngine, lookupEngine, subjectPermissionEngine, diff --git a/pkg/dsl/utils/util.go b/pkg/dsl/utils/util.go index f0456229a..c11912364 100644 --- a/pkg/dsl/utils/util.go +++ b/pkg/dsl/utils/util.go @@ -66,8 +66,8 @@ func ConvertProtoAnyToInterface(a *anypb.Any) interface{} { return "" } return stringValue.GetData() - case "type.googleapis.com/base.v1.BoolValue": - boolValue := &base.BoolValue{} + case "type.googleapis.com/base.v1.BooleanValue": + boolValue := &base.BooleanValue{} if err := anypb.UnmarshalTo(a, boolValue, proto.UnmarshalOptions{}); err != nil { return false } @@ -90,8 +90,8 @@ func ConvertProtoAnyToInterface(a *anypb.Any) interface{} { return []string{} } return stringArrayValue.GetData() - case "type.googleapis.com/base.v1.BoolArrayValue": - booleanArrayValue := &base.BoolArrayValue{} + case "type.googleapis.com/base.v1.BooleanArrayValue": + booleanArrayValue := &base.BooleanArrayValue{} if err := anypb.UnmarshalTo(a, booleanArrayValue, proto.UnmarshalOptions{}); err != nil { return []bool{} } diff --git a/pkg/tuple/tuple.go b/pkg/tuple/tuple.go index 91c14d463..ddc84404b 100644 --- a/pkg/tuple/tuple.go +++ b/pkg/tuple/tuple.go @@ -61,24 +61,8 @@ func SubjectToEAR(subject *base.Subject) *base.EntityAndRelation { } // EntityAndRelationToString converts an EntityAndRelation object to string format -func EntityAndRelationToString(entityAndRelation *base.EntityAndRelation, arguments ...*base.Argument) string { - return EntityToString(entityAndRelation.GetEntity()) + fmt.Sprintf(RELATION, RelationToString(entityAndRelation.GetRelation(), arguments...)) -} - -// RelationToString converts a relation string to string format -func RelationToString(relation string, arguments ...*base.Argument) string { - if len(arguments) > 0 { - var args []string - for _, arg := range arguments { - if arg.GetComputedAttribute() != nil { - args = append(args, arg.GetComputedAttribute().GetName()) - } else { - args = append(args, "request."+arg.GetContextAttribute().GetName()) - } - } - return fmt.Sprintf("%s(%s)", relation, strings.Join(args, ",")) - } - return relation +func EntityAndRelationToString(entity *base.Entity, relation string) string { + return EntityToString(entity) + fmt.Sprintf(RELATION, relation) } // EntityToString converts an Entity object to string format