PageRenderTime 127ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 1ms

/src/Symfony/Component/Security/Acl/Dbal/MutableAclProvider.php

https://github.com/Exercise/symfony
PHP | 881 lines | 552 code | 108 blank | 221 comment | 82 complexity | 426272925580d9abfe285601d515567e 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\Dbal;
  11. use Doctrine\Common\PropertyChangedListener;
  12. use Doctrine\DBAL\Driver\Connection;
  13. use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity;
  14. use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity;
  15. use Symfony\Component\Security\Acl\Exception\AclAlreadyExistsException;
  16. use Symfony\Component\Security\Acl\Exception\ConcurrentModificationException;
  17. use Symfony\Component\Security\Acl\Exception\Exception;
  18. use Symfony\Component\Security\Acl\Model\AclCacheInterface;
  19. use Symfony\Component\Security\Acl\Model\AclInterface;
  20. use Symfony\Component\Security\Acl\Model\EntryInterface;
  21. use Symfony\Component\Security\Acl\Model\MutableAclInterface;
  22. use Symfony\Component\Security\Acl\Model\MutableAclProviderInterface;
  23. use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface;
  24. use Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface;
  25. use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface;
  26. /**
  27. * An implementation of the MutableAclProviderInterface using Doctrine DBAL.
  28. *
  29. * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  30. */
  31. class MutableAclProvider extends AclProvider implements MutableAclProviderInterface, PropertyChangedListener
  32. {
  33. private $propertyChanges;
  34. /**
  35. * {@inheritDoc}
  36. */
  37. public function __construct(Connection $connection, PermissionGrantingStrategyInterface $permissionGrantingStrategy, array $options, AclCacheInterface $cache = null)
  38. {
  39. parent::__construct($connection, $permissionGrantingStrategy, $options, $cache);
  40. $this->propertyChanges = new \SplObjectStorage();
  41. }
  42. /**
  43. * {@inheritDoc}
  44. */
  45. public function createAcl(ObjectIdentityInterface $oid)
  46. {
  47. if (false !== $this->retrieveObjectIdentityPrimaryKey($oid)) {
  48. throw new AclAlreadyExistsException(sprintf('%s is already associated with an ACL.', $oid));
  49. }
  50. $this->connection->beginTransaction();
  51. try {
  52. $this->createObjectIdentity($oid);
  53. $pk = $this->retrieveObjectIdentityPrimaryKey($oid);
  54. $this->connection->executeQuery($this->getInsertObjectIdentityRelationSql($pk, $pk));
  55. $this->connection->commit();
  56. } catch (\Exception $failed) {
  57. $this->connection->rollBack();
  58. throw $failed;
  59. }
  60. // re-read the ACL from the database to ensure proper caching, etc.
  61. return $this->findAcl($oid);
  62. }
  63. /**
  64. * {@inheritDoc}
  65. */
  66. public function deleteAcl(ObjectIdentityInterface $oid)
  67. {
  68. $this->connection->beginTransaction();
  69. try {
  70. foreach ($this->findChildren($oid, true) as $childOid) {
  71. $this->deleteAcl($childOid);
  72. }
  73. $oidPK = $this->retrieveObjectIdentityPrimaryKey($oid);
  74. $this->deleteAccessControlEntries($oidPK);
  75. $this->deleteObjectIdentityRelations($oidPK);
  76. $this->deleteObjectIdentity($oidPK);
  77. $this->connection->commit();
  78. } catch (\Exception $failed) {
  79. $this->connection->rollBack();
  80. throw $failed;
  81. }
  82. // evict the ACL from the in-memory identity map
  83. if (isset($this->loadedAcls[$oid->getType()][$oid->getIdentifier()])) {
  84. $this->propertyChanges->offsetUnset($this->loadedAcls[$oid->getType()][$oid->getIdentifier()]);
  85. unset($this->loadedAcls[$oid->getType()][$oid->getIdentifier()]);
  86. }
  87. // evict the ACL from any caches
  88. if (null !== $this->cache) {
  89. $this->cache->evictFromCacheByIdentity($oid);
  90. }
  91. }
  92. /**
  93. * {@inheritDoc}
  94. */
  95. public function findAcls(array $oids, array $sids = array())
  96. {
  97. $result = parent::findAcls($oids, $sids);
  98. foreach ($result as $oid) {
  99. $acl = $result->offsetGet($oid);
  100. if (false === $this->propertyChanges->contains($acl) && $acl instanceof MutableAclInterface) {
  101. $acl->addPropertyChangedListener($this);
  102. $this->propertyChanges->attach($acl, array());
  103. }
  104. $parentAcl = $acl->getParentAcl();
  105. while (null !== $parentAcl) {
  106. if (false === $this->propertyChanges->contains($parentAcl) && $acl instanceof MutableAclInterface) {
  107. $parentAcl->addPropertyChangedListener($this);
  108. $this->propertyChanges->attach($parentAcl, array());
  109. }
  110. $parentAcl = $parentAcl->getParentAcl();
  111. }
  112. }
  113. return $result;
  114. }
  115. /**
  116. * Implementation of PropertyChangedListener
  117. *
  118. * This allows us to keep track of which values have been changed, so we don't
  119. * have to do a full introspection when ->updateAcl() is called.
  120. *
  121. * @param mixed $sender
  122. * @param string $propertyName
  123. * @param mixed $oldValue
  124. * @param mixed $newValue
  125. */
  126. public function propertyChanged($sender, $propertyName, $oldValue, $newValue)
  127. {
  128. if (!$sender instanceof MutableAclInterface && !$sender instanceof EntryInterface) {
  129. throw new \InvalidArgumentException('$sender must be an instance of MutableAclInterface, or EntryInterface.');
  130. }
  131. if ($sender instanceof EntryInterface) {
  132. if (null === $sender->getId()) {
  133. return;
  134. }
  135. $ace = $sender;
  136. $sender = $ace->getAcl();
  137. } else {
  138. $ace = null;
  139. }
  140. if (false === $this->propertyChanges->contains($sender)) {
  141. throw new \InvalidArgumentException('$sender is not being tracked by this provider.');
  142. }
  143. $propertyChanges = $this->propertyChanges->offsetGet($sender);
  144. if (null === $ace) {
  145. if (isset($propertyChanges[$propertyName])) {
  146. $oldValue = $propertyChanges[$propertyName][0];
  147. if ($oldValue === $newValue) {
  148. unset($propertyChanges[$propertyName]);
  149. } else {
  150. $propertyChanges[$propertyName] = array($oldValue, $newValue);
  151. }
  152. } else {
  153. $propertyChanges[$propertyName] = array($oldValue, $newValue);
  154. }
  155. } else {
  156. if (!isset($propertyChanges['aces'])) {
  157. $propertyChanges['aces'] = new \SplObjectStorage();
  158. }
  159. $acePropertyChanges = $propertyChanges['aces']->contains($ace)? $propertyChanges['aces']->offsetGet($ace) : array();
  160. if (isset($acePropertyChanges[$propertyName])) {
  161. $oldValue = $acePropertyChanges[$propertyName][0];
  162. if ($oldValue === $newValue) {
  163. unset($acePropertyChanges[$propertyName]);
  164. } else {
  165. $acePropertyChanges[$propertyName] = array($oldValue, $newValue);
  166. }
  167. } else {
  168. $acePropertyChanges[$propertyName] = array($oldValue, $newValue);
  169. }
  170. if (count($acePropertyChanges) > 0) {
  171. $propertyChanges['aces']->offsetSet($ace, $acePropertyChanges);
  172. } else {
  173. $propertyChanges['aces']->offsetUnset($ace);
  174. if (0 === count($propertyChanges['aces'])) {
  175. unset($propertyChanges['aces']);
  176. }
  177. }
  178. }
  179. $this->propertyChanges->offsetSet($sender, $propertyChanges);
  180. }
  181. /**
  182. * {@inheritDoc}
  183. */
  184. public function updateAcl(MutableAclInterface $acl)
  185. {
  186. if (!$this->propertyChanges->contains($acl)) {
  187. throw new \InvalidArgumentException('$acl is not tracked by this provider.');
  188. }
  189. $propertyChanges = $this->propertyChanges->offsetGet($acl);
  190. // check if any changes were made to this ACL
  191. if (0 === count($propertyChanges)) {
  192. return;
  193. }
  194. $sets = $sharedPropertyChanges = array();
  195. $this->connection->beginTransaction();
  196. try {
  197. if (isset($propertyChanges['entriesInheriting'])) {
  198. $sets[] = 'entries_inheriting = '.$this->connection->getDatabasePlatform()->convertBooleans($propertyChanges['entriesInheriting'][1]);
  199. }
  200. if (isset($propertyChanges['parentAcl'])) {
  201. if (null === $propertyChanges['parentAcl'][1]) {
  202. $sets[] = 'parent_object_identity_id = NULL';
  203. } else {
  204. $sets[] = 'parent_object_identity_id = '.intval($propertyChanges['parentAcl'][1]->getId());
  205. }
  206. $this->regenerateAncestorRelations($acl);
  207. $childAcls = $this->findAcls($this->findChildren($acl->getObjectIdentity(), false));
  208. foreach ($childAcls as $childOid) {
  209. $this->regenerateAncestorRelations($childAcls[$childOid]);
  210. }
  211. }
  212. // this includes only updates of existing ACEs, but neither the creation, nor
  213. // the deletion of ACEs; these are tracked by changes to the ACL's respective
  214. // properties (classAces, classFieldAces, objectAces, objectFieldAces)
  215. if (isset($propertyChanges['aces'])) {
  216. $this->updateAces($propertyChanges['aces']);
  217. }
  218. // check properties for deleted, and created ACEs
  219. if (isset($propertyChanges['classAces'])) {
  220. $this->updateAceProperty('classAces', $propertyChanges['classAces']);
  221. $sharedPropertyChanges['classAces'] = $propertyChanges['classAces'];
  222. }
  223. if (isset($propertyChanges['classFieldAces'])) {
  224. $this->updateFieldAceProperty('classFieldAces', $propertyChanges['classFieldAces']);
  225. $sharedPropertyChanges['classFieldAces'] = $propertyChanges['classFieldAces'];
  226. }
  227. if (isset($propertyChanges['objectAces'])) {
  228. $this->updateAceProperty('objectAces', $propertyChanges['objectAces']);
  229. }
  230. if (isset($propertyChanges['objectFieldAces'])) {
  231. $this->updateFieldAceProperty('objectFieldAces', $propertyChanges['objectFieldAces']);
  232. }
  233. // if there have been changes to shared properties, we need to synchronize other
  234. // ACL instances for object identities of the same type that are already in-memory
  235. if (count($sharedPropertyChanges) > 0) {
  236. $classAcesProperty = new \ReflectionProperty('Symfony\Component\Security\Acl\Domain\Acl', 'classAces');
  237. $classAcesProperty->setAccessible(true);
  238. $classFieldAcesProperty = new \ReflectionProperty('Symfony\Component\Security\Acl\Domain\Acl', 'classFieldAces');
  239. $classFieldAcesProperty->setAccessible(true);
  240. foreach ($this->loadedAcls[$acl->getObjectIdentity()->getType()] as $sameTypeAcl) {
  241. if (isset($sharedPropertyChanges['classAces'])) {
  242. if ($acl !== $sameTypeAcl && $classAcesProperty->getValue($sameTypeAcl) !== $sharedPropertyChanges['classAces'][0]) {
  243. throw new ConcurrentModificationException('The "classAces" property has been modified concurrently.');
  244. }
  245. $classAcesProperty->setValue($sameTypeAcl, $sharedPropertyChanges['classAces'][1]);
  246. }
  247. if (isset($sharedPropertyChanges['classFieldAces'])) {
  248. if ($acl !== $sameTypeAcl && $classFieldAcesProperty->getValue($sameTypeAcl) !== $sharedPropertyChanges['classFieldAces'][0]) {
  249. throw new ConcurrentModificationException('The "classFieldAces" property has been modified concurrently.');
  250. }
  251. $classFieldAcesProperty->setValue($sameTypeAcl, $sharedPropertyChanges['classFieldAces'][1]);
  252. }
  253. }
  254. }
  255. // persist any changes to the acl_object_identities table
  256. if (count($sets) > 0) {
  257. $this->connection->executeQuery($this->getUpdateObjectIdentitySql($acl->getId(), $sets));
  258. }
  259. $this->connection->commit();
  260. } catch (\Exception $failed) {
  261. $this->connection->rollBack();
  262. throw $failed;
  263. }
  264. $this->propertyChanges->offsetSet($acl, array());
  265. if (null !== $this->cache) {
  266. if (count($sharedPropertyChanges) > 0) {
  267. // FIXME: Currently, there is no easy way to clear the cache for ACLs
  268. // of a certain type. The problem here is that we need to make
  269. // sure to clear the cache of all child ACLs as well, and these
  270. // child ACLs might be of a different class type.
  271. $this->cache->clearCache();
  272. } else {
  273. // if there are no shared property changes, it's sufficient to just delete
  274. // the cache for this ACL
  275. $this->cache->evictFromCacheByIdentity($acl->getObjectIdentity());
  276. foreach ($this->findChildren($acl->getObjectIdentity()) as $childOid) {
  277. $this->cache->evictFromCacheByIdentity($childOid);
  278. }
  279. }
  280. }
  281. }
  282. /**
  283. * Constructs the SQL for deleting access control entries.
  284. *
  285. * @param integer $oidPK
  286. * @return string
  287. */
  288. protected function getDeleteAccessControlEntriesSql($oidPK)
  289. {
  290. return sprintf(
  291. 'DELETE FROM %s WHERE object_identity_id = %d',
  292. $this->options['entry_table_name'],
  293. $oidPK
  294. );
  295. }
  296. /**
  297. * Constructs the SQL for deleting a specific ACE.
  298. *
  299. * @param integer $acePK
  300. * @return string
  301. */
  302. protected function getDeleteAccessControlEntrySql($acePK)
  303. {
  304. return sprintf(
  305. 'DELETE FROM %s WHERE id = %d',
  306. $this->options['entry_table_name'],
  307. $acePK
  308. );
  309. }
  310. /**
  311. * Constructs the SQL for deleting an object identity.
  312. *
  313. * @param integer $pk
  314. * @return string
  315. */
  316. protected function getDeleteObjectIdentitySql($pk)
  317. {
  318. return sprintf(
  319. 'DELETE FROM %s WHERE id = %d',
  320. $this->options['oid_table_name'],
  321. $pk
  322. );
  323. }
  324. /**
  325. * Constructs the SQL for deleting relation entries.
  326. *
  327. * @param integer $pk
  328. * @return string
  329. */
  330. protected function getDeleteObjectIdentityRelationsSql($pk)
  331. {
  332. return sprintf(
  333. 'DELETE FROM %s WHERE object_identity_id = %d',
  334. $this->options['oid_ancestors_table_name'],
  335. $pk
  336. );
  337. }
  338. /**
  339. * Constructs the SQL for inserting an ACE.
  340. *
  341. * @param integer $classId
  342. * @param integer|null $objectIdentityId
  343. * @param string|null $field
  344. * @param integer $aceOrder
  345. * @param integer $securityIdentityId
  346. * @param string $strategy
  347. * @param integer $mask
  348. * @param Boolean $granting
  349. * @param Boolean $auditSuccess
  350. * @param Boolean $auditFailure
  351. * @return string
  352. */
  353. protected function getInsertAccessControlEntrySql($classId, $objectIdentityId, $field, $aceOrder, $securityIdentityId, $strategy, $mask, $granting, $auditSuccess, $auditFailure)
  354. {
  355. $query = <<<QUERY
  356. INSERT INTO %s (
  357. class_id,
  358. object_identity_id,
  359. field_name,
  360. ace_order,
  361. security_identity_id,
  362. mask,
  363. granting,
  364. granting_strategy,
  365. audit_success,
  366. audit_failure
  367. )
  368. VALUES (%d, %s, %s, %d, %d, %d, %s, %s, %s, %s)
  369. QUERY;
  370. return sprintf(
  371. $query,
  372. $this->options['entry_table_name'],
  373. $classId,
  374. null === $objectIdentityId? 'NULL' : intval($objectIdentityId),
  375. null === $field? 'NULL' : $this->connection->quote($field),
  376. $aceOrder,
  377. $securityIdentityId,
  378. $mask,
  379. $this->connection->getDatabasePlatform()->convertBooleans($granting),
  380. $this->connection->quote($strategy),
  381. $this->connection->getDatabasePlatform()->convertBooleans($auditSuccess),
  382. $this->connection->getDatabasePlatform()->convertBooleans($auditFailure)
  383. );
  384. }
  385. /**
  386. * Constructs the SQL for inserting a new class type.
  387. *
  388. * @param string $classType
  389. * @return string
  390. */
  391. protected function getInsertClassSql($classType)
  392. {
  393. return sprintf(
  394. 'INSERT INTO %s (class_type) VALUES (%s)',
  395. $this->options['class_table_name'],
  396. $this->connection->quote($classType)
  397. );
  398. }
  399. /**
  400. * Constructs the SQL for inserting a relation entry.
  401. *
  402. * @param integer $objectIdentityId
  403. * @param integer $ancestorId
  404. * @return string
  405. */
  406. protected function getInsertObjectIdentityRelationSql($objectIdentityId, $ancestorId)
  407. {
  408. return sprintf(
  409. 'INSERT INTO %s (object_identity_id, ancestor_id) VALUES (%d, %d)',
  410. $this->options['oid_ancestors_table_name'],
  411. $objectIdentityId,
  412. $ancestorId
  413. );
  414. }
  415. /**
  416. * Constructs the SQL for inserting an object identity.
  417. *
  418. * @param string $identifier
  419. * @param integer $classId
  420. * @param Boolean $entriesInheriting
  421. * @return string
  422. */
  423. protected function getInsertObjectIdentitySql($identifier, $classId, $entriesInheriting)
  424. {
  425. $query = <<<QUERY
  426. INSERT INTO %s (class_id, object_identifier, entries_inheriting)
  427. VALUES (%d, %s, %s)
  428. QUERY;
  429. return sprintf(
  430. $query,
  431. $this->options['oid_table_name'],
  432. $classId,
  433. $this->connection->quote($identifier),
  434. $this->connection->getDatabasePlatform()->convertBooleans($entriesInheriting)
  435. );
  436. }
  437. /**
  438. * Constructs the SQL for inserting a security identity.
  439. *
  440. * @param SecurityIdentityInterface $sid
  441. * @throws \InvalidArgumentException
  442. * @return string
  443. */
  444. protected function getInsertSecurityIdentitySql(SecurityIdentityInterface $sid)
  445. {
  446. if ($sid instanceof UserSecurityIdentity) {
  447. $identifier = $sid->getClass().'-'.$sid->getUsername();
  448. $username = true;
  449. } elseif ($sid instanceof RoleSecurityIdentity) {
  450. $identifier = $sid->getRole();
  451. $username = false;
  452. } else {
  453. throw new \InvalidArgumentException('$sid must either be an instance of UserSecurityIdentity, or RoleSecurityIdentity.');
  454. }
  455. return sprintf(
  456. 'INSERT INTO %s (identifier, username) VALUES (%s, %s)',
  457. $this->options['sid_table_name'],
  458. $this->connection->quote($identifier),
  459. $this->connection->getDatabasePlatform()->convertBooleans($username)
  460. );
  461. }
  462. /**
  463. * Constructs the SQL for selecting an ACE.
  464. *
  465. * @param integer $classId
  466. * @param integer $oid
  467. * @param string $field
  468. * @param integer $order
  469. * @return string
  470. */
  471. protected function getSelectAccessControlEntryIdSql($classId, $oid, $field, $order)
  472. {
  473. return sprintf(
  474. 'SELECT id FROM %s WHERE class_id = %d AND %s AND %s AND ace_order = %d',
  475. $this->options['entry_table_name'],
  476. $classId,
  477. null === $oid ?
  478. $this->connection->getDatabasePlatform()->getIsNullExpression('object_identity_id')
  479. : 'object_identity_id = '.intval($oid),
  480. null === $field ?
  481. $this->connection->getDatabasePlatform()->getIsNullExpression('field_name')
  482. : 'field_name = '.$this->connection->quote($field),
  483. $order
  484. );
  485. }
  486. /**
  487. * Constructs the SQL for selecting the primary key associated with
  488. * the passed class type.
  489. *
  490. * @param string $classType
  491. * @return string
  492. */
  493. protected function getSelectClassIdSql($classType)
  494. {
  495. return sprintf(
  496. 'SELECT id FROM %s WHERE class_type = %s',
  497. $this->options['class_table_name'],
  498. $this->connection->quote($classType)
  499. );
  500. }
  501. /**
  502. * Constructs the SQL for selecting the primary key of a security identity.
  503. *
  504. * @param SecurityIdentityInterface $sid
  505. * @throws \InvalidArgumentException
  506. * @return string
  507. */
  508. protected function getSelectSecurityIdentityIdSql(SecurityIdentityInterface $sid)
  509. {
  510. if ($sid instanceof UserSecurityIdentity) {
  511. $identifier = $sid->getClass().'-'.$sid->getUsername();
  512. $username = true;
  513. } elseif ($sid instanceof RoleSecurityIdentity) {
  514. $identifier = $sid->getRole();
  515. $username = false;
  516. } else {
  517. throw new \InvalidArgumentException('$sid must either be an instance of UserSecurityIdentity, or RoleSecurityIdentity.');
  518. }
  519. return sprintf(
  520. 'SELECT id FROM %s WHERE identifier = %s AND username = %s',
  521. $this->options['sid_table_name'],
  522. $this->connection->quote($identifier),
  523. $this->connection->getDatabasePlatform()->convertBooleans($username)
  524. );
  525. }
  526. /**
  527. * Constructs the SQL for updating an object identity.
  528. *
  529. * @param integer $pk
  530. * @param array $changes
  531. * @throws \InvalidArgumentException
  532. * @return string
  533. */
  534. protected function getUpdateObjectIdentitySql($pk, array $changes)
  535. {
  536. if (0 === count($changes)) {
  537. throw new \InvalidArgumentException('There are no changes.');
  538. }
  539. return sprintf(
  540. 'UPDATE %s SET %s WHERE id = %d',
  541. $this->options['oid_table_name'],
  542. implode(', ', $changes),
  543. $pk
  544. );
  545. }
  546. /**
  547. * Constructs the SQL for updating an ACE.
  548. *
  549. * @param integer $pk
  550. * @param array $sets
  551. * @throws \InvalidArgumentException
  552. * @return string
  553. */
  554. protected function getUpdateAccessControlEntrySql($pk, array $sets)
  555. {
  556. if (0 === count($sets)) {
  557. throw new \InvalidArgumentException('There are no changes.');
  558. }
  559. return sprintf(
  560. 'UPDATE %s SET %s WHERE id = %d',
  561. $this->options['entry_table_name'],
  562. implode(', ', $sets),
  563. $pk
  564. );
  565. }
  566. /**
  567. * Creates the ACL for the passed object identity
  568. *
  569. * @param ObjectIdentityInterface $oid
  570. */
  571. private function createObjectIdentity(ObjectIdentityInterface $oid)
  572. {
  573. $classId = $this->createOrRetrieveClassId($oid->getType());
  574. $this->connection->executeQuery($this->getInsertObjectIdentitySql($oid->getIdentifier(), $classId, true));
  575. }
  576. /**
  577. * Returns the primary key for the passed class type.
  578. *
  579. * If the type does not yet exist in the database, it will be created.
  580. *
  581. * @param string $classType
  582. * @return integer
  583. */
  584. private function createOrRetrieveClassId($classType)
  585. {
  586. if (false !== $id = $this->connection->executeQuery($this->getSelectClassIdSql($classType))->fetchColumn()) {
  587. return $id;
  588. }
  589. $this->connection->executeQuery($this->getInsertClassSql($classType));
  590. return $this->connection->executeQuery($this->getSelectClassIdSql($classType))->fetchColumn();
  591. }
  592. /**
  593. * Returns the primary key for the passed security identity.
  594. *
  595. * If the security identity does not yet exist in the database, it will be
  596. * created.
  597. *
  598. * @param SecurityIdentityInterface $sid
  599. * @return integer
  600. */
  601. private function createOrRetrieveSecurityIdentityId(SecurityIdentityInterface $sid)
  602. {
  603. if (false !== $id = $this->connection->executeQuery($this->getSelectSecurityIdentityIdSql($sid))->fetchColumn()) {
  604. return $id;
  605. }
  606. $this->connection->executeQuery($this->getInsertSecurityIdentitySql($sid));
  607. return $this->connection->executeQuery($this->getSelectSecurityIdentityIdSql($sid))->fetchColumn();
  608. }
  609. /**
  610. * Deletes all ACEs for the given object identity primary key.
  611. *
  612. * @param integer $oidPK
  613. */
  614. private function deleteAccessControlEntries($oidPK)
  615. {
  616. $this->connection->executeQuery($this->getDeleteAccessControlEntriesSql($oidPK));
  617. }
  618. /**
  619. * Deletes the object identity from the database.
  620. *
  621. * @param integer $pk
  622. */
  623. private function deleteObjectIdentity($pk)
  624. {
  625. $this->connection->executeQuery($this->getDeleteObjectIdentitySql($pk));
  626. }
  627. /**
  628. * Deletes all entries from the relations table from the database.
  629. *
  630. * @param integer $pk
  631. */
  632. private function deleteObjectIdentityRelations($pk)
  633. {
  634. $this->connection->executeQuery($this->getDeleteObjectIdentityRelationsSql($pk));
  635. }
  636. /**
  637. * This regenerates the ancestor table which is used for fast read access.
  638. *
  639. * @param AclInterface $acl
  640. */
  641. private function regenerateAncestorRelations(AclInterface $acl)
  642. {
  643. $pk = $acl->getId();
  644. $this->connection->executeQuery($this->getDeleteObjectIdentityRelationsSql($pk));
  645. $this->connection->executeQuery($this->getInsertObjectIdentityRelationSql($pk, $pk));
  646. $parentAcl = $acl->getParentAcl();
  647. while (null !== $parentAcl) {
  648. $this->connection->executeQuery($this->getInsertObjectIdentityRelationSql($pk, $parentAcl->getId()));
  649. $parentAcl = $parentAcl->getParentAcl();
  650. }
  651. }
  652. /**
  653. * This processes changes on an ACE related property (classFieldAces, or objectFieldAces).
  654. *
  655. * @param string $name
  656. * @param array $changes
  657. */
  658. private function updateFieldAceProperty($name, array $changes)
  659. {
  660. $sids = new \SplObjectStorage();
  661. $classIds = new \SplObjectStorage();
  662. $currentIds = array();
  663. foreach ($changes[1] as $field => $new) {
  664. for ($i=0,$c=count($new); $i<$c; $i++) {
  665. $ace = $new[$i];
  666. if (null === $ace->getId()) {
  667. if ($sids->contains($ace->getSecurityIdentity())) {
  668. $sid = $sids->offsetGet($ace->getSecurityIdentity());
  669. } else {
  670. $sid = $this->createOrRetrieveSecurityIdentityId($ace->getSecurityIdentity());
  671. }
  672. $oid = $ace->getAcl()->getObjectIdentity();
  673. if ($classIds->contains($oid)) {
  674. $classId = $classIds->offsetGet($oid);
  675. } else {
  676. $classId = $this->createOrRetrieveClassId($oid->getType());
  677. }
  678. $objectIdentityId = $name === 'classFieldAces' ? null : $ace->getAcl()->getId();
  679. $this->connection->executeQuery($this->getInsertAccessControlEntrySql($classId, $objectIdentityId, $field, $i, $sid, $ace->getStrategy(), $ace->getMask(), $ace->isGranting(), $ace->isAuditSuccess(), $ace->isAuditFailure()));
  680. $aceId = $this->connection->executeQuery($this->getSelectAccessControlEntryIdSql($classId, $objectIdentityId, $field, $i))->fetchColumn();
  681. $this->loadedAces[$aceId] = $ace;
  682. $aceIdProperty = new \ReflectionProperty('Symfony\Component\Security\Acl\Domain\Entry', 'id');
  683. $aceIdProperty->setAccessible(true);
  684. $aceIdProperty->setValue($ace, intval($aceId));
  685. } else {
  686. $currentIds[$ace->getId()] = true;
  687. }
  688. }
  689. }
  690. foreach ($changes[0] as $old) {
  691. for ($i=0,$c=count($old); $i<$c; $i++) {
  692. $ace = $old[$i];
  693. if (!isset($currentIds[$ace->getId()])) {
  694. $this->connection->executeQuery($this->getDeleteAccessControlEntrySql($ace->getId()));
  695. unset($this->loadedAces[$ace->getId()]);
  696. }
  697. }
  698. }
  699. }
  700. /**
  701. * This processes changes on an ACE related property (classAces, or objectAces).
  702. *
  703. * @param string $name
  704. * @param array $changes
  705. */
  706. private function updateAceProperty($name, array $changes)
  707. {
  708. list($old, $new) = $changes;
  709. $sids = new \SplObjectStorage();
  710. $classIds = new \SplObjectStorage();
  711. $currentIds = array();
  712. for ($i=0,$c=count($new); $i<$c; $i++) {
  713. $ace = $new[$i];
  714. if (null === $ace->getId()) {
  715. if ($sids->contains($ace->getSecurityIdentity())) {
  716. $sid = $sids->offsetGet($ace->getSecurityIdentity());
  717. } else {
  718. $sid = $this->createOrRetrieveSecurityIdentityId($ace->getSecurityIdentity());
  719. }
  720. $oid = $ace->getAcl()->getObjectIdentity();
  721. if ($classIds->contains($oid)) {
  722. $classId = $classIds->offsetGet($oid);
  723. } else {
  724. $classId = $this->createOrRetrieveClassId($oid->getType());
  725. }
  726. $objectIdentityId = $name === 'classAces' ? null : $ace->getAcl()->getId();
  727. $this->connection->executeQuery($this->getInsertAccessControlEntrySql($classId, $objectIdentityId, null, $i, $sid, $ace->getStrategy(), $ace->getMask(), $ace->isGranting(), $ace->isAuditSuccess(), $ace->isAuditFailure()));
  728. $aceId = $this->connection->executeQuery($this->getSelectAccessControlEntryIdSql($classId, $objectIdentityId, null, $i))->fetchColumn();
  729. $this->loadedAces[$aceId] = $ace;
  730. $aceIdProperty = new \ReflectionProperty($ace, 'id');
  731. $aceIdProperty->setAccessible(true);
  732. $aceIdProperty->setValue($ace, intval($aceId));
  733. } else {
  734. $currentIds[$ace->getId()] = true;
  735. }
  736. }
  737. for ($i=0,$c=count($old); $i<$c; $i++) {
  738. $ace = $old[$i];
  739. if (!isset($currentIds[$ace->getId()])) {
  740. $this->connection->executeQuery($this->getDeleteAccessControlEntrySql($ace->getId()));
  741. unset($this->loadedAces[$ace->getId()]);
  742. }
  743. }
  744. }
  745. /**
  746. * Persists the changes which were made to ACEs to the database.
  747. *
  748. * @param \SplObjectStorage $aces
  749. */
  750. private function updateAces(\SplObjectStorage $aces)
  751. {
  752. foreach ($aces as $ace) {
  753. $propertyChanges = $aces->offsetGet($ace);
  754. $sets = array();
  755. if (isset($propertyChanges['mask'])) {
  756. $sets[] = sprintf('mask = %d', $propertyChanges['mask'][1]);
  757. }
  758. if (isset($propertyChanges['strategy'])) {
  759. $sets[] = sprintf('granting_strategy = %s', $this->connection->quote($propertyChanges['strategy']));
  760. }
  761. if (isset($propertyChanges['aceOrder'])) {
  762. $sets[] = sprintf('ace_order = %d', $propertyChanges['aceOrder'][1]);
  763. }
  764. if (isset($propertyChanges['auditSuccess'])) {
  765. $sets[] = sprintf('audit_success = %s', $this->connection->getDatabasePlatform()->convertBooleans($propertyChanges['auditSuccess'][1]));
  766. }
  767. if (isset($propertyChanges['auditFailure'])) {
  768. $sets[] = sprintf('audit_failure = %s', $this->connection->getDatabasePlatform()->convertBooleans($propertyChanges['auditFailure'][1]));
  769. }
  770. $this->connection->executeQuery($this->getUpdateAccessControlEntrySql($ace->getId(), $sets));
  771. }
  772. }
  773. }