From d60007871a900b01625fe887dbdcd96f58c0dbeb Mon Sep 17 00:00:00 2001 From: Tolga Ozen Date: Mon, 18 Sep 2023 10:20:39 -0700 Subject: [PATCH] fix: #681 visited map for recursive schema walker --- internal/schema/walker.go | 19 ++++++++++++++- internal/schema/walker_test.go | 42 ++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/internal/schema/walker.go b/internal/schema/walker.go index b1a99ae7f..0a9776614 100644 --- a/internal/schema/walker.go +++ b/internal/schema/walker.go @@ -3,18 +3,23 @@ package schema import ( "errors" + "github.com/Permify/permify/pkg/dsl/utils" base "github.com/Permify/permify/pkg/pb/base/v1" ) // Walker is a struct used for traversing a schema type Walker struct { schema *base.SchemaDefinition + + // map used to track visited nodes and avoid infinite recursion + visited map[string]struct{} } // NewWalker is a constructor for the Walker struct func NewWalker(schema *base.SchemaDefinition) *Walker { return &Walker{ - schema: schema, + schema: schema, + visited: make(map[string]struct{}), } } @@ -23,6 +28,18 @@ func (w *Walker) Walk( entityType string, permission string, ) error { + // Generate a unique key for the entityType and permission combination + key := utils.Key(entityType, permission) + + // Check if the entity-permission combination has already been visited + if _, ok := w.visited[key]; ok { + // If already visited, exit early to avoid redundant processing or infinite recursion + return nil + } + + // Mark the entity-permission combination as visited + w.visited[key] = struct{}{} + // Lookup the entity definition in the schema def, ok := w.schema.EntityDefinitions[entityType] if !ok { diff --git a/internal/schema/walker_test.go b/internal/schema/walker_test.go index 06b38e4cf..7ab02a930 100644 --- a/internal/schema/walker_test.go +++ b/internal/schema/walker_test.go @@ -107,5 +107,47 @@ var _ = Describe("walker", func() { Expect(err).Should(Equal(ErrUnimplemented)) }) + + It("Case 3", func() { + sch, err := parser.NewParser(` + entity user {} + + entity tag { + relation assignee @department + permission view_document = assignee.view_document + } + + entity document { + relation owner @department + + permission edit = owner.edit_document + permission view = owner.view_document or owner.peek_document + } + + entity department { + relation parent @department + relation admin @user + relation viewer @user + relation assigned_tag @tag + + permission peek_document = assigned_tag.view_document or parent.peek_document + permission edit_document = admin or parent.edit_document + permission view_document = viewer or admin or parent.view_document + } + `).Parse() + + Expect(err).ShouldNot(HaveOccurred()) + + c := compiler.NewCompiler(true, sch) + e, r, err := c.Compile() + + Expect(err).ShouldNot(HaveOccurred()) + + w := NewWalker(NewSchemaFromEntityAndRuleDefinitions(e, r)) + + err = w.Walk("document", "view") + + Expect(err).ShouldNot(HaveOccurred()) + }) }) })