Acl.php 19 KB

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