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

Commit

Permalink
Merge pull request #107 from arekkas/master
Browse files Browse the repository at this point in the history
fixes #106
  • Loading branch information
bakura10 committed Dec 7, 2013
2 parents c5ee121 + c11cc4f commit 89c02d6
Show file tree
Hide file tree
Showing 6 changed files with 253 additions and 46 deletions.
22 changes: 7 additions & 15 deletions src/ZfcRbac/Guard/AbstractGuard.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,23 +108,15 @@ public function onRoute(MvcEvent $event)

$eventManager->trigger(MvcEvent::EVENT_DISPATCH_ERROR, $event);
}

/**
* Load a rule inside the Rbac container
*
* This allows to only add one permission relative to the route guard/controller guard, instead
* of adding permissions for all the rules
*
* @param array $roles
* @param string $permission
* @return void
* Checks if the current identity statisfies any of the required roles.
*
* @param array $allowedRoles
* @return boolean
*/
protected function loadRule(array $roles, $permission)
protected function isAllowed(array $allowedRoles)
{
$rbac = $this->authorizationService->getRbac();

foreach ($roles as $role) {
$rbac->getRole($role)->addPermission($permission);
}
return $this->authorizationService->doesIdentityStatisfyRoles($allowedRoles);
}
}
15 changes: 1 addition & 14 deletions src/ZfcRbac/Guard/ControllerGuard.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,6 @@ class ControllerGuard extends AbstractGuard
*/
const EVENT_PRIORITY = -20;

/**
* Rule prefix that is used to avoid conflicts in the Rbac container
*
* Rules will be added to the Rbac container using the following syntax:
* __controller__.$controller.$action
*/
const RULE_PREFIX = '__controller__';

/**
* Controller guard rules
*
Expand Down Expand Up @@ -123,10 +115,8 @@ public function isGranted(MvcEvent $event)

if (isset($this->rules[$controller][$action])) {
$allowedRoles = $this->rules[$controller][$action];
$permission = self::RULE_PREFIX . '.' . $controller . '.' . $action;
} elseif (isset($this->rules[$controller][0])) {
$allowedRoles = $this->rules[$controller][0];
$permission = self::RULE_PREFIX . '.' . $controller;
} else {
return $this->protectionPolicy === self::POLICY_ALLOW;
}
Expand All @@ -135,9 +125,6 @@ public function isGranted(MvcEvent $event)
return true;
}

// Load the needed permission inside the container
$this->loadRule($allowedRoles, $permission);

return $this->authorizationService->isGranted($permission);
return $this->isAllowed($allowedRoles);
}
}
18 changes: 3 additions & 15 deletions src/ZfcRbac/Guard/RouteGuard.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,6 @@ class RouteGuard extends AbstractGuard
*/
const EVENT_PRIORITY = -10;

/**
* Rule prefix that is used to avoid conflicts in the Rbac container
*
* Rules will be added to the Rbac container using the following syntax: __route__.$routeRule
*/
const RULE_PREFIX = '__route__';

/**
* Route guard rules
*
Expand Down Expand Up @@ -101,12 +94,10 @@ public function isGranted(MvcEvent $event)
$matchedRouteName = $event->getRouteMatch()->getMatchedRouteName();

$allowedRoles = [];
$permission = null;

foreach (array_keys($this->rules) as $routeRule) {
if (fnmatch($routeRule, $matchedRouteName, FNM_CASEFOLD)) {
$allowedRoles = $this->rules[$routeRule];
$permission = self::RULE_PREFIX . '.' . $routeRule;

break;
}
Expand All @@ -115,15 +106,12 @@ public function isGranted(MvcEvent $event)
if (in_array('*', $allowedRoles)) {
return true;
}

// If no rules apply, it is considered as granted or not based on the protection policy
if (empty($permission)) {
if (empty($allowedRoles)) {
return $this->protectionPolicy === self::POLICY_ALLOW;
}

// Load the needed permission inside the container
$this->loadRule($allowedRoles, $permission);

return $this->authorizationService->isGranted($permission);
return $this->isAllowed($allowedRoles);
}
}
85 changes: 85 additions & 0 deletions src/ZfcRbac/Service/AuthorizationService.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
use ZfcRbac\Exception;
use ZfcRbac\Identity\IdentityInterface;
use ZfcRbac\Identity\IdentityProviderInterface;
use Zend\Permissions\Rbac\RoleInterface;
use RecursiveIteratorIterator;

/**
* Authorization service is a simple service that internally uses a Rbac container
Expand Down Expand Up @@ -188,6 +190,89 @@ public function isGranted($permission, $assertion = null)

return false;
}

/**
* Returns true, if the current identity holds (statisfies) one of the given roles.
*
* @todo Refactor
* @internal This is a hotfix for AbstractGuard and should not be used by any other component.
* @param Traversable|array $roles
* @throws Exception\InvalidArgumentException
* @return boolean
*/
public function doesIdentityStatisfyRoles($roles)
{
$identityRoles = $this->getIdentityRoles();

if (empty($identityRoles)) {
return false;
}

// Load everything inside the container
$this->load($identityRoles);

// If roles is an instance of RoleInterface, we convert it to a string.
$roleNames = [];
foreach ($roles as $role) {
if ($role instanceof RoleInterface) {
$roleNames[] = $role->getName();
} else {
$roleNames[] = $role;
}
}

$flattenedIdentityRoles = $this->flattenRoles($identityRoles);

// check for intersetions
$intersect = array_intersect($flattenedIdentityRoles, $roleNames);

if (!empty($intersect)) {
// Intersection found -> identity has at least one of the required roles
return true;
}

return false;
}

