|
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 } |