Acl.php 41 KB

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