spiral/framework

RBAC Decision Strategy

gam6itko opened this issue · 1 comments

It's about https://spiral.dev/docs/security-rbac/current/en

Description

Imagine that there are 2 user roles ROLE_USER, ROLE_BAD_GUY. Initially, the user has the ROLE_USER and has access to some area.

$actor->getRoles(); // ['ROLE_USER']
if ($guard->allows('some.area')) { // true
    return 'can read';
}

After the user gets the ROLE_BAD_GUY access to the the area should be denied.

$actor->getRoles(); // ['ROLE_USER', 'ROLE_BAD_GUY']
if ($guard->allows('some.area')) { // false
    return 'never get here';
}

Now this cannot be done because GuardScope allows access if as least one $allows = true.

$allows = $allows || $rule->allows($this->getActor(), $permission, $context);

Solution

I ask you to realize something like Symfony decision manager. Then SF programmer can set DecisionStrategy for GuardScope

For example

final class GuardScope implements GuardInterface
{
    public function __construct(
        private readonly PermissionsInterface $permissions,    
        private readonly DecisionMakerInterface $decisionMaker
         //...
    ) {
    }

    public function allows(string $permission, array $context = []): bool
    {
        $allowsList = [];
        foreach ($this->getRoles() as $role) {
            if (!$this->permissions->hasRole($role)) {
                continue;
            }

            $rule = $this->permissions->getRule($role, $permission);

            //Checking our rule
            $allowsList[] = $rule->allows($this->getActor(), $permission, $context);
        }

        return $this->decisionMaker->makeDecision($allowsList);
    }
    
    // ...
}
class AffirmativeDecisionMaker
{
    public function makeDecision(array $list): bool
    {
        return \array_reduce(
            $list,
            static fn (bool $res, bool $b) => $res || $b,
	    false
        );
    }
}

class ConsensusDecisionMaker {
    public function makeDecision(array $list): bool
    {
         $result = \array_reduce(
	    $list,
	    static fn (int $res, bool $b) => $res + ($b ? 1 : -1),
	    0
        );
        return $result > 0;
    }
}

class UnanimousDecisionMaker {
    public function makeDecision(array $list): bool
    {
      return \array_reduce(
          $list,
          static fn (bool $res, bool $b) => $res && $b,
	  true
      );
    }
}

At this moment GuardScope realize only Affirmative strategy. And this is not goot for me.

I've found more native way to achive this wtih Custom checker.