MutableAclProviderTest.php 17 KB

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