little-pager-97837
09/29/2022, 2:43 PMclass User implements Namespace {
}
class Organization implements Namespace {
related: {
members: (User | Organization)[]
}
}
class Group implements Namespace {
related: {
members: User[]
}
}
class Product implements Namespace {
related: {
owners: Organization[]
viewers: (User | SubjectSet<Group, "members">)[]
editors: (User | SubjectSet<Group, "members">)[]
}
permits = {
view: (ctx: Context): boolean =>
this.related.owners.traverse((org) => org.related.members.includes(ctx.subject)) &&
(this.related.viewers.includes(ctx.subject) || this.related.editors.includes(ctx.subject)),
edit: (ctx: Context): boolean =>
this.related.owners.traverse((org) => org.related.members.includes(ctx.subject)) &&
this.related.editors.includes(ctx.subject),
}
}
I just wanna check that user is member of Product.owners organization AND he has enough permissions to access Product (in my case I just check that he is member of corresponding department - development or marketing)
My initial relations looks like:
NAMESPACE OBJECT RELATION NAME SUBJECT
Organization hazemag members User:Bob
Organization hazemag members User:Alice
Organization bmw members User:John
Group developer members User:John
Group developer members User:Bob
Group marketing members User:Alice
Product product/1 owners Organization:hazemag
Product product/1 creators Group:developer#members
Product product/1 viewers Group:marketing#members
Product product/2 owners Organization:bmw
Product product/2 creators Group:developer#members
Product product/2 viewers Group:marketing#members
And now I realized, that if I wanna add new Group, for example finance, and give to members of finance Group "view" product/* permission
I need to iterate through all products and for each product add line like:
Product product/2 viewers Group:finance#members
It looks really redundant
So, I decided to try something like that
class Group implements Namespace {
}
class User implements Namespace {
related: {
groups: Group[]
}
}
class Organization implements Namespace {
related: {
members: User[]
}
}
class Product implements Namespace {
related: {
owners: Organization[]
}
permits = {
view: (ctx: Context): boolean =>
this.related.owners.traverse((org) =>
org.related.members.traverse((user) =>
user == ctx.subject && (user.related.groups.includes("development") || user.related.groups.includes("marketing"))
)
),
edit: (ctx: Context): boolean =>
this.related.owners.traverse((org) =>
org.related.members.traverse((user) =>
user == ctx.subject && user.related.groups.includes("development")
)
)
}
}
But keto doesn't allow to traverse inside traverse:
error=map[message:error from 23:66 to 23:74: expected "includes", got "traverse"
keto |
keto | 21 | view: (ctx: Context): boolean =>
keto | 22 | this.related.owners.traverse((org) => org.related.members.traverse((user)
Is it solvable? Or I should copy that policies for each product?
Product product/2 creators Group:developer#members
Product product/2 viewers Group:marketing#members
little-pager-97837
09/29/2022, 3:06 PMp.match(
".", &subjectSetRel, ".", "includes", "(", "ctx", ".", "subject",
optional(","), ")", optional(","), ")",
)
little-pager-97837
09/29/2022, 3:16 PMlittle-pager-97837
09/29/2022, 3:39 PMsteep-lamp-91158
little-pager-97837
09/29/2022, 3:41 PM