Added the zend framework 2 library, the path is specified in line no.26 in zend_modul...
[openemr.git] / interface / modules / zend_modules / library / Zend / Permissions / Acl / Acl.php
bloba9cad33928ac4383976eef0f612cf298cc92cf39
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-2013 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 = array();
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 = array(
69 'allResources' => array(
70 'allRoles' => array(
71 'allPrivileges' => array(
72 'type' => self::TYPE_DENY,
73 'assert' => null
75 'byPrivilegeId' => array()
77 'byRoleId' => array()
79 'byResourceId' => array()
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'
112 $this->getRoleRegistry()->add($role, $parents);
114 return $this;
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
136 * @return bool
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
155 * @return bool
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();
176 } else {
177 $roleId = $role;
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]);
195 return $this;
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]);
216 return $this;
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) {
249 try {
250 if ($parent instanceof Resource\ResourceInterface) {
251 $resourceParentId = $parent->getResourceId();
252 } else {
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',
259 $resourceParentId
260 ), 0, $e);
262 $this->resources[$resourceParentId]['children'][$resourceId] = $resource;
265 $this->resources[$resourceId] = array(
266 'instance' => $resource,
267 'parent' => $resourceParent,
268 'children' => array()
271 return $this;
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
281 * @return Resource
283 public function getResource($resource)
285 if ($resource instanceof Resource\ResourceInterface) {
286 $resourceId = $resource->getResourceId();
287 } else {
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
304 * @return bool
306 public function hasResource($resource)
308 if ($resource instanceof Resource\ResourceInterface) {
309 $resourceId = $resource->getResourceId();
310 } else {
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
330 * @return bool
332 public function inheritsResource($resource, $inherit, $onlyParent = false)
334 try {
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) {
344 return true;
345 } elseif ($onlyParent) {
346 return false;
348 } else {
349 return false;
352 while (null !== $this->resources[$parentId]['parent']) {
353 $parentId = $this->resources[$parentId]['parent']->getResourceId();
354 if ($inheritId === $parentId) {
355 return true;
359 return false;
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)
373 try {
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]);
398 return $this;
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();
418 return $this;
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:
481 * OP_ADD specifics:
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
490 * ACL.
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"',
533 self::TYPE_ALLOW,
534 self::TYPE_DENY
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);
544 $rolesTemp = $roles;
545 $roles = array();
546 foreach ($rolesTemp as $role) {
547 if (null !== $role) {
548 $roles[] = $this->getRoleRegistry()->get($role);
549 } else {
550 $roles[] = null;
553 unset($rolesTemp);
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);
563 } else {
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;
578 } else {
579 $resources[] = null;
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) {
592 // add to the rules
593 case self::OP_ADD:
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();
603 } else {
604 foreach ($privileges as $privilege) {
605 $rules['byPrivilegeId'][$privilege]['type'] = $type;
606 $rules['byPrivilegeId'][$privilege]['assert'] = $assert;
611 break;
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) {
619 continue;
621 if (0 === count($privileges)) {
622 if (null === $resource && null === $role) {
623 if ($type === $rules['allPrivileges']['type']) {
624 $rules = array(
625 'allPrivileges' => array(
626 'type' => self::TYPE_DENY,
627 'assert' => null
629 'byPrivilegeId' => array()
632 continue;
635 if (isset($rules['allPrivileges']['type']) &&
636 $type === $rules['allPrivileges']['type'])
638 unset($rules['allPrivileges']);
640 } else {
641 foreach ($privileges as $privilege) {
642 if (isset($rules['byPrivilegeId'][$privilege]) &&
643 $type === $rules['byPrivilegeId'][$privilege]['type'])
645 unset($rules['byPrivilegeId'][$privilege]);
651 break;
653 default:
654 throw new Exception\InvalidArgumentException(sprintf(
655 'Unsupported operation; must be either "%s" or "%s"',
656 self::OP_ADD,
657 self::OP_REMOVE
661 return $this;
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)
672 $return = array();
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);
683 return $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
710 * @return bool
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
739 do {
740 // depth-first search on $role if it is not 'allRoles' pseudo-parent
741 if (null !== $role && null !== ($result = $this->roleDFSAllPrivileges($role, $resource, $privilege))) {
742 return $result;
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))) {
749 return false;
752 if (null !== ($ruleTypeAllPrivileges = $this->getRuleType($resource, null, null))) {
753 return self::TYPE_ALLOW === $ruleTypeAllPrivileges;
757 // try next Resource
758 $resource = $this->resources[$resource->getResourceId()]['parent'];
760 } while (true); // loop terminates at 'allResources' pseudo-parent
761 } else {
762 $this->isAllowedPrivilege = $privilege;
763 // query on one privilege
764 do {
765 // depth-first search on $role if it is not 'allRoles' pseudo-parent
766 if (null !== $role && null !== ($result = $this->roleDFSOnePrivilege($role, $resource, $privilege))) {
767 return $result;
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) {
776 return $result;
780 // try next 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
812 * @return bool|null
814 protected function roleDFSAllPrivileges(Role\RoleInterface $role, Resource\ResourceInterface $resource = null)
816 $dfs = array(
817 'visited' => array(),
818 'stack' => array()
821 if (null !== ($result = $this->roleDFSVisitAllPrivileges($role, $resource, $dfs))) {
822 return $result;
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))) {
829 return $result;
834 return null;
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
847 * @param array $dfs
848 * @return bool|null
849 * @throws Exception\RuntimeException
851 protected function roleDFSVisitAllPrivileges(Role\RoleInterface $role, Resource\ResourceInterface $resource = null, &$dfs = null)
853 if (null === $dfs) {
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))) {
860 return false;
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;
873 return null;
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
886 * @return bool|null
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');
895 $dfs = array(
896 'visited' => array(),
897 'stack' => array()
900 if (null !== ($result = $this->roleDFSVisitOnePrivilege($role, $resource, $privilege, $dfs))) {
901 return $result;
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))) {
908 return $result;
913 return null;
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
927 * @param array $dfs
928 * @return bool|null
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');
941 if (null === $dfs) {
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;
959 return null;
963 * Returns the rule type associated with the specified Resource, Role, and privilege
964 * combination.
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))) {
987 return null;
990 // follow $privilege
991 if (null === $privilege) {
992 if (isset($rules['allPrivileges'])) {
993 $rule = $rules['allPrivileges'];
994 } else {
995 return null;
997 } elseif (!isset($rules['byPrivilegeId'][$privilege])) {
998 return null;
999 } else {
1000 $rule = $rules['byPrivilegeId'][$privilege];
1003 // check assertion first
1004 if ($rule['assert']) {
1005 $assertion = $rule['assert'];
1006 $assertionValue = $assertion->assert(
1007 $this,
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) {
1017 return null;
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
1041 $null = null;
1042 $nullRef =& $null;
1044 // follow $resource
1045 do {
1046 if (null === $resource) {
1047 $visitor =& $this->rules['allResources'];
1048 break;
1050 $resourceId = $resource->getResourceId();
1051 if (!isset($this->rules['byResourceId'][$resourceId])) {
1052 if (!$create) {
1053 return $nullRef;
1055 $this->rules['byResourceId'][$resourceId] = array();
1057 $visitor =& $this->rules['byResourceId'][$resourceId];
1058 } while (false);
1061 // follow $role
1062 if (null === $role) {
1063 if (!isset($visitor['allRoles'])) {
1064 if (!$create) {
1065 return $nullRef;
1067 $visitor['allRoles']['byPrivilegeId'] = array();
1069 return $visitor['allRoles'];
1071 $roleId = $role->getRoleId();
1072 if (!isset($visitor['byRoleId'][$roleId])) {
1073 if (!$create) {
1074 return $nullRef;
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);