Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support string paramters and string literal arguments in permits definition #1113

Open
3 of 6 tasks
stonelgh opened this issue Nov 7, 2022 · 6 comments
Open
3 of 6 tasks
Labels
feat New feature or request.

Comments

@stonelgh
Copy link
Contributor

stonelgh commented Nov 7, 2022

Preflight checklist

Describe your problem

I want to implement a flexible permissions-and-users bindings. It can easily grant users a batch of permissions by specifying a set of permissions and a set of users in a policy. And then we can attach a set of policies to a resource.

Currently, a possible implementation could be like this:

class User implements Namespace {}

class PolicyA implements Namespace {
  related: {
    creators: User[]
    updaters: User[]
    getters: User[]
    drivers: User[]
  }

  permits = {
    create: (ctx: Context): boolean => this.related.creators.includes(ctx.subject),
    update: (ctx: Context): boolean => this.related.updaters.includes(ctx.subject),
    get: (ctx: Context): boolean => this.related.getters.includes(ctx.subject),
    drive: (ctx: Context): boolean => this.related.drivers.includes(ctx.subject),
  }
}

class PolicyB implements Namespace {
  related: {
    creators: User[]
    updaters: User[]
    getters: User[]
    fliers: User[]
  }

  permits = {
    create: (ctx: Context): boolean => this.related.creators.includes(ctx.subject),
    update: (ctx: Context): boolean => this.related.updaters.includes(ctx.subject),
    get: (ctx: Context): boolean => this.related.getters.includes(ctx.subject),
    fly: (ctx: Context): boolean => this.related.fliers.includes(ctx.subject),
  }
}

class ResourceA implements Namespace {
  related: {
    policies: PolicyA[]
  }

  permits = {
    create: (ctx: Context): boolean => this.related.policies.traverse((p) => p.permits.create(perm)),
    update: (ctx: Context): boolean => this.related.policies.traverse((p) => p.permits.update(perm)),
    get: (ctx: Context): boolean => this.related.policies.traverse((p) => p.permits.get(perm)),
    drive: (ctx: Context): boolean => this.related.policies.traverse((p) => p.permits.drive(perm)),
  }
}

class ResourceB implements Namespace {
  related: {
    policies: PolicyB[]
  }

  permits = {
    create: (ctx: Context): boolean => this.related.policies.traverse((p) => p.permits.create(perm)),
    update: (ctx: Context): boolean => this.related.policies.traverse((p) => p.permits.update(perm)),
    get: (ctx: Context): boolean => this.related.policies.traverse((p) => p.permits.get(perm)),
    fly: (ctx: Context): boolean => this.related.policies.traverse((p) => p.permits.fly(perm)),
  }
}

We end up with one Policy class per Resource type and each Policy has a set of similar relations.

Describe your ideal solution

class Perm implements Namespace {}
class User implements Namespace {}

class Policy implements Namespace {
  related: {
    perms: Perm[]
    users: User[]
  }

  permits = {
    has_perm: (ctx: Context, perm string): boolean => this.related.perms.includes(perm) && this.related.perms.includes(ctx.subject),
  }
}

class ResourceA implements Namespace {
  related: {
    policies: Policy[]
  }

  permits = {
    check: (ctx: Context, perm string): boolean => this.related.policies.traverse((p) => p.permits.has_perm(perm)),
    create: (ctx: Context): boolean => this.permits.check(ctx, "a.create"),
    update: (ctx: Context): boolean => this.permits.check(ctx, "a.update"),
    get: (ctx: Context): boolean => this.permits.check(ctx, "a.get"),
    drive: (ctx: Context): boolean => this.permits.check(ctx, "a.drive"),
  }
}

class ResourceB implements Namespace {
  related: {
    policies: Policy[]
  }

  permits = {
    check: (ctx: Context, perm string): boolean => this.related.policies.traverse((p) => p.permits.has_perm(perm)),
    create: (ctx: Context): boolean => this.permits.check(ctx, "b.create"),
    update: (ctx: Context): boolean => this.permits.check(ctx, "b.update"),
    get: (ctx: Context): boolean => this.permits.check(ctx, "b.get"),
    fly: (ctx: Context): boolean => this.permits.check(ctx, "b.fly"),
  }
}

With the supposed solution, there is only one Policy and clear permission literals.

Workarounds or alternatives

Version

v0.10.0-alpha.0

Additional Context

No response

@stonelgh stonelgh added the feat New feature or request. label Nov 7, 2022
@stonelgh
Copy link
Contributor Author

stonelgh commented Nov 7, 2022

When we introduce the Role concept as a set of permissions, and instead bind roles and users together in policies, we can easily achieve that and new role objects can be freely created without the change of namespace definitions with the help of string parameters. Otherwise, we probably have to specify every effective role in the policies and make lengthy checks in each permit definition, and end up with more and more complex namespace definitions. Discussion #1122 contains a complete sample.

@zepatrik
Copy link
Member

So the use-case is defining new roles/policies/... without the need to update the namespace definitions. The role would then be created by defining tuples similar to

Role:admin#has_perm@Perm:create

and then adding users to that role:

Role:admin#member@User:foo

correct?

@zepatrik
Copy link
Member

zepatrik commented Nov 10, 2022

OK, I see that you gave that example in the discussion 👍

@stonelgh
Copy link
Contributor Author

Yes, the discussion demonstrates a basic role/permission/policy model use-case.

@arieltorti
Copy link

@zepatrik We have a similar use case, is there a way to represent Policy/Role/User or Parametrized RBAC using the OPL ?

Copy link

Hello contributors!

I am marking this issue as stale as it has not received any engagement from the community or maintainers for a year. That does not imply that the issue has no merit! If you feel strongly about this issue

  • open a PR referencing and resolving the issue;
  • leave a comment on it and discuss ideas on how you could contribute towards resolving it;
  • leave a comment and describe in detail why this issue is critical for your use case;
  • open a new issue with updated details and a plan for resolving the issue.

Throughout its lifetime, Ory has received over 10.000 issues and PRs. To sustain that growth, we need to prioritize and focus on issues that are important to the community. A good indication of importance, and thus priority, is activity on a topic.

Unfortunately, burnout has become a topic of concern amongst open-source projects.

It can lead to severe personal and health issues as well as opening catastrophic attack vectors.

The motivation for this automation is to help prioritize issues in the backlog and not ignore, reject, or belittle anyone.

If this issue was marked as stale erroneously you can exempt it by adding the backlog label, assigning someone, or setting a milestone for it.

Thank you for your understanding and to anyone who participated in the conversation! And as written above, please do participate in the conversation if this topic is important to you!

Thank you 🙏✌️

@github-actions github-actions bot added the stale Feedback from one or more authors is required to proceed. label Mar 27, 2024
@zepatrik zepatrik removed the stale Feedback from one or more authors is required to proceed. label Mar 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feat New feature or request.
Projects
None yet
Development

No branches or pull requests

3 participants