Skip to content
This repository has been archived by the owner on Jul 3, 2020. It is now read-only.

Dynamic role support #318

Open
Wilt opened this issue Nov 24, 2015 · 8 comments
Open

Dynamic role support #318

Wilt opened this issue Nov 24, 2015 · 8 comments

Comments

@Wilt
Copy link

Wilt commented Nov 24, 2015

I have a project where I customized Rbac. The thing was that I needed dynamic roles; a person can be an Owner/Admin for one resource instance and for another resource instance of the same type (so a different resource identifier) he has for example no role at all (Guest role). To be able to implement that I introduced a RoleResolver.

In my config I tie resource classes to certain role resolvers to be able to resolve the roles for the provided $resource instance for the current AuthenticatedIdentity at run time (or optional another identity passed to the resolver).

something like:

'role_resolvers' => [
    'Application\Resource\Object'    => 'Application\Role\Resolver\ObjectRoleResolver'
    //... more role resolver configs
)

My RoleResolverInterface looks like this:

<?php

namespace Rbac\Role;

use Rbac\Identity\IdentityInterface;

/**
 * A role resolver is a class that collects role names (strings)
 */
interface RoleResolverInterface
{
    /**
     * Get the roles for the resource
     *
     * @param IdentityInterface $identity
     * @param object $resource
     * @return array
     */
    public function getRoles(IdentityInterface $identity, $resource);
}

I created a RoleResolverPluginManager where I load the config. This allows easy access to the correct role resolver.

In my authorization event I get the correct resource (or a resource reference) and resolve the roles for the current user for the resource so that the isAuthorized method in my AuthorizationService can be executed with the correct roles for that particular resource.

Would it be interesting to add such dynamic role support to this RBAC module? Or would it be interesting to make another module called DynamicRBAC? Or do you see other ways of achieving the same results with the current module?

@danizord
Copy link
Member

@Wilt thanks for your showing that up. We are planning the next major release of ZfcRbac that would bring middlewares concept, do you know it? As I said in #316, I'd like to allow developers to register their own "role provider" middleware, so that they could apply some custom logic for resolving the user roles:

namespace UserLand\Middleware; 

class MyCustomRoleProviderMiddleware
{
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
    {
        $roles = /* Custom logic to fetch identity roles */;

        return $next($request->withAttribute('roles', $roles), $response);
    }
}

While I feel that this resource x role x identity stuff sounds more like ACL, I think you could implement that logic with custom role provider middlewares. What do you think?

@Wilt
Copy link
Author

Wilt commented Nov 26, 2015

@danizord I am not familiar with the Middleware concept yet, but I see how that would work. It seems it would for sure help me to get my 'roles' into the authorization request. But how do those custom attributes find their way into the AuthorizationService. It would probably still mean writing my own custom service that checks permission for these roles?

Rbac seems to me a module that should allow users more out-of-the-box with common features like dynamic roles? Otherwise the module becomes much less usable in practice IMHO.

In our application each resource has a known set of roles (Admin, Guest, Owner), we just need to collect the roles for that specific resource instance for the current Identity to be able to do the isAuthorized check. I still don't see how this could work in the current Rbac module? Or can you provide a full example?

@bakura10
Copy link
Member

Should we invent a new authorisation system that is neither called Acl not Rbac and that does all this? So that we stop saying "this does not sound like Acl/Rbac" ?

Ideally I'd love this library to be able to provide an authorisation as granular as what AWS provide for its IAM service.

@Wilt
Copy link
Author

Wilt commented Dec 1, 2015

@bakura10 First I thought you were being sarcastic, but I sincerely hope you are serious. And yes, we should do that, I am in. And if necessary I would also not mind contributing to this.

RARA - Roles, Assertions and Resources Authorization :)

@bakura10
Copy link
Member

bakura10 commented Dec 1, 2015

I'm not sarcastic at all :). In my previous project I had this mechanism of "team" where one user could actually have different roles based on the sub-project he had created, so the same user could be potentially an admin, a super-user and a user at the same time, depending on the project.

Therefore this is where AWS mechanism shines, where you can create permissions using simple logic, so you could have a permission for a given resource (so it looks a bit like a mix of RBAC and ACL) - this could actually be removed completely and only use "Condition" as shown after):

