3 * Zend Framework (http://framework.zend.com/)
5 * @link http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license http://framework.zend.com/license/new-bsd New BSD License
10 namespace Zend\Permissions\Acl
;
12 class Acl
implements AclInterface
17 const TYPE_ALLOW
= 'TYPE_ALLOW';
22 const TYPE_DENY
= 'TYPE_DENY';
27 const OP_ADD
= 'OP_ADD';
30 * Rule operation: remove
32 const OP_REMOVE
= 'OP_REMOVE';
39 protected $roleRegistry = null;
46 protected $resources = array();
49 * @var Role\RoleInterface
51 protected $isAllowedRole = null;
56 protected $isAllowedResource = null;
61 protected $isAllowedPrivilege = null;
64 * ACL rules; whitelist (deny everything to all) by default
68 protected $rules = array(
69 'allResources' => array(
71 'allPrivileges' => array(
72 'type' => self
::TYPE_DENY
,
75 'byPrivilegeId' => array()
79 'byResourceId' => array()
83 * Adds a Role having an identifier unique to the registry
85 * The $parents parameter may be a reference to, or the string identifier for,
86 * a Role existing in the registry, or $parents may be passed as an array of
87 * these - mixing string identifiers and objects is ok - to indicate the Roles
88 * from which the newly added Role will directly inherit.
90 * In order to resolve potential ambiguities with conflicting rules inherited
91 * from different parents, the most recently added parent takes precedence over
92 * parents that were previously added. In other words, the first parent added
93 * will have the least priority, and the last parent added will have the
96 * @param Role\RoleInterface|string $role
97 * @param Role\RoleInterface|string|array $parents
98 * @throws Exception\InvalidArgumentException
99 * @return Acl Provides a fluent interface
101 public function addRole($role, $parents = null)
103 if (is_string($role)) {
104 $role = new Role\
GenericRole($role);
105 } elseif (!$role instanceof Role\RoleInterface
) {
106 throw new Exception\
InvalidArgumentException(
107 'addRole() expects $role to be of type Zend\Permissions\Acl\Role\RoleInterface'
112 $this->getRoleRegistry()->add($role, $parents);
118 * Returns the identified Role
120 * The $role parameter can either be a Role or Role identifier.
122 * @param Role\RoleInterface|string $role
123 * @return Role\RoleInterface
125 public function getRole($role)
127 return $this->getRoleRegistry()->get($role);
131 * Returns true if and only if the Role exists in the registry
133 * The $role parameter can either be a Role or a Role identifier.
135 * @param Role\RoleInterface|string $role
138 public function hasRole($role)
140 return $this->getRoleRegistry()->has($role);
144 * Returns true if and only if $role inherits from $inherit
146 * Both parameters may be either a Role or a Role identifier. If
147 * $onlyParents is true, then $role must inherit directly from
148 * $inherit in order to return true. By default, this method looks
149 * through the entire inheritance DAG to determine whether $role
150 * inherits from $inherit through its ancestor Roles.
152 * @param Role\RoleInterface|string $role
153 * @param Role\RoleInterface|string $inherit
154 * @param bool $onlyParents
157 public function inheritsRole($role, $inherit, $onlyParents = false)
159 return $this->getRoleRegistry()->inherits($role, $inherit, $onlyParents);
163 * Removes the Role from the registry
165 * The $role parameter can either be a Role or a Role identifier.
167 * @param Role\RoleInterface|string $role
168 * @return Acl Provides a fluent interface
170 public function removeRole($role)
172 $this->getRoleRegistry()->remove($role);
174 if ($role instanceof Role\RoleInterface
) {
175 $roleId = $role->getRoleId();
180 foreach ($this->rules
['allResources']['byRoleId'] as $roleIdCurrent => $rules) {
181 if ($roleId === $roleIdCurrent) {
182 unset($this->rules
['allResources']['byRoleId'][$roleIdCurrent]);
185 foreach ($this->rules
['byResourceId'] as $resourceIdCurrent => $visitor) {
186 if (array_key_exists('byRoleId', $visitor)) {
187 foreach ($visitor['byRoleId'] as $roleIdCurrent => $rules) {
188 if ($roleId === $roleIdCurrent) {
189 unset($this->rules
['byResourceId'][$resourceIdCurrent]['byRoleId'][$roleIdCurrent]);
199 * Removes all Roles from the registry
201 * @return Acl Provides a fluent interface
203 public function removeRoleAll()
205 $this->getRoleRegistry()->removeAll();
207 foreach ($this->rules
['allResources']['byRoleId'] as $roleIdCurrent => $rules) {
208 unset($this->rules
['allResources']['byRoleId'][$roleIdCurrent]);
210 foreach ($this->rules
['byResourceId'] as $resourceIdCurrent => $visitor) {
211 foreach ($visitor['byRoleId'] as $roleIdCurrent => $rules) {
212 unset($this->rules
['byResourceId'][$resourceIdCurrent]['byRoleId'][$roleIdCurrent]);
220 * Adds a Resource having an identifier unique to the ACL
222 * The $parent parameter may be a reference to, or the string identifier for,
223 * the existing Resource from which the newly added Resource will inherit.
225 * @param Resource\ResourceInterface|string $resource
226 * @param Resource\ResourceInterface|string $parent
227 * @throws Exception\InvalidArgumentException
228 * @return Acl Provides a fluent interface
230 public function addResource($resource, $parent = null)
232 if (is_string($resource)) {
233 $resource = new Resource\
GenericResource($resource);
234 } elseif (!$resource instanceof Resource\ResourceInterface
) {
235 throw new Exception\
InvalidArgumentException(
236 'addResource() expects $resource to be of type Zend\Permissions\Acl\Resource\ResourceInterface'
240 $resourceId = $resource->getResourceId();
242 if ($this->hasResource($resourceId)) {
243 throw new Exception\
InvalidArgumentException("Resource id '$resourceId' already exists in the ACL");
246 $resourceParent = null;
248 if (null !== $parent) {
250 if ($parent instanceof Resource\ResourceInterface
) {
251 $resourceParentId = $parent->getResourceId();
253 $resourceParentId = $parent;
255 $resourceParent = $this->getResource($resourceParentId);
256 } catch (\Exception
$e) {
257 throw new Exception\
InvalidArgumentException(sprintf(
258 'Parent Resource id "%s" does not exist',
262 $this->resources
[$resourceParentId]['children'][$resourceId] = $resource;
265 $this->resources
[$resourceId] = array(
266 'instance' => $resource,
267 'parent' => $resourceParent,
268 'children' => array()
275 * Returns the identified Resource
277 * The $resource parameter can either be a Resource or a Resource identifier.
279 * @param Resource\ResourceInterface|string $resource
280 * @throws Exception\InvalidArgumentException
283 public function getResource($resource)
285 if ($resource instanceof Resource\ResourceInterface
) {
286 $resourceId = $resource->getResourceId();
288 $resourceId = (string) $resource;
291 if (!$this->hasResource($resource)) {
292 throw new Exception\
InvalidArgumentException("Resource '$resourceId' not found");
295 return $this->resources
[$resourceId]['instance'];
299 * Returns true if and only if the Resource exists in the ACL
301 * The $resource parameter can either be a Resource or a Resource identifier.
303 * @param Resource\ResourceInterface|string $resource
306 public function hasResource($resource)
308 if ($resource instanceof Resource\ResourceInterface
) {
309 $resourceId = $resource->getResourceId();
311 $resourceId = (string) $resource;
314 return isset($this->resources
[$resourceId]);
318 * Returns true if and only if $resource inherits from $inherit
320 * Both parameters may be either a Resource or a Resource identifier. If
321 * $onlyParent is true, then $resource must inherit directly from
322 * $inherit in order to return true. By default, this method looks
323 * through the entire inheritance tree to determine whether $resource
324 * inherits from $inherit through its ancestor Resources.
326 * @param Resource\ResourceInterface|string $resource
327 * @param Resource\ResourceInterface|string inherit
328 * @param bool $onlyParent
329 * @throws Exception\InvalidArgumentException
332 public function inheritsResource($resource, $inherit, $onlyParent = false)
335 $resourceId = $this->getResource($resource)->getResourceId();
336 $inheritId = $this->getResource($inherit)->getResourceId();
337 } catch (Exception\ExceptionInterface
$e) {
338 throw new Exception\
InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
341 if (null !== $this->resources
[$resourceId]['parent']) {
342 $parentId = $this->resources
[$resourceId]['parent']->getResourceId();
343 if ($inheritId === $parentId) {
345 } elseif ($onlyParent) {
352 while (null !== $this->resources
[$parentId]['parent']) {
353 $parentId = $this->resources
[$parentId]['parent']->getResourceId();
354 if ($inheritId === $parentId) {
363 * Removes a Resource and all of its children
365 * The $resource parameter can either be a Resource or a Resource identifier.
367 * @param Resource\ResourceInterface|string $resource
368 * @throws Exception\InvalidArgumentException
369 * @return Acl Provides a fluent interface
371 public function removeResource($resource)
374 $resourceId = $this->getResource($resource)->getResourceId();
375 } catch (Exception\ExceptionInterface
$e) {
376 throw new Exception\
InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
379 $resourcesRemoved = array($resourceId);
380 if (null !== ($resourceParent = $this->resources
[$resourceId]['parent'])) {
381 unset($this->resources
[$resourceParent->getResourceId()]['children'][$resourceId]);
383 foreach ($this->resources
[$resourceId]['children'] as $childId => $child) {
384 $this->removeResource($childId);
385 $resourcesRemoved[] = $childId;
388 foreach ($resourcesRemoved as $resourceIdRemoved) {
389 foreach ($this->rules
['byResourceId'] as $resourceIdCurrent => $rules) {
390 if ($resourceIdRemoved === $resourceIdCurrent) {
391 unset($this->rules
['byResourceId'][$resourceIdCurrent]);
396 unset($this->resources
[$resourceId]);
402 * Removes all Resources
404 * @return Acl Provides a fluent interface
406 public function removeResourceAll()
408 foreach ($this->resources
as $resourceId => $resource) {
409 foreach ($this->rules
['byResourceId'] as $resourceIdCurrent => $rules) {
410 if ($resourceId === $resourceIdCurrent) {
411 unset($this->rules
['byResourceId'][$resourceIdCurrent]);
416 $this->resources
= array();
422 * Adds an "allow" rule to the ACL
424 * @param Role\RoleInterface|string|array $roles
425 * @param Resource\ResourceInterface|string|array $resources
426 * @param string|array $privileges
427 * @param Assertion\AssertionInterface $assert
428 * @return Acl Provides a fluent interface
430 public function allow($roles = null, $resources = null, $privileges = null, Assertion\AssertionInterface
$assert = null)
432 return $this->setRule(self
::OP_ADD
, self
::TYPE_ALLOW
, $roles, $resources, $privileges, $assert);
436 * Adds a "deny" rule to the ACL
438 * @param Role\RoleInterface|string|array $roles
439 * @param Resource\ResourceInterface|string|array $resources
440 * @param string|array $privileges
441 * @param Assertion\AssertionInterface $assert
442 * @return Acl Provides a fluent interface
444 public function deny($roles = null, $resources = null, $privileges = null, Assertion\AssertionInterface
$assert = null)
446 return $this->setRule(self
::OP_ADD
, self
::TYPE_DENY
, $roles, $resources, $privileges, $assert);
450 * Removes "allow" permissions from the ACL
452 * @param Role\RoleInterface|string|array $roles
453 * @param Resource\ResourceInterface|string|array $resources
454 * @param string|array $privileges
455 * @return Acl Provides a fluent interface
457 public function removeAllow($roles = null, $resources = null, $privileges = null)
459 return $this->setRule(self
::OP_REMOVE
, self
::TYPE_ALLOW
, $roles, $resources, $privileges);
463 * Removes "deny" restrictions from the ACL
465 * @param Role\RoleInterface|string|array $roles
466 * @param Resource\ResourceInterface|string|array $resources
467 * @param string|array $privileges
468 * @return Acl Provides a fluent interface
470 public function removeDeny($roles = null, $resources = null, $privileges = null)
472 return $this->setRule(self
::OP_REMOVE
, self
::TYPE_DENY
, $roles, $resources, $privileges);
476 * Performs operations on ACL rules
478 * The $operation parameter may be either OP_ADD or OP_REMOVE, depending on whether the
479 * user wants to add or remove a rule, respectively:
483 * A rule is added that would allow one or more Roles access to [certain $privileges
484 * upon] the specified Resource(s).
486 * OP_REMOVE specifics:
488 * The rule is removed only in the context of the given Roles, Resources, and privileges.
489 * Existing rules to which the remove operation does not apply would remain in the
492 * The $type parameter may be either TYPE_ALLOW or TYPE_DENY, depending on whether the
493 * rule is intended to allow or deny permission, respectively.
495 * The $roles and $resources parameters may be references to, or the string identifiers for,
496 * existing Resources/Roles, or they may be passed as arrays of these - mixing string identifiers
497 * and objects is ok - to indicate the Resources and Roles to which the rule applies. If either
498 * $roles or $resources is null, then the rule applies to all Roles or all Resources, respectively.
499 * Both may be null in order to work with the default rule of the ACL.
501 * The $privileges parameter may be used to further specify that the rule applies only
502 * to certain privileges upon the Resource(s) in question. This may be specified to be a single
503 * privilege with a string, and multiple privileges may be specified as an array of strings.
505 * If $assert is provided, then its assert() method must return true in order for
506 * the rule to apply. If $assert is provided with $roles, $resources, and $privileges all
507 * equal to null, then a rule having a type of:
509 * TYPE_ALLOW will imply a type of TYPE_DENY, and
511 * TYPE_DENY will imply a type of TYPE_ALLOW
513 * when the rule's assertion fails. This is because the ACL needs to provide expected
514 * behavior when an assertion upon the default ACL rule fails.
516 * @param string $operation
517 * @param string $type
518 * @param Role\RoleInterface|string|array $roles
519 * @param Resource\ResourceInterface|string|array $resources
520 * @param string|array $privileges
521 * @param Assertion\AssertionInterface $assert
522 * @throws Exception\InvalidArgumentException
523 * @return Acl Provides a fluent interface
525 public function setRule($operation, $type, $roles = null, $resources = null,
526 $privileges = null, Assertion\AssertionInterface
$assert = null
528 // ensure that the rule type is valid; normalize input to uppercase
529 $type = strtoupper($type);
530 if (self
::TYPE_ALLOW
!== $type && self
::TYPE_DENY
!== $type) {
531 throw new Exception\
InvalidArgumentException(sprintf(
532 'Unsupported rule type; must be either "%s" or "%s"',
538 // ensure that all specified Roles exist; normalize input to array of Role objects or null
539 if (!is_array($roles)) {
540 $roles = array($roles);
541 } elseif (0 === count($roles)) {
542 $roles = array(null);
546 foreach ($rolesTemp as $role) {
547 if (null !== $role) {
548 $roles[] = $this->getRoleRegistry()->get($role);
555 // ensure that all specified Resources exist; normalize input to array of Resource objects or null
556 if (!is_array($resources)) {
557 if (null === $resources && count($this->resources
) > 0) {
558 $resources = array_keys($this->resources
);
559 // Passing a null resource; make sure "global" permission is also set!
560 if (!in_array(null, $resources)) {
561 array_unshift($resources, null);
564 $resources = array($resources);
566 } elseif (0 === count($resources)) {
567 $resources = array(null);
569 $resourcesTemp = $resources;
570 $resources = array();
571 foreach ($resourcesTemp as $resource) {
572 if (null !== $resource) {
573 $resourceObj = $this->getResource($resource);
574 $resourceId = $resourceObj->getResourceId();
575 $children = $this->getChildResources($resourceObj);
576 $resources = array_merge($resources, $children);
577 $resources[$resourceId] = $resourceObj;
582 unset($resourcesTemp);
584 // normalize privileges to array
585 if (null === $privileges) {
586 $privileges = array();
587 } elseif (!is_array($privileges)) {
588 $privileges = array($privileges);
591 switch ($operation) {
594 foreach ($resources as $resource) {
595 foreach ($roles as $role) {
596 $rules =& $this->getRules($resource, $role, true);
597 if (0 === count($privileges)) {
598 $rules['allPrivileges']['type'] = $type;
599 $rules['allPrivileges']['assert'] = $assert;
600 if (!isset($rules['byPrivilegeId'])) {
601 $rules['byPrivilegeId'] = array();
604 foreach ($privileges as $privilege) {
605 $rules['byPrivilegeId'][$privilege]['type'] = $type;
606 $rules['byPrivilegeId'][$privilege]['assert'] = $assert;
613 // remove from the rules
614 case self
::OP_REMOVE
:
615 foreach ($resources as $resource) {
616 foreach ($roles as $role) {
617 $rules =& $this->getRules($resource, $role);
618 if (null === $rules) {
621 if (0 === count($privileges)) {
622 if (null === $resource && null === $role) {
623 if ($type === $rules['allPrivileges']['type']) {
625 'allPrivileges' => array(
626 'type' => self
::TYPE_DENY
,
629 'byPrivilegeId' => array()
635 if (isset($rules['allPrivileges']['type']) &&
636 $type === $rules['allPrivileges']['type'])
638 unset($rules['allPrivileges']);
641 foreach ($privileges as $privilege) {
642 if (isset($rules['byPrivilegeId'][$privilege]) &&
643 $type === $rules['byPrivilegeId'][$privilege]['type'])
645 unset($rules['byPrivilegeId'][$privilege]);
654 throw new Exception\
InvalidArgumentException(sprintf(
655 'Unsupported operation; must be either "%s" or "%s"',
665 * Returns all child resources from the given resource.
667 * @param Resource\ResourceInterface|string $resource
668 * @return Resource\ResourceInterface[]
670 protected function getChildResources(Resource\ResourceInterface
$resource)
673 $id = $resource->getResourceId();
675 $children = $this->resources
[$id]['children'];
676 foreach ($children as $child) {
677 $child_return = $this->getChildResources($child);
678 $child_return[$child->getResourceId()] = $child;
680 $return = array_merge($return, $child_return);
687 * Returns true if and only if the Role has access to the Resource
689 * The $role and $resource parameters may be references to, or the string identifiers for,
690 * an existing Resource and Role combination.
692 * If either $role or $resource is null, then the query applies to all Roles or all Resources,
693 * respectively. Both may be null to query whether the ACL has a "blacklist" rule
694 * (allow everything to all). By default, Zend\Permissions\Acl creates a "whitelist" rule (deny
695 * everything to all), and this method would return false unless this default has
696 * been overridden (i.e., by executing $acl->allow()).
698 * If a $privilege is not provided, then this method returns false if and only if the
699 * Role is denied access to at least one privilege upon the Resource. In other words, this
700 * method returns true if and only if the Role is allowed all privileges on the Resource.
702 * This method checks Role inheritance using a depth-first traversal of the Role registry.
703 * The highest priority parent (i.e., the parent most recently added) is checked first,
704 * and its respective parents are checked similarly before the lower-priority parents of
705 * the Role are checked.
707 * @param Role\RoleInterface|string $role
708 * @param Resource\ResourceInterface|string $resource
709 * @param string $privilege
712 public function isAllowed($role = null, $resource = null, $privilege = null)
714 // reset role & resource to null
715 $this->isAllowedRole
= null;
716 $this->isAllowedResource
= null;
717 $this->isAllowedPrivilege
= null;
719 if (null !== $role) {
720 // keep track of originally called role
721 $this->isAllowedRole
= $role;
722 $role = $this->getRoleRegistry()->get($role);
723 if (!$this->isAllowedRole
instanceof Role\RoleInterface
) {
724 $this->isAllowedRole
= $role;
728 if (null !== $resource) {
729 // keep track of originally called resource
730 $this->isAllowedResource
= $resource;
731 $resource = $this->getResource($resource);
732 if (!$this->isAllowedResource
instanceof Resource\ResourceInterface
) {
733 $this->isAllowedResource
= $resource;
737 if (null === $privilege) {
738 // query on all privileges
740 // depth-first search on $role if it is not 'allRoles' pseudo-parent
741 if (null !== $role && null !== ($result = $this->roleDFSAllPrivileges($role, $resource, $privilege))) {
745 // look for rule on 'allRoles' pseudo-parent
746 if (null !== ($rules = $this->getRules($resource, null))) {
747 foreach ($rules['byPrivilegeId'] as $privilege => $rule) {
748 if (self
::TYPE_DENY
=== ($ruleTypeOnePrivilege = $this->getRuleType($resource, null, $privilege))) {
752 if (null !== ($ruleTypeAllPrivileges = $this->getRuleType($resource, null, null))) {
753 return self
::TYPE_ALLOW
=== $ruleTypeAllPrivileges;
758 $resource = $this->resources
[$resource->getResourceId()]['parent'];
760 } while (true); // loop terminates at 'allResources' pseudo-parent
762 $this->isAllowedPrivilege
= $privilege;
763 // query on one privilege
765 // depth-first search on $role if it is not 'allRoles' pseudo-parent
766 if (null !== $role && null !== ($result = $this->roleDFSOnePrivilege($role, $resource, $privilege))) {
770 // look for rule on 'allRoles' pseudo-parent
771 if (null !== ($ruleType = $this->getRuleType($resource, null, $privilege))) {
772 return self
::TYPE_ALLOW
=== $ruleType;
773 } elseif (null !== ($ruleTypeAllPrivileges = $this->getRuleType($resource, null, null))) {
774 $result = self
::TYPE_ALLOW
=== $ruleTypeAllPrivileges;
775 if ($result ||
null === $resource) {
781 $resource = $this->resources
[$resource->getResourceId()]['parent'];
783 } while (true); // loop terminates at 'allResources' pseudo-parent
788 * Returns the Role registry for this ACL
790 * If no Role registry has been created yet, a new default Role registry
791 * is created and returned.
793 * @return Role\Registry
795 protected function getRoleRegistry()
797 if (null === $this->roleRegistry
) {
798 $this->roleRegistry
= new Role\
Registry();
800 return $this->roleRegistry
;
804 * Performs a depth-first search of the Role DAG, starting at $role, in order to find a rule
805 * allowing/denying $role access to all privileges upon $resource
807 * This method returns true if a rule is found and allows access. If a rule exists and denies access,
808 * then this method returns false. If no applicable rule is found, then this method returns null.
810 * @param Role\RoleInterface $role
811 * @param Resource\ResourceInterface $resource
814 protected function roleDFSAllPrivileges(Role\RoleInterface
$role, Resource\ResourceInterface
$resource = null)
817 'visited' => array(),
821 if (null !== ($result = $this->roleDFSVisitAllPrivileges($role, $resource, $dfs))) {
825 // This comment is needed due to a strange php-cs-fixer bug
826 while (null !== ($role = array_pop($dfs['stack']))) {
827 if (!isset($dfs['visited'][$role->getRoleId()])) {
828 if (null !== ($result = $this->roleDFSVisitAllPrivileges($role, $resource, $dfs))) {
838 * Visits an $role in order to look for a rule allowing/denying $role access to all privileges upon $resource
840 * This method returns true if a rule is found and allows access. If a rule exists and denies access,
841 * then this method returns false. If no applicable rule is found, then this method returns null.
843 * This method is used by the internal depth-first search algorithm and may modify the DFS data structure.
845 * @param Role\RoleInterface $role
846 * @param Resource\ResourceInterface $resource
849 * @throws Exception\RuntimeException
851 protected function roleDFSVisitAllPrivileges(Role\RoleInterface
$role, Resource\ResourceInterface
$resource = null, &$dfs = null)
854 throw new Exception\
RuntimeException('$dfs parameter may not be null');
857 if (null !== ($rules = $this->getRules($resource, $role))) {
858 foreach ($rules['byPrivilegeId'] as $privilege => $rule) {
859 if (self
::TYPE_DENY
=== ($ruleTypeOnePrivilege = $this->getRuleType($resource, $role, $privilege))) {
863 if (null !== ($ruleTypeAllPrivileges = $this->getRuleType($resource, $role, null))) {
864 return self
::TYPE_ALLOW
=== $ruleTypeAllPrivileges;
868 $dfs['visited'][$role->getRoleId()] = true;
869 foreach ($this->getRoleRegistry()->getParents($role) as $roleParent) {
870 $dfs['stack'][] = $roleParent;
877 * Performs a depth-first search of the Role DAG, starting at $role, in order to find a rule
878 * allowing/denying $role access to a $privilege upon $resource
880 * This method returns true if a rule is found and allows access. If a rule exists and denies access,
881 * then this method returns false. If no applicable rule is found, then this method returns null.
883 * @param Role\RoleInterface $role
884 * @param Resource\ResourceInterface $resource
885 * @param string $privilege
887 * @throws Exception\RuntimeException
889 protected function roleDFSOnePrivilege(Role\RoleInterface
$role, Resource\ResourceInterface
$resource = null, $privilege = null)
891 if (null === $privilege) {
892 throw new Exception\
RuntimeException('$privilege parameter may not be null');
896 'visited' => array(),
900 if (null !== ($result = $this->roleDFSVisitOnePrivilege($role, $resource, $privilege, $dfs))) {
904 // This comment is needed due to a strange php-cs-fixer bug
905 while (null !== ($role = array_pop($dfs['stack']))) {
906 if (!isset($dfs['visited'][$role->getRoleId()])) {
907 if (null !== ($result = $this->roleDFSVisitOnePrivilege($role, $resource, $privilege, $dfs))) {
917 * Visits an $role in order to look for a rule allowing/denying $role access to a $privilege upon $resource
919 * This method returns true if a rule is found and allows access. If a rule exists and denies access,
920 * then this method returns false. If no applicable rule is found, then this method returns null.
922 * This method is used by the internal depth-first search algorithm and may modify the DFS data structure.
924 * @param Role\RoleInterface $role
925 * @param Resource\ResourceInterface $resource
926 * @param string $privilege
929 * @throws Exception\RuntimeException
931 protected function roleDFSVisitOnePrivilege(Role\RoleInterface
$role, Resource\ResourceInterface
$resource = null,
932 $privilege = null, &$dfs = null
934 if (null === $privilege) {
936 * @see Zend\Permissions\Acl\Exception
938 throw new Exception\
RuntimeException('$privilege parameter may not be null');
943 * @see Zend\Permissions\Acl\Exception
945 throw new Exception\
RuntimeException('$dfs parameter may not be null');
948 if (null !== ($ruleTypeOnePrivilege = $this->getRuleType($resource, $role, $privilege))) {
949 return self
::TYPE_ALLOW
=== $ruleTypeOnePrivilege;
950 } elseif (null !== ($ruleTypeAllPrivileges = $this->getRuleType($resource, $role, null))) {
951 return self
::TYPE_ALLOW
=== $ruleTypeAllPrivileges;
954 $dfs['visited'][$role->getRoleId()] = true;
955 foreach ($this->getRoleRegistry()->getParents($role) as $roleParent) {
956 $dfs['stack'][] = $roleParent;
963 * Returns the rule type associated with the specified Resource, Role, and privilege
966 * If a rule does not exist or its attached assertion fails, which means that
967 * the rule is not applicable, then this method returns null. Otherwise, the
968 * rule type applies and is returned as either TYPE_ALLOW or TYPE_DENY.
970 * If $resource or $role is null, then this means that the rule must apply to
971 * all Resources or Roles, respectively.
973 * If $privilege is null, then the rule must apply to all privileges.
975 * If all three parameters are null, then the default ACL rule type is returned,
976 * based on whether its assertion method passes.
978 * @param null|Resource\ResourceInterface $resource
979 * @param null|Role\RoleInterface $role
980 * @param null|string $privilege
981 * @return string|null
983 protected function getRuleType(Resource\ResourceInterface
$resource = null, Role\RoleInterface
$role = null, $privilege = null)
985 // get the rules for the $resource and $role
986 if (null === ($rules = $this->getRules($resource, $role))) {
991 if (null === $privilege) {
992 if (isset($rules['allPrivileges'])) {
993 $rule = $rules['allPrivileges'];
997 } elseif (!isset($rules['byPrivilegeId'][$privilege])) {
1000 $rule = $rules['byPrivilegeId'][$privilege];
1003 // check assertion first
1004 if ($rule['assert']) {
1005 $assertion = $rule['assert'];
1006 $assertionValue = $assertion->assert(
1008 ($this->isAllowedRole
instanceof Role\RoleInterface
) ?
$this->isAllowedRole
: $role,
1009 ($this->isAllowedResource
instanceof Resource\ResourceInterface
) ?
$this->isAllowedResource
: $resource,
1010 $this->isAllowedPrivilege
1014 if (null === $rule['assert'] ||
$assertionValue) {
1015 return $rule['type'];
1016 } elseif (null !== $resource ||
null !== $role ||
null !== $privilege) {
1018 } elseif (self
::TYPE_ALLOW
=== $rule['type']) {
1019 return self
::TYPE_DENY
;
1022 return self
::TYPE_ALLOW
;
1026 * Returns the rules associated with a Resource and a Role, or null if no such rules exist
1028 * If either $resource or $role is null, this means that the rules returned are for all Resources or all Roles,
1029 * respectively. Both can be null to return the default rule set for all Resources and all Roles.
1031 * If the $create parameter is true, then a rule set is first created and then returned to the caller.
1033 * @param Resource\ResourceInterface $resource
1034 * @param Role\RoleInterface $role
1035 * @param bool $create
1036 * @return array|null
1038 protected function &getRules(Resource\ResourceInterface
$resource = null, Role\RoleInterface
$role = null, $create = false)
1040 // create a reference to null
1046 if (null === $resource) {
1047 $visitor =& $this->rules
['allResources'];
1050 $resourceId = $resource->getResourceId();
1051 if (!isset($this->rules
['byResourceId'][$resourceId])) {
1055 $this->rules
['byResourceId'][$resourceId] = array();
1057 $visitor =& $this->rules
['byResourceId'][$resourceId];
1062 if (null === $role) {
1063 if (!isset($visitor['allRoles'])) {
1067 $visitor['allRoles']['byPrivilegeId'] = array();
1069 return $visitor['allRoles'];
1071 $roleId = $role->getRoleId();
1072 if (!isset($visitor['byRoleId'][$roleId])) {
1076 $visitor['byRoleId'][$roleId]['byPrivilegeId'] = array();
1078 return $visitor['byRoleId'][$roleId];
1082 * @return array of registered roles
1084 public function getRoles()
1086 return array_keys($this->getRoleRegistry()->getRoles());
1090 * @return array of registered resources
1092 public function getResources()
1094 return array_keys($this->resources
);