Acl.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  1. <?php
  2. /**
  3. *
  4. * Cube Framework $Id$ +ZjSTmVALawSGSDsJRY8yHBeEWJfviBwiv8jozAo7sA=
  5. *
  6. * @link http://codecu.be/framework
  7. * @copyright Copyright (c) 2015 CodeCube SRL
  8. * @license http://codecu.be/framework/license Commercial License
  9. *
  10. * @version 1.4
  11. */
  12. namespace Cube\Permissions;
  13. /**
  14. * access control list management class
  15. *
  16. * Class Acl
  17. *
  18. * @package Cube\Permissions
  19. */
  20. class Acl
  21. {
  22. /**
  23. * rule type = allow
  24. */
  25. const TYPE_ALLOW = 'ALLOW';
  26. /**
  27. * rule type = deny
  28. */
  29. const TYPE_DENY = 'DENY';
  30. /**
  31. * rule operation = add
  32. */
  33. const OPERATION_ADD = 'ADD';
  34. /**
  35. * rule operation = remove
  36. */
  37. const OPERATION_REMOVE = 'REMOVE';
  38. /**
  39. *
  40. * array of roles
  41. *
  42. * @var array
  43. */
  44. protected $_roles = array();
  45. /**
  46. *
  47. * array of resources
  48. *
  49. * @var array
  50. */
  51. protected $_resources = array();
  52. /**
  53. *
  54. * array of rules
  55. *
  56. * array format:
  57. *
  58. * array($resourceId => array(
  59. * $roleId = array(
  60. * array(
  61. * 'name' => $privilegeName|null (all privileges)
  62. * 'type' => ALLOW|DENY
  63. * )
  64. * )
  65. * )
  66. *
  67. * @var array
  68. */
  69. protected $_rules = array();
  70. /**
  71. *
  72. * add a role to the roles array
  73. *
  74. * @param \Cube\Permissions\RoleInterface $role
  75. * @param mixed $parents
  76. *
  77. * @throws \RuntimeException
  78. * @throws \InvalidArgumentException
  79. * @throws \DomainException
  80. * @return $this
  81. */
  82. public function addRole(RoleInterface $role, $parents = null)
  83. {
  84. $roleId = $role->getId();
  85. $roleParents = array();
  86. if ($this->hasRole($role)) {
  87. throw new \InvalidArgumentException(
  88. sprintf("The role with the id '%s' already exists.", $roleId));
  89. }
  90. if ($parents !== null) {
  91. if (!is_array($parents)) {
  92. $parents = array($parents);
  93. }
  94. foreach ((array)$parents as $parent) {
  95. if (!$parent instanceof RoleInterface) {
  96. throw new \RuntimeException("All role parents must be of type \Cube\Permissions\RoleInterface");
  97. }
  98. $roleParentId = $parent->getId();
  99. if ($roleId === $roleParentId) {
  100. throw new \DomainException(
  101. sprintf("Cannot set a parent role as itself, role id '%s'.", $roleId));
  102. }
  103. $roleParents[$roleParentId] = $parent;
  104. $this->_roles[$roleParentId]['children'][$roleId] = $role;
  105. }
  106. }
  107. $this->_roles[$roleId] = array(
  108. 'instance' => $role,
  109. 'parents' => $roleParents,
  110. 'children' => array()
  111. );
  112. return $this;
  113. }
  114. /**
  115. *
  116. * check if a role exists in the acl
  117. *
  118. * @param string|\Cube\Permissions\RoleInterface $role
  119. *
  120. * @return bool
  121. */
  122. public function hasRole($role)
  123. {
  124. if ($role instanceof RoleInterface) {
  125. $roleId = $role->getId();
  126. }
  127. else {
  128. $roleId = (string)$role;
  129. }
  130. return isset($this->_roles[$roleId]);
  131. }
  132. /**
  133. *
  134. * return the role instance
  135. *
  136. * @param string|\Cube\Permissions\RoleInterface $role
  137. *
  138. * @return \Cube\Permissions\RoleInterface
  139. * @throws \InvalidArgumentException
  140. */
  141. public function getRole($role)
  142. {
  143. if ($role instanceof RoleInterface) {
  144. $roleId = $role->getId();
  145. }
  146. else {
  147. $roleId = (string)$role;
  148. }
  149. if ($this->hasRole($role) === false) {
  150. throw new \InvalidArgumentException(
  151. sprintf("The role '%s' was not found.", $roleId));
  152. }
  153. return $this->_roles[$roleId]['instance'];
  154. }
  155. /**
  156. *
  157. * remove a role from the roles array, including parent and child dependencies
  158. *
  159. * @param string|\Cube\Permissions\RoleInterface $role
  160. *
  161. * @return $this
  162. */
  163. public function removeRole($role)
  164. {
  165. $roleId = $this->getRole($role)->getId();
  166. foreach ($this->_roles[$roleId]['children'] as $childId => $child) {
  167. unset($this->_roles[$childId]['parents'][$roleId]);
  168. }
  169. foreach ($this->_roles[$roleId]['parents'] as $parentId => $parent) {
  170. unset($this->_roles[$parentId]['children'][$roleId]);
  171. }
  172. unset($this->_roles[$roleId]);
  173. return $this;
  174. }
  175. /**
  176. *
  177. * reset roles array
  178. *
  179. * @return $this
  180. */
  181. public function removeAllRoles()
  182. {
  183. $this->_roles = array();
  184. foreach ($this->_rules['allResources']['byRoleId'] as $roleIdCurrent => $rules) {
  185. unset($this->_rules['allResources']['byRoleId'][$roleIdCurrent]);
  186. }
  187. foreach ($this->_rules['byResourceId'] as $resourceIdCurrent => $visitor) {
  188. foreach ($visitor['byRoleId'] as $roleIdCurrent => $rules) {
  189. unset($this->_rules['byResourceId'][$resourceIdCurrent]['byRoleId'][$roleIdCurrent]);
  190. }
  191. }
  192. return $this;
  193. }
  194. /**
  195. *
  196. * get all roles from the acl
  197. *
  198. * @return array
  199. */
  200. public function getRoles()
  201. {
  202. return $this->_roles;
  203. }
  204. /**
  205. *
  206. * get the parents of a role
  207. *
  208. * @param string|\Cube\Permissions\RoleInterface $role
  209. *
  210. * @return array
  211. */
  212. public function getRoleParents($role)
  213. {
  214. $roleId = $this->getRole($role)->getId();
  215. return $this->_roles[$roleId]['parents'];
  216. }
  217. /**
  218. *
  219. * add resource to acl
  220. *
  221. * @param \Cube\Permissions\ResourceInterface $resource
  222. * @param mixed $parent
  223. *
  224. * @return $this
  225. * @throws \InvalidArgumentException
  226. */
  227. public function addResource(ResourceInterface $resource, $parent = null)
  228. {
  229. $resourceId = $resource->getId();
  230. if ($this->hasResource($resource)) {
  231. throw new \InvalidArgumentException(
  232. sprintf("The resource with the id '%s' already exists.", $resourceId));
  233. }
  234. $resourceParent = null;
  235. if ($parent !== null) {
  236. if ($parent instanceof ResourceInterface) {
  237. $resourceParentId = $parent->getId();
  238. }
  239. else {
  240. $resourceParentId = $parent;
  241. }
  242. $resourceParent = $this->getResource($resourceParentId);
  243. $this->_resources[$resourceParentId]['children'][$resourceId] = $resource;
  244. }
  245. $this->_resources[$resourceId] = array(
  246. 'instance' => $resource,
  247. 'parent' => $resourceParent,
  248. 'children' => array()
  249. );
  250. return $this;
  251. }
  252. /**
  253. *
  254. * check if a resource exists in the acl
  255. *
  256. * @param string|\Cube\Permissions\ResourceInterface $resource
  257. *
  258. * @return bool
  259. */
  260. public function hasResource($resource)
  261. {
  262. if ($resource instanceof ResourceInterface) {
  263. $resourceId = $resource->getId();
  264. }
  265. else {
  266. $resourceId = (string)$resource;
  267. }
  268. return isset($this->_resources[$resourceId]);
  269. }
  270. /**
  271. *
  272. * get a resource instance
  273. *
  274. * @param string|\Cube\Permissions\ResourceInterface $resource
  275. *
  276. * @return \Cube\Permissions\ResourceInterface
  277. * @throws \InvalidArgumentException
  278. */
  279. public function getResource($resource)
  280. {
  281. if ($resource instanceof ResourceInterface) {
  282. $resourceId = $resource->getId();
  283. }
  284. else {
  285. $resourceId = (string)$resource;
  286. }
  287. if ($this->hasResource($resource) === false) {
  288. throw new \InvalidArgumentException(
  289. sprintf("The resource '%s' was not found.", $resourceId));
  290. }
  291. return $this->_resources[$resourceId]['instance'];
  292. }
  293. /**
  294. *
  295. * remove a resource from the resources array, including parent and child dependencies
  296. *
  297. * @param string|\Cube\Permissions\ResourceInterface $resource
  298. *
  299. * @return $this
  300. */
  301. public function removeResource($resource)
  302. {
  303. $resourceId = $this->getResource($resource)->getId();
  304. $resourcesRemoved = array($resourceId);
  305. if (null !== ($resourceParent = $this->_resources[$resourceId]['parent'])) {
  306. unset($this->_resources[$resourceParent->getResourceId()]['children'][$resourceId]);
  307. }
  308. foreach ($this->_resources[$resourceId]['children'] as $childId => $child) {
  309. $this->removeResource($childId);
  310. $resourcesRemoved[] = $childId;
  311. }
  312. unset($this->_roles[$resourceId]);
  313. unset($this->_resources[$resourceId]);
  314. return $this;
  315. }
  316. /**
  317. *
  318. * reset resources array
  319. *
  320. * @return $this
  321. */
  322. public function removeAllResources()
  323. {
  324. $this->_rules = array();
  325. $this->_resources = array();
  326. return $this;
  327. }
  328. /**
  329. *
  330. * set acl rule
  331. *
  332. * @param string $operation
  333. * @param string $type
  334. * @param string|array|\Cube\Permissions\RoleInterface $roles
  335. * @param string|array|\Cube\Permissions\ResourceInterface $resources
  336. * @param string|array $privileges
  337. * @param \Cube\Permissions\AssertInterface $assert
  338. *
  339. * @return $this
  340. */
  341. protected function _setRule($operation, $type, $roles, $resources, $privileges = null, AssertInterface $assert = null)
  342. {
  343. if (!is_array($roles)) {
  344. $roles = array($roles);
  345. }
  346. $rolesTmp = $roles;
  347. $roles = array();
  348. foreach ($rolesTmp as $role) {
  349. $roles[] = $this->getRole($role)->getId();
  350. }
  351. unset($rolesTmp);
  352. if (!is_array($resources)) {
  353. $resources = array($resources);
  354. }
  355. $resourcesTmp = $resources;
  356. $resources = array();
  357. foreach ($resourcesTmp as $resource) {
  358. $resources[] = $this->getResource($resource)->getId();
  359. }
  360. if (!is_array($privileges)) {
  361. $privileges = array($privileges);
  362. }
  363. switch ($operation) {
  364. case self::OPERATION_ADD:
  365. foreach ($resources as $resource) {
  366. foreach ($roles as $role) {
  367. foreach ($privileges as $privilege) {
  368. $this->_rules[$resource][$role][(string)$privilege] = array(
  369. 'type' => $type,
  370. 'assert' => $assert,
  371. );
  372. }
  373. }
  374. }
  375. break;
  376. case self::OPERATION_REMOVE:
  377. foreach ($resources as $resource) {
  378. foreach ($roles as $role) {
  379. if ($privileges !== null) {
  380. foreach ($privileges as $privilege) {
  381. unset($this->_rules[$resource][$role][(string)$privilege]);
  382. }
  383. }
  384. else {
  385. unset($this->_rules[$resource][$role]);
  386. }
  387. if (empty($this->_rules[$resource][$role])) {
  388. unset($this->_rules[$resource][$role]);
  389. }
  390. }
  391. if (empty($this->_rules[$resource])) {
  392. unset($this->_rules[$resource]);
  393. }
  394. }
  395. break;
  396. }
  397. return $this;
  398. }
  399. /**
  400. *
  401. * set a rule of type allow
  402. *
  403. * @param string|array|\Cube\Permissions\RoleInterface $roles
  404. * @param string|array|\Cube\Permissions\ResourceInterface $resources
  405. * @param string|array $privileges
  406. * @param \Cube\Permissions\AssertInterface $assert
  407. *
  408. * @return $this
  409. */
  410. public function allow($roles = null, $resources = null, $privileges = null, AssertInterface $assert = null)
  411. {
  412. return $this->_setRule(self::OPERATION_ADD, self::TYPE_ALLOW, $roles, $resources, $privileges, $assert);
  413. }
  414. /**
  415. *
  416. * set a rule of type deny
  417. *
  418. * @param string|array|\Cube\Permissions\RoleInterface $roles
  419. * @param string|array|\Cube\Permissions\ResourceInterface $resources
  420. * @param string|array $privileges
  421. * @param \Cube\Permissions\AssertInterface $assert
  422. *
  423. * @return $this
  424. */
  425. public function deny($roles = null, $resources = null, $privileges = null, AssertInterface $assert = null)
  426. {
  427. return $this->_setRule(self::OPERATION_ADD, self::TYPE_DENY, $roles, $resources, $privileges, $assert);
  428. }
  429. /**
  430. *
  431. * remove a rule of type allow
  432. *
  433. * @param string|array|\Cube\Permissions\RoleInterface $roles
  434. * @param string|array|\Cube\Permissions\ResourceInterface $resources
  435. * @param string|array $privileges
  436. * @param \Cube\Permissions\AssertInterface $assert
  437. *
  438. * @return $this
  439. */
  440. public function removeAllow($roles = null, $resources = null, $privileges = null, AssertInterface $assert = null)
  441. {
  442. return $this->_setRule(self::OPERATION_REMOVE, self::TYPE_ALLOW, $roles, $resources, $privileges, $assert);
  443. }
  444. /**
  445. *
  446. * remove a rule of type deny
  447. *
  448. * @param string|array|\Cube\Permissions\RoleInterface $roles
  449. * @param string|array|\Cube\Permissions\ResourceInterface $resources
  450. * @param string|array $privileges
  451. * @param \Cube\Permissions\AssertInterface $assert
  452. *
  453. * @return $this
  454. */
  455. public function removeDeny($roles = null, $resources = null, $privileges = null, AssertInterface $assert = null)
  456. {
  457. return $this->_setRule(self::OPERATION_REMOVE, self::TYPE_DENY, $roles, $resources, $privileges, $assert);
  458. }
  459. /**
  460. *
  461. * check if one or more roles are allowed for a certain resource
  462. * for allowed to be true, at least one role needs to be allowed
  463. *
  464. * @param array|string|\Cube\Permissions\RoleInterface $roles
  465. * @param string|\Cube\Permissions\ResourceInterface $resource
  466. * @param string $privilege
  467. *
  468. * @return bool
  469. */
  470. public function isAllowed($roles, $resource, $privilege = null)
  471. {
  472. if (!is_array($roles)) {
  473. $roles = array($roles);
  474. }
  475. $allowed = false;
  476. foreach ($roles as $role) {
  477. $allowed = ($this->_isAllowed($role, $resource, $privilege) === true) ? true : $allowed;
  478. }
  479. return $allowed;
  480. }
  481. /**
  482. *
  483. * check if a role is allowed to access a certain resource
  484. *
  485. * if the rule is defined by a parent, then if one parent allows the resource,
  486. * then return true
  487. * only return false if either specified by a rule of the role or if all parents
  488. * assert to false
  489. *
  490. * TODO: CUSTOM ASSERTIONS
  491. * RULE WITH NO PARENTS AND NO ACTION DOESNT SEEM TO WORK
  492. *
  493. * @param array|string|\Cube\Permissions\RoleInterface $role
  494. * @param string|\Cube\Permissions\ResourceInterface $resource
  495. * @param string $privilege
  496. *
  497. * @return bool
  498. */
  499. protected function _isAllowed($role, $resource, $privilege = null)
  500. {
  501. $roleId = $this->getRole($role)->getId();
  502. $resourceId = $this->getResource($resource)->getId();
  503. $privilege = (string)$privilege;
  504. $rule = null;
  505. $allowed = false;
  506. if (isset($this->_rules[$resourceId][$roleId][$privilege])) {
  507. // applies to a single action
  508. $rule = $this->_rules[$resourceId][$roleId][$privilege];
  509. }
  510. else if (isset($this->_rules[$resourceId][$roleId][''])) {
  511. // the rule applies to all actions
  512. $rule = $this->_rules[$resourceId][$roleId][''];
  513. }
  514. if ($rule !== null) {
  515. if ($rule['assert'] !== null) {
  516. $allowed = $rule['assert']->assert($this, $role, $resource, $privilege);
  517. }
  518. else if ($rule['type'] === self::TYPE_ALLOW) {
  519. $allowed = true;
  520. }
  521. else if ($rule['type'] === self::TYPE_DENY) {
  522. $allowed = false;
  523. }
  524. }
  525. else {
  526. $parents = $this->getRoleParents($role);
  527. foreach ($parents as $parent) {
  528. if ($this->isAllowed($parent, $resource, $privilege) === true) {
  529. return true;
  530. }
  531. }
  532. }
  533. return $allowed;
  534. }
  535. }