Skip to content

Commit

Permalink
Fix experimental LookupResources2 to shear the tree earlier on indire…
Browse files Browse the repository at this point in the history
…ct permissions
  • Loading branch information
josephschorr committed Jul 30, 2024
1 parent 383a05f commit 639b959
Show file tree
Hide file tree
Showing 5 changed files with 459 additions and 97 deletions.
299 changes: 299 additions & 0 deletions internal/dispatch/graph/lookupresources2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"slices"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -802,6 +803,27 @@ func TestLookupResources2EnsureCheckHints(t *testing.T) {
},
expectedResources: []string{"masterplan"},
},
{
name: "indirect nested",
schema: `definition user {}
definition document {
relation editor: user
relation viewer: user
permission indirect_view = viewer & editor
permission view = indirect_view
}`,
relationships: []*core.RelationTuple{
tuple.MustParse("document:masterplan#viewer@user:tom"),
tuple.MustParse("document:masterplan#editor@user:tom"),
},
resourceRelation: RR("document", "view"),
subject: ONR("user", "tom", "..."),
disallowedQueries: []*core.RelationReference{
RR("document", "viewer"),
},
expectedResources: []string{"masterplan"},
},
{
name: "indirect result with alias",
schema: `definition user {}
Expand Down Expand Up @@ -844,6 +866,278 @@ func TestLookupResources2EnsureCheckHints(t *testing.T) {
},
expectedResources: []string{"masterplan"},
},
{
name: "multiple paths for checking",
schema: `definition user {}
definition document {
relation editor: user
relation viewer: user
relation viewer2: user
relation admin: user
permission viewer_of_some_kind = viewer + viewer2
permission view = viewer_of_some_kind & editor
permission view_and_admin = view & admin
}`,
relationships: []*core.RelationTuple{
tuple.MustParse("document:masterplan#viewer@user:tom"),
tuple.MustParse("document:masterplan#viewer2@user:tom"),
tuple.MustParse("document:masterplan#editor@user:tom"),
tuple.MustParse("document:masterplan#admin@user:tom"),
},
resourceRelation: RR("document", "view_and_admin"),
subject: ONR("user", "tom", "..."),
disallowedQueries: []*core.RelationReference{
RR("document", "viewer"),
RR("document", "viewer2"),
},
expectedResources: []string{"masterplan"},
},
{
name: "multiple paths for checking variation",
schema: `definition user {}
definition document {
relation editor: user
relation viewer: user
relation viewer2: user
relation admin: user
permission viewer_of_some_kind = viewer + viewer2
permission view_and_admin = viewer_of_some_kind & editor & admin
}`,
relationships: []*core.RelationTuple{
tuple.MustParse("document:masterplan#viewer@user:tom"),
tuple.MustParse("document:masterplan#viewer2@user:tom"),
tuple.MustParse("document:masterplan#editor@user:tom"),
tuple.MustParse("document:masterplan#admin@user:tom"),
},
resourceRelation: RR("document", "view_and_admin"),
subject: ONR("user", "tom", "..."),
disallowedQueries: []*core.RelationReference{
RR("document", "viewer"),
RR("document", "viewer2"),
},
expectedResources: []string{"masterplan"},
},
{
name: "multiple paths with relation walk",
schema: `definition user {}
definition group {
relation member: user
}
definition document {
relation editor: user
relation viewer: group#member
permission view = viewer & editor
}`,
relationships: []*core.RelationTuple{
tuple.MustParse("document:masterplan#viewer@group:first#member"),
tuple.MustParse("document:masterplan#viewer@group:second#member"),
tuple.MustParse("document:masterplan#editor@user:tom"),
tuple.MustParse("group:first#member@user:tom"),
tuple.MustParse("group:second#member@user:tom"),
},
resourceRelation: RR("document", "view"),
subject: ONR("user", "tom", "..."),
disallowedQueries: []*core.RelationReference{
RR("document", "viewer"),
},
expectedResources: []string{"masterplan"},
},
{
name: "multiple paths with relation walk on right",
schema: `definition user {}
definition group {
relation member: user
}
definition document {
relation editor: user
relation viewer: group#member
permission view = editor & viewer
}`,
relationships: []*core.RelationTuple{
tuple.MustParse("document:masterplan#viewer@group:first#member"),
tuple.MustParse("document:masterplan#viewer@group:second#member"),
tuple.MustParse("document:masterplan#editor@user:tom"),
tuple.MustParse("group:first#member@user:tom"),
tuple.MustParse("group:second#member@user:tom"),
},
resourceRelation: RR("document", "view"),
subject: ONR("user", "tom", "..."),
disallowedQueries: []*core.RelationReference{
RR("document", "editor"),
},
expectedResources: []string{"masterplan"},
},
{
name: "multiple paths with arrow on left",
schema: `definition user {}
definition group {
relation member: user
}
definition document {
relation editor: user
relation viewer: group
permission view = viewer->member & editor
}`,
relationships: []*core.RelationTuple{
tuple.MustParse("document:masterplan#viewer@group:first"),
tuple.MustParse("document:masterplan#viewer@group:second"),
tuple.MustParse("document:masterplan#editor@user:tom"),
tuple.MustParse("group:first#member@user:tom"),
tuple.MustParse("group:second#member@user:tom"),
},
resourceRelation: RR("document", "view"),
subject: ONR("user", "tom", "..."),
disallowedQueries: []*core.RelationReference{
RR("document", "viewer"),
},
expectedResources: []string{"masterplan"},
},
{
name: "multiple paths with arrow on right",
schema: `definition user {}
definition group {
relation member: user
}
definition document {
relation editor: user
relation viewer: group
permission view = editor & viewer->member
}`,
relationships: []*core.RelationTuple{
tuple.MustParse("document:masterplan#viewer@group:first"),
tuple.MustParse("document:masterplan#viewer@group:second"),
tuple.MustParse("document:masterplan#editor@user:tom"),
tuple.MustParse("group:first#member@user:tom"),
tuple.MustParse("group:second#member@user:tom"),
},
resourceRelation: RR("document", "view"),
subject: ONR("user", "tom", "..."),
disallowedQueries: []*core.RelationReference{
RR("document", "editor"),
},
expectedResources: []string{"masterplan"},
},
{
name: "duplicate resources for checking",
schema: `definition user {}
definition group {
relation member: user
}
definition folder {
relation group: group
relation editor: user
permission view = group->member & editor
}
definition document {
relation folder: folder
permission view = folder->view
}
`,
relationships: []*core.RelationTuple{
tuple.MustParse("group:first#member@user:tom"),
tuple.MustParse("group:second#member@user:tom"),
tuple.MustParse("folder:folder1#group@group:first"),
tuple.MustParse("folder:folder1#group@group:second"),
tuple.MustParse("folder:folder1#editor@user:tom"),
tuple.MustParse("document:masterplan#folder@folder:folder1"),
},
resourceRelation: RR("document", "view"),
subject: ONR("user", "tom", "..."),
disallowedQueries: []*core.RelationReference{
RR("group", "member"),
RR("folder", "group"),
},
expectedResources: []string{"masterplan"},
},
{
name: "duplicate resources for checking with missing caveat context on checked side",
schema: `definition user {}
definition group {
relation member: user
}
caveat somecaveat(somecondition int) {
somecondition == 42
}
definition folder {
relation group: group
relation editor: user with somecaveat
permission view = group->member & editor
}
definition document {
relation folder: folder
permission view = folder->view
}
`,
relationships: []*core.RelationTuple{
tuple.MustParse("group:first#member@user:tom"),
tuple.MustParse("group:second#member@user:tom"),
tuple.MustParse("folder:folder1#group@group:first"),
tuple.MustParse("folder:folder1#group@group:second"),
tuple.MustParse("folder:folder1#editor@user:tom[somecaveat]"),
tuple.MustParse("document:masterplan#folder@folder:folder1"),
},
resourceRelation: RR("document", "view"),
subject: ONR("user", "tom", "..."),
disallowedQueries: []*core.RelationReference{
RR("group", "member"),
RR("folder", "group"),
},
expectedResources: []string{"masterplan[somecondition]"},
},
{
name: "duplicate resources for checking with missing caveat context on hinted side",
schema: `definition user {}
definition group {
relation member: user | user with somecaveat
}
caveat somecaveat(somecondition int) {
somecondition == 42
}
definition folder {
relation group: group
relation editor: user
permission view = group->member & editor
}
definition document {
relation folder: folder
permission view = folder->view
}
`,
relationships: []*core.RelationTuple{
tuple.MustParse("group:first#member@user:tom[somecaveat]"),
tuple.MustParse("folder:folder1#group@group:first"),
tuple.MustParse("folder:folder1#editor@user:tom"),
tuple.MustParse("document:masterplan#folder@folder:folder1"),
},
resourceRelation: RR("document", "view"),
subject: ONR("user", "tom", "..."),
disallowedQueries: []*core.RelationReference{
RR("group", "member"),
RR("folder", "group"),
},
expectedResources: []string{"masterplan[somecondition]"},
},
}

for _, tc := range tcs {
Expand Down Expand Up @@ -888,6 +1182,11 @@ func TestLookupResources2EnsureCheckHints(t *testing.T) {

foundResourceIDs := mapz.NewSet[string]()
for _, result := range stream.Results() {
if len(result.Resource.MissingContextParams) > 0 {
foundResourceIDs.Insert(result.Resource.ResourceId + "[" + strings.Join(result.Resource.MissingContextParams, ",") + "]")
continue
}

foundResourceIDs.Insert(result.Resource.ResourceId)
}

Expand Down
4 changes: 2 additions & 2 deletions internal/graph/lookupresources2.go
Original file line number Diff line number Diff line change
Expand Up @@ -615,8 +615,8 @@ func (crr *CursoredLookupResources2) redispatchOrReport(
}, stream)
}

// Otherwise, we need to dispatch and filter results by batch checking along the way.
return runDispatchAndChecker(
// Otherwise, we need to filter results by batch checking along the way before dispatching.
return runCheckerAndDispatch(
ctx,
parentRequest,
foundResources,
Expand Down
Loading

0 comments on commit 639b959

Please sign in to comment.