Acl.php 39 KB

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