Acl.php 46 KB

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