PageRenderTime 50ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/src/Symfony/Component/Security/Acl/Domain/Acl.php

https://github.com/Exercise/symfony
PHP | 666 lines | 374 code | 76 blank | 216 comment | 44 complexity | 0f1a81604a5cdb50a1b7bd147af3365c MD5 | raw file
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Security\Acl\Domain;
  11. use Doctrine\Common\NotifyPropertyChanged;
  12. use Doctrine\Common\PropertyChangedListener;
  13. use Symfony\Component\Security\Acl\Model\AclInterface;
  14. use Symfony\Component\Security\Acl\Model\AuditableAclInterface;
  15. use Symfony\Component\Security\Acl\Model\EntryInterface;
  16. use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface;
  17. use Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface;
  18. use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface;
  19. /**
  20. * An ACL implementation.
  21. *
  22. * Each object identity has exactly one associated ACL. Each ACL can have four
  23. * different types of ACEs (class ACEs, object ACEs, class field ACEs, object field
  24. * ACEs).
  25. *
  26. * You should not iterate over the ACEs yourself, but instead use isGranted(),
  27. * or isFieldGranted(). These will utilize an implementation of PermissionGrantingStrategy
  28. * internally.
  29. *
  30. * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  31. */
  32. class Acl implements AuditableAclInterface, NotifyPropertyChanged
  33. {
  34. private $parentAcl;
  35. private $permissionGrantingStrategy;
  36. private $objectIdentity;
  37. private $classAces;
  38. private $classFieldAces;
  39. private $objectAces;
  40. private $objectFieldAces;
  41. private $id;
  42. private $loadedSids;
  43. private $entriesInheriting;
  44. private $listeners;
  45. /**
  46. * Constructor
  47. *
  48. * @param integer $id
  49. * @param ObjectIdentityInterface $objectIdentity
  50. * @param PermissionGrantingStrategyInterface $permissionGrantingStrategy
  51. * @param array $loadedSids
  52. * @param Boolean $entriesInheriting
  53. */
  54. public function __construct($id, ObjectIdentityInterface $objectIdentity, PermissionGrantingStrategyInterface $permissionGrantingStrategy, array $loadedSids = array(), $entriesInheriting)
  55. {
  56. $this->id = $id;
  57. $this->objectIdentity = $objectIdentity;
  58. $this->permissionGrantingStrategy = $permissionGrantingStrategy;
  59. $this->loadedSids = $loadedSids;
  60. $this->entriesInheriting = $entriesInheriting;
  61. $this->parentAcl = null;
  62. $this->classAces = array();
  63. $this->classFieldAces = array();
  64. $this->objectAces = array();
  65. $this->objectFieldAces = array();
  66. $this->listeners = array();
  67. }
  68. /**
  69. * Adds a property changed listener
  70. *
  71. * @param PropertyChangedListener $listener
  72. */
  73. public function addPropertyChangedListener(PropertyChangedListener $listener)
  74. {
  75. $this->listeners[] = $listener;
  76. }
  77. /**
  78. * {@inheritDoc}
  79. */
  80. public function deleteClassAce($index)
  81. {
  82. $this->deleteAce('classAces', $index);
  83. }
  84. /**
  85. * {@inheritDoc}
  86. */
  87. public function deleteClassFieldAce($index, $field)
  88. {
  89. $this->deleteFieldAce('classFieldAces', $index, $field);
  90. }
  91. /**
  92. * {@inheritDoc}
  93. */
  94. public function deleteObjectAce($index)
  95. {
  96. $this->deleteAce('objectAces', $index);
  97. }
  98. /**
  99. * {@inheritDoc}
  100. */
  101. public function deleteObjectFieldAce($index, $field)
  102. {
  103. $this->deleteFieldAce('objectFieldAces', $index, $field);
  104. }
  105. /**
  106. * {@inheritDoc}
  107. */
  108. public function getClassAces()
  109. {
  110. return $this->classAces;
  111. }
  112. /**
  113. * {@inheritDoc}
  114. */
  115. public function getClassFieldAces($field)
  116. {
  117. return isset($this->classFieldAces[$field])? $this->classFieldAces[$field] : array();
  118. }
  119. /**
  120. * {@inheritDoc}
  121. */
  122. public function getObjectAces()
  123. {
  124. return $this->objectAces;
  125. }
  126. /**
  127. * {@inheritDoc}
  128. */
  129. public function getObjectFieldAces($field)
  130. {
  131. return isset($this->objectFieldAces[$field]) ? $this->objectFieldAces[$field] : array();
  132. }
  133. /**
  134. * {@inheritDoc}
  135. */
  136. public function getId()
  137. {
  138. return $this->id;
  139. }
  140. /**
  141. * {@inheritDoc}
  142. */
  143. public function getObjectIdentity()
  144. {
  145. return $this->objectIdentity;
  146. }
  147. /**
  148. * {@inheritDoc}
  149. */
  150. public function getParentAcl()
  151. {
  152. return $this->parentAcl;
  153. }
  154. /**
  155. * {@inheritDoc}
  156. */
  157. public function insertClassAce(SecurityIdentityInterface $sid, $mask, $index = 0, $granting = true, $strategy = null)
  158. {
  159. $this->insertAce('classAces', $index, $mask, $sid, $granting, $strategy);
  160. }
  161. /**
  162. * {@inheritDoc}
  163. */
  164. public function insertClassFieldAce($field, SecurityIdentityInterface $sid, $mask, $index = 0, $granting = true, $strategy = null)
  165. {
  166. $this->insertFieldAce('classFieldAces', $index, $field, $mask, $sid, $granting, $strategy);
  167. }
  168. /**
  169. * {@inheritDoc}
  170. */
  171. public function insertObjectAce(SecurityIdentityInterface $sid, $mask, $index = 0, $granting = true, $strategy = null)
  172. {
  173. $this->insertAce('objectAces', $index, $mask, $sid, $granting, $strategy);
  174. }
  175. /**
  176. * {@inheritDoc}
  177. */
  178. public function insertObjectFieldAce($field, SecurityIdentityInterface $sid, $mask, $index = 0, $granting = true, $strategy = null)
  179. {
  180. $this->insertFieldAce('objectFieldAces', $index, $field, $mask, $sid, $granting, $strategy);
  181. }
  182. /**
  183. * {@inheritDoc}
  184. */
  185. public function isEntriesInheriting()
  186. {
  187. return $this->entriesInheriting;
  188. }
  189. /**
  190. * {@inheritDoc}
  191. */
  192. public function isFieldGranted($field, array $masks, array $securityIdentities, $administrativeMode = false)
  193. {
  194. return $this->permissionGrantingStrategy->isFieldGranted($this, $field, $masks, $securityIdentities, $administrativeMode);
  195. }
  196. /**
  197. * {@inheritDoc}
  198. */
  199. public function isGranted(array $masks, array $securityIdentities, $administrativeMode = false)
  200. {
  201. return $this->permissionGrantingStrategy->isGranted($this, $masks, $securityIdentities, $administrativeMode);
  202. }
  203. /**
  204. * {@inheritDoc}
  205. */
  206. public function isSidLoaded($sids)
  207. {
  208. if (!$this->loadedSids) {
  209. return true;
  210. }
  211. if (!is_array($sids)) {
  212. $sids = array($sids);
  213. }
  214. foreach ($sids as $sid) {
  215. if (!$sid instanceof SecurityIdentityInterface) {
  216. throw new \InvalidArgumentException(
  217. '$sid must be an instance of SecurityIdentityInterface.');
  218. }
  219. foreach ($this->loadedSids as $loadedSid) {
  220. if ($loadedSid->equals($sid)) {
  221. continue 2;
  222. }
  223. }
  224. return false;
  225. }
  226. return true;
  227. }
  228. /**
  229. * Implementation for the \Serializable interface
  230. *
  231. * @return string
  232. */
  233. public function serialize()
  234. {
  235. return serialize(array(
  236. null === $this->parentAcl ? null : $this->parentAcl->getId(),
  237. $this->objectIdentity,
  238. $this->classAces,
  239. $this->classFieldAces,
  240. $this->objectAces,
  241. $this->objectFieldAces,
  242. $this->id,
  243. $this->loadedSids,
  244. $this->entriesInheriting,
  245. ));
  246. }
  247. /**
  248. * Implementation for the \Serializable interface
  249. *
  250. * @param string $serialized
  251. */
  252. public function unserialize($serialized)
  253. {
  254. list($this->parentAcl,
  255. $this->objectIdentity,
  256. $this->classAces,
  257. $this->classFieldAces,
  258. $this->objectAces,
  259. $this->objectFieldAces,
  260. $this->id,
  261. $this->loadedSids,
  262. $this->entriesInheriting
  263. ) = unserialize($serialized);
  264. $this->listeners = array();
  265. }
  266. /**
  267. * {@inheritDoc}
  268. */
  269. public function setEntriesInheriting($boolean)
  270. {
  271. if ($this->entriesInheriting !== $boolean) {
  272. $this->onPropertyChanged('entriesInheriting', $this->entriesInheriting, $boolean);
  273. $this->entriesInheriting = $boolean;
  274. }
  275. }
  276. /**
  277. * {@inheritDoc}
  278. */
  279. public function setParentAcl(AclInterface $acl = null)
  280. {
  281. if (null !== $acl && null === $acl->getId()) {
  282. throw new \InvalidArgumentException('$acl must have an ID.');
  283. }
  284. if ($this->parentAcl !== $acl) {
  285. $this->onPropertyChanged('parentAcl', $this->parentAcl, $acl);
  286. $this->parentAcl = $acl;
  287. }
  288. }
  289. /**
  290. * {@inheritDoc}
  291. */
  292. public function updateClassAce($index, $mask, $strategy = null)
  293. {
  294. $this->updateAce('classAces', $index, $mask, $strategy);
  295. }
  296. /**
  297. * {@inheritDoc}
  298. */
  299. public function updateClassFieldAce($index, $field, $mask, $strategy = null)
  300. {
  301. $this->updateFieldAce('classFieldAces', $index, $field, $mask, $strategy);
  302. }
  303. /**
  304. * {@inheritDoc}
  305. */
  306. public function updateObjectAce($index, $mask, $strategy = null)
  307. {
  308. $this->updateAce('objectAces', $index, $mask, $strategy);
  309. }
  310. /**
  311. * {@inheritDoc}
  312. */
  313. public function updateObjectFieldAce($index, $field, $mask, $strategy = null)
  314. {
  315. $this->updateFieldAce('objectFieldAces', $index, $field, $mask, $strategy);
  316. }
  317. /**
  318. * {@inheritDoc}
  319. */
  320. public function updateClassAuditing($index, $auditSuccess, $auditFailure)
  321. {
  322. $this->updateAuditing($this->classAces, $index, $auditSuccess, $auditFailure);
  323. }
  324. /**
  325. * {@inheritDoc}
  326. */
  327. public function updateClassFieldAuditing($index, $field, $auditSuccess, $auditFailure)
  328. {
  329. if (!isset($this->classFieldAces[$field])) {
  330. throw new \InvalidArgumentException(sprintf('There are no ACEs for field "%s".', $field));
  331. }
  332. $this->updateAuditing($this->classFieldAces[$field], $index, $auditSuccess, $auditFailure);
  333. }
  334. /**
  335. * {@inheritDoc}
  336. */
  337. public function updateObjectAuditing($index, $auditSuccess, $auditFailure)
  338. {
  339. $this->updateAuditing($this->objectAces, $index, $auditSuccess, $auditFailure);
  340. }
  341. /**
  342. * {@inheritDoc}
  343. */
  344. public function updateObjectFieldAuditing($index, $field, $auditSuccess, $auditFailure)
  345. {
  346. if (!isset($this->objectFieldAces[$field])) {
  347. throw new \InvalidArgumentException(sprintf('There are no ACEs for field "%s".', $field));
  348. }
  349. $this->updateAuditing($this->objectFieldAces[$field], $index, $auditSuccess, $auditFailure);
  350. }
  351. /**
  352. * Deletes an ACE
  353. *
  354. * @param string $property
  355. * @param integer $index
  356. * @throws \OutOfBoundsException
  357. */
  358. private function deleteAce($property, $index)
  359. {
  360. $aces =& $this->$property;
  361. if (!isset($aces[$index])) {
  362. throw new \OutOfBoundsException(sprintf('The index "%d" does not exist.', $index));
  363. }
  364. $oldValue = $this->$property;
  365. unset($aces[$index]);
  366. $this->$property = array_values($this->$property);
  367. $this->onPropertyChanged($property, $oldValue, $this->$property);
  368. for ($i=$index,$c=count($this->$property); $i<$c; $i++) {
  369. $this->onEntryPropertyChanged($aces[$i], 'aceOrder', $i+1, $i);
  370. }
  371. }
  372. /**
  373. * Deletes a field-based ACE
  374. *
  375. * @param string $property
  376. * @param integer $index
  377. * @param string $field
  378. * @throws \OutOfBoundsException
  379. */
  380. private function deleteFieldAce($property, $index, $field)
  381. {
  382. $aces =& $this->$property;
  383. if (!isset($aces[$field][$index])) {
  384. throw new \OutOfBoundsException(sprintf('The index "%d" does not exist.', $index));
  385. }
  386. $oldValue = $this->$property;
  387. unset($aces[$field][$index]);
  388. $aces[$field] = array_values($aces[$field]);
  389. $this->onPropertyChanged($property, $oldValue, $this->$property);
  390. for ($i=$index,$c=count($aces[$field]); $i<$c; $i++) {
  391. $this->onEntryPropertyChanged($aces[$field][$i], 'aceOrder', $i+1, $i);
  392. }
  393. }
  394. /**
  395. * Inserts an ACE
  396. *
  397. * @param string $property
  398. * @param integer $index
  399. * @param integer $mask
  400. * @param SecurityIdentityInterface $sid
  401. * @param Boolean $granting
  402. * @param string $strategy
  403. * @throws \OutOfBoundsException
  404. * @throws \InvalidArgumentException
  405. */
  406. private function insertAce($property, $index, $mask, SecurityIdentityInterface $sid, $granting, $strategy = null)
  407. {
  408. if ($index < 0 || $index > count($this->$property)) {
  409. throw new \OutOfBoundsException(sprintf('The index must be in the interval [0, %d].', count($this->$property)));
  410. }
  411. if (!is_int($mask)) {
  412. throw new \InvalidArgumentException('$mask must be an integer.');
  413. }
  414. if (null === $strategy) {
  415. if (true === $granting) {
  416. $strategy = PermissionGrantingStrategy::ALL;
  417. } else {
  418. $strategy = PermissionGrantingStrategy::ANY;
  419. }
  420. }
  421. $aces =& $this->$property;
  422. $oldValue = $this->$property;
  423. if (isset($aces[$index])) {
  424. $this->$property = array_merge(
  425. array_slice($this->$property, 0, $index),
  426. array(true),
  427. array_slice($this->$property, $index)
  428. );
  429. for ($i=$index,$c=count($this->$property)-1; $i<$c; $i++) {
  430. $this->onEntryPropertyChanged($aces[$i+1], 'aceOrder', $i, $i+1);
  431. }
  432. }
  433. $aces[$index] = new Entry(null, $this, $sid, $strategy, $mask, $granting, false, false);
  434. $this->onPropertyChanged($property, $oldValue, $this->$property);
  435. }
  436. /**
  437. * Inserts a field-based ACE
  438. *
  439. * @param string $property
  440. * @param integer $index
  441. * @param string $field
  442. * @param integer $mask
  443. * @param SecurityIdentityInterface $sid
  444. * @param Boolean $granting
  445. * @param string $strategy
  446. * @throws \InvalidArgumentException
  447. * @throws \OutOfBoundsException
  448. */
  449. private function insertFieldAce($property, $index, $field, $mask, SecurityIdentityInterface $sid, $granting, $strategy = null)
  450. {
  451. if (0 === strlen($field)) {
  452. throw new \InvalidArgumentException('$field cannot be empty.');
  453. }
  454. if (!is_int($mask)) {
  455. throw new \InvalidArgumentException('$mask must be an integer.');
  456. }
  457. if (null === $strategy) {
  458. if (true === $granting) {
  459. $strategy = PermissionGrantingStrategy::ALL;
  460. } else {
  461. $strategy = PermissionGrantingStrategy::ANY;
  462. }
  463. }
  464. $aces =& $this->$property;
  465. if (!isset($aces[$field])) {
  466. $aces[$field] = array();
  467. }
  468. if ($index < 0 || $index > count($aces[$field])) {
  469. throw new \OutOfBoundsException(sprintf('The index must be in the interval [0, %d].', count($this->$property)));
  470. }
  471. $oldValue = $aces;
  472. if (isset($aces[$field][$index])) {
  473. $aces[$field] = array_merge(
  474. array_slice($aces[$field], 0, $index),
  475. array(true),
  476. array_slice($aces[$field], $index)
  477. );
  478. for ($i=$index,$c=count($aces[$field])-1; $i<$c; $i++) {
  479. $this->onEntryPropertyChanged($aces[$field][$i+1], 'aceOrder', $i, $i+1);
  480. }
  481. }
  482. $aces[$field][$index] = new FieldEntry(null, $this, $field, $sid, $strategy, $mask, $granting, false, false);
  483. $this->onPropertyChanged($property, $oldValue, $this->$property);
  484. }
  485. /**
  486. * Updates an ACE
  487. *
  488. * @param string $property
  489. * @param integer $index
  490. * @param integer $mask
  491. * @param string $strategy
  492. * @throws \OutOfBoundsException
  493. */
  494. private function updateAce($property, $index, $mask, $strategy = null)
  495. {
  496. $aces =& $this->$property;
  497. if (!isset($aces[$index])) {
  498. throw new \OutOfBoundsException(sprintf('The index "%d" does not exist.', $index));
  499. }
  500. $ace = $aces[$index];
  501. if ($mask !== $oldMask = $ace->getMask()) {
  502. $this->onEntryPropertyChanged($ace, 'mask', $oldMask, $mask);
  503. $ace->setMask($mask);
  504. }
  505. if (null !== $strategy && $strategy !== $oldStrategy = $ace->getStrategy()) {
  506. $this->onEntryPropertyChanged($ace, 'strategy', $oldStrategy, $strategy);
  507. $ace->setStrategy($strategy);
  508. }
  509. }
  510. /**
  511. * Updates auditing for an ACE
  512. *
  513. * @param array &$aces
  514. * @param integer $index
  515. * @param Boolean $auditSuccess
  516. * @param Boolean $auditFailure
  517. * @throws \OutOfBoundsException
  518. */
  519. private function updateAuditing(array &$aces, $index, $auditSuccess, $auditFailure)
  520. {
  521. if (!isset($aces[$index])) {
  522. throw new \OutOfBoundsException(sprintf('The index "%d" does not exist.', $index));
  523. }
  524. if ($auditSuccess !== $aces[$index]->isAuditSuccess()) {
  525. $this->onEntryPropertyChanged($aces[$index], 'auditSuccess', !$auditSuccess, $auditSuccess);
  526. $aces[$index]->setAuditSuccess($auditSuccess);
  527. }
  528. if ($auditFailure !== $aces[$index]->isAuditFailure()) {
  529. $this->onEntryPropertyChanged($aces[$index], 'auditFailure', !$auditFailure, $auditFailure);
  530. $aces[$index]->setAuditFailure($auditFailure);
  531. }
  532. }
  533. /**
  534. * Updates a field-based ACE
  535. *
  536. * @param string $property
  537. * @param integer $index
  538. * @param string $field
  539. * @param integer $mask
  540. * @param string $strategy
  541. * @throws \InvalidArgumentException
  542. * @throws \OutOfBoundsException
  543. */
  544. private function updateFieldAce($property, $index, $field, $mask, $strategy = null)
  545. {
  546. if (0 === strlen($field)) {
  547. throw new \InvalidArgumentException('$field cannot be empty.');
  548. }
  549. $aces =& $this->$property;
  550. if (!isset($aces[$field][$index])) {
  551. throw new \OutOfBoundsException(sprintf('The index "%d" does not exist.', $index));
  552. }
  553. $ace = $aces[$field][$index];
  554. if ($mask !== $oldMask = $ace->getMask()) {
  555. $this->onEntryPropertyChanged($ace, 'mask', $oldMask, $mask);
  556. $ace->setMask($mask);
  557. }
  558. if (null !== $strategy && $strategy !== $oldStrategy = $ace->getStrategy()) {
  559. $this->onEntryPropertyChanged($ace, 'strategy', $oldStrategy, $strategy);
  560. $ace->setStrategy($strategy);
  561. }
  562. }
  563. /**
  564. * Called when a property of the ACL changes
  565. *
  566. * @param string $name
  567. * @param mixed $oldValue
  568. * @param mixed $newValue
  569. */
  570. private function onPropertyChanged($name, $oldValue, $newValue)
  571. {
  572. foreach ($this->listeners as $listener) {
  573. $listener->propertyChanged($this, $name, $oldValue, $newValue);
  574. }
  575. }
  576. /**
  577. * Called when a property of an ACE associated with this ACL changes
  578. *
  579. * @param EntryInterface $entry
  580. * @param string $name
  581. * @param mixed $oldValue
  582. * @param mixed $newValue
  583. */
  584. private function onEntryPropertyChanged(EntryInterface $entry, $name, $oldValue, $newValue)
  585. {
  586. foreach ($this->listeners as $listener) {
  587. $listener->propertyChanged($entry, $name, $oldValue, $newValue);
  588. }
  589. }
  590. }