ZF-Commons / zfc-rbac

Role-based access control module to provide additional features on top of Zend\Permissions\Rbac
BSD 3-Clause "New" or "Revised" License
181 stars 111 forks source link

Multiple roles given in context #258

Open bakura10 opened 10 years ago

bakura10 commented 10 years ago

Hi everyone,

Just a question to know how you guys/girls would solve this problem (I'd like to add that to the cookbook, eventually).

Imagine a project where each user has a given role (most of the time, "member"). In this system, users can create project where they have a bunch of permissions related to their project (like "project.create", "project.delete", "project.add_foo").

Now, you have the ability to invite other users to this project. Those users are users of the system, so they are also member, and they also have the "project.create", "project.delete"... permissions). But when I invite a user, I could give him a specific role for this project, so that he is read-only for instance, only on this project.

How would you tackle this problem?

ping @arekkas @danizord

danizord commented 10 years ago

I think this problem is currently impossible to solve with ZfcRbac, but I see two ways:

jmleroux commented 10 years ago

Off topic : could we use labels to be able to filter issues ? This one could be a "use case" or "tips and tricks" for example.

zionsg commented 9 years ago

Hi, might the following 2 solutions work? Solution 1 involves passing the project name as $context to getRoles() while the more complicated Solution 2 involves prepending the project name to roles and permissions.

First of all, let's assume 4 tables in the database: role, user, project, map_project_user. Each user is assigned different roles for each project. A member can do everything but a guest is readonly.

role: member, guest
user: Alice, Bob
project: Gamma, Delta

map_project_user
|:-------:|:-----:|:------:|
| project |  user |  role  |
|:-------:|:-----:|:------:|
|  Gamma  | Alice | member |
|  Delta  |  Bob  | member |
|  Delta  | Alice | guest  |
|:-------:|:-----:|:------:|

Solution 1

1) Modify ZfcRbac\Identity\IdentityInterface::getRoles() to take in an optional $context param.

public function getRoles($context = null);

2) The authenticated identity (likely populated from the database) will store its roles for each project and return only the relevant roles given the project name.

namespace App\Identity;

use ZfcRbac\Identity\IdentityInterface;

class Identity implements IdentityInterface
{
    public function getRoles($context = null)
    {
        /*
          In this case, $context will be the project name.
          Using Alice as an example, if $context is 'gamma', return ['member'].
          If $context is 'delta', return ['guest'].
          What is safe to return if $context is null?
        */
    }
}

3) Modify ZfcRbac\Service\AuthorizationService::isGranted() and pass the context to getIdentityRoles().

public function isGranted($permission, $context = null)
{
    $roles = $this->roleService->getIdentityRoles($context);

    /* no change to rest of code */
}

4) Modify ZfcRbac\Service\RoleService::getIdentityRoles() to take in an optional $context param and pass it to getRoles().

public function getIdentityRoles($context = null)
{
    /* code remain the same except for the last return line */

    return $this->convertRoles($identity->getRoles($context));
}

5) Define roles as per normal.

return [
    'zfc_rbac' => [
        'role_provider' => [
            'ZfcRbac\Role\InMemoryRoleProvider' => [
                'member' => [
                    'permissions' => ['project.create', 'project.delete', 'project.add_foo']
                ],
                'guest' => [
                    'permissions' => ['project.view']
                ]
            ]
        ]
    ]
];

6) In controller, pass the project name as context, eg. $this->authorizationService->isGranted('project.create', 'delta'). This will return true for Bob but false for Alice.

Solution 2

1) Write a custom role and modify hasPermission() to handle the prepended project name.

namespace App\Role;

use Rbac\Role\Role as RbacRole;

class Role extends RbacRole
{
    public function hasPermission($permission)
    {
        $roleParts = explode(':', $this->name);
        $permParts = explode(':', $permission);

        if (count($roleParts) > 1 && count($permParts) > 1) {
           if ($roleParts[0] != $permParts[0]) {
               return false;
           }
        }

        return isset($this->permissions[(string) $permission]);
    }
}

2) Write a custom role provider to use the custom role.

namespace App\Role;

use ZfcRbac\Role\InMemoryRoleProvider;
use App\Role\HierarchicalRole;
use App\Role\Role;

class CustomRoleProvider extends InMemoryProvider
{
    /* Assuming the custom roles will be used instead of the original ones */
}

3) Define roles as per normal using custom role provider

return [
    'zfc_rbac' => [
        'role_provider' => [
            'App\Role\CustomRoleProvider' => [
                'member' => [
                    'permissions' => ['project.create', 'project.delete', 'project.add_foo']
                ],
                'guest' => [
                    'permissions' => ['project.view']
                ]
            ]
        ]
    ]
];

4) The authenticated identity (likely populated from the database) will store its roles for each project and prepend project names when returning roles.

namespace App\Identity;

use ZfcRbac\Identity\IdentityInterface;

class Identity implements IdentityInterface
{
    public function getRoles()
    {
        /*
          Using Alice as an example, ['gamma:project.member', 'delta:project.guest'] will be returned.
        */
    }
}

5) In controller, prepend project name to permission, eg. $this->authorizationService->isGranted('delta:project.create'). This will return true for Bob but false for Alice.