/**
* Flattens a given set of roles.
*
* @param array $roles
* @return array
*/
protected function flattenRoles(array $roles)
{
$flattenedRoles = [];

foreach ($roles as $role) {

// Skip duplicates (saves us some time)
if (in_array($role, $flattenedRoles)) {
continue;
}

$flattenedRoles[] = $role;

if ($this->rbac->hasRole($role)) {
$roleFromRbac = $this->rbac->getRole($role);

// We need to iterate through the identities children.
$it = new RecursiveIteratorIterator($roleFromRbac, RecursiveIteratorIterator::SELF_FIRST);
foreach ($it as $leaf) {

if (in_array($leaf->getName(), $flattenedRoles)) {
// Skip treesearch, as we've been here already
break;
}

$flattenedRoles[] = $leaf->getName();
}

}
}

return $flattenedRoles;
}

/**
* Load roles and permissions inside the container by triggering load events
Expand Down
22 changes: 20 additions & 2 deletions tests/ZfcRbacTest/Guard/RouteGuardTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -254,20 +254,38 @@ public function routeDataProvider()
[
'rules' => ['home' => 'guest'],
'matchedRouteName' => 'home',
'rolesToCreate' => ['admin', 'guest' => 'admin'],
'rolesToCreate' => ['admin', 'login' => 'admin', 'guest' => 'login'],
'identityRole' => 'admin',
'isGranted' => true,
'policy' => GuardInterface::POLICY_ALLOW
],
[
'rules' => ['home' => 'guest'],
'matchedRouteName' => 'home',
'rolesToCreate' => ['admin', 'guest' => 'admin'],
'rolesToCreate' => ['admin', 'login' => 'admin', 'guest' => 'login'],
'identityRole' => 'admin',
'isGranted' => true,
'policy' => GuardInterface::POLICY_DENY
],

// Assert it can deny access although using child-parent relationship between roles (just to be sure)
[
'rules' => ['route' => 'admin'],
'matchedRouteName' => 'route',
'rolesToCreate' => ['admin', 'login' => 'admin'],
'identityRole' => 'login',
'isGranted' => false,
'policy' => GuardInterface::POLICY_ALLOW
],
[
'rules' => ['route' => 'admin'],
'matchedRouteName' => 'route',
'rolesToCreate' => ['admin', 'login' => 'admin'],
'identityRole' => 'login',
'isGranted' => false,
'policy' => GuardInterface::POLICY_DENY
],

// Assert wildcard in role
[
'rules' => ['home' => '*'],
Expand Down
137 changes: 137 additions & 0 deletions tests/ZfcRbacTest/Service/AuthorizationServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,143 @@ public function testGranted($role, $permission, $assertion = null, $isGranted)
$this->assertEquals($isGranted, $authorizationService->isGranted($permission, $assertion));
}

public function roleProvider()
{
return [
// Simple
[
'rolesToCreate' => [
'login',
'guest' => 'login'
],
'identityRoles' => [
'guest'
],
'rolesToCheck' => [
'login'
],
'doesStatisfy' => false
],
[
'rolesToCreate' => [
'login',
'guest' => 'login'
],
'identityRoles' => [
'login'
],
'rolesToCheck' => [
'login'
],
'doesStatisfy' => true
],

// Complex role inheritance
[
'rolesToCreate' => [
'admin',
'moderator' => 'admin',
'login' => 'moderator',
'guest' => 'login'
],
'identityRoles' => [
'login',
'moderator'
],
'rolesToCheck' => [
'admin'
],
'doesStatisfy' => false
],
[
'rolesToCreate' => [
'admin',
'moderator' => 'admin',
'login' => 'moderator',
'guest' => 'login'
],
'identityRoles' => [
'login',
'admin'
],
'rolesToCheck' => [
'moderator'
],
'doesStatisfy' => true
],

// Complex role inheritance and multiple check
[
'rolesToCreate' => [
'sysadmin',
'siteadmin' => 'sysadmin',
'admin' => 'sysadmin',
'moderator' => 'admin',
'login' => 'moderator',
'guest' => 'login'
],
'identityRoles' => [
'login',
'moderator'
],
'rolesToCheck' => [
'admin',
'sysadmin'
],
'doesStatisfy' => false
],
[
'rolesToCreate' => [
'sysadmin',
'siteadmin' => 'sysadmin',
'admin' => 'sysadmin',
'moderator' => 'admin',
'login' => 'moderator',
'guest' => 'login'
],
'identityRoles' => [
'moderator',
'admin'
],
'rolesToCheck' => [
'sysadmin',
'siteadmin',
'login'
],
'doesStatisfy' => true
]
];
}

/**
* @dataProvider roleProvider
*/
public function testDoesIdentityStatisfyRoles(array $rolesToCreate, array $identityRoles, array $rolesToCheck, $doesStatisfy)
{
// Let's fill the RBAC container with some values
$rbac = new Rbac();

foreach ($rolesToCreate as $roleToCreate => $parent) {
if (is_int($roleToCreate)) {
$rbac->addRole($parent);
} else {
$rbac->addRole($roleToCreate, $parent);
}
}

$identity = $this->getMock('ZfcRbac\Identity\IdentityInterface');
$identity->expects($this->once())->method('getRoles')->will($this->returnValue($identityRoles));

$identityProvider = $this->getMock('ZfcRbac\Identity\IdentityProviderInterface');
$identityProvider->expects($this->any())
->method('getIdentity')
->will($this->returnValue($identity));

$authorizationService = new AuthorizationService($rbac, $identityProvider);

$this->assertEquals($doesStatisfy, $authorizationService->doesIdentityStatisfyRoles($rolesToCheck));
}

/**
* Assert that event to load roles and permissions is not triggered if no role can be found in an
* identity, because it will be refused anyway
Expand Down

0 comments on commit 89c02d6

Please sign in to comment.