{
   "Effect": "allow",
   "Permission": "read_article",
   "Resource": "343" // An article ID
}

Or going more deeper and creating conditions, based on context (and this context info would be exposed by the app, depending on the resource):

{
   "Effect": "allow",
   "Permission": "read_article",
   "Condition": {
      "StringLike": {
         "article.team.name": "team-bar-*"
      }
   }
}

This user would have the permission to read the article only if the current context (the article) belongs to a team of writer whoes name starts by "team-bar-".

You could also combine things and set a wildcard for "Permission": "*_article".

@danizord what do you tihnk?

@DavidHavl
Copy link

Only my humble 5 cents about assertions etc. here, but...

I think the proposed conditions above are really interesting idea. Similarly to Assertions.

I certainly love the assertions and would not like to see it going away.
In fact I would love to see ability to add multiple assertions to one permission. I have extended this module for my own project purposes and it works excellently.

As for it not belonging to the Rbac... well you might be right but this module (despite it's name) is to most people more than an Rbac. It's a module to solve most authorization needs.
But if new repository is required (ZfrAuthorization?), why not.

Background info (if interested):
I work with a large scale system, social network type of application that has hundreds of item types. Each item has specified visibility (so permission) to whom it can be shown :
friends / sameNetwork / registeredUsers / everyone
For regular things Acl is ok, but it became nightmare when it came to feed posts (similar to facebook) with hundreds of thousands (potentially millions) of feed items/posts.
The displaying it itself is done via so called stream table so that's fine, but it still has to have access permissions in place in case someone access it differently.
Sadly this system was using simple Acl (item_type/item_id/user_id permission type records in db table) so say on creation of each feed post that is accessible to friends only it needs to find all friends of the creator and add new permission row for each friend to DB .
That seemed as a good idea at first but with tens of thousands of feed posts and users it grew like crazy. So that was one big expensive table of data.
Each time the feed post was accessed it needed to check the permission row in DB.

I have solved the issue by using (customized) zfcrbac and it’s assertion plugins (in some cases multiple ones).

Having an assertion say visibilityAssertion that reads column ‘visibility’ of the context (feed post) and doing the check between the creator and viewer makes more sense.
So for post with visibility ‘friends’ it would also run one simple query (one trip to DB) to see if creator and viewer are friends,
but the result will be cached for next posts with same visibility and creator/viewer and it is now much more performant. Plus there is no huge extra table of data.

@bakura10
Copy link
Member

I definitely agree with the fact that assertions should not be gone. This is part of a very standard authorization system.

As of today, the "AWS model" would work a bit like what you were doing. For instance, in AWS, whenever you create a new resource (for instance a new database...) and want to grant access for a user to this resource, you need to explicitly give the user the permission:

{
   "Effect": "allow",
   "Permission": "read_db",
   "Resource": "db-123b"
}

So whenever you would create a new db, you would need to add a new policy rule for the user. Checking the "read_db" permission would therefore means retrieving all the given policies for this permission and user, and verify their validity.

But as said before, AWS also uses a "condition" system that would allow to solve your issue with one rule. For instance, if the context for this permission would contain the provided data, you could grant a user a read access to all databases created after a given date:

{
   "Effect": "allow",
   "Permission": "read_db",
   "Resource": "*",
   "Condition": [
     "DateAfter": {
        "db.created_at": "2015-01-01"
     }
   ]
}

And of course, you could mix everything, and give a user access to all db, created after a given date and before another date:

{
   "Effect": "allow",
   "Permission": "read_db",
   "Resource": "123-*",
   "Condition": [
     "DateAfter": {
        "db.created_at": "2015-01-01"
     },
     "DateBefore": {
       "db.created_at": "2016-01-01"
     }
   ]
}

Maybe such a module should enforce some convention. For instance in AWS a "Resource" always references what Amazon calls an ARN, which is like a identifier that allows to uniquely identify a resource.

But that's definitely a big work, in the next few months I'll work on projects tht won't require advanced authorization so I'll likely won't work on that unfortunately :(. But feel free to submit other ideas!

@DavidHavl
Copy link

Thanks for the comprehensive answer. It makes sense.

For now (v2) I will submit PR for multiple assertions in couple of days, and you see if you like it.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants