2
0

Acl.php 38 KB

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