composer package updates
[openemr.git] / vendor / zendframework / zend-permissions-acl / src / Acl.php
blob96e34e3313d76cd2e2164e10c7c9b2fadb429bf6
1 <?php
2 /**
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
8 */
10 namespace Zend\Permissions\Acl;
12 class Acl implements AclInterface
14 /**
15 * Rule type: allow
17 const TYPE_ALLOW = 'TYPE_ALLOW';
19 /**
20 * Rule type: deny
22 const TYPE_DENY = 'TYPE_DENY';
24 /**
25 * Rule operation: add
27 const OP_ADD = 'OP_ADD';
29 /**
30 * Rule operation: remove
32 const OP_REMOVE = 'OP_REMOVE';
34 /**
35 * Role registry
37 * @var Role\Registry
39 protected $roleRegistry = null;
41 /**
42 * Resource tree
44 * @var array
46 protected $resources = [];
48 /**
49 * @var Role\RoleInterface
51 protected $isAllowedRole = null;
53 /**
54 * @var Resource
56 protected $isAllowedResource = null;
58 /**
59 * @var string
61 protected $isAllowedPrivilege = null;
63 /**
64 * ACL rules; whitelist (deny everything to all) by default
66 * @var array
68 protected $rules = [
69 'allResources' => [
70 'allRoles' => [
71 'allPrivileges' => [
72 'type' => self::TYPE_DENY,
73 'assert' => null
75 'byPrivilegeId' => []
77 'byRoleId' => []
79 'byResourceId' => []
82 /**
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
94 * highest priority.
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);
113 return $this;
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
135 * @return bool
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
154 * @return bool
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();
175 } else {
176 $roleId = $role;
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]);
194 return $this;
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]);
215 return $this;
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) {
248 try {
249 if ($parent instanceof Resource\ResourceInterface) {
250 $resourceParentId = $parent->getResourceId();
251 } else {
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',
258 $resourceParentId
259 ), 0, $e);
261 $this->resources[$resourceParentId]['children'][$resourceId] = $resource;
264 $this->resources[$resourceId] = [
265 'instance' => $resource,
266 'parent' => $resourceParent,
267 'children' => []
270 return $this;
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
280 * @return Resource
282 public function getResource($resource)
284 if ($resource instanceof Resource\ResourceInterface) {
285 $resourceId = $resource->getResourceId();
286 } else {
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
303 * @return bool
305 public function hasResource($resource)
307 if ($resource instanceof Resource\ResourceInterface) {
308 $resourceId = $resource->getResourceId();
309 } else {
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
329 * @return bool
331 public function inheritsResource($resource, $inherit, $onlyParent = false)
333 try {
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) {
343 return true;
344 } elseif ($onlyParent) {
345 return false;
347 } else {
348 return false;
351 while (null !== $this->resources[$parentId]['parent']) {
352 $parentId = $this->resources[$parentId]['parent']->getResourceId();
353 if ($inheritId === $parentId) {
354 return true;
358 return false;
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)
372 try {
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]);
397 return $this;
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 = [];
413 return $this;
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(
426 $roles = null,
427 $resources = null,
428 $privileges = null,
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(
444 $roles = null,
445 $resources = null,
446 $privileges = null,
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:
484 * OP_ADD specifics:
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
493 * ACL.
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(
529 $operation,
530 $type,
531 $roles = null,
532 $resources = null,
533 $privileges = null,
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"',
541 self::TYPE_ALLOW,
542 self::TYPE_DENY
546 // ensure that all specified Roles exist; normalize input to array of Role objects or null
547 if (! is_array($roles)) {
548 $roles = [$roles];
549 } elseif (0 === count($roles)) {
550 $roles = [null];
552 $rolesTemp = $roles;
553 $roles = [];
554 foreach ($rolesTemp as $role) {
555 if (null !== $role) {
556 $roles[] = $this->getRoleRegistry()->get($role);
557 } else {
558 $roles[] = null;
561 unset($rolesTemp);
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);
571 } else {
572 $resources = [$resources];
574 } elseif (0 === count($resources)) {
575 $resources = [null];
577 $resourcesTemp = $resources;
578 $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;
586 } else {
587 $resources[] = null;
590 unset($resourcesTemp);
592 // normalize privileges to array
593 if (null === $privileges) {
594 $privileges = [];
595 } elseif (! is_array($privileges)) {
596 $privileges = [$privileges];
599 switch ($operation) {
600 // add to the rules
601 case self::OP_ADD:
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'] = [];
611 } else {
612 foreach ($privileges as $privilege) {
613 $rules['byPrivilegeId'][$privilege]['type'] = $type;
614 $rules['byPrivilegeId'][$privilege]['assert'] = $assert;
619 break;
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) {
627 continue;
629 if (0 === count($privileges)) {
630 if (null === $resource && null === $role) {
631 if ($type === $rules['allPrivileges']['type']) {
632 $rules = [
633 'allPrivileges' => [
634 'type' => self::TYPE_DENY,
635 'assert' => null
637 'byPrivilegeId' => []
640 continue;
643 if (isset($rules['allPrivileges']['type']) && $type === $rules['allPrivileges']['type']) {
644 unset($rules['allPrivileges']);
646 } else {
647 foreach ($privileges as $privilege) {
648 if (isset($rules['byPrivilegeId'][$privilege])
649 && $type === $rules['byPrivilegeId'][$privilege]['type']
651 unset($rules['byPrivilegeId'][$privilege]);
657 break;
659 default:
660 throw new Exception\InvalidArgumentException(sprintf(
661 'Unsupported operation; must be either "%s" or "%s"',
662 self::OP_ADD,
663 self::OP_REMOVE
667 return $this;
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)
678 $return = [];
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);
689 return $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
716 * @return bool
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
745 do {
746 // depth-first search on $role if it is not 'allRoles' pseudo-parent
747 if (null !== $role && null !== ($result = $this->roleDFSAllPrivileges($role, $resource, $privilege))) {
748 return $result;
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)
756 )) {
757 return false;
760 if (null !== ($ruleTypeAllPrivileges = $this->getRuleType($resource, null, null))) {
761 return self::TYPE_ALLOW === $ruleTypeAllPrivileges;
765 // try next Resource
766 $resource = $this->resources[$resource->getResourceId()]['parent'];
767 } while (true); // loop terminates at 'allResources' pseudo-parent
768 } else {
769 $this->isAllowedPrivilege = $privilege;
770 // query on one privilege
771 do {
772 // depth-first search on $role if it is not 'allRoles' pseudo-parent
773 if (null !== $role && null !== ($result = $this->roleDFSOnePrivilege($role, $resource, $privilege))) {
774 return $result;
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) {
783 return $result;
787 // try next 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
818 * @return bool|null
820 protected function roleDFSAllPrivileges(Role\RoleInterface $role, Resource\ResourceInterface $resource = null)
822 $dfs = [
823 'visited' => [],
824 'stack' => []
827 if (null !== ($result = $this->roleDFSVisitAllPrivileges($role, $resource, $dfs))) {
828 return $result;
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))) {
835 return $result;
840 return;
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
853 * @param array $dfs
854 * @return bool|null
855 * @throws Exception\RuntimeException
857 protected function roleDFSVisitAllPrivileges(
858 Role\RoleInterface $role,
859 Resource\ResourceInterface $resource = null,
860 &$dfs = null
862 if (null === $dfs) {
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))) {
869 return false;
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;
882 return;
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
895 * @return bool|null
896 * @throws Exception\RuntimeException
898 protected function roleDFSOnePrivilege(
899 Role\RoleInterface $role,
900 Resource\ResourceInterface $resource = null,
901 $privilege = null
903 if (null === $privilege) {
904 throw new Exception\RuntimeException('$privilege parameter may not be null');
907 $dfs = [
908 'visited' => [],
909 'stack' => []
912 if (null !== ($result = $this->roleDFSVisitOnePrivilege($role, $resource, $privilege, $dfs))) {
913 return $result;
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))) {
920 return $result;
925 return;
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
939 * @param array $dfs
940 * @return bool|null
941 * @throws Exception\RuntimeException
943 protected function roleDFSVisitOnePrivilege(
944 Role\RoleInterface $role,
945 Resource\ResourceInterface $resource = null,
946 $privilege = null,
947 &$dfs = null
949 if (null === $privilege) {
951 * @see Zend\Permissions\Acl\Exception
953 throw new Exception\RuntimeException('$privilege parameter may not be null');
956 if (null === $dfs) {
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;
974 return;
978 * Returns the rule type associated with the specified Resource, Role, and privilege
979 * combination.
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,
1001 $privilege = null
1003 // get the rules for the $resource and $role
1004 if (null === ($rules = $this->getRules($resource, $role))) {
1005 return;
1008 // follow $privilege
1009 if (null === $privilege) {
1010 if (isset($rules['allPrivileges'])) {
1011 $rule = $rules['allPrivileges'];
1012 } else {
1013 return;
1015 } elseif (! isset($rules['byPrivilegeId'][$privilege])) {
1016 return;
1017 } else {
1018 $rule = $rules['byPrivilegeId'][$privilege];
1021 // check assertion first
1022 if ($rule['assert']) {
1023 $assertion = $rule['assert'];
1024 $assertionValue = $assertion->assert(
1025 $this,
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) {
1035 return;
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,
1059 $create = false
1061 // create a reference to null
1062 $null = null;
1063 $nullRef =& $null;
1065 // follow $resource
1066 do {
1067 if (null === $resource) {
1068 $visitor =& $this->rules['allResources'];
1069 break;
1071 $resourceId = $resource->getResourceId();
1072 if (! isset($this->rules['byResourceId'][$resourceId])) {
1073 if (! $create) {
1074 return $nullRef;
1076 $this->rules['byResourceId'][$resourceId] = [];
1078 $visitor =& $this->rules['byResourceId'][$resourceId];
1079 } while (false);
1081 // follow $role
1082 if (null === $role) {
1083 if (! isset($visitor['allRoles'])) {
1084 if (! $create) {
1085 return $nullRef;
1087 $visitor['allRoles']['byPrivilegeId'] = [];
1089 return $visitor['allRoles'];
1091 $roleId = $role->getRoleId();
1092 if (! isset($visitor['byRoleId'][$roleId])) {
1093 if (! $create) {
1094 return $nullRef;
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);