oso icon indicating copy to clipboard operation
oso copied to clipboard

Built-in `get_allowed_permissions`-style method to retrieve all allowed (action, resource) pairs for a given actor

Open asyncee opened this issue 3 years ago • 7 comments

Hello!

We are creating an RBAC implementation based on OSO and it is working fine.

Currently the library provides a way to retrieve actions for a single resource using oso.get_allowed_actions().

For example, John can "read" a "Post 1". By asking oso.get_allowed_actions("Josh", "Post 1") we can get following result: ["read"].

Unfortunately, our project has a complex UI so it is not possible to ask backend for available actions per resource on the page because it may cause:

  1. high amount of similar http requests asking about allowed actions for resource
  2. high traffic
  3. resource consumption on the server
  4. poor user experience and other related issues

Is it possible to somehow get result like [("read", "Post 1")] in a single query? We are trying to feed UI with all available permissions using one HTTP query.

We managed to get almost desired results using following python code:

# Predicates have following signature:
# assign_role(user, role) — maps user to role
# role_allow(role, action, resource, user) — describes which action role's owner can do, user is a hacky way to provide more context to the predicate.
# User -assigned_to-> Role -grants-> Action-On-Resource 

user = User(2)
assigned_roles = oso.query_rule("assign_role", user, Variable("role"))
for result in assigned_roles:
    role = result.get("bindings").get("role")
    permissions = oso.query_rule("role_allow", role, Variable("action"), Variable("resource"), user, accept_expression=True)
    for p in permissions:
        print(p)

    # The output is following:
        # {'bindings': {'resource': 'Post1', 'action': 'read'}, 'trace': None}
        # {'bindings': {'action': 'delete', 'resource': Expression(And, [Expression(Isa, [Variable('_this'), Pattern(Post, {})]), Expression(Unify, [Expression(Dot, [Variable('_this'), 'id']), 2])])}, 'trace': None}
        # {'bindings': {'resource': 'Post2', 'action': 'write'}, 'trace': None}

Provided solution

  • feels like a hack,
  • has N+1 query problem (caching must be used to partially solve the issue),
  • we have to parse returned Expression to figure out which instance (object) is described in results

It would be great to have a way to achieve this task in more readable and performant way.

Thanks!

asyncee avatar Aug 24 '21 14:08 asyncee

Thanks for opening the issue @asyncee! I agree this would be a nice feature to have — I'm imagining an API in the same vein as Oso.get_allowed_actions (renamed to Enforcer.authorized_actions in the new beta release).

It should be possible to get around the N+1 issue by setting up your policy like so:

has_role("Abhi", "writer");

role_allow("writer", "write", "Post 1");
role_allow("writer", "write", "Post 2");

allow(user, action, resource) if
  has_role(user, role) and
  role_allow(role, action, resource);

And then querying for:

abhi = "Abhi"
permissions = oso.query_rule("allow", abhi, Variable("action"), Variable("resource"), accept_expression=True)
for p in permissions:
    print(p)

gj avatar Aug 25 '21 15:08 gj

Great news, thanks, i'll try it!

asyncee avatar Aug 25 '21 16:08 asyncee

It works great, not sure why this elegant solution missed my head :)

asyncee avatar Aug 26 '21 08:08 asyncee

It works great, not sure why this elegant solution missed my head :)

In fairness, it also missed mine while I was talking to you on Slack before you opened this issue haha. I re-read the issue and had an "aha" moment 😆

FWIW I still think we could ultimately add a built-in get_allowed_actions-style API for this — Polar is very well-suited to this "for a given rule, keep N arguments concrete and query for all possible combinations of the remaining arguments" type of policy introspection.

gj avatar Aug 26 '21 20:08 gj

@asyncee does the new issue title look good? Feel free to adjust

gj avatar Aug 26 '21 20:08 gj

Very accurate, looks good.

asyncee avatar Aug 27 '21 05:08 asyncee

This seems like, from an implementation perspective, it would pair well with #1427 as a pull request in one go

kkirsche avatar Feb 02 '23 02:02 kkirsche