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