Skip to content

Commit

Permalink
feat: support subject sets in check
Browse files Browse the repository at this point in the history
  • Loading branch information
hperl authored and zepatrik committed Aug 24, 2022
1 parent ec979df commit 1760459
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 38 deletions.
32 changes: 27 additions & 5 deletions cmd/check/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package check

import (
"fmt"
"strings"

"github.com/ory/keto/ketoapi"
rts "github.com/ory/keto/proto/ory/keto/relation_tuples/v1alpha2"

"github.com/ory/keto/internal/check"
Expand Down Expand Up @@ -43,12 +45,20 @@ func newCheckCmd() *cobra.Command {
}

cl := rts.NewCheckServiceClient(conn)

sub, err := parseSubject(args[0])
if err != nil {
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not parse subject %q: %s\n", args[0], err)
return err
}
resp, err := cl.Check(cmd.Context(), &rts.CheckRequest{
Subject: rts.NewSubjectID(args[0]),
Relation: args[1],
Namespace: args[2],
Object: args[3],
MaxDepth: maxDepth,
Tuple: &rts.RelationTuple{
Namespace: args[2],
Object: args[3],
Relation: args[1],
Subject: sub,
},
MaxDepth: maxDepth,
})
if err != nil {
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not make request: %s\n", err)
Expand All @@ -70,3 +80,15 @@ func newCheckCmd() *cobra.Command {
func RegisterCommandsRecursive(parent *cobra.Command) {
parent.AddCommand(newCheckCmd())
}

func parseSubject(s string) (*rts.Subject, error) {
if strings.Contains(s, ":") {
su, err := (&ketoapi.SubjectSet{}).FromString(s)
if err != nil {
return nil, err
}

return rts.NewSubjectSet(su.Namespace, su.Object, su.Relation), nil
}
return rts.NewSubjectID(s), nil
}
4 changes: 2 additions & 2 deletions internal/check/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ func (e *Engine) checkExpandSubject(r *relationTuple, restDepth int) checkgroup.
continue
}
subjectSet, ok := s.Subject.(*relationtuple.SubjectSet)
if !ok || subjectSet.Relation == WildcardRelation || subjectSet.Relation == "" {
if !ok || subjectSet.Relation == "" {
continue
}
g.Add(e.checkIsAllowed(
Expand Down Expand Up @@ -209,7 +209,7 @@ func (e *Engine) checkIsAllowed(ctx context.Context, r *relationTuple, restDepth
func (e *Engine) astRelationFor(ctx context.Context, r *relationTuple) (*ast.Relation, error) {
// Special case: If the relationTuple's relation is empty, then it is not an
// error that the relation was not found.
if r.Relation == WildcardRelation || r.Relation == "" {
if r.Relation == "" {
return nil, nil
}

Expand Down
38 changes: 27 additions & 11 deletions internal/check/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,21 +116,37 @@ func TestEngine(t *testing.T) {
})

t.Run("direct inclusion", func(t *testing.T) {
rel := relationtuple.RelationTuple{
Relation: "access",
Object: uuid.Must(uuid.NewV4()),
Namespace: t.Name(),
Subject: &relationtuple.SubjectID{ID: uuid.Must(uuid.NewV4())},
reg := newDepsProvider(t, []*namespace.Namespace{{Name: "n"}, {Name: "u"}})
tuples := []string{
`n:o#r@subject_id`,
`n:o#r@u:with_relation#r`,
`n:o#r@u:empty_relation#`,
`n:o#r@u:missing_relation`,
}

reg := newDepsProvider(t, []*namespace.Namespace{{Name: rel.Namespace}})
require.NoError(t, reg.RelationTupleManager().WriteRelationTuples(ctx, &rel))

insertFixtures(t, reg.RelationTupleManager(), tuples)
e := check.NewEngine(reg)

res, err := e.CheckIsMember(ctx, &rel, 0)
require.NoError(t, err)
assert.True(t, res)
cases := []struct {
tuple string
}{
{tuple: "n:o#r@subject_id"},
{tuple: "n:o#r@u:with_relation#r"},

{tuple: "n:o#r@u:empty_relation"},
{tuple: "n:o#r@u:empty_relation#"},

{tuple: "n:o#r@u:missing_relation"},
{tuple: "n:o#r@u:missing_relation#"},
}

for _, tc := range cases {
t.Run("case="+tc.tuple, func(t *testing.T) {
res, err := e.CheckIsMember(ctx, tupleFromString(t, tc.tuple), 0)
require.NoError(t, err)
assert.True(t, res)
})
}
})

t.Run("indirect inclusion level 1", func(t *testing.T) {
Expand Down
43 changes: 23 additions & 20 deletions internal/check/rewrites_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,15 +104,17 @@ func TestUsersetRewrites(t *testing.T) {
reg.Logger().Logger.SetLevel(logrus.TraceLevel)

insertFixtures(t, reg.RelationTupleManager(), []string{
"doc:document#owner@users:user#...", // user owns doc
"doc:doc_in_folder#parent@doc:folder#...", // doc_in_folder is in folder
"doc:folder#owner@users:user#...", // user owns folder
"doc:document#owner@plain_user", // user owns doc
"doc:document#owner@users:user", // user owns doc
"doc:doc_in_folder#parent@doc:folder", // doc_in_folder is in folder
"doc:folder#owner@plain_user", // user owns folder
"doc:folder#owner@users:user", // user owns folder

// Folder hierarchy folder_a -> folder_b -> folder_c -> file
// and folder_a is owned by user. Then user should have access to file.
"doc:file#parent@doc:folder_c#",
"doc:folder_c#parent@doc:folder_b#",
"doc:folder_b#parent@doc:folder_a#",
"doc:file#parent@doc:folder_c",
"doc:folder_c#parent@doc:folder_b",
"doc:folder_b#parent@doc:folder_a",
"doc:folder_a#owner@user",

"group:editors#member@mark",
Expand Down Expand Up @@ -140,31 +142,32 @@ func TestUsersetRewrites(t *testing.T) {
},
}, {
// userset rewrite
query: "doc:document#editor@users:user#...",
query: "doc:document#editor@users:user",
expected: checkgroup.Result{
Membership: checkgroup.IsMember,
},
}, {
// userset rewrite
query: "doc:document#editor@plain_user",
expected: checkgroup.ResultIsMember,
}, {
// transitive userset rewrite
query: "doc:document#viewer@users:user#",
// expected: checkgroup.ResultIsMember,
expected: checkgroup.Result{
Membership: checkgroup.IsMember,
},
query: "doc:document#viewer@users:user",
expected: checkgroup.ResultIsMember,
}, {
query: "doc:document#editor@nobody",
expected: checkgroup.ResultNotMember,
}, {
query: "doc:folder#viewer@user",
expected: checkgroup.Result{
Membership: checkgroup.IsMember,
},
query: "doc:folder#viewer@users:user",
expected: checkgroup.ResultIsMember,
}, {
// tuple to userset
query: "doc:doc_in_folder#viewer@user",
expected: checkgroup.Result{
Membership: checkgroup.IsMember,
},
query: "doc:doc_in_folder#viewer@users:user",
expected: checkgroup.ResultIsMember,
}, {
// tuple to userset
query: "doc:doc_in_folder#viewer@plain_user",
expected: checkgroup.ResultIsMember,
}, {
// tuple to userset
query: "doc:doc_in_folder#viewer@nobody",
Expand Down

0 comments on commit 1760459

Please sign in to comment.