3 * Zend Framework (http://framework.zend.com/)
5 * @link http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2015 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 = [];
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
72 'type' => self
::TYPE_DENY
,
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'
111 $this->getRoleRegistry()->add($role, $parents);
117 * Returns the identified Role
119 * The $role parameter can either be a Role or Role identifier.
121 * @param Role\RoleInterface|string $role
122 * @return Role\RoleInterface
124 public function getRole($role)
126 return $this->getRoleRegistry()->get($role);
130 * Returns true if and only if the Role exists in the registry
132 * The $role parameter can either be a Role or a Role identifier.
134 * @param Role\RoleInterface|string $role
137 public function hasRole($role)
139 return $this->getRoleRegistry()->has($role);
143 * Returns true if and only if $role inherits from $inherit
145 * Both parameters may be either a Role or a Role identifier. If
146 * $onlyParents is true, then $role must inherit directly from
147 * $inherit in order to return true. By default, this method looks
148 * through the entire inheritance DAG to determine whether $role
149 * inherits from $inherit through its ancestor Roles.
151 * @param Role\RoleInterface|string $role
152 * @param Role\RoleInterface|string $inherit
153 * @param bool $onlyParents
156 public function inheritsRole($role, $inherit, $onlyParents = false)
158 return $this->getRoleRegistry()->inherits($role, $inherit, $onlyParents);
162 * Removes the Role from the registry
164 * The $role parameter can either be a Role or a Role identifier.
166 * @param Role\RoleInterface|string $role
167 * @return Acl Provides a fluent interface
169 public function removeRole($role)
171 $this->getRoleRegistry()->remove($role);
173 if ($role instanceof Role\RoleInterface
) {
174 $roleId = $role->getRoleId();
179 foreach ($this->rules
['allResources']['byRoleId'] as $roleIdCurrent => $rules) {
180 if ($roleId === $roleIdCurrent) {
181 unset($this->rules
['allResources']['byRoleId'][$roleIdCurrent]);
184 foreach ($this->rules
['byResourceId'] as $resourceIdCurrent => $visitor) {
185 if (array_key_exists('byRoleId', $visitor)) {
186 foreach ($visitor['byRoleId'] as $roleIdCurrent => $rules) {
187 if ($roleId === $roleIdCurrent) {
188 unset($this->rules
['byResourceId'][$resourceIdCurrent]['byRoleId'][$roleIdCurrent]);
198 * Removes all Roles from the registry
200 * @return Acl Provides a fluent interface
202 public function removeRoleAll()
204 $this->getRoleRegistry()->removeAll();
206 foreach ($this->rules
['allResources']['byRoleId'] as $roleIdCurrent => $rules) {
207 unset($this->rules
['allResources']['byRoleId'][$roleIdCurrent]);
209 foreach ($this->rules
['byResourceId'] as $resourceIdCurrent => $visitor) {
210 foreach ($visitor['byRoleId'] as $roleIdCurrent => $rules) {
211 unset($this->rules
['byResourceId'][$resourceIdCurrent]['byRoleId'][$roleIdCurrent]);
219 * Adds a Resource having an identifier unique to the ACL
221 * The $parent parameter may be a reference to, or the string identifier for,
222 * the existing Resource from which the newly added Resource will inherit.
224 * @param Resource\ResourceInterface|string $resource
225 * @param Resource\ResourceInterface|string $parent
226 * @throws Exception\InvalidArgumentException
227 * @return Acl Provides a fluent interface
229 public function addResource($resource, $parent = null)
231 if (is_string($resource)) {
232 $resource = new Resource\
GenericResource($resource);
233 } elseif (! $resource instanceof Resource\ResourceInterface
) {
234 throw new Exception\
InvalidArgumentException(
235 'addResource() expects $resource to be of type Zend\Permissions\Acl\Resource\ResourceInterface'
239 $resourceId = $resource->getResourceId();
241 if ($this->hasResource($resourceId)) {
242 throw new Exception\
InvalidArgumentException("Resource id '$resourceId' already exists in the ACL");
245 $resourceParent = null;
247 if (null !== $parent) {
249 if ($parent instanceof Resource\ResourceInterface
) {
250 $resourceParentId = $parent->getResourceId();
252 $resourceParentId = $parent;
254 $resourceParent = $this->getResource($resourceParentId);
255 } catch (\Exception
$e) {
256 throw new Exception\
InvalidArgumentException(sprintf(
257 'Parent Resource id "%s" does not exist',
261 $this->resources
[$resourceParentId]['children'][$resourceId] = $resource;
264 $this->resources
[$resourceId] = [
265 'instance' => $resource,
266 'parent' => $resourceParent,
274 * Returns the identified Resource
276 * The $resource parameter can either be a Resource or a Resource identifier.
278 * @param Resource\ResourceInterface|string $resource
279 * @throws Exception\InvalidArgumentException
282 public function getResource($resource)
284 if ($resource instanceof Resource\ResourceInterface
) {
285 $resourceId = $resource->getResourceId();
287 $resourceId = (string) $resource;
290 if (! $this->hasResource($resource)) {
291 throw new Exception\
InvalidArgumentException("Resource '$resourceId' not found");
294 return $this->resources
[$resourceId]['instance'];
298 * Returns true if and only if the Resource exists in the ACL
300 * The $resource parameter can either be a Resource or a Resource identifier.
302 * @param Resource\ResourceInterface|string $resource
305 public function hasResource($resource)
307 if ($resource instanceof Resource\ResourceInterface
) {
308 $resourceId = $resource->getResourceId();
310 $resourceId = (string) $resource;
313 return isset($this->resources
[$resourceId]);
317 * Returns true if and only if $resource inherits from $inherit
319 * Both parameters may be either a Resource or a Resource identifier. If
320 * $onlyParent is true, then $resource must inherit directly from
321 * $inherit in order to return true. By default, this method looks
322 * through the entire inheritance tree to determine whether $resource
323 * inherits from $inherit through its ancestor Resources.
325 * @param Resource\ResourceInterface|string $resource
326 * @param Resource\ResourceInterface|string inherit
327 * @param bool $onlyParent
328 * @throws Exception\InvalidArgumentException
331 public function inheritsResource($resource, $inherit, $onlyParent = false)
334 $resourceId = $this->getResource($resource)->getResourceId();
335 $inheritId = $this->getResource($inherit)->getResourceId();
336 } catch (Exception\ExceptionInterface
$e) {
337 throw new Exception\
InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
340 if (null !== $this->resources
[$resourceId]['parent']) {
341 $parentId = $this->resources
[$resourceId]['parent']->getResourceId();
342 if ($inheritId === $parentId) {
344 } elseif ($onlyParent) {
351 while (null !== $this->resources
[$parentId]['parent']) {
352 $parentId = $this->resources
[$parentId]['parent']->getResourceId();
353 if ($inheritId === $parentId) {
362 * Removes a Resource and all of its children
364 * The $resource parameter can either be a Resource or a Resource identifier.
366 * @param Resource\ResourceInterface|string $resource
367 * @throws Exception\InvalidArgumentException
368 * @return Acl Provides a fluent interface
370 public function removeResource($resource)
373 $resourceId = $this->getResource($resource)->getResourceId();
374 } catch (Exception\ExceptionInterface
$e) {
375 throw new Exception\
InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
378 $resourcesRemoved = [$resourceId];
379 if (null !== ($resourceParent = $this->resources
[$resourceId]['parent'])) {
380 unset($this->resources
[$resourceParent->getResourceId()]['children'][$resourceId]);
382 foreach ($this->resources
[$resourceId]['children'] as $childId => $child) {
383 $this->removeResource($childId);
384 $resourcesRemoved[] = $childId;
387 foreach ($resourcesRemoved as $resourceIdRemoved) {
388 foreach ($this->rules
['byResourceId'] as $resourceIdCurrent => $rules) {
389 if ($resourceIdRemoved === $resourceIdCurrent) {
390 unset($this->rules
['byResourceId'][$resourceIdCurrent]);
395 unset($this->resources
[$resourceId]);
401 * Removes all Resources
403 * @return Acl Provides a fluent interface
405 public function removeResourceAll()
407 foreach ($this->resources
as $resourceId => $resource) {
408 unset($this->rules
['byResourceId'][$resourceId]);
411 $this->resources
= [];
417 * Adds an "allow" rule to the ACL
419 * @param Role\RoleInterface|string|array $roles
420 * @param Resource\ResourceInterface|string|array $resources
421 * @param string|array $privileges
422 * @param Assertion\AssertionInterface $assert
423 * @return Acl Provides a fluent interface
425 public function allow(
429 Assertion\AssertionInterface
$assert = null
431 return $this->setRule(self
::OP_ADD
, self
::TYPE_ALLOW
, $roles, $resources, $privileges, $assert);
435 * Adds a "deny" rule to the ACL
437 * @param Role\RoleInterface|string|array $roles
438 * @param Resource\ResourceInterface|string|array $resources
439 * @param string|array $privileges
440 * @param Assertion\AssertionInterface $assert
441 * @return Acl Provides a fluent interface
443 public function deny(
447 Assertion\AssertionInterface
$assert = null
449 return $this->setRule(self
::OP_ADD
, self
::TYPE_DENY
, $roles, $resources, $privileges, $assert);
453 * Removes "allow" permissions from the ACL
455 * @param Role\RoleInterface|string|array $roles
456 * @param Resource\ResourceInterface|string|array $resources
457 * @param string|array $privileges
458 * @return Acl Provides a fluent interface
460 public function removeAllow($roles = null, $resources = null, $privileges = null)
462 return $this->setRule(self
::OP_REMOVE
, self
::TYPE_ALLOW
, $roles, $resources, $privileges);
466 * Removes "deny" restrictions from the ACL
468 * @param Role\RoleInterface|string|array $roles
469 * @param Resource\ResourceInterface|string|array $resources
470 * @param string|array $privileges
471 * @return Acl Provides a fluent interface
473 public function removeDeny($roles = null, $resources = null, $privileges = null)
475 return $this->setRule(self
::OP_REMOVE
, self
::TYPE_DENY
, $roles, $resources, $privileges);
479 * Performs operations on ACL rules
481 * The $operation parameter may be either OP_ADD or OP_REMOVE, depending on whether the
482 * user wants to add or remove a rule, respectively:
486 * A rule is added that would allow one or more Roles access to [certain $privileges
487 * upon] the specified Resource(s).
489 * OP_REMOVE specifics:
491 * The rule is removed only in the context of the given Roles, Resources, and privileges.
492 * Existing rules to which the remove operation does not apply would remain in the
495 * The $type parameter may be either TYPE_ALLOW or TYPE_DENY, depending on whether the
496 * rule is intended to allow or deny permission, respectively.
498 * The $roles and $resources parameters may be references to, or the string identifiers for,
499 * existing Resources/Roles, or they may be passed as arrays of these - mixing string identifiers
500 * and objects is ok - to indicate the Resources and Roles to which the rule applies. If either
501 * $roles or $resources is null, then the rule applies to all Roles or all Resources, respectively.
502 * Both may be null in order to work with the default rule of the ACL.
504 * The $privileges parameter may be used to further specify that the rule applies only
505 * to certain privileges upon the Resource(s) in question. This may be specified to be a single
506 * privilege with a string, and multiple privileges may be specified as an array of strings.
508 * If $assert is provided, then its assert() method must return true in order for
509 * the rule to apply. If $assert is provided with $roles, $resources, and $privileges all
510 * equal to null, then a rule having a type of:
512 * TYPE_ALLOW will imply a type of TYPE_DENY, and
514 * TYPE_DENY will imply a type of TYPE_ALLOW
516 * when the rule's assertion fails. This is because the ACL needs to provide expected
517 * behavior when an assertion upon the default ACL rule fails.
519 * @param string $operation
520 * @param string $type
521 * @param Role\RoleInterface|string|array $roles
522 * @param Resource\ResourceInterface|string|array $resources
523 * @param string|array $privileges
524 * @param Assertion\AssertionInterface $assert
525 * @throws Exception\InvalidArgumentException
526 * @return Acl Provides a fluent interface
528 public function setRule(
534 Assertion\AssertionInterface
$assert = null
536 // ensure that the rule type is valid; normalize input to uppercase
537 $type = strtoupper($type);
538 if (self
::TYPE_ALLOW
!== $type && self
::TYPE_DENY
!== $type) {
539 throw new Exception\
InvalidArgumentException(sprintf(
540 'Unsupported rule type; must be either "%s" or "%s"',
546 // ensure that all specified Roles exist; normalize input to array of Role objects or null
547 if (! is_array($roles)) {
549 } elseif (0 === count($roles)) {
554 foreach ($rolesTemp as $role) {
555 if (null !== $role) {
556 $roles[] = $this->getRoleRegistry()->get($role);
563 // ensure that all specified Resources exist; normalize input to array of Resource objects or null
564 if (! is_array($resources)) {
565 if (null === $resources && count($this->resources
) > 0) {
566 $resources = array_keys($this->resources
);
567 // Passing a null resource; make sure "global" permission is also set!
568 if (! in_array(null, $resources)) {
569 array_unshift($resources, null);
572 $resources = [$resources];
574 } elseif (0 === count($resources)) {
577 $resourcesTemp = $resources;
579 foreach ($resourcesTemp as $resource) {
580 if (null !== $resource) {
581 $resourceObj = $this->getResource($resource);
582 $resourceId = $resourceObj->getResourceId();
583 $children = $this->getChildResources($resourceObj);
584 $resources = array_merge($resources, $children);
585 $resources[$resourceId] = $resourceObj;
590 unset($resourcesTemp);
592 // normalize privileges to array
593 if (null === $privileges) {
595 } elseif (! is_array($privileges)) {
596 $privileges = [$privileges];
599 switch ($operation) {
602 foreach ($resources as $resource) {
603 foreach ($roles as $role) {
604 $rules =& $this->getRules($resource, $role, true);
605 if (0 === count($privileges)) {
606 $rules['allPrivileges']['type'] = $type;
607 $rules['allPrivileges']['assert'] = $assert;
608 if (! isset($rules['byPrivilegeId'])) {
609 $rules['byPrivilegeId'] = [];
612 foreach ($privileges as $privilege) {
613 $rules['byPrivilegeId'][$privilege]['type'] = $type;
614 $rules['byPrivilegeId'][$privilege]['assert'] = $assert;
621 // remove from the rules
622 case self
::OP_REMOVE
:
623 foreach ($resources as $resource) {
624 foreach ($roles as $role) {
625 $rules =& $this->getRules($resource, $role);
626 if (null === $rules) {
629 if (0 === count($privileges)) {
630 if (null === $resource && null === $role) {
631 if ($type === $rules['allPrivileges']['type']) {
634 'type' => self
::TYPE_DENY
,
637 'byPrivilegeId' => []
643 if (isset($rules['allPrivileges']['type']) && $type === $rules['allPrivileges']['type']) {
644 unset($rules['allPrivileges']);
647 foreach ($privileges as $privilege) {
648 if (isset($rules['byPrivilegeId'][$privilege])
649 && $type === $rules['byPrivilegeId'][$privilege]['type']
651 unset($rules['byPrivilegeId'][$privilege]);
660 throw new Exception\
InvalidArgumentException(sprintf(
661 'Unsupported operation; must be either "%s" or "%s"',
671 * Returns all child resources from the given resource.
673 * @param Resource\ResourceInterface|string $resource
674 * @return Resource\ResourceInterface[]
676 protected function getChildResources(Resource\ResourceInterface
$resource)
679 $id = $resource->getResourceId();
681 $children = $this->resources
[$id]['children'];
682 foreach ($children as $child) {
683 $child_return = $this->getChildResources($child);
684 $child_return[$child->getResourceId()] = $child;
686 $return = array_merge($return, $child_return);
693 * Returns true if and only if the Role has access to the Resource
695 * The $role and $resource parameters may be references to, or the string identifiers for,
696 * an existing Resource and Role combination.
698 * If either $role or $resource is null, then the query applies to all Roles or all Resources,
699 * respectively. Both may be null to query whether the ACL has a "blacklist" rule
700 * (allow everything to all). By default, Zend\Permissions\Acl creates a "whitelist" rule (deny
701 * everything to all), and this method would return false unless this default has
702 * been overridden (i.e., by executing $acl->allow()).
704 * If a $privilege is not provided, then this method returns false if and only if the
705 * Role is denied access to at least one privilege upon the Resource. In other words, this
706 * method returns true if and only if the Role is allowed all privileges on the Resource.
708 * This method checks Role inheritance using a depth-first traversal of the Role registry.
709 * The highest priority parent (i.e., the parent most recently added) is checked first,
710 * and its respective parents are checked similarly before the lower-priority parents of
711 * the Role are checked.
713 * @param Role\RoleInterface|string $role
714 * @param Resource\ResourceInterface|string $resource
715 * @param string $privilege
718 public function isAllowed($role = null, $resource = null, $privilege = null)
720 // reset role & resource to null
721 $this->isAllowedRole
= null;
722 $this->isAllowedResource
= null;
723 $this->isAllowedPrivilege
= null;
725 if (null !== $role) {
726 // keep track of originally called role
727 $this->isAllowedRole
= $role;
728 $role = $this->getRoleRegistry()->get($role);
729 if (! $this->isAllowedRole
instanceof Role\RoleInterface
) {
730 $this->isAllowedRole
= $role;
734 if (null !== $resource) {
735 // keep track of originally called resource
736 $this->isAllowedResource
= $resource;
737 $resource = $this->getResource($resource);
738 if (! $this->isAllowedResource
instanceof Resource\ResourceInterface
) {
739 $this->isAllowedResource
= $resource;
743 if (null === $privilege) {
744 // query on all privileges
746 // depth-first search on $role if it is not 'allRoles' pseudo-parent
747 if (null !== $role && null !== ($result = $this->roleDFSAllPrivileges($role, $resource, $privilege))) {
751 // look for rule on 'allRoles' pseudo-parent
752 if (null !== ($rules = $this->getRules($resource, null))) {
753 foreach ($rules['byPrivilegeId'] as $privilege => $rule) {
754 if (self
::TYPE_DENY
=== (
755 $ruleTypeOnePrivilege = $this->getRuleType($resource, null, $privilege)
760 if (null !== ($ruleTypeAllPrivileges = $this->getRuleType($resource, null, null))) {
761 return self
::TYPE_ALLOW
=== $ruleTypeAllPrivileges;
766 $resource = $this->resources
[$resource->getResourceId()]['parent'];
767 } while (true); // loop terminates at 'allResources' pseudo-parent
769 $this->isAllowedPrivilege
= $privilege;
770 // query on one privilege
772 // depth-first search on $role if it is not 'allRoles' pseudo-parent
773 if (null !== $role && null !== ($result = $this->roleDFSOnePrivilege($role, $resource, $privilege))) {
777 // look for rule on 'allRoles' pseudo-parent
778 if (null !== ($ruleType = $this->getRuleType($resource, null, $privilege))) {
779 return self
::TYPE_ALLOW
=== $ruleType;
780 } elseif (null !== ($ruleTypeAllPrivileges = $this->getRuleType($resource, null, null))) {
781 $result = self
::TYPE_ALLOW
=== $ruleTypeAllPrivileges;
782 if ($result ||
null === $resource) {
788 $resource = $this->resources
[$resource->getResourceId()]['parent'];
789 } while (true); // loop terminates at 'allResources' pseudo-parent
794 * Returns the Role registry for this ACL
796 * If no Role registry has been created yet, a new default Role registry
797 * is created and returned.
799 * @return Role\Registry
801 protected function getRoleRegistry()
803 if (null === $this->roleRegistry
) {
804 $this->roleRegistry
= new Role\
Registry();
806 return $this->roleRegistry
;
810 * Performs a depth-first search of the Role DAG, starting at $role, in order to find a rule
811 * allowing/denying $role access to all privileges upon $resource
813 * This method returns true if a rule is found and allows access. If a rule exists and denies access,
814 * then this method returns false. If no applicable rule is found, then this method returns null.
816 * @param Role\RoleInterface $role
817 * @param Resource\ResourceInterface $resource
820 protected function roleDFSAllPrivileges(Role\RoleInterface
$role, Resource\ResourceInterface
$resource = null)
827 if (null !== ($result = $this->roleDFSVisitAllPrivileges($role, $resource, $dfs))) {
831 // This comment is needed due to a strange php-cs-fixer bug
832 while (null !== ($role = array_pop($dfs['stack']))) {
833 if (! isset($dfs['visited'][$role->getRoleId()])) {
834 if (null !== ($result = $this->roleDFSVisitAllPrivileges($role, $resource, $dfs))) {
844 * Visits an $role in order to look for a rule allowing/denying $role access to all privileges upon $resource
846 * This method returns true if a rule is found and allows access. If a rule exists and denies access,
847 * then this method returns false. If no applicable rule is found, then this method returns null.
849 * This method is used by the internal depth-first search algorithm and may modify the DFS data structure.
851 * @param Role\RoleInterface $role
852 * @param Resource\ResourceInterface $resource
855 * @throws Exception\RuntimeException
857 protected function roleDFSVisitAllPrivileges(
858 Role\RoleInterface
$role,
859 Resource\ResourceInterface
$resource = null,
863 throw new Exception\
RuntimeException('$dfs parameter may not be null');
866 if (null !== ($rules = $this->getRules($resource, $role))) {
867 foreach ($rules['byPrivilegeId'] as $privilege => $rule) {
868 if (self
::TYPE_DENY
=== ($ruleTypeOnePrivilege = $this->getRuleType($resource, $role, $privilege))) {
872 if (null !== ($ruleTypeAllPrivileges = $this->getRuleType($resource, $role, null))) {
873 return self
::TYPE_ALLOW
=== $ruleTypeAllPrivileges;
877 $dfs['visited'][$role->getRoleId()] = true;
878 foreach ($this->getRoleRegistry()->getParents($role) as $roleParent) {
879 $dfs['stack'][] = $roleParent;
886 * Performs a depth-first search of the Role DAG, starting at $role, in order to find a rule
887 * allowing/denying $role access to a $privilege upon $resource
889 * This method returns true if a rule is found and allows access. If a rule exists and denies access,
890 * then this method returns false. If no applicable rule is found, then this method returns null.
892 * @param Role\RoleInterface $role
893 * @param Resource\ResourceInterface $resource
894 * @param string $privilege
896 * @throws Exception\RuntimeException
898 protected function roleDFSOnePrivilege(
899 Role\RoleInterface
$role,
900 Resource\ResourceInterface
$resource = null,
903 if (null === $privilege) {
904 throw new Exception\
RuntimeException('$privilege parameter may not be null');
912 if (null !== ($result = $this->roleDFSVisitOnePrivilege($role, $resource, $privilege, $dfs))) {
916 // This comment is needed due to a strange php-cs-fixer bug
917 while (null !== ($role = array_pop($dfs['stack']))) {
918 if (! isset($dfs['visited'][$role->getRoleId()])) {
919 if (null !== ($result = $this->roleDFSVisitOnePrivilege($role, $resource, $privilege, $dfs))) {
929 * Visits an $role in order to look for a rule allowing/denying $role access to a $privilege upon $resource
931 * This method returns true if a rule is found and allows access. If a rule exists and denies access,
932 * then this method returns false. If no applicable rule is found, then this method returns null.
934 * This method is used by the internal depth-first search algorithm and may modify the DFS data structure.
936 * @param Role\RoleInterface $role
937 * @param Resource\ResourceInterface $resource
938 * @param string $privilege
941 * @throws Exception\RuntimeException
943 protected function roleDFSVisitOnePrivilege(
944 Role\RoleInterface
$role,
945 Resource\ResourceInterface
$resource = null,
949 if (null === $privilege) {
951 * @see Zend\Permissions\Acl\Exception
953 throw new Exception\
RuntimeException('$privilege parameter may not be null');
958 * @see Zend\Permissions\Acl\Exception
960 throw new Exception\
RuntimeException('$dfs parameter may not be null');
963 if (null !== ($ruleTypeOnePrivilege = $this->getRuleType($resource, $role, $privilege))) {
964 return self
::TYPE_ALLOW
=== $ruleTypeOnePrivilege;
965 } elseif (null !== ($ruleTypeAllPrivileges = $this->getRuleType($resource, $role, null))) {
966 return self
::TYPE_ALLOW
=== $ruleTypeAllPrivileges;
969 $dfs['visited'][$role->getRoleId()] = true;
970 foreach ($this->getRoleRegistry()->getParents($role) as $roleParent) {
971 $dfs['stack'][] = $roleParent;
978 * Returns the rule type associated with the specified Resource, Role, and privilege
981 * If a rule does not exist or its attached assertion fails, which means that
982 * the rule is not applicable, then this method returns null. Otherwise, the
983 * rule type applies and is returned as either TYPE_ALLOW or TYPE_DENY.
985 * If $resource or $role is null, then this means that the rule must apply to
986 * all Resources or Roles, respectively.
988 * If $privilege is null, then the rule must apply to all privileges.
990 * If all three parameters are null, then the default ACL rule type is returned,
991 * based on whether its assertion method passes.
993 * @param null|Resource\ResourceInterface $resource
994 * @param null|Role\RoleInterface $role
995 * @param null|string $privilege
996 * @return string|null
998 protected function getRuleType(
999 Resource\ResourceInterface
$resource = null,
1000 Role\RoleInterface
$role = null,
1003 // get the rules for the $resource and $role
1004 if (null === ($rules = $this->getRules($resource, $role))) {
1008 // follow $privilege
1009 if (null === $privilege) {
1010 if (isset($rules['allPrivileges'])) {
1011 $rule = $rules['allPrivileges'];
1015 } elseif (! isset($rules['byPrivilegeId'][$privilege])) {
1018 $rule = $rules['byPrivilegeId'][$privilege];
1021 // check assertion first
1022 if ($rule['assert']) {
1023 $assertion = $rule['assert'];
1024 $assertionValue = $assertion->assert(
1026 ($this->isAllowedRole
instanceof Role\RoleInterface
) ?
$this->isAllowedRole
: $role,
1027 ($this->isAllowedResource
instanceof Resource\ResourceInterface
) ?
$this->isAllowedResource
: $resource,
1028 $this->isAllowedPrivilege
1032 if (null === $rule['assert'] ||
$assertionValue) {
1033 return $rule['type'];
1034 } elseif (null !== $resource ||
null !== $role ||
null !== $privilege) {
1036 } elseif (self
::TYPE_ALLOW
=== $rule['type']) {
1037 return self
::TYPE_DENY
;
1040 return self
::TYPE_ALLOW
;
1044 * Returns the rules associated with a Resource and a Role, or null if no such rules exist
1046 * If either $resource or $role is null, this means that the rules returned are for all Resources or all Roles,
1047 * respectively. Both can be null to return the default rule set for all Resources and all Roles.
1049 * If the $create parameter is true, then a rule set is first created and then returned to the caller.
1051 * @param Resource\ResourceInterface $resource
1052 * @param Role\RoleInterface $role
1053 * @param bool $create
1054 * @return array|null
1056 protected function &getRules(
1057 Resource\ResourceInterface
$resource = null,
1058 Role\RoleInterface
$role = null,
1061 // create a reference to null
1067 if (null === $resource) {
1068 $visitor =& $this->rules
['allResources'];
1071 $resourceId = $resource->getResourceId();
1072 if (! isset($this->rules
['byResourceId'][$resourceId])) {
1076 $this->rules
['byResourceId'][$resourceId] = [];
1078 $visitor =& $this->rules
['byResourceId'][$resourceId];
1082 if (null === $role) {
1083 if (! isset($visitor['allRoles'])) {
1087 $visitor['allRoles']['byPrivilegeId'] = [];
1089 return $visitor['allRoles'];
1091 $roleId = $role->getRoleId();
1092 if (! isset($visitor['byRoleId'][$roleId])) {
1096 $visitor['byRoleId'][$roleId]['byPrivilegeId'] = [];
1098 return $visitor['byRoleId'][$roleId];
1102 * @return array of registered roles
1104 public function getRoles()
1106 return array_keys($this->getRoleRegistry()->getRoles());
1110 * @return array of registered resources
1112 public function getResources()
1114 return array_keys($this->resources
);