diff options
Diffstat (limited to 'Acl/Tests/Dbal')
-rw-r--r-- | Acl/Tests/Dbal/AclProviderBenchmarkTest.php | 267 | ||||
-rw-r--r-- | Acl/Tests/Dbal/AclProviderTest.php | 280 | ||||
-rw-r--r-- | Acl/Tests/Dbal/MutableAclProviderTest.php | 572 |
3 files changed, 1119 insertions, 0 deletions
diff --git a/Acl/Tests/Dbal/AclProviderBenchmarkTest.php b/Acl/Tests/Dbal/AclProviderBenchmarkTest.php new file mode 100644 index 0000000..c95b474 --- /dev/null +++ b/Acl/Tests/Dbal/AclProviderBenchmarkTest.php @@ -0,0 +1,267 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Acl\Tests\Dbal; + +use Symfony\Component\Security\Acl\Dbal\AclProvider; +use Symfony\Component\Security\Acl\Domain\PermissionGrantingStrategy; +use Symfony\Component\Security\Acl\Domain\ObjectIdentity; +use Symfony\Component\Security\Acl\Dbal\Schema; +use Doctrine\DBAL\DriverManager; + +/** + * @group benchmark + */ +class AclProviderBenchmarkTest extends \PHPUnit_Framework_TestCase +{ + /** @var \Doctrine\DBAL\Connection */ + protected $con; + protected $insertClassStmt; + protected $insertSidStmt; + protected $insertOidAncestorStmt; + protected $insertOidStmt; + protected $insertEntryStmt; + + protected function setUp() + { + try { + $this->con = DriverManager::getConnection(array( + 'driver' => 'pdo_mysql', + 'host' => 'localhost', + 'user' => 'root', + 'dbname' => 'testdb', + )); + $this->con->connect(); + } catch (\Exception $e) { + $this->markTestSkipped('Unable to connect to the database: '.$e->getMessage()); + } + } + + protected function tearDown() + { + $this->con = null; + } + + public function testFindAcls() + { + // $this->generateTestData(); + + // get some random test object identities from the database + $oids = array(); + $stmt = $this->con->executeQuery('SELECT object_identifier, class_type FROM acl_object_identities o INNER JOIN acl_classes c ON c.id = o.class_id ORDER BY RAND() LIMIT 25'); + foreach ($stmt->fetchAll() as $oid) { + $oids[] = new ObjectIdentity($oid['object_identifier'], $oid['class_type']); + } + + $provider = $this->getProvider(); + + $start = microtime(true); + $provider->findAcls($oids); + $time = microtime(true) - $start; + echo 'Total Time: '.$time."s\n"; + } + + /** + * This generates a huge amount of test data to be used mainly for benchmarking + * purposes, not so much for testing. That's why it's not called by default. + */ + protected function generateTestData() + { + $sm = $this->con->getSchemaManager(); + $sm->dropAndCreateDatabase('testdb'); + $this->con->exec('USE testdb'); + + // import the schema + $schema = new Schema($options = $this->getOptions()); + foreach ($schema->toSql($this->con->getDatabasePlatform()) as $sql) { + $this->con->exec($sql); + } + + // setup prepared statements + $this->insertClassStmt = $this->con->prepare('INSERT INTO acl_classes (id, class_type) VALUES (?, ?)'); + $this->insertSidStmt = $this->con->prepare('INSERT INTO acl_security_identities (id, identifier, username) VALUES (?, ?, ?)'); + $this->insertOidStmt = $this->con->prepare('INSERT INTO acl_object_identities (id, class_id, object_identifier, parent_object_identity_id, entries_inheriting) VALUES (?, ?, ?, ?, ?)'); + $this->insertEntryStmt = $this->con->prepare('INSERT INTO acl_entries (id, class_id, object_identity_id, field_name, ace_order, security_identity_id, mask, granting, granting_strategy, audit_success, audit_failure) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'); + $this->insertOidAncestorStmt = $this->con->prepare('INSERT INTO acl_object_identity_ancestors (object_identity_id, ancestor_id) VALUES (?, ?)'); + + for ($i = 0; $i < 40000; ++$i) { + $this->generateAclHierarchy(); + } + } + + protected function generateAclHierarchy() + { + $rootId = $this->generateAcl($this->chooseClassId(), null, array()); + + $this->generateAclLevel(rand(1, 15), $rootId, array($rootId)); + } + + protected function generateAclLevel($depth, $parentId, $ancestors) + { + $level = count($ancestors); + for ($i = 0, $t = rand(1, 10); $i < $t; ++$i) { + $id = $this->generateAcl($this->chooseClassId(), $parentId, $ancestors); + + if ($level < $depth) { + $this->generateAclLevel($depth, $id, array_merge($ancestors, array($id))); + } + } + } + + protected function chooseClassId() + { + static $id = 1000; + + if ($id === 1000 || ($id < 1500 && rand(0, 1))) { + $this->insertClassStmt->execute(array($id, $this->getRandomString(rand(20, 100), 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\\_'))); + ++$id; + + return $id - 1; + } else { + return rand(1000, $id - 1); + } + } + + protected function generateAcl($classId, $parentId, $ancestors) + { + static $id = 1000; + + $this->insertOidStmt->execute(array( + $id, + $classId, + $this->getRandomString(rand(20, 50)), + $parentId, + rand(0, 1), + )); + + $this->insertOidAncestorStmt->execute(array($id, $id)); + foreach ($ancestors as $ancestor) { + $this->insertOidAncestorStmt->execute(array($id, $ancestor)); + } + + $this->generateAces($classId, $id); + ++$id; + + return $id - 1; + } + + protected function chooseSid() + { + static $id = 1000; + + if ($id === 1000 || ($id < 11000 && rand(0, 1))) { + $this->insertSidStmt->execute(array( + $id, + $this->getRandomString(rand(5, 30)), + rand(0, 1), + )); + ++$id; + + return $id - 1; + } else { + return rand(1000, $id - 1); + } + } + + protected function generateAces($classId, $objectId) + { + static $id = 1000; + + $sids = array(); + $fieldOrder = array(); + + for ($i = 0; $i <= 30; ++$i) { + $fieldName = rand(0, 1) ? null : $this->getRandomString(rand(10, 20)); + + do { + $sid = $this->chooseSid(); + } while (array_key_exists($sid, $sids) && in_array($fieldName, $sids[$sid], true)); + + $fieldOrder[$fieldName] = array_key_exists($fieldName, $fieldOrder) ? $fieldOrder[$fieldName] + 1 : 0; + if (!isset($sids[$sid])) { + $sids[$sid] = array(); + } + $sids[$sid][] = $fieldName; + + $strategy = rand(0, 2); + if ($strategy === 0) { + $strategy = PermissionGrantingStrategy::ALL; + } elseif ($strategy === 1) { + $strategy = PermissionGrantingStrategy::ANY; + } else { + $strategy = PermissionGrantingStrategy::EQUAL; + } + + // id, cid, oid, field, order, sid, mask, granting, strategy, a success, a failure + $this->insertEntryStmt->execute(array( + $id, + $classId, + rand(0, 5) ? $objectId : null, + $fieldName, + $fieldOrder[$fieldName], + $sid, + $this->generateMask(), + rand(0, 1), + $strategy, + rand(0, 1), + rand(0, 1), + )); + + ++$id; + } + } + + protected function generateMask() + { + $i = rand(1, 30); + $mask = 0; + + while ($i <= 30) { + $mask |= 1 << rand(0, 30); + ++$i; + } + + return $mask; + } + + protected function getRandomString($length, $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') + { + $s = ''; + $cLength = strlen($chars); + + while (strlen($s) < $length) { + $s .= $chars[mt_rand(0, $cLength - 1)]; + } + + return $s; + } + + protected function getOptions() + { + return array( + 'oid_table_name' => 'acl_object_identities', + 'oid_ancestors_table_name' => 'acl_object_identity_ancestors', + 'class_table_name' => 'acl_classes', + 'sid_table_name' => 'acl_security_identities', + 'entry_table_name' => 'acl_entries', + ); + } + + protected function getStrategy() + { + return new PermissionGrantingStrategy(); + } + + protected function getProvider() + { + return new AclProvider($this->con, $this->getStrategy(), $this->getOptions()); + } +} diff --git a/Acl/Tests/Dbal/AclProviderTest.php b/Acl/Tests/Dbal/AclProviderTest.php new file mode 100644 index 0000000..680c6c3 --- /dev/null +++ b/Acl/Tests/Dbal/AclProviderTest.php @@ -0,0 +1,280 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Acl\Tests\Dbal; + +use Symfony\Component\Security\Acl\Dbal\AclProvider; +use Symfony\Component\Security\Acl\Domain\PermissionGrantingStrategy; +use Symfony\Component\Security\Acl\Domain\ObjectIdentity; +use Symfony\Component\Security\Acl\Dbal\Schema; +use Doctrine\DBAL\DriverManager; + +/** + * @requires extension pdo_sqlite + */ +class AclProviderTest extends \PHPUnit_Framework_TestCase +{ + protected $con; + protected $insertClassStmt; + protected $insertEntryStmt; + protected $insertOidStmt; + protected $insertOidAncestorStmt; + protected $insertSidStmt; + + /** + * @expectedException \Symfony\Component\Security\Acl\Exception\AclNotFoundException + * @expectedMessage There is no ACL for the given object identity. + */ + public function testFindAclThrowsExceptionWhenNoAclExists() + { + $this->getProvider()->findAcl(new ObjectIdentity('foo', 'foo')); + } + + public function testFindAclsThrowsExceptionUnlessAnACLIsFoundForEveryOID() + { + $oids = array(); + $oids[] = new ObjectIdentity('1', 'foo'); + $oids[] = new ObjectIdentity('foo', 'foo'); + + try { + $this->getProvider()->findAcls($oids); + + $this->fail('Provider did not throw an expected exception.'); + } catch (\Exception $e) { + $this->assertInstanceOf('Symfony\Component\Security\Acl\Exception\AclNotFoundException', $e); + $this->assertInstanceOf('Symfony\Component\Security\Acl\Exception\NotAllAclsFoundException', $e); + + $partialResult = $e->getPartialResult(); + $this->assertTrue($partialResult->contains($oids[0])); + $this->assertFalse($partialResult->contains($oids[1])); + } + } + + public function testFindAcls() + { + $oids = array(); + $oids[] = new ObjectIdentity('1', 'foo'); + $oids[] = new ObjectIdentity('2', 'foo'); + + $provider = $this->getProvider(); + + $acls = $provider->findAcls($oids); + $this->assertInstanceOf('SplObjectStorage', $acls); + $this->assertCount(2, $acls); + $this->assertInstanceOf('Symfony\Component\Security\Acl\Domain\Acl', $acl0 = $acls->offsetGet($oids[0])); + $this->assertInstanceOf('Symfony\Component\Security\Acl\Domain\Acl', $acl1 = $acls->offsetGet($oids[1])); + $this->assertTrue($oids[0]->equals($acl0->getObjectIdentity())); + $this->assertTrue($oids[1]->equals($acl1->getObjectIdentity())); + } + + public function testFindAclsWithDifferentTypes() + { + $oids = array(); + $oids[] = new ObjectIdentity('123', 'Bundle\SomeVendor\MyBundle\Entity\SomeEntity'); + $oids[] = new ObjectIdentity('123', 'Bundle\MyBundle\Entity\AnotherEntity'); + + $provider = $this->getProvider(); + + $acls = $provider->findAcls($oids); + $this->assertInstanceOf('SplObjectStorage', $acls); + $this->assertCount(2, $acls); + $this->assertInstanceOf('Symfony\Component\Security\Acl\Domain\Acl', $acl0 = $acls->offsetGet($oids[0])); + $this->assertInstanceOf('Symfony\Component\Security\Acl\Domain\Acl', $acl1 = $acls->offsetGet($oids[1])); + $this->assertTrue($oids[0]->equals($acl0->getObjectIdentity())); + $this->assertTrue($oids[1]->equals($acl1->getObjectIdentity())); + } + + public function testFindAclCachesAclInMemory() + { + $oid = new ObjectIdentity('1', 'foo'); + $provider = $this->getProvider(); + + $acl = $provider->findAcl($oid); + $this->assertSame($acl, $cAcl = $provider->findAcl($oid)); + + $cAces = $cAcl->getObjectAces(); + foreach ($acl->getObjectAces() as $index => $ace) { + $this->assertSame($ace, $cAces[$index]); + } + } + + public function testFindAcl() + { + $oid = new ObjectIdentity('1', 'foo'); + $provider = $this->getProvider(); + + $acl = $provider->findAcl($oid); + + $this->assertInstanceOf('Symfony\Component\Security\Acl\Domain\Acl', $acl); + $this->assertTrue($oid->equals($acl->getObjectIdentity())); + $this->assertEquals(4, $acl->getId()); + $this->assertCount(0, $acl->getClassAces()); + $this->assertCount(0, $this->getField($acl, 'classFieldAces')); + $this->assertCount(3, $acl->getObjectAces()); + $this->assertCount(0, $this->getField($acl, 'objectFieldAces')); + + $aces = $acl->getObjectAces(); + $this->assertInstanceOf('Symfony\Component\Security\Acl\Domain\Entry', $aces[0]); + $this->assertTrue($aces[0]->isGranting()); + $this->assertTrue($aces[0]->isAuditSuccess()); + $this->assertTrue($aces[0]->isAuditFailure()); + $this->assertEquals('all', $aces[0]->getStrategy()); + $this->assertSame(2, $aces[0]->getMask()); + + // check ACE are in correct order + $i = 0; + foreach ($aces as $index => $ace) { + $this->assertEquals($i, $index); + ++$i; + } + + $sid = $aces[0]->getSecurityIdentity(); + $this->assertInstanceOf('Symfony\Component\Security\Acl\Domain\UserSecurityIdentity', $sid); + $this->assertEquals('john.doe', $sid->getUsername()); + $this->assertEquals('SomeClass', $sid->getClass()); + } + + protected function setUp() + { + $this->con = DriverManager::getConnection(array( + 'driver' => 'pdo_sqlite', + 'memory' => true, + )); + + // import the schema + $schema = new Schema($options = $this->getOptions()); + foreach ($schema->toSql($this->con->getDatabasePlatform()) as $sql) { + $this->con->exec($sql); + } + + // populate the schema with some test data + $this->insertClassStmt = $this->con->prepare('INSERT INTO acl_classes (id, class_type) VALUES (?, ?)'); + foreach ($this->getClassData() as $data) { + $this->insertClassStmt->execute($data); + } + + $this->insertSidStmt = $this->con->prepare('INSERT INTO acl_security_identities (id, identifier, username) VALUES (?, ?, ?)'); + foreach ($this->getSidData() as $data) { + $this->insertSidStmt->execute($data); + } + + $this->insertOidStmt = $this->con->prepare('INSERT INTO acl_object_identities (id, class_id, object_identifier, parent_object_identity_id, entries_inheriting) VALUES (?, ?, ?, ?, ?)'); + foreach ($this->getOidData() as $data) { + $this->insertOidStmt->execute($data); + } + + $this->insertEntryStmt = $this->con->prepare('INSERT INTO acl_entries (id, class_id, object_identity_id, field_name, ace_order, security_identity_id, mask, granting, granting_strategy, audit_success, audit_failure) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'); + foreach ($this->getEntryData() as $data) { + $this->insertEntryStmt->execute($data); + } + + $this->insertOidAncestorStmt = $this->con->prepare('INSERT INTO acl_object_identity_ancestors (object_identity_id, ancestor_id) VALUES (?, ?)'); + foreach ($this->getOidAncestorData() as $data) { + $this->insertOidAncestorStmt->execute($data); + } + } + + protected function tearDown() + { + $this->con = null; + } + + protected function getField($object, $field) + { + $reflection = new \ReflectionProperty($object, $field); + $reflection->setAccessible(true); + + return $reflection->getValue($object); + } + + protected function getEntryData() + { + // id, cid, oid, field, order, sid, mask, granting, strategy, a success, a failure + return array( + array(1, 1, 1, null, 0, 1, 1, 1, 'all', 1, 1), + array(2, 1, 1, null, 1, 2, 1 << 2 | 1 << 1, 0, 'any', 0, 0), + array(3, 3, 4, null, 0, 1, 2, 1, 'all', 1, 1), + array(4, 3, 4, null, 2, 2, 1, 1, 'all', 1, 1), + array(5, 3, 4, null, 1, 3, 1, 1, 'all', 1, 1), + ); + } + + protected function getOidData() + { + // id, cid, oid, parent_oid, entries_inheriting + return array( + array(1, 1, '123', null, 1), + array(2, 2, '123', 1, 1), + array(3, 2, 'i:3:123', 1, 1), + array(4, 3, '1', 2, 1), + array(5, 3, '2', 2, 1), + ); + } + + protected function getOidAncestorData() + { + return array( + array(1, 1), + array(2, 1), + array(2, 2), + array(3, 1), + array(3, 3), + array(4, 2), + array(4, 1), + array(4, 4), + array(5, 2), + array(5, 1), + array(5, 5), + ); + } + + protected function getSidData() + { + return array( + array(1, 'SomeClass-john.doe', 1), + array(2, 'MyClass-john.doe@foo.com', 1), + array(3, 'FooClass-123', 1), + array(4, 'MooClass-ROLE_USER', 1), + array(5, 'ROLE_USER', 0), + array(6, 'IS_AUTHENTICATED_FULLY', 0), + ); + } + + protected function getClassData() + { + return array( + array(1, 'Bundle\SomeVendor\MyBundle\Entity\SomeEntity'), + array(2, 'Bundle\MyBundle\Entity\AnotherEntity'), + array(3, 'foo'), + ); + } + + protected function getOptions() + { + return array( + 'oid_table_name' => 'acl_object_identities', + 'oid_ancestors_table_name' => 'acl_object_identity_ancestors', + 'class_table_name' => 'acl_classes', + 'sid_table_name' => 'acl_security_identities', + 'entry_table_name' => 'acl_entries', + ); + } + + protected function getStrategy() + { + return new PermissionGrantingStrategy(); + } + + protected function getProvider() + { + return new AclProvider($this->con, $this->getStrategy(), $this->getOptions()); + } +} diff --git a/Acl/Tests/Dbal/MutableAclProviderTest.php b/Acl/Tests/Dbal/MutableAclProviderTest.php new file mode 100644 index 0000000..c2169e4 --- /dev/null +++ b/Acl/Tests/Dbal/MutableAclProviderTest.php @@ -0,0 +1,572 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Acl\Tests\Dbal; + +use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity; +use Symfony\Component\Security\Acl\Model\FieldEntryInterface; +use Symfony\Component\Security\Acl\Model\AuditableEntryInterface; +use Symfony\Component\Security\Acl\Model\EntryInterface; +use Symfony\Component\Security\Acl\Domain\Entry; +use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity; +use Symfony\Component\Security\Acl\Domain\Acl; +use Symfony\Component\Security\Acl\Exception\AclNotFoundException; +use Symfony\Component\Security\Acl\Exception\ConcurrentModificationException; +use Symfony\Component\Security\Acl\Dbal\AclProvider; +use Symfony\Component\Security\Acl\Domain\PermissionGrantingStrategy; +use Symfony\Component\Security\Acl\Dbal\MutableAclProvider; +use Symfony\Component\Security\Acl\Dbal\Schema; +use Doctrine\DBAL\DriverManager; +use Symfony\Component\Security\Acl\Domain\ObjectIdentity; + +/** + * @requires extension pdo_sqlite + */ +class MutableAclProviderTest extends \PHPUnit_Framework_TestCase +{ + protected $con; + + public static function assertAceEquals(EntryInterface $a, EntryInterface $b) + { + self::assertInstanceOf(get_class($a), $b); + + foreach (array('getId', 'getMask', 'getStrategy', 'isGranting') as $getter) { + self::assertSame($a->$getter(), $b->$getter()); + } + + self::assertTrue($a->getSecurityIdentity()->equals($b->getSecurityIdentity())); + self::assertSame($a->getAcl()->getId(), $b->getAcl()->getId()); + + if ($a instanceof AuditableEntryInterface) { + self::assertSame($a->isAuditSuccess(), $b->isAuditSuccess()); + self::assertSame($a->isAuditFailure(), $b->isAuditFailure()); + } + + if ($a instanceof FieldEntryInterface) { + self::assertSame($a->getField(), $b->getField()); + } + } + + /** + * @expectedException \Symfony\Component\Security\Acl\Exception\AclAlreadyExistsException + */ + public function testCreateAclThrowsExceptionWhenAclAlreadyExists() + { + $provider = $this->getProvider(); + $oid = new ObjectIdentity('123456', 'FOO'); + $provider->createAcl($oid); + $provider->createAcl($oid); + } + + public function testCreateAcl() + { + $provider = $this->getProvider(); + $oid = new ObjectIdentity('123456', 'FOO'); + $acl = $provider->createAcl($oid); + $cachedAcl = $provider->findAcl($oid); + + $this->assertInstanceOf('Symfony\Component\Security\Acl\Domain\Acl', $acl); + $this->assertSame($acl, $cachedAcl); + $this->assertTrue($acl->getObjectIdentity()->equals($oid)); + } + + public function testDeleteAcl() + { + $provider = $this->getProvider(); + $oid = new ObjectIdentity(1, 'Foo'); + $acl = $provider->createAcl($oid); + + $provider->deleteAcl($oid); + $loadedAcls = $this->getField($provider, 'loadedAcls'); + $this->assertCount(0, $loadedAcls['Foo']); + + try { + $provider->findAcl($oid); + $this->fail('ACL has not been properly deleted.'); + } catch (AclNotFoundException $e) { + } + } + + public function testDeleteAclDeletesChildren() + { + $provider = $this->getProvider(); + $acl = $provider->createAcl(new ObjectIdentity(1, 'Foo')); + $parentAcl = $provider->createAcl(new ObjectIdentity(2, 'Foo')); + $acl->setParentAcl($parentAcl); + $provider->updateAcl($acl); + $provider->deleteAcl($parentAcl->getObjectIdentity()); + + try { + $provider->findAcl(new ObjectIdentity(1, 'Foo')); + $this->fail('Child-ACLs have not been deleted.'); + } catch (AclNotFoundException $e) { + } + } + + public function testFindAclsAddsPropertyListener() + { + $provider = $this->getProvider(); + $acl = $provider->createAcl(new ObjectIdentity(1, 'Foo')); + + $propertyChanges = $this->getField($provider, 'propertyChanges'); + $this->assertCount(1, $propertyChanges); + $this->assertTrue($propertyChanges->contains($acl)); + $this->assertEquals(array(), $propertyChanges->offsetGet($acl)); + + $listeners = $this->getField($acl, 'listeners'); + $this->assertSame($provider, $listeners[0]); + } + + public function testFindAclsAddsPropertyListenerOnlyOnce() + { + $provider = $this->getProvider(); + $acl = $provider->createAcl(new ObjectIdentity(1, 'Foo')); + $acl = $provider->findAcl(new ObjectIdentity(1, 'Foo')); + + $propertyChanges = $this->getField($provider, 'propertyChanges'); + $this->assertCount(1, $propertyChanges); + $this->assertTrue($propertyChanges->contains($acl)); + $this->assertEquals(array(), $propertyChanges->offsetGet($acl)); + + $listeners = $this->getField($acl, 'listeners'); + $this->assertCount(1, $listeners); + $this->assertSame($provider, $listeners[0]); + } + + public function testFindAclsAddsPropertyListenerToParentAcls() + { + $provider = $this->getProvider(); + $this->importAcls($provider, array( + 'main' => array( + 'object_identifier' => '1', + 'class_type' => 'foo', + 'parent_acl' => 'parent', + ), + 'parent' => array( + 'object_identifier' => '1', + 'class_type' => 'anotherFoo', + ), + )); + + $propertyChanges = $this->getField($provider, 'propertyChanges'); + $this->assertCount(0, $propertyChanges); + + $acl = $provider->findAcl(new ObjectIdentity('1', 'foo')); + $this->assertCount(2, $propertyChanges); + $this->assertTrue($propertyChanges->contains($acl)); + $this->assertTrue($propertyChanges->contains($acl->getParentAcl())); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testPropertyChangedDoesNotTrackUnmanagedAcls() + { + $provider = $this->getProvider(); + $acl = new Acl(1, new ObjectIdentity(1, 'foo'), new PermissionGrantingStrategy(), array(), false); + + $provider->propertyChanged($acl, 'classAces', array(), array('foo')); + } + + public function testPropertyChangedTracksChangesToAclProperties() + { + $provider = $this->getProvider(); + $acl = $provider->createAcl(new ObjectIdentity(1, 'Foo')); + $propertyChanges = $this->getField($provider, 'propertyChanges'); + + $provider->propertyChanged($acl, 'entriesInheriting', false, true); + $changes = $propertyChanges->offsetGet($acl); + $this->assertTrue(isset($changes['entriesInheriting'])); + $this->assertFalse($changes['entriesInheriting'][0]); + $this->assertTrue($changes['entriesInheriting'][1]); + + $provider->propertyChanged($acl, 'entriesInheriting', true, false); + $provider->propertyChanged($acl, 'entriesInheriting', false, true); + $provider->propertyChanged($acl, 'entriesInheriting', true, false); + $changes = $propertyChanges->offsetGet($acl); + $this->assertFalse(isset($changes['entriesInheriting'])); + } + + public function testPropertyChangedTracksChangesToAceProperties() + { + $provider = $this->getProvider(); + $acl = $provider->createAcl(new ObjectIdentity(1, 'Foo')); + $ace = new Entry(1, $acl, new UserSecurityIdentity('foo', 'FooClass'), 'all', 1, true, true, true); + $ace2 = new Entry(2, $acl, new UserSecurityIdentity('foo', 'FooClass'), 'all', 1, true, true, true); + $propertyChanges = $this->getField($provider, 'propertyChanges'); + + $provider->propertyChanged($ace, 'mask', 1, 3); + $changes = $propertyChanges->offsetGet($acl); + $this->assertTrue(isset($changes['aces'])); + $this->assertInstanceOf('\SplObjectStorage', $changes['aces']); + $this->assertTrue($changes['aces']->contains($ace)); + $aceChanges = $changes['aces']->offsetGet($ace); + $this->assertTrue(isset($aceChanges['mask'])); + $this->assertEquals(1, $aceChanges['mask'][0]); + $this->assertEquals(3, $aceChanges['mask'][1]); + + $provider->propertyChanged($ace, 'strategy', 'all', 'any'); + $changes = $propertyChanges->offsetGet($acl); + $this->assertTrue(isset($changes['aces'])); + $this->assertInstanceOf('\SplObjectStorage', $changes['aces']); + $this->assertTrue($changes['aces']->contains($ace)); + $aceChanges = $changes['aces']->offsetGet($ace); + $this->assertTrue(isset($aceChanges['mask'])); + $this->assertTrue(isset($aceChanges['strategy'])); + $this->assertEquals('all', $aceChanges['strategy'][0]); + $this->assertEquals('any', $aceChanges['strategy'][1]); + + $provider->propertyChanged($ace, 'mask', 3, 1); + $changes = $propertyChanges->offsetGet($acl); + $aceChanges = $changes['aces']->offsetGet($ace); + $this->assertFalse(isset($aceChanges['mask'])); + $this->assertTrue(isset($aceChanges['strategy'])); + + $provider->propertyChanged($ace2, 'mask', 1, 3); + $provider->propertyChanged($ace, 'strategy', 'any', 'all'); + $changes = $propertyChanges->offsetGet($acl); + $this->assertTrue(isset($changes['aces'])); + $this->assertFalse($changes['aces']->contains($ace)); + $this->assertTrue($changes['aces']->contains($ace2)); + + $provider->propertyChanged($ace2, 'mask', 3, 4); + $provider->propertyChanged($ace2, 'mask', 4, 1); + $changes = $propertyChanges->offsetGet($acl); + $this->assertFalse(isset($changes['aces'])); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testUpdateAclDoesNotAcceptUntrackedAcls() + { + $provider = $this->getProvider(); + $acl = new Acl(1, new ObjectIdentity(1, 'Foo'), new PermissionGrantingStrategy(), array(), true); + $provider->updateAcl($acl); + } + + public function testUpdateDoesNothingWhenThereAreNoChanges() + { + $con = $this->getMock('Doctrine\DBAL\Connection', array(), array(), '', false); + $con + ->expects($this->never()) + ->method('beginTransaction') + ; + $con + ->expects($this->never()) + ->method('executeQuery') + ; + + $provider = new MutableAclProvider($con, new PermissionGrantingStrategy(), array()); + $acl = new Acl(1, new ObjectIdentity(1, 'Foo'), new PermissionGrantingStrategy(), array(), true); + $propertyChanges = $this->getField($provider, 'propertyChanges'); + $propertyChanges->offsetSet($acl, array()); + $provider->updateAcl($acl); + } + + public function testUpdateAclThrowsExceptionOnConcurrentModificationOfSharedProperties() + { + $provider = $this->getProvider(); + $acl1 = $provider->createAcl(new ObjectIdentity(1, 'Foo')); + $acl2 = $provider->createAcl(new ObjectIdentity(2, 'Foo')); + $acl3 = $provider->createAcl(new ObjectIdentity(1, 'AnotherFoo')); + $sid = new RoleSecurityIdentity('ROLE_FOO'); + + $acl1->insertClassAce($sid, 1); + $acl3->insertClassAce($sid, 1); + $provider->updateAcl($acl1); + $provider->updateAcl($acl3); + + $acl2->insertClassAce($sid, 16); + $provider->updateAcl($acl2); + + $acl1->insertClassAce($sid, 3); + $acl2->insertClassAce($sid, 5); + try { + $provider->updateAcl($acl1); + $this->fail('Provider failed to detect a concurrent modification.'); + } catch (ConcurrentModificationException $e) { + } + } + + public function testUpdateAcl() + { + $provider = $this->getProvider(); + $acl = $provider->createAcl(new ObjectIdentity(1, 'Foo')); + $sid = new UserSecurityIdentity('johannes', 'FooClass'); + $acl->setEntriesInheriting(!$acl->isEntriesInheriting()); + + $acl->insertObjectAce($sid, 1); + $acl->insertClassAce($sid, 5, 0, false); + $acl->insertObjectAce($sid, 2, 1, true); + $acl->insertClassFieldAce('field', $sid, 2, 0, true); + $provider->updateAcl($acl); + + $acl->updateObjectAce(0, 3); + $acl->deleteObjectAce(1); + $acl->updateObjectAuditing(0, true, false); + $acl->updateClassFieldAce(0, 'field', 15); + $provider->updateAcl($acl); + + $reloadProvider = $this->getProvider(); + $reloadedAcl = $reloadProvider->findAcl(new ObjectIdentity(1, 'Foo')); + $this->assertNotSame($acl, $reloadedAcl); + $this->assertSame($acl->isEntriesInheriting(), $reloadedAcl->isEntriesInheriting()); + + $aces = $acl->getObjectAces(); + $reloadedAces = $reloadedAcl->getObjectAces(); + $this->assertEquals(count($aces), count($reloadedAces)); + foreach ($aces as $index => $ace) { + $this->assertAceEquals($ace, $reloadedAces[$index]); + } + } + + public function testUpdateAclWorksForChangingTheParentAcl() + { + $provider = $this->getProvider(); + $acl = $provider->createAcl(new ObjectIdentity(1, 'Foo')); + $parentAcl = $provider->createAcl(new ObjectIdentity(1, 'AnotherFoo')); + $acl->setParentAcl($parentAcl); + $provider->updateAcl($acl); + + $reloadProvider = $this->getProvider(); + $reloadedAcl = $reloadProvider->findAcl(new ObjectIdentity(1, 'Foo')); + $this->assertNotSame($acl, $reloadedAcl); + $this->assertSame($parentAcl->getId(), $reloadedAcl->getParentAcl()->getId()); + } + + public function testUpdateAclUpdatesChildAclsCorrectly() + { + $provider = $this->getProvider(); + $acl = $provider->createAcl(new ObjectIdentity(1, 'Foo')); + + $parentAcl = $provider->createAcl(new ObjectIdentity(1, 'Bar')); + $acl->setParentAcl($parentAcl); + $provider->updateAcl($acl); + + $parentParentAcl = $provider->createAcl(new ObjectIdentity(1, 'Baz')); + $parentAcl->setParentAcl($parentParentAcl); + $provider->updateAcl($parentAcl); + + $newParentParentAcl = $provider->createAcl(new ObjectIdentity(2, 'Baz')); + $parentAcl->setParentAcl($newParentParentAcl); + $provider->updateAcl($parentAcl); + + $reloadProvider = $this->getProvider(); + $reloadedAcl = $reloadProvider->findAcl(new ObjectIdentity(1, 'Foo')); + $this->assertEquals($newParentParentAcl->getId(), $reloadedAcl->getParentAcl()->getParentAcl()->getId()); + } + + public function testUpdateAclInsertingMultipleObjectFieldAcesThrowsDBConstraintViolations() + { + $provider = $this->getProvider(); + $oid = new ObjectIdentity(1, 'Foo'); + $sid1 = new UserSecurityIdentity('johannes', 'FooClass'); + $sid2 = new UserSecurityIdentity('guilro', 'FooClass'); + $sid3 = new UserSecurityIdentity('bmaz', 'FooClass'); + $fieldName = 'fieldName'; + + $acl = $provider->createAcl($oid); + $acl->insertObjectFieldAce($fieldName, $sid1, 4); + $provider->updateAcl($acl); + + $acl = $provider->findAcl($oid); + $acl->insertObjectFieldAce($fieldName, $sid2, 4); + $provider->updateAcl($acl); + + $acl = $provider->findAcl($oid); + $acl->insertObjectFieldAce($fieldName, $sid3, 4); + $provider->updateAcl($acl); + } + + public function testUpdateAclDeletingObjectFieldAcesThrowsDBConstraintViolations() + { + $provider = $this->getProvider(); + $oid = new ObjectIdentity(1, 'Foo'); + $sid1 = new UserSecurityIdentity('johannes', 'FooClass'); + $sid2 = new UserSecurityIdentity('guilro', 'FooClass'); + $sid3 = new UserSecurityIdentity('bmaz', 'FooClass'); + $fieldName = 'fieldName'; + + $acl = $provider->createAcl($oid); + $acl->insertObjectFieldAce($fieldName, $sid1, 4); + $provider->updateAcl($acl); + + $acl = $provider->findAcl($oid); + $acl->insertObjectFieldAce($fieldName, $sid2, 4); + $provider->updateAcl($acl); + + $index = 0; + $acl->deleteObjectFieldAce($index, $fieldName); + $provider->updateAcl($acl); + + $acl = $provider->findAcl($oid); + $acl->insertObjectFieldAce($fieldName, $sid3, 4); + $provider->updateAcl($acl); + } + + public function testUpdateUserSecurityIdentity() + { + $provider = $this->getProvider(); + $acl = $provider->createAcl(new ObjectIdentity(1, 'Foo')); + $sid = new UserSecurityIdentity('johannes', 'FooClass'); + $acl->setEntriesInheriting(!$acl->isEntriesInheriting()); + + $acl->insertObjectAce($sid, 1); + $acl->insertClassAce($sid, 5, 0, false); + $acl->insertObjectAce($sid, 2, 1, true); + $acl->insertClassFieldAce('field', $sid, 2, 0, true); + $provider->updateAcl($acl); + + $newSid = new UserSecurityIdentity('mathieu', 'FooClass'); + $provider->updateUserSecurityIdentity($newSid, 'johannes'); + + $reloadProvider = $this->getProvider(); + $reloadedAcl = $reloadProvider->findAcl(new ObjectIdentity(1, 'Foo')); + + $this->assertNotSame($acl, $reloadedAcl); + $this->assertSame($acl->isEntriesInheriting(), $reloadedAcl->isEntriesInheriting()); + + $aces = $acl->getObjectAces(); + $reloadedAces = $reloadedAcl->getObjectAces(); + $this->assertEquals(count($aces), count($reloadedAces)); + foreach ($reloadedAces as $ace) { + $this->assertTrue($ace->getSecurityIdentity()->equals($newSid)); + } + } + + /** + * Imports acls. + * + * Data must have the following format: + * array( + * *name* => array( + * 'object_identifier' => *required* + * 'class_type' => *required*, + * 'parent_acl' => *name (optional)* + * ), + * ) + * + * @param AclProvider $provider + * @param array $data + * + * @throws \InvalidArgumentException + * @throws \Exception + */ + protected function importAcls(AclProvider $provider, array $data) + { + $aclIds = $parentAcls = array(); + $con = $this->getField($provider, 'connection'); + $con->beginTransaction(); + try { + foreach ($data as $name => $aclData) { + if (!isset($aclData['object_identifier'], $aclData['class_type'])) { + throw new \InvalidArgumentException('"object_identifier", and "class_type" must be present.'); + } + + $this->callMethod($provider, 'createObjectIdentity', array(new ObjectIdentity($aclData['object_identifier'], $aclData['class_type']))); + $aclId = $con->lastInsertId(); + $aclIds[$name] = $aclId; + + $sql = $this->callMethod($provider, 'getInsertObjectIdentityRelationSql', array($aclId, $aclId)); + $con->executeQuery($sql); + + if (isset($aclData['parent_acl'])) { + if (isset($aclIds[$aclData['parent_acl']])) { + $con->executeQuery('UPDATE acl_object_identities SET parent_object_identity_id = '.$aclIds[$aclData['parent_acl']].' WHERE id = '.$aclId); + $con->executeQuery($this->callMethod($provider, 'getInsertObjectIdentityRelationSql', array($aclId, $aclIds[$aclData['parent_acl']]))); + } else { + $parentAcls[$aclId] = $aclData['parent_acl']; + } + } + } + + foreach ($parentAcls as $aclId => $name) { + if (!isset($aclIds[$name])) { + throw new \InvalidArgumentException(sprintf('"%s" does not exist.', $name)); + } + + $con->executeQuery(sprintf('UPDATE acl_object_identities SET parent_object_identity_id = %d WHERE id = %d', $aclIds[$name], $aclId)); + $con->executeQuery($this->callMethod($provider, 'getInsertObjectIdentityRelationSql', array($aclId, $aclIds[$name]))); + } + + $con->commit(); + } catch (\Exception $e) { + $con->rollBack(); + + throw $e; + } + } + + protected function callMethod($object, $method, array $args) + { + $method = new \ReflectionMethod($object, $method); + $method->setAccessible(true); + + return $method->invokeArgs($object, $args); + } + + protected function setUp() + { + $this->con = DriverManager::getConnection(array( + 'driver' => 'pdo_sqlite', + 'memory' => true, + )); + + // import the schema + $schema = new Schema($this->getOptions()); + foreach ($schema->toSql($this->con->getDatabasePlatform()) as $sql) { + $this->con->exec($sql); + } + } + + protected function tearDown() + { + $this->con = null; + } + + protected function getField($object, $field) + { + $reflection = new \ReflectionProperty($object, $field); + $reflection->setAccessible(true); + + return $reflection->getValue($object); + } + + public function setField($object, $field, $value) + { + $reflection = new \ReflectionProperty($object, $field); + $reflection->setAccessible(true); + $reflection->setValue($object, $value); + $reflection->setAccessible(false); + } + + protected function getOptions() + { + return array( + 'oid_table_name' => 'acl_object_identities', + 'oid_ancestors_table_name' => 'acl_object_identity_ancestors', + 'class_table_name' => 'acl_classes', + 'sid_table_name' => 'acl_security_identities', + 'entry_table_name' => 'acl_entries', + ); + } + + protected function getStrategy() + { + return new PermissionGrantingStrategy(); + } + + protected function getProvider($cache = null) + { + return new MutableAclProvider($this->con, $this->getStrategy(), $this->getOptions(), $cache); + } +} |