Skip to content

Commit

Permalink
docs: add initial documentation example for rewrites
Browse files Browse the repository at this point in the history
  • Loading branch information
hperl authored and zepatrik committed Aug 24, 2022
1 parent 0ce1519 commit 065ce46
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 9 deletions.
7 changes: 7 additions & 0 deletions contrib/rewrites-example/keto.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
dsn: memory

namespaces:
location: file://./namespaces.keto.ts

log:
level: trace
18 changes: 18 additions & 0 deletions contrib/rewrites-example/lib.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/// <reference no-default-lib="true"/>

type Context = { subject: never }

interface Namespace {
related?: { [relation: string]: Namespace[] }
permits?: { [method: string]: (ctx: Context) => boolean }
}

interface Array<Namespace> {
includes(element: Namespace): boolean
traverse(iteratorfn: (element: Namespace) => boolean): boolean
}

type SubjectSet<
A extends Namespace,
R extends keyof A["related"],
> = A["related"][R] extends Array<infer T> ? T : never
44 changes: 44 additions & 0 deletions contrib/rewrites-example/namespaces.keto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/// <reference path="./lib.ts" />

class User implements Namespace {
related: {
manager: User[]
}
}

class Group implements Namespace {
related: {
members: (User | Group)[]
}
}

class Folder implements Namespace {
related: {
parents: (File | Folder)[]
viewers: SubjectSet<Group, "members">[]
}

permits = {
view: (ctx: Context): boolean =>
this.related.viewers.includes(ctx.subject) ||
this.related.parents.traverse((p) => p.permits.view(ctx)),
}
}

class File implements Namespace {
related: {
parents: (File | Folder)[]
viewers: (User | SubjectSet<Group, "members">)[]
owners: (User | SubjectSet<Group, "members">)[]
}

// Some comment
permits = {
view: (ctx: Context): boolean =>
this.related.parents.traverse((p) => p.permits.view(ctx)) ||
this.related.viewers.includes(ctx.subject) ||
this.related.owners.includes(ctx.subject),

edit: (ctx: Context) => this.related.owners.includes(ctx.subject),
}
}
72 changes: 72 additions & 0 deletions contrib/rewrites-example/relation-tuples/tuples.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
[
{
"namespace": "Group",
"object": "developer",
"relation": "members",
"subject_id": "patrik"
},
{
"namespace": "Group",
"object": "developer",
"relation": "members",
"subject_set": {
"namespace": "User",
"object": "Patrik"
}
},
{
"namespace": "Group",
"object": "developer",
"relation": "members",
"subject_set": {
"namespace": "User",
"object": "Henning"
}
},
{
"namespace": "Folder",
"object": "keto/",
"relation": "viewers",
"subject_set": {
"namespace": "Group",
"object": "developer",
"relation": "members"
}
},
{
"namespace": "File",
"object": "keto/README.md",
"relation": "parents",
"subject_set": {
"namespace": "Folder",
"object": "keto/"
}
},
{
"namespace": "Folder",
"object": "keto/src/",
"relation": "parents",
"subject_set": {
"namespace": "Folder",
"object": "keto/"
}
},
{
"namespace": "File",
"object": "keto/src/main.go",
"relation": "parents",
"subject_set": {
"namespace": "Folder",
"object": "keto/src/"
}
},
{
"namespace": "File",
"object": "private",
"relation": "owners",
"subject_set": {
"namespace": "User",
"object": "Henning"
}
}
]
8 changes: 7 additions & 1 deletion 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 {
if !ok || subjectSet.Relation == WildcardRelation || subjectSet.Relation == "" {
continue
}
g.Add(e.checkIsAllowed(
Expand Down Expand Up @@ -207,6 +207,12 @@ 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 == "" {
return nil, nil
}

ns, err := e.namespaceFor(ctx, r)
if err != nil {
// On an unknown namespace the answer should be "not allowed", not "not
Expand Down
16 changes: 8 additions & 8 deletions internal/check/rewrites_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,22 +103,22 @@ func TestUsersetRewrites(t *testing.T) {
reg.Logger().Logger.SetLevel(logrus.TraceLevel)

insertFixtures(t, reg.RelationTupleManager(), []string{
"doc:document#owner@user", // user owns doc
"doc:doc_in_folder#parent@doc:folder#...", // doc_in_folder is in folder
"doc:folder#owner@user", // user owns folder
"doc:document#owner@user", // user owns doc
"doc:doc_in_folder#parent@doc:folder#", // doc_in_folder is in folder
"doc:folder#owner@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",
"level:superadmin#member@mark",
"level:superadmin#member@sandy",
"resource:topsecret#owner@group:editors#...",
"resource:topsecret#level@level:superadmin#...",
"resource:topsecret#owner@group:editors#",
"resource:topsecret#level@level:superadmin#",
"resource:topsecret#owner@mike",

"acl:document#allow@alice",
Expand Down

0 comments on commit 065ce46

Please sign in to comment.