MutableAclProviderTest.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  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\Tests\Component\Security\Acl\Dbal;
  11. use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity;
  12. use Symfony\Component\Security\Acl\Model\FieldEntryInterface;
  13. use Symfony\Component\Security\Acl\Model\AuditableEntryInterface;
  14. use Symfony\Component\Security\Acl\Model\EntryInterface;
  15. use Symfony\Component\Security\Acl\Domain\Entry;
  16. use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity;
  17. use Symfony\Component\Security\Acl\Domain\Acl;
  18. use Symfony\Component\Security\Acl\Exception\AclNotFoundException;
  19. use Symfony\Component\Security\Acl\Exception\ConcurrentModificationException;
  20. use Symfony\Component\Security\Acl\Dbal\AclProvider;
  21. use Symfony\Component\Security\Acl\Domain\PermissionGrantingStrategy;
  22. use Symfony\Component\Security\Acl\Dbal\MutableAclProvider;
  23. use Symfony\Component\Security\Acl\Dbal\Schema;
  24. use Doctrine\DBAL\DriverManager;
  25. use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
  26. class MutableAclProviderTest extends \PHPUnit_Framework_TestCase
  27. {
  28. protected $con;
  29. public static function assertAceEquals(EntryInterface $a, EntryInterface $b)
  30. {
  31. self::assertInstanceOf(get_class($a), $b);
  32. foreach (array('getId', 'getMask', 'getStrategy', 'isGranting') as $getter) {
  33. self::assertSame($a->$getter(), $b->$getter());
  34. }
  35. self::assertTrue($a->getSecurityIdentity()->equals($b->getSecurityIdentity()));
  36. self::assertSame($a->getAcl()->getId(), $b->getAcl()->getId());
  37. if ($a instanceof AuditableEntryInterface) {
  38. self::assertSame($a->isAuditSuccess(), $b->isAuditSuccess());
  39. self::assertSame($a->isAuditFailure(), $b->isAuditFailure());
  40. }
  41. if ($a instanceof FieldEntryInterface) {
  42. self::assertSame($a->getField(), $b->getField());
  43. }
  44. }
  45. /**
  46. * @expectedException Symfony\Component\Security\Acl\Exception\AclAlreadyExistsException
  47. */
  48. public function testCreateAclThrowsExceptionWhenAclAlreadyExists()
  49. {
  50. $provider = $this->getProvider();
  51. $oid = new ObjectIdentity('123456', 'FOO');
  52. $provider->createAcl($oid);
  53. $provider->createAcl($oid);
  54. }
  55. public function testCreateAcl()
  56. {
  57. $provider = $this->getProvider();
  58. $oid = new ObjectIdentity('123456', 'FOO');
  59. $acl = $provider->createAcl($oid);
  60. $cachedAcl = $provider->findAcl($oid);
  61. $this->assertInstanceOf('Symfony\Component\Security\Acl\Domain\Acl', $acl);
  62. $this->assertSame($acl, $cachedAcl);
  63. $this->assertTrue($acl->getObjectIdentity()->equals($oid));
  64. }
  65. public function testDeleteAcl()
  66. {
  67. $provider = $this->getProvider();
  68. $oid = new ObjectIdentity(1, 'Foo');
  69. $acl = $provider->createAcl($oid);
  70. $provider->deleteAcl($oid);
  71. $loadedAcls = $this->getField($provider, 'loadedAcls');
  72. $this->assertEquals(0, count($loadedAcls['Foo']));
  73. try {
  74. $provider->findAcl($oid);
  75. $this->fail('ACL has not been properly deleted.');
  76. } catch (AclNotFoundException $notFound) { }
  77. }
  78. public function testDeleteAclDeletesChildren()
  79. {
  80. $provider = $this->getProvider();
  81. $acl = $provider->createAcl(new ObjectIdentity(1, 'Foo'));
  82. $parentAcl = $provider->createAcl(new ObjectIdentity(2, 'Foo'));
  83. $acl->setParentAcl($parentAcl);
  84. $provider->updateAcl($acl);
  85. $provider->deleteAcl($parentAcl->getObjectIdentity());
  86. try {
  87. $provider->findAcl(new ObjectIdentity(1, 'Foo'));
  88. $this->fail('Child-ACLs have not been deleted.');
  89. } catch (AclNotFoundException $notFound) { }
  90. }
  91. public function testFindAclsAddsPropertyListener()
  92. {
  93. $provider = $this->getProvider();
  94. $acl = $provider->createAcl(new ObjectIdentity(1, 'Foo'));
  95. $propertyChanges = $this->getField($provider, 'propertyChanges');
  96. $this->assertEquals(1, count($propertyChanges));
  97. $this->assertTrue($propertyChanges->contains($acl));
  98. $this->assertEquals(array(), $propertyChanges->offsetGet($acl));
  99. $listeners = $this->getField($acl, 'listeners');
  100. $this->assertSame($provider, $listeners[0]);
  101. }
  102. public function testFindAclsAddsPropertyListenerOnlyOnce()
  103. {
  104. $provider = $this->getProvider();
  105. $acl = $provider->createAcl(new ObjectIdentity(1, 'Foo'));
  106. $acl = $provider->findAcl(new ObjectIdentity(1, 'Foo'));
  107. $propertyChanges = $this->getField($provider, 'propertyChanges');
  108. $this->assertEquals(1, count($propertyChanges));
  109. $this->assertTrue($propertyChanges->contains($acl));
  110. $this->assertEquals(array(), $propertyChanges->offsetGet($acl));
  111. $listeners = $this->getField($acl, 'listeners');
  112. $this->assertEquals(1, count($listeners));
  113. $this->assertSame($provider, $listeners[0]);
  114. }
  115. public function testFindAclsAddsPropertyListenerToParentAcls()
  116. {
  117. $provider = $this->getProvider();
  118. $this->importAcls($provider, array(
  119. 'main' => array(
  120. 'object_identifier' => '1',
  121. 'class_type' => 'foo',
  122. 'parent_acl' => 'parent',
  123. ),
  124. 'parent' => array(
  125. 'object_identifier' => '1',
  126. 'class_type' => 'anotherFoo',
  127. )
  128. ));
  129. $propertyChanges = $this->getField($provider, 'propertyChanges');
  130. $this->assertEquals(0, count($propertyChanges));
  131. $acl = $provider->findAcl(new ObjectIdentity('1', 'foo'));
  132. $this->assertEquals(2, count($propertyChanges));
  133. $this->assertTrue($propertyChanges->contains($acl));
  134. $this->assertTrue($propertyChanges->contains($acl->getParentAcl()));
  135. }
  136. /**
  137. * @expectedException \InvalidArgumentException
  138. */
  139. public function testPropertyChangedDoesNotTrackUnmanagedAcls()
  140. {
  141. $provider = $this->getProvider();
  142. $acl = new Acl(1, new ObjectIdentity(1, 'foo'), new PermissionGrantingStrategy(), array(), false);
  143. $provider->propertyChanged($acl, 'classAces', array(), array('foo'));
  144. }
  145. public function testPropertyChangedTracksChangesToAclProperties()
  146. {
  147. $provider = $this->getProvider();
  148. $acl = $provider->createAcl(new ObjectIdentity(1, 'Foo'));
  149. $propertyChanges = $this->getField($provider, 'propertyChanges');
  150. $provider->propertyChanged($acl, 'entriesInheriting', false, true);
  151. $changes = $propertyChanges->offsetGet($acl);
  152. $this->assertTrue(isset($changes['entriesInheriting']));
  153. $this->assertFalse($changes['entriesInheriting'][0]);
  154. $this->assertTrue($changes['entriesInheriting'][1]);
  155. $provider->propertyChanged($acl, 'entriesInheriting', true, false);
  156. $provider->propertyChanged($acl, 'entriesInheriting', false, true);
  157. $provider->propertyChanged($acl, 'entriesInheriting', true, false);
  158. $changes = $propertyChanges->offsetGet($acl);
  159. $this->assertFalse(isset($changes['entriesInheriting']));
  160. }
  161. public function testPropertyChangedTracksChangesToAceProperties()
  162. {
  163. $provider = $this->getProvider();
  164. $acl = $provider->createAcl(new ObjectIdentity(1, 'Foo'));
  165. $ace = new Entry(1, $acl, new UserSecurityIdentity('foo', 'FooClass'), 'all', 1, true, true, true);
  166. $ace2 = new Entry(2, $acl, new UserSecurityIdentity('foo', 'FooClass'), 'all', 1, true, true, true);
  167. $propertyChanges = $this->getField($provider, 'propertyChanges');
  168. $provider->propertyChanged($ace, 'mask', 1, 3);
  169. $changes = $propertyChanges->offsetGet($acl);
  170. $this->assertTrue(isset($changes['aces']));
  171. $this->assertInstanceOf('\SplObjectStorage', $changes['aces']);
  172. $this->assertTrue($changes['aces']->contains($ace));
  173. $aceChanges = $changes['aces']->offsetGet($ace);
  174. $this->assertTrue(isset($aceChanges['mask']));
  175. $this->assertEquals(1, $aceChanges['mask'][0]);
  176. $this->assertEquals(3, $aceChanges['mask'][1]);
  177. $provider->propertyChanged($ace, 'strategy', 'all', 'any');
  178. $changes = $propertyChanges->offsetGet($acl);
  179. $this->assertTrue(isset($changes['aces']));
  180. $this->assertInstanceOf('\SplObjectStorage', $changes['aces']);
  181. $this->assertTrue($changes['aces']->contains($ace));
  182. $aceChanges = $changes['aces']->offsetGet($ace);
  183. $this->assertTrue(isset($aceChanges['mask']));
  184. $this->assertTrue(isset($aceChanges['strategy']));
  185. $this->assertEquals('all', $aceChanges['strategy'][0]);
  186. $this->assertEquals('any', $aceChanges['strategy'][1]);
  187. $provider->propertyChanged($ace, 'mask', 3, 1);
  188. $changes = $propertyChanges->offsetGet($acl);
  189. $aceChanges = $changes['aces']->offsetGet($ace);
  190. $this->assertFalse(isset($aceChanges['mask']));
  191. $this->assertTrue(isset($aceChanges['strategy']));
  192. $provider->propertyChanged($ace2, 'mask', 1, 3);
  193. $provider->propertyChanged($ace, 'strategy', 'any', 'all');
  194. $changes = $propertyChanges->offsetGet($acl);
  195. $this->assertTrue(isset($changes['aces']));
  196. $this->assertFalse($changes['aces']->contains($ace));
  197. $this->assertTrue($changes['aces']->contains($ace2));
  198. $provider->propertyChanged($ace2, 'mask', 3, 4);
  199. $provider->propertyChanged($ace2, 'mask', 4, 1);
  200. $changes = $propertyChanges->offsetGet($acl);
  201. $this->assertFalse(isset($changes['aces']));
  202. }
  203. /**
  204. * @expectedException \InvalidArgumentException
  205. */
  206. public function testUpdateAclDoesNotAcceptUntrackedAcls()
  207. {
  208. $provider = $this->getProvider();
  209. $acl = new Acl(1, new ObjectIdentity(1, 'Foo'), new PermissionGrantingStrategy(), array(), true);
  210. $provider->updateAcl($acl);
  211. }
  212. public function testUpdateDoesNothingWhenThereAreNoChanges()
  213. {
  214. $con = $this->getMock('Doctrine\DBAL\Connection', array(), array(), '', false);
  215. $con
  216. ->expects($this->never())
  217. ->method('beginTransaction')
  218. ;
  219. $con
  220. ->expects($this->never())
  221. ->method('executeQuery')
  222. ;
  223. $provider = new MutableAclProvider($con, new PermissionGrantingStrategy(), array());
  224. $acl = new Acl(1, new ObjectIdentity(1, 'Foo'), new PermissionGrantingStrategy(), array(), true);
  225. $propertyChanges = $this->getField($provider, 'propertyChanges');
  226. $propertyChanges->offsetSet($acl, array());
  227. $provider->updateAcl($acl);
  228. }
  229. public function testUpdateAclThrowsExceptionOnConcurrentModifcationOfSharedProperties()
  230. {
  231. $provider = $this->getProvider();
  232. $acl1 = $provider->createAcl(new ObjectIdentity(1, 'Foo'));
  233. $acl2 = $provider->createAcl(new ObjectIdentity(2, 'Foo'));
  234. $acl3 = $provider->createAcl(new ObjectIdentity(1, 'AnotherFoo'));
  235. $sid = new RoleSecurityIdentity('ROLE_FOO');
  236. $acl1->insertClassAce($sid, 1);
  237. $acl3->insertClassAce($sid, 1);
  238. $provider->updateAcl($acl1);
  239. $provider->updateAcl($acl3);
  240. $acl2->insertClassAce($sid, 16);
  241. $provider->updateAcl($acl2);
  242. $acl1->insertClassAce($sid, 3);
  243. $acl2->insertClassAce($sid, 5);
  244. try {
  245. $provider->updateAcl($acl1);
  246. $this->fail('Provider failed to detect a concurrent modification.');
  247. } catch (ConcurrentModificationException $ex) { }
  248. }
  249. public function testUpdateAcl()
  250. {
  251. $provider = $this->getProvider();
  252. $acl = $provider->createAcl(new ObjectIdentity(1, 'Foo'));
  253. $sid = new UserSecurityIdentity('johannes', 'FooClass');
  254. $acl->setEntriesInheriting(!$acl->isEntriesInheriting());
  255. $acl->insertObjectAce($sid, 1);
  256. $acl->insertClassAce($sid, 5, 0, false);
  257. $acl->insertObjectAce($sid, 2, 1, true);
  258. $acl->insertClassFieldAce('field', $sid, 2, 0, true);
  259. $provider->updateAcl($acl);
  260. $acl->updateObjectAce(0, 3);
  261. $acl->deleteObjectAce(1);
  262. $acl->updateObjectAuditing(0, true, false);
  263. $acl->updateClassFieldAce(0, 'field', 15);
  264. $provider->updateAcl($acl);
  265. $reloadProvider = $this->getProvider();
  266. $reloadedAcl = $reloadProvider->findAcl(new ObjectIdentity(1, 'Foo'));
  267. $this->assertNotSame($acl, $reloadedAcl);
  268. $this->assertSame($acl->isEntriesInheriting(), $reloadedAcl->isEntriesInheriting());
  269. $aces = $acl->getObjectAces();
  270. $reloadedAces = $reloadedAcl->getObjectAces();
  271. $this->assertEquals(count($aces), count($reloadedAces));
  272. foreach ($aces as $index => $ace) {
  273. $this->assertAceEquals($ace, $reloadedAces[$index]);
  274. }
  275. }
  276. public function testUpdateAclWorksForChangingTheParentAcl()
  277. {
  278. $provider = $this->getProvider();
  279. $acl = $provider->createAcl(new ObjectIdentity(1, 'Foo'));
  280. $parentAcl = $provider->createAcl(new ObjectIdentity(1, 'AnotherFoo'));
  281. $acl->setParentAcl($parentAcl);
  282. $provider->updateAcl($acl);
  283. $reloadProvider = $this->getProvider();
  284. $reloadedAcl = $reloadProvider->findAcl(new ObjectIdentity(1, 'Foo'));
  285. $this->assertNotSame($acl, $reloadedAcl);
  286. $this->assertSame($parentAcl->getId(), $reloadedAcl->getParentAcl()->getId());
  287. }
  288. public function testUpdateAclUpdatesChildAclsCorrectly()
  289. {
  290. $provider = $this->getProvider();
  291. $acl = $provider->createAcl(new ObjectIdentity(1, 'Foo'));
  292. $parentAcl = $provider->createAcl(new ObjectIdentity(1, 'Bar'));
  293. $acl->setParentAcl($parentAcl);
  294. $provider->updateAcl($acl);
  295. $parentParentAcl = $provider->createAcl(new ObjectIdentity(1, 'Baz'));
  296. $parentAcl->setParentAcl($parentParentAcl);
  297. $provider->updateAcl($parentAcl);
  298. $newParentParentAcl = $provider->createAcl(new ObjectIdentity(2, 'Baz'));
  299. $parentAcl->setParentAcl($newParentParentAcl);
  300. $provider->updateAcl($parentAcl);
  301. $reloadProvider = $this->getProvider();
  302. $reloadedAcl = $reloadProvider->findAcl(new ObjectIdentity(1, 'Foo'));
  303. $this->assertEquals($newParentParentAcl->getId(), $reloadedAcl->getParentAcl()->getParentAcl()->getId());
  304. }
  305. /**
  306. * Data must have the following format:
  307. * array(
  308. * *name* => array(
  309. * 'object_identifier' => *required*
  310. * 'class_type' => *required*,
  311. * 'parent_acl' => *name (optional)*
  312. * ),
  313. * )
  314. *
  315. * @param AclProvider $provider
  316. * @param array $data
  317. * @throws \InvalidArgumentException
  318. * @throws Exception
  319. */
  320. protected function importAcls(AclProvider $provider, array $data)
  321. {
  322. $aclIds = $parentAcls = array();
  323. $con = $this->getField($provider, 'connection');
  324. $con->beginTransaction();
  325. try {
  326. foreach ($data as $name => $aclData) {
  327. if (!isset($aclData['object_identifier'], $aclData['class_type'])) {
  328. throw new \InvalidArgumentException('"object_identifier", and "class_type" must be present.');
  329. }
  330. $this->callMethod($provider, 'createObjectIdentity', array(new ObjectIdentity($aclData['object_identifier'], $aclData['class_type'])));
  331. $aclId = $con->lastInsertId();
  332. $aclIds[$name] = $aclId;
  333. $sql = $this->callMethod($provider, 'getInsertObjectIdentityRelationSql', array($aclId, $aclId));
  334. $con->executeQuery($sql);
  335. if (isset($aclData['parent_acl'])) {
  336. if (isset($aclIds[$aclData['parent_acl']])) {
  337. $con->executeQuery("UPDATE acl_object_identities SET parent_object_identity_id = ".$aclIds[$aclData['parent_acl']]." WHERE id = ".$aclId);
  338. $con->executeQuery($this->callMethod($provider, 'getInsertObjectIdentityRelationSql', array($aclId, $aclIds[$aclData['parent_acl']])));
  339. } else {
  340. $parentAcls[$aclId] = $aclData['parent_acl'];
  341. }
  342. }
  343. }
  344. foreach ($parentAcls as $aclId => $name) {
  345. if (!isset($aclIds[$name])) {
  346. throw new \InvalidArgumentException(sprintf('"%s" does not exist.', $name));
  347. }
  348. $con->executeQuery(sprintf("UPDATE acl_object_identities SET parent_object_identity_id = %d WHERE id = %d", $aclIds[$name], $aclId));
  349. $con->executeQuery($this->callMethod($provider, 'getInsertObjectIdentityRelationSql', array($aclId, $aclIds[$name])));
  350. }
  351. $con->commit();
  352. } catch (\Exception $e) {
  353. $con->rollBack();
  354. throw $e;
  355. }
  356. }
  357. protected function callMethod($object, $method, array $args)
  358. {
  359. $method = new \ReflectionMethod($object, $method);
  360. $method->setAccessible(true);
  361. return $method->invokeArgs($object, $args);
  362. }
  363. protected function setUp()
  364. {
  365. if (!class_exists('Doctrine\DBAL\DriverManager')) {
  366. $this->markTestSkipped('The Doctrine2 DBAL is required for this test');
  367. }
  368. if (!class_exists('PDO') || !in_array('sqlite', \PDO::getAvailableDrivers())) {
  369. self::markTestSkipped('This test requires SQLite support in your environment');
  370. }
  371. $this->con = DriverManager::getConnection(array(
  372. 'driver' => 'pdo_sqlite',
  373. 'memory' => true,
  374. ));
  375. // import the schema
  376. $schema = new Schema($this->getOptions());
  377. foreach ($schema->toSql($this->con->getDatabasePlatform()) as $sql) {
  378. $this->con->exec($sql);
  379. }
  380. }
  381. protected function tearDown()
  382. {
  383. $this->con = null;
  384. }
  385. protected function getField($object, $field)
  386. {
  387. $reflection = new \ReflectionProperty($object, $field);
  388. $reflection->setAccessible(true);
  389. return $reflection->getValue($object);
  390. }
  391. public function setField($object, $field, $value)
  392. {
  393. $reflection = new \ReflectionProperty($object, $field);
  394. $reflection->setAccessible(true);
  395. $reflection->setValue($object, $value);
  396. $reflection->setAccessible(false);
  397. }
  398. protected function getOptions()
  399. {
  400. return array(
  401. 'oid_table_name' => 'acl_object_identities',
  402. 'oid_ancestors_table_name' => 'acl_object_identity_ancestors',
  403. 'class_table_name' => 'acl_classes',
  404. 'sid_table_name' => 'acl_security_identities',
  405. 'entry_table_name' => 'acl_entries',
  406. );
  407. }
  408. protected function getStrategy()
  409. {
  410. return new PermissionGrantingStrategy();
  411. }
  412. protected function getProvider($cache = null)
  413. {
  414. return new MutableAclProvider($this->con, $this->getStrategy(), $this->getOptions(), $cache);
  415. }
  416. }