Acl.php source code
Contents of file
Acl.php
1
<?php
2 /**
3 * Zend Framework
4 *
5 * LICENSE
6 *
7 * This source file is subject to the new BSD license that is bundled
8 * with this package in the file LICENSE.txt.
9 * It is also available through the world-wide-web at this URL:
10 * http://framework.zend.com/license/new-bsd
11 * If you did not receive a copy of the license and are unable to
12 * obtain it through the world-wide-web, please send an email
13 * to license@zend.com so we can send you a copy immediately.
14 *
15 * @category Zend
16 * @package Zend_Acl
17 * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
18 * @license http://framework.zend.com/license/new-bsd New BSD License
19 * @version $Id: Acl.php 17515 2009-08-10 13:48:44Z ralph $
20 */
21
22
23 /**
24 * @see Zend_Acl_Resource_Interface
25 */
26 require_once 'Zend/Acl/Resource/Interface.php';
27
28
29 /**
30 * @see Zend_Acl_Role_Registry
31 */
32 require_once 'Zend/Acl/Role/Registry.php';
33
34
35 /**
36 * @see Zend_Acl_Assert_Interface
37 */
38 require_once 'Zend/Acl/Assert/Interface.php';
39
40
41 /**
42 * @category Zend
43 * @package Zend_Acl
44 * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
45 * @license http://framework.zend.com/license/new-bsd New BSD License
46 */
47 class Zend_Acl
48 {
49 /**
50 * Rule type: allow
51 */
52 const TYPE_ALLOW = 'TYPE_ALLOW';
53
54 /**
55 * Rule type: deny
56 */
57 const TYPE_DENY = 'TYPE_DENY';
58
59 /**
60 * Rule operation: add
61 */
62 const OP_ADD = 'OP_ADD';
63
64 /**
65 * Rule operation: remove
66 */
67 const OP_REMOVE = 'OP_REMOVE';
68
69 /**
70 * Role registry
71 *
72 * @var Zend_Acl_Role_Registry
73 */
74 protected $_roleRegistry = null;
75
76 /**
77 * Resource tree
78 *
79 * @var array
80 */
81 protected $_resources = array();
82
83 /**
84 * @var Zend_Acl_Role_Interface
85 */
86 protected $_isAllowedRole = null;
87
88 /**
89 * @var Zend_Acl_Resource_Interface
90 */
91 protected $_isAllowedResource = null;
92
93 /**
94 * ACL rules; whitelist (deny everything to all) by default
95 *
96 * @var array
97 */
98 protected $_rules = array(
99 'allResources' => array(
100 'allRoles' => array(
101 'allPrivileges' => array(
102 'type' => self::TYPE_DENY,
103 'assert' => null
104 ),
105 'byPrivilegeId' => array()
106 ),
107 'byRoleId' => array()
108 ),
109 'byResourceId' => array()
110 );
111
112 /**
113 * Adds a Role having an identifier unique to the registry
114 *
115 * The $parents parameter may be a reference to, or the string identifier for,
116 * a Role existing in the registry, or $parents may be passed as an array of
117 * these - mixing string identifiers and objects is ok - to indicate the Roles
118 * from which the newly added Role will directly inherit.
119 *
120 * In order to resolve potential ambiguities with conflicting rules inherited
121 * from different parents, the most recently added parent takes precedence over
122 * parents that were previously added. In other words, the first parent added
123 * will have the least priority, and the last parent added will have the
124 * highest priority.
125 *
126 * @param Zend_Acl_Role_Interface $role
127 * @param Zend_Acl_Role_Interface|string|array $parents
128 * @uses Zend_Acl_Role_Registry::add()
129 * @return Zend_Acl Provides a fluent interface
130 */
131 public function addRole($role, $parents = null)
132 {
133 if (is_string($role)) {
134 $role = new Zend_Acl_Role($role);
135 }
136
137 if (!$role instanceof Zend_Acl_Role_Interface) {
138 require_once 'Zend/Acl/Exception.php';
139 throw new Zend_Acl_Exception('addRole() expects $role to be of type Zend_Acl_Role_Interface');
140 }
141
142
143 $this->_getRoleRegistry()->add($role, $parents);
144
145 return $this;
146 }
147
148 /**
149 * Returns the identified Role
150 *
151 * The $role parameter can either be a Role or Role identifier.
152 *
153 * @param Zend_Acl_Role_Interface|string $role
154 * @uses Zend_Acl_Role_Registry::get()
155 * @return Zend_Acl_Role_Interface
156 */
157 public function getRole($role)
158 {
159 return $this->_getRoleRegistry()->get($role);
160 }
161
162 /**
163 * Returns true if and only if the Role exists in the registry
164 *
165 * The $role parameter can either be a Role or a Role identifier.
166 *
167 * @param Zend_Acl_Role_Interface|string $role
168 * @uses Zend_Acl_Role_Registry::has()
169 * @return boolean
170 */
171 public function hasRole($role)
172 {
173 return $this->_getRoleRegistry()->has($role);
174 }
175
176 /**
177 * Returns true if and only if $role inherits from $inherit
178 *
179 * Both parameters may be either a Role or a Role identifier. If
180 * $onlyParents is true, then $role must inherit directly from
181 * $inherit in order to return true. By default, this method looks
182 * through the entire inheritance DAG to determine whether $role
183 * inherits from $inherit through its ancestor Roles.
184 *
185 * @param Zend_Acl_Role_Interface|string $role
186 * @param Zend_Acl_Role_Interface|string $inherit
187 * @param boolean $onlyParents
188 * @uses Zend_Acl_Role_Registry::inherits()
189 * @return boolean
190 */
191 public function inheritsRole($role, $inherit, $onlyParents = false)
192 {
193 return $this->_getRoleRegistry()->inherits($role, $inherit, $onlyParents);
194 }
195
196 /**
197 * Removes the Role from the registry
198 *
199 * The $role parameter can either be a Role or a Role identifier.
200 *
201 * @param Zend_Acl_Role_Interface|string $role
202 * @uses Zend_Acl_Role_Registry::remove()
203 * @return Zend_Acl Provides a fluent interface
204 */
205 public function removeRole($role)
206 {
207 $this->_getRoleRegistry()->remove($role);
208
209 if ($role instanceof Zend_Acl_Role_Interface) {
210 $roleId = $role->getRoleId();
211 } else {
212 $roleId = $role;
213 }
214
215 foreach ($this->_rules['allResources']['byRoleId'] as $roleIdCurrent => $rules) {
216 if ($roleId === $roleIdCurrent) {
217 unset($this->_rules['allResources']['byRoleId'][$roleIdCurrent]);
218 }
219 }
220 foreach ($this->_rules['byResourceId'] as $resourceIdCurrent => $visitor) {
221 foreach ($visitor['byRoleId'] as $roleIdCurrent => $rules) {
222 if ($roleId === $roleIdCurrent) {
223 unset($this->_rules['byResourceId'][$resourceIdCurrent]['byRoleId'][$roleIdCurrent]);
224 }
225 }
226 }
227
228 return $this;
229 }
230
231 /**
232 * Removes all Roles from the registry
233 *
234 * @uses Zend_Acl_Role_Registry::removeAll()
235 * @return Zend_Acl Provides a fluent interface
236 */
237 public function removeRoleAll()
238 {
239 $this->_getRoleRegistry()->removeAll();
240
241 foreach ($this->_rules['allResources']['byRoleId'] as $roleIdCurrent => $rules) {
242 unset($this->_rules['allResources']['byRoleId'][$roleIdCurrent]);
243 }
244 foreach ($this->_rules['byResourceId'] as $resourceIdCurrent => $visitor) {
245 foreach ($visitor['byRoleId'] as $roleIdCurrent => $rules) {
246 unset($this->_rules['byResourceId'][$resourceIdCurrent]['byRoleId'][$roleIdCurrent]);
247 }
248 }
249
250 return $this;
251 }
252
253 /**
254 * Adds a Resource having an identifier unique to the ACL
255 *
256 * The $parent parameter may be a reference to, or the string identifier for,
257 * the existing Resource from which the newly added Resource will inherit.
258 *
259 * @param Zend_Acl_Resource_Interface|string $resource
260 * @param Zend_Acl_Resource_Interface|string $parent
261 * @throws Zend_Acl_Exception
262 * @return Zend_Acl Provides a fluent interface
263 */
264 public function addResource($resource, $parent = null)
265 {
266 if (is_string($resource)) {
267 $resource = new Zend_Acl_Resource($resource);
268 }
269
270 if (!$resource instanceof Zend_Acl_Resource_Interface) {
271 require_once 'Zend/Acl/Exception.php';
272 throw new Zend_Acl_Exception('addResource() expects $resource to be of type Zend_Acl_Resource_Interface');
273 }
274
275 $resourceId = $resource->getResourceId();
276
277 if ($this->has($resourceId)) {
278 require_once 'Zend/Acl/Exception.php';
279 throw new Zend_Acl_Exception("Resource id '$resourceId' already exists in the ACL");
280 }
281
282 $resourceParent = null;
283
284 if (null !== $parent) {
285 try {
286 if ($parent instanceof Zend_Acl_Resource_Interface) {
287 $resourceParentId = $parent->getResourceId();
288 } else {
289 $resourceParentId = $parent;
290 }
291 $resourceParent = $this->get($resourceParentId);
292 } catch (Zend_Acl_Exception $e) {
293 throw new Zend_Acl_Exception("Parent Resource id '$resourceParentId' does not exist");
294 }
295 $this->_resources[$resourceParentId]['children'][$resourceId] = $resource;
296 }
297
298 $this->_resources[$resourceId] = array(
299 'instance' => $resource,
300 'parent' => $resourceParent,
301 'children' => array()
302 );
303
304 return $this;
305 }
306
307 /**
308 * Adds a Resource having an identifier unique to the ACL
309 *
310 * The $parent parameter may be a reference to, or the string identifier for,
311 * the existing Resource from which the newly added Resource will inherit.
312 *
313 * @deprecated in version 1.9.1 and will be available till 2.0. New code
314 * should use addResource() instead.
315 *
316 * @param Zend_Acl_Resource_Interface $resource
317 * @param Zend_Acl_Resource_Interface|string $parent
318 * @throws Zend_Acl_Exception
319 * @return Zend_Acl Provides a fluent interface
320 */
321 public function add(Zend_Acl_Resource_Interface $resource, $parent = null)
322 {
323 return $this->addResource($resource, $parent);
324 }
325
326 /**
327 * Returns the identified Resource
328 *
329 * The $resource parameter can either be a Resource or a Resource identifier.
330 *
331 * @param Zend_Acl_Resource_Interface|string $resource
332 * @throws Zend_Acl_Exception
333 * @return Zend_Acl_Resource_Interface
334 */
335 public function get($resource)
336 {
337 if ($resource instanceof Zend_Acl_Resource_Interface) {
338 $resourceId = $resource->getResourceId();
339 } else {
340 $resourceId = (string) $resource;
341 }
342
343 if (!$this->has($resource)) {
344 require_once 'Zend/Acl/Exception.php';
345 throw new Zend_Acl_Exception("Resource '$resourceId' not found");
346 }
347
348 return $this->_resources[$resourceId]['instance'];
349 }
350
351 /**
352 * Returns true if and only if the Resource exists in the ACL
353 *
354 * The $resource parameter can either be a Resource or a Resource identifier.
355 *
356 * @param Zend_Acl_Resource_Interface|string $resource
357 * @return boolean
358 */
359 public function has($resource)
360 {
361 if ($resource instanceof Zend_Acl_Resource_Interface) {
362 $resourceId = $resource->getResourceId();
363 } else {
364 $resourceId = (string) $resource;
365 }
366
367 return isset($this->_resources[$resourceId]);
368 }
369
370 /**
371 * Returns true if and only if $resource inherits from $inherit
372 *
373 * Both parameters may be either a Resource or a Resource identifier. If
374 * $onlyParent is true, then $resource must inherit directly from
375 * $inherit in order to return true. By default, this method looks
376 * through the entire inheritance tree to determine whether $resource
377 * inherits from $inherit through its ancestor Resources.
378 *
379 * @param Zend_Acl_Resource_Interface|string $resource
380 * @param Zend_Acl_Resource_Interface|string $inherit
381 * @param boolean $onlyParent
382 * @throws Zend_Acl_Resource_Registry_Exception
383 * @return boolean
384 */
385 public function inherits($resource, $inherit, $onlyParent = false)
386 {
387 try {
388 $resourceId = $this->get($resource)->getResourceId();
389 $inheritId = $this->get($inherit)->getResourceId();
390 } catch (Zend_Acl_Exception $e) {
391 throw $e;
392 }
393
394 if (null !== $this->_resources[$resourceId]['parent']) {
395 $parentId = $this->_resources[$resourceId]['parent']->getResourceId();
396 if ($inheritId === $parentId) {
397 return true;
398 } else if ($onlyParent) {
399 return false;
400 }
401 } else {
402 return false;
403 }
404
405 while (null !== $this->_resources[$parentId]['parent']) {
406 $parentId = $this->_resources[$parentId]['parent']->getResourceId();
407 if ($inheritId === $parentId) {
408 return true;
409 }
410 }
411
412 return false;
413 }
414
415 /**
416 * Removes a Resource and all of its children
417 *
418 * The $resource parameter can either be a Resource or a Resource identifier.
419 *
420 * @param Zend_Acl_Resource_Interface|string $resource
421 * @throws Zend_Acl_Exception
422 * @return Zend_Acl Provides a fluent interface
423 */
424 public function remove($resource)
425 {
426 try {
427 $resourceId = $this->get($resource)->getResourceId();
428 } catch (Zend_Acl_Exception $e) {
429 throw $e;
430 }
431
432 $resourcesRemoved = array($resourceId);
433 if (null !== ($resourceParent = $this->_resources[$resourceId]['parent'])) {
434 unset($this->_resources[$resourceParent->getResourceId()]['children'][$resourceId]);
435 }
436 foreach ($this->_resources[$resourceId]['children'] as $childId => $child) {
437 $this->remove($childId);
438 $resourcesRemoved[] = $childId;
439 }
440
441 foreach ($resourcesRemoved as $resourceIdRemoved) {
442 foreach ($this->_rules['byResourceId'] as $resourceIdCurrent => $rules) {
443 if ($resourceIdRemoved === $resourceIdCurrent) {
444 unset($this->_rules['byResourceId'][$resourceIdCurrent]);
445 }
446 }
447 }
448
449 unset($this->_resources[$resourceId]);
450
451 return $this;
452 }
453
454 /**
455 * Removes all Resources
456 *
457 * @return Zend_Acl Provides a fluent interface
458 */
459 public function removeAll()
460 {
461 foreach ($this->_resources as $resourceId => $resource) {
462 foreach ($this->_rules['byResourceId'] as $resourceIdCurrent => $rules) {
463 if ($resourceId === $resourceIdCurrent) {
464 unset($this->_rules['byResourceId'][$resourceIdCurrent]);
465 }
466 }
467 }
468
469 $this->_resources = array();
470
471 return $this;
472 }
473
474 /**
475 * Adds an "allow" rule to the ACL
476 *
477 * @param Zend_Acl_Role_Interface|string|array $roles
478 * @param Zend_Acl_Resource_Interface|string|array $resources
479 * @param string|array $privileges
480 * @param Zend_Acl_Assert_Interface $assert
481 * @uses Zend_Acl::setRule()
482 * @return Zend_Acl Provides a fluent interface
483 */
484 public function allow($roles = null, $resources = null, $privileges = null, Zend_Acl_Assert_Interface $assert = null)
485 {
486 return $this->setRule(self::OP_ADD, self::TYPE_ALLOW, $roles, $resources, $privileges, $assert);
487 }
488
489 /**
490 * Adds a "deny" rule to the ACL
491 *
492 * @param Zend_Acl_Role_Interface|string|array $roles
493 * @param Zend_Acl_Resource_Interface|string|array $resources
494 * @param string|array $privileges
495 * @param Zend_Acl_Assert_Interface $assert
496 * @uses Zend_Acl::setRule()
497 * @return Zend_Acl Provides a fluent interface
498 */
499 public function deny($roles = null, $resources = null, $privileges = null, Zend_Acl_Assert_Interface $assert = null)
500 {
501 return $this->setRule(self::OP_ADD, self::TYPE_DENY, $roles, $resources, $privileges, $assert);
502 }
503
504 /**
505 * Removes "allow" permissions from the ACL
506 *
507 * @param Zend_Acl_Role_Interface|string|array $roles
508 * @param Zend_Acl_Resource_Interface|string|array $resources
509 * @param string|array $privileges
510 * @uses Zend_Acl::setRule()
511 * @return Zend_Acl Provides a fluent interface
512 */
513 public function removeAllow($roles = null, $resources = null, $privileges = null)
514 {
515 return $this->setRule(self::OP_REMOVE, self::TYPE_ALLOW, $roles, $resources, $privileges);
516 }
517
518 /**
519 * Removes "deny" restrictions from the ACL
520 *
521 * @param Zend_Acl_Role_Interface|string|array $roles
522 * @param Zend_Acl_Resource_Interface|string|array $resources
523 * @param string|array $privileges
524 * @uses Zend_Acl::setRule()
525 * @return Zend_Acl Provides a fluent interface
526 */
527 public function removeDeny($roles = null, $resources = null, $privileges = null)
528 {
529 return $this->setRule(self::OP_REMOVE, self::TYPE_DENY, $roles, $resources, $privileges);
530 }
531
532 /**
533 * Performs operations on ACL rules
534 *
535 * The $operation parameter may be either OP_ADD or OP_REMOVE, depending on whether the
536 * user wants to add or remove a rule, respectively:
537 *
538 * OP_ADD specifics:
539 *
540 * A rule is added that would allow one or more Roles access to [certain $privileges
541 * upon] the specified Resource(s).
542 *
543 * OP_REMOVE specifics:
544 *
545 * The rule is removed only in the context of the given Roles, Resources, and privileges.
546 * Existing rules to which the remove operation does not apply would remain in the
547 * ACL.
548 *
549 * The $type parameter may be either TYPE_ALLOW or TYPE_DENY, depending on whether the
550 * rule is intended to allow or deny permission, respectively.
551 *
552 * The $roles and $resources parameters may be references to, or the string identifiers for,
553 * existing Resources/Roles, or they may be passed as arrays of these - mixing string identifiers
554 * and objects is ok - to indicate the Resources and Roles to which the rule applies. If either
555 * $roles or $resources is null, then the rule applies to all Roles or all Resources, respectively.
556 * Both may be null in order to work with the default rule of the ACL.
557 *
558 * The $privileges parameter may be used to further specify that the rule applies only
559 * to certain privileges upon the Resource(s) in question. This may be specified to be a single
560 * privilege with a string, and multiple privileges may be specified as an array of strings.
561 *
562 * If $assert is provided, then its assert() method must return true in order for
563 * the rule to apply. If $assert is provided with $roles, $resources, and $privileges all
564 * equal to null, then a rule having a type of:
565 *
566 * TYPE_ALLOW will imply a type of TYPE_DENY, and
567 *
568 * TYPE_DENY will imply a type of TYPE_ALLOW
569 *
570 * when the rule's assertion fails. This is because the ACL needs to provide expected
571 * behavior when an assertion upon the default ACL rule fails.
572 *
573 * @param string $operation
574 * @param string $type
575 * @param Zend_Acl_Role_Interface|string|array $roles
576 * @param Zend_Acl_Resource_Interface|string|array $resources
577 * @param string|array $privileges
578 * @param Zend_Acl_Assert_Interface $assert
579 * @throws Zend_Acl_Exception
580 * @uses Zend_Acl_Role_Registry::get()
581 * @uses Zend_Acl::get()
582 * @return Zend_Acl Provides a fluent interface
583 */
584 public function setRule($operation, $type, $roles = null, $resources = null, $privileges = null,
585 Zend_Acl_Assert_Interface $assert = null)
586 {
587 // ensure that the rule type is valid; normalize input to uppercase
588 $type = strtoupper($type);
589 if (self::TYPE_ALLOW !== $type && self::TYPE_DENY !== $type) {
590 require_once 'Zend/Acl/Exception.php';
591 throw new Zend_Acl_Exception("Unsupported rule type; must be either '" . self::TYPE_ALLOW . "' or '"
592 . self::TYPE_DENY . "'");
593 }
594
595 // ensure that all specified Roles exist; normalize input to array of Role objects or null
596 if (!is_array($roles)) {
597 $roles = array($roles);
598 } else if (0 === count($roles)) {
599 $roles = array(null);
600 }
601 $rolesTemp = $roles;
602 $roles = array();
603 foreach ($rolesTemp as $role) {
604 if (null !== $role) {
605 $roles[] = $this->_getRoleRegistry()->get($role);
606 } else {
607 $roles[] = null;
608 }
609 }
610 unset($rolesTemp);
611
612 // ensure that all specified Resources exist; normalize input to array of Resource objects or null
613 if (!is_array($resources)) {
614 $resources = array($resources);
615 } else if (0 === count($resources)) {
616 $resources = array(null);
617 }
618 $resourcesTemp = $resources;
619 $resources = array();
620 foreach ($resourcesTemp as $resource) {
621 if (null !== $resource) {
622 $resources[] = $this->get($resource);
623 } else {
624 $resources[] = null;
625 }
626 }
627 unset($resourcesTemp);
628
629 // normalize privileges to array
630 if (null === $privileges) {
631 $privileges = array();
632 } else if (!is_array($privileges)) {
633 $privileges = array($privileges);
634 }
635
636 switch ($operation) {
637
638 // add to the rules
639 case self::OP_ADD:
640 foreach ($resources as $resource) {
641 foreach ($roles as $role) {
642 $rules =& $this->_getRules($resource, $role, true);
643 if (0 === count($privileges)) {
644 $rules['allPrivileges']['type'] = $type;
645 $rules['allPrivileges']['assert'] = $assert;
646 if (!isset($rules['byPrivilegeId'])) {
647 $rules['byPrivilegeId'] = array();
648 }
649 } else {
650 foreach ($privileges as $privilege) {
651 $rules['byPrivilegeId'][$privilege]['type'] = $type;
652 $rules['byPrivilegeId'][$privilege]['assert'] = $assert;
653 }
654 }
655 }
656 }
657 break;
658
659 // remove from the rules
660 case self::OP_REMOVE:
661 foreach ($resources as $resource) {
662 foreach ($roles as $role) {
663 $rules =& $this->_getRules($resource, $role);
664 if (null === $rules) {
665 continue;
666 }
667 if (0 === count($privileges)) {
668 if (null === $resource && null === $role) {
669 if ($type === $rules['allPrivileges']['type']) {
670 $rules = array(
671 'allPrivileges' => array(
672 'type' => self::TYPE_DENY,
673 'assert' => null
674 ),
675 'byPrivilegeId' => array()
676 );
677 }
678 continue;
679 }
680 if ($type === $rules['allPrivileges']['type']) {
681 unset($rules['allPrivileges']);
682 }
683 } else {
684 foreach ($privileges as $privilege) {
685 if (isset($rules['byPrivilegeId'][$privilege]) &&
686 $type === $rules['byPrivilegeId'][$privilege]['type']) {
687 unset($rules['byPrivilegeId'][$privilege]);
688 }
689 }
690 }
691 }
692 }
693 break;
694
695 default:
696 require_once 'Zend/Acl/Exception.php';
697 throw new Zend_Acl_Exception("Unsupported operation; must be either '" . self::OP_ADD . "' or '"
698 . self::OP_REMOVE . "'");
699 }
700
701 return $this;
702 }
703
704 /**
705 * Returns true if and only if the Role has access to the Resource
706 *
707 * The $role and $resource parameters may be references to, or the string identifiers for,
708 * an existing Resource and Role combination.
709 *
710 * If either $role or $resource is null, then the query applies to all Roles or all Resources,
711 * respectively. Both may be null to query whether the ACL has a "blacklist" rule
712 * (allow everything to all). By default, Zend_Acl creates a "whitelist" rule (deny
713 * everything to all), and this method would return false unless this default has
714 * been overridden (i.e., by executing $acl->allow()).
715 *
716 * If a $privilege is not provided, then this method returns false if and only if the
717 * Role is denied access to at least one privilege upon the Resource. In other words, this
718 * method returns true if and only if the Role is allowed all privileges on the Resource.
719 *
720 * This method checks Role inheritance using a depth-first traversal of the Role registry.
721 * The highest priority parent (i.e., the parent most recently added) is checked first,
722 * and its respective parents are checked similarly before the lower-priority parents of
723 * the Role are checked.
724 *
725 * @param Zend_Acl_Role_Interface|string $role
726 * @param Zend_Acl_Resource_Interface|string $resource
727 * @param string $privilege
728 * @uses Zend_Acl::get()
729 * @uses Zend_Acl_Role_Registry::get()
730 * @return boolean
731 */
732 public function isAllowed($role = null, $resource = null, $privilege = null)
733 {
734 // reset role & resource to null
735 $this->_isAllowedRole = $this->_isAllowedResource = null;
736
737 if (null !== $role) {
738 // keep track of originally called role
739 $this->_isAllowedRole = $role;
740 $role = $this->_getRoleRegistry()->get($role);
741 if (!$this->_isAllowedRole instanceof Zend_Acl_Role_Interface) {
742 $this->_isAllowedRole = $role;
743 }
744 }
745
746 if (null !== $resource) {
747 // keep track of originally called resource
748 $this->_isAllowedResource = $resource;
749 $resource = $this->get($resource);
750 if (!$this->_isAllowedResource instanceof Zend_Acl_Resource_Interface) {
751 $this->_isAllowedResource = $resource;
752 }
753 }
754
755 if (null === $privilege) {
756 // query on all privileges
757 do {
758 // depth-first search on $role if it is not 'allRoles' pseudo-parent
759 if (null !== $role && null !== ($result = $this->_roleDFSAllPrivileges($role, $resource, $privilege))) {
760 return $result;
761 }
762
763 // look for rule on 'allRoles' psuedo-parent
764 if (null !== ($rules = $this->_getRules($resource, null))) {
765 foreach ($rules['byPrivilegeId'] as $privilege => $rule) {
766 if (self::TYPE_DENY === ($ruleTypeOnePrivilege = $this->_getRuleType($resource, null, $privilege))) {
767 return false;
768 }
769 }
770 if (null !== ($ruleTypeAllPrivileges = $this->_getRuleType($resource, null, null))) {
771 return self::TYPE_ALLOW === $ruleTypeAllPrivileges;
772 }
773 }
774
775 // try next Resource
776 $resource = $this->_resources[$resource->getResourceId()]['parent'];
777
778 } while (true); // loop terminates at 'allResources' pseudo-parent
779 } else {
780 // query on one privilege
781 do {
782 // depth-first search on $role if it is not 'allRoles' pseudo-parent
783 if (null !== $role && null !== ($result = $this->_roleDFSOnePrivilege($role, $resource, $privilege))) {
784 return $result;
785 }
786
787 // look for rule on 'allRoles' pseudo-parent
788 if (null !== ($ruleType = $this->_getRuleType($resource, null, $privilege))) {
789 return self::TYPE_ALLOW === $ruleType;
790 } else if (null !== ($ruleTypeAllPrivileges = $this->_getRuleType($resource, null, null))) {
791 return self::TYPE_ALLOW === $ruleTypeAllPrivileges;
792 }
793
794 // try next Resource
795 $resource = $this->_resources[$resource->getResourceId()]['parent'];
796
797 } while (true); // loop terminates at 'allResources' pseudo-parent
798 }
799 }
800
801 /**
802 * Returns the Role registry for this ACL
803 *
804 * If no Role registry has been created yet, a new default Role registry
805 * is created and returned.
806 *
807 * @return Zend_Acl_Role_Registry
808 */
809 protected function _getRoleRegistry()
810 {
811 if (null === $this->_roleRegistry) {
812 $this->_roleRegistry = new Zend_Acl_Role_Registry();
813 }
814 return $this->_roleRegistry;
815 }
816
817 /**
818 * Performs a depth-first search of the Role DAG, starting at $role, in order to find a rule
819 * allowing/denying $role access to all privileges upon $resource
820 *
821 * This method returns true if a rule is found and allows access. If a rule exists and denies access,
822 * then this method returns false. If no applicable rule is found, then this method returns null.
823 *
824 * @param Zend_Acl_Role_Interface $role
825 * @param Zend_Acl_Resource_Interface $resource
826 * @return boolean|null
827 */
828 protected function _roleDFSAllPrivileges(Zend_Acl_Role_Interface $role, Zend_Acl_Resource_Interface $resource = null)
829 {
830 $dfs = array(
831 'visited' => array(),
832 'stack' => array()
833 );
834
835 if (null !== ($result = $this->_roleDFSVisitAllPrivileges($role, $resource, $dfs))) {
836 return $result;
837 }
838
839 while (null !== ($role = array_pop($dfs['stack']))) {
840 if (!isset($dfs['visited'][$role->getRoleId()])) {
841 if (null !== ($result = $this->_roleDFSVisitAllPrivileges($role, $resource, $dfs))) {
842 return $result;
843 }
844 }
845 }
846
847 return null;
848 }
849
850 /**
851 * Visits an $role in order to look for a rule allowing/denying $role access to all privileges upon $resource
852 *
853 * This method returns true if a rule is found and allows access. If a rule exists and denies access,
854 * then this method returns false. If no applicable rule is found, then this method returns null.
855 *
856 * This method is used by the internal depth-first search algorithm and may modify the DFS data structure.
857 *
858 * @param Zend_Acl_Role_Interface $role
859 * @param Zend_Acl_Resource_Interface $resource
860 * @param array $dfs
861 * @return boolean|null
862 * @throws Zend_Acl_Exception
863 */
864 protected function _roleDFSVisitAllPrivileges(Zend_Acl_Role_Interface $role, Zend_Acl_Resource_Interface $resource = null,
865 &$dfs = null)
866 {
867 if (null === $dfs) {
868 /**
869 * @see Zend_Acl_Exception
870 */
871 require_once 'Zend/Acl/Exception.php';
872 throw new Zend_Acl_Exception('$dfs parameter may not be null');
873 }
874
875 if (null !== ($rules = $this->_getRules($resource, $role))) {
876 foreach ($rules['byPrivilegeId'] as $privilege => $rule) {
877 if (self::TYPE_DENY === ($ruleTypeOnePrivilege = $this->_getRuleType($resource, $role, $privilege))) {
878 return false;
879 }
880 }
881 if (null !== ($ruleTypeAllPrivileges = $this->_getRuleType($resource, $role, null))) {
882 return self::TYPE_ALLOW === $ruleTypeAllPrivileges;
883 }
884 }
885
886 $dfs['visited'][$role->getRoleId()] = true;
887 foreach ($this->_getRoleRegistry()->getParents($role) as $roleParentId => $roleParent) {
888 $dfs['stack'][] = $roleParent;
889 }
890
891 return null;
892 }
893
894 /**
895 * Performs a depth-first search of the Role DAG, starting at $role, in order to find a rule
896 * allowing/denying $role access to a $privilege upon $resource
897 *
898 * This method returns true if a rule is found and allows access. If a rule exists and denies access,
899 * then this method returns false. If no applicable rule is found, then this method returns null.
900 *
901 * @param Zend_Acl_Role_Interface $role
902 * @param Zend_Acl_Resource_Interface $resource
903 * @param string $privilege
904 * @return boolean|null
905 * @throws Zend_Acl_Exception
906 */
907 protected function _roleDFSOnePrivilege(Zend_Acl_Role_Interface $role, Zend_Acl_Resource_Interface $resource = null,
908 $privilege = null)
909 {
910 if (null === $privilege) {
911 /**
912 * @see Zend_Acl_Exception
913 */
914 require_once 'Zend/Acl/Exception.php';
915 throw new Zend_Acl_Exception('$privilege parameter may not be null');
916 }
917
918 $dfs = array(
919 'visited' => array(),
920 'stack' => array()
921 );
922
923 if (null !== ($result = $this->_roleDFSVisitOnePrivilege($role, $resource, $privilege, $dfs))) {
924 return $result;
925 }
926
927 while (null !== ($role = array_pop($dfs['stack']))) {
928 if (!isset($dfs['visited'][$role->getRoleId()])) {
929 if (null !== ($result = $this->_roleDFSVisitOnePrivilege($role, $resource, $privilege, $dfs))) {
930 return $result;
931 }
932 }
933 }
934
935 return null;
936 }
937
938 /**
939 * Visits an $role in order to look for a rule allowing/denying $role access to a $privilege upon $resource
940 *
941 * This method returns true if a rule is found and allows access. If a rule exists and denies access,
942 * then this method returns false. If no applicable rule is found, then this method returns null.
943 *
944 * This method is used by the internal depth-first search algorithm and may modify the DFS data structure.
945 *
946 * @param Zend_Acl_Role_Interface $role
947 * @param Zend_Acl_Resource_Interface $resource
948 * @param string $privilege
949 * @param array $dfs
950 * @return boolean|null
951 * @throws Zend_Acl_Exception
952 */
953 protected function _roleDFSVisitOnePrivilege(Zend_Acl_Role_Interface $role, Zend_Acl_Resource_Interface $resource = null,
954 $privilege = null, &$dfs = null)
955 {
956 if (null === $privilege) {
957 /**
958 * @see Zend_Acl_Exception
959 */
960 require_once 'Zend/Acl/Exception.php';
961 throw new Zend_Acl_Exception('$privilege parameter may not be null');
962 }
963
964 if (null === $dfs) {
965 /**
966 * @see Zend_Acl_Exception
967 */
968 require_once 'Zend/Acl/Exception.php';
969 throw new Zend_Acl_Exception('$dfs parameter may not be null');
970 }
971
972 if (null !== ($ruleTypeOnePrivilege = $this->_getRuleType($resource, $role, $privilege))) {
973 return self::TYPE_ALLOW === $ruleTypeOnePrivilege;
974 } else if (null !== ($ruleTypeAllPrivileges = $this->_getRuleType($resource, $role, null))) {
975 return self::TYPE_ALLOW === $ruleTypeAllPrivileges;
976 }
977
978 $dfs['visited'][$role->getRoleId()] = true;
979 foreach ($this->_getRoleRegistry()->getParents($role) as $roleParentId => $roleParent) {
980 $dfs['stack'][] = $roleParent;
981 }
982
983 return null;
984 }
985
986 /**
987 * Returns the rule type associated with the specified Resource, Role, and privilege
988 * combination.
989 *
990 * If a rule does not exist or its attached assertion fails, which means that
991 * the rule is not applicable, then this method returns null. Otherwise, the
992 * rule type applies and is returned as either TYPE_ALLOW or TYPE_DENY.
993 *
994 * If $resource or $role is null, then this means that the rule must apply to
995 * all Resources or Roles, respectively.
996 *
997 * If $privilege is null, then the rule must apply to all privileges.
998 *
999 * If all three parameters are null, then the default ACL rule type is returned,
1000 * based on whether its assertion method passes.
1001 *
1002 * @param Zend_Acl_Resource_Interface $resource
1003 * @param Zend_Acl_Role_Interface $role
1004 * @param string $privilege
1005 * @return string|null
1006 */
1007 protected function _getRuleType(Zend_Acl_Resource_Interface $resource = null, Zend_Acl_Role_Interface $role = null,
1008 $privilege = null)
1009 {
1010 // get the rules for the $resource and $role
1011 if (null === ($rules = $this->_getRules($resource, $role))) {
1012 return null;
1013 }
1014
1015 // follow $privilege
1016 if (null === $privilege) {
1017 if (isset($rules['allPrivileges'])) {
1018 $rule = $rules['allPrivileges'];
1019 } else {
1020 return null;
1021 }
1022 } else if (!isset($rules['byPrivilegeId'][$privilege])) {
1023 return null;
1024 } else {
1025 $rule = $rules['byPrivilegeId'][$privilege];
1026 }
1027
1028 // check assertion first
1029 if ($rule['assert']) {
1030 $assertion = $rule['assert'];
1031 $assertionValue = $assertion->assert(
1032 $this,
1033 ($this->_isAllowedRole instanceof Zend_Acl_Role_Interface) ? $this->_isAllowedRole : $role,
1034 ($this->_isAllowedResource instanceof Zend_Acl_Resource_Interface) ? $this->_isAllowedResource : $resource,
1035 $privilege
1036 );
1037 }
1038
1039 if (null === $rule['assert'] || $assertionValue) {
1040 return $rule['type'];
1041 } else if (null !== $resource || null !== $role || null !== $privilege) {
1042 return null;
1043 } else if (self::TYPE_ALLOW === $rule['type']) {
1044 return self::TYPE_DENY;
1045 } else {
1046 return self::TYPE_ALLOW;
1047 }
1048 }
1049
1050 /**
1051 * Returns the rules associated with a Resource and a Role, or null if no such rules exist
1052 *
1053 * If either $resource or $role is null, this means that the rules returned are for all Resources or all Roles,
1054 * respectively. Both can be null to return the default rule set for all Resources and all Roles.
1055 *
1056 * If the $create parameter is true, then a rule set is first created and then returned to the caller.
1057 *
1058 * @param Zend_Acl_Resource_Interface $resource
1059 * @param Zend_Acl_Role_Interface $role
1060 * @param boolean $create
1061 * @return array|null
1062 */
1063 protected function &_getRules(Zend_Acl_Resource_Interface $resource = null, Zend_Acl_Role_Interface $role = null,
1064 $create = false)
1065 {
1066 // create a reference to null
1067 $null = null;
1068 $nullRef =& $null;
1069
1070 // follow $resource
1071 do {
1072 if (null === $resource) {
1073 $visitor =& $this->_rules['allResources'];
1074 break;
1075 }
1076 $resourceId = $resource->getResourceId();
1077 if (!isset($this->_rules['byResourceId'][$resourceId])) {
1078 if (!$create) {
1079 return $nullRef;
1080 }
1081 $this->_rules['byResourceId'][$resourceId] = array();
1082 }
1083 $visitor =& $this->_rules['byResourceId'][$resourceId];
1084 } while (false);
1085
1086
1087 // follow $role
1088 if (null === $role) {
1089 if (!isset($visitor['allRoles'])) {
1090 if (!$create) {
1091 return $nullRef;
1092 }
1093 $visitor['allRoles']['byPrivilegeId'] = array();
1094 }
1095 return $visitor['allRoles'];
1096 }
1097 $roleId = $role->getRoleId();
1098 if (!isset($visitor['byRoleId'][$roleId])) {
1099 if (!$create) {
1100 return $nullRef;
1101 }
1102 $visitor['byRoleId'][$roleId]['byPrivilegeId'] = array();
1103 }
1104 return $visitor['byRoleId'][$roleId];
1105 }
1106
1107 }
1108