Hey hey I'm back after playing with ts policies de...
# talk-keto
l
Hey hey I'm back after playing with ts policies definition I wanna to achieve multi-tenancy and role-based access control So, I've created such typescript policies definition file:
Copy code
class 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:
Copy code
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:
Copy code
Product         product/2       viewers         Group:finance#members
It looks really redundant So, I decided to try something like that
Copy code
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:
Copy code
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?
Copy code
Product         product/2       creators        Group:developer#members
Product         product/2       viewers         Group:marketing#members
ok I see that internal/schema/parser.go line 432 restrict using traverse inside traverse
Copy code
p.match(
			".", &subjectSetRel, ".", "includes", "(", "ctx", ".", "subject",
			optional(","), ")", optional(","), ")",
		)
So, I should always control resource access using ACL? I can't define user.groups which has access to resource inside namespaces.keto.ts file? Do you understand my question or I should to clarify something? I know, it looks complex 😞
Approach with defining permission for group for each product is very inconvenient 1. A lot of redundant data in database 2. If I wanna to disable "view" permission for some group I should iterate through all products and delete relation: products/{id} viewers Group:name#members 3. Also if I wanna to create new group and give "view" permission to members of that group I should iterate through all products and add relation: products/{id} viewers Group:name#members Could you tell me please, if there are some workaround for implementation of simple rbac model without need to duplicate a lot of relations in ACL style @steep-lamp-91158, just ping you in case you missed that thread
s
I can take a look tomorrow, maybe @narrow-van-43826 can also help
l
Thanks! Looking forward for some advice or explanation how to organize policies for such case