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

Multiple roles given in context #258

Open
bakura10 opened this issue Jul 2, 2014 · 3 comments
Open

Multiple roles given in context #258

bakura10 opened this issue Jul 2, 2014 · 3 comments

Comments

@bakura10
Copy link
Member

bakura10 commented Jul 2, 2014

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
Copy link
Member

danizord commented Jul 3, 2014

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

  • First, the more reasonable way IMHO would be filtering the identity roles before the traversal by using some kind of custom role provider. But this is currently impossible because the role providers in ZfcRbac 2 are not "true" role providers, they only convert strings to objects (more like a role factory) and they are not really aware of the identity roles. So, the actual "Role Provider" of ZfcRbac is currently the $identity->getRoles() method, that lacks a lot of flexibility. That said, I plan to propose some refactor of ZfcRbac providers for a long time, but the tradeoff is that it would be a huge BC break =\
  • The other way would be moving the assertion stuff to Rbac component, then we would be able to continue the traversal until the assertion returns true.

@jmleroux
Copy link
Contributor

jmleroux commented Sep 2, 2014

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
Copy link

zionsg commented May 22, 2015

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);
  1. 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?
        */
    }
}
  1. 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 */
}
  1. 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));
}
  1. 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']
                ]
            ]
        ]
    ]
];
  1. 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]);
    }
}
  1. 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 */
}
  1. 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']
                ]
            ]
        ]
    ]
];
  1. 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.
        */
    }
}
  1. In controller, prepend project name to permission, eg.
    $this->authorizationService->isGranted('delta:project.create').
    This will return true for Bob but false for Alice.

zionsg added a commit to zionsg/zfc-rbac that referenced this issue Jun 5, 2015
This allows the identity to return different roles for different contexts. A scenario would be a user having different roles for different projects and the project name is passed in as the context. Implements Solution 1 in ZF-Commons#258 (comment)
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