diff options
Diffstat (limited to 'Acl')
24 files changed, 3289 insertions, 15 deletions
diff --git a/Acl/.gitignore b/Acl/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/Acl/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/Acl/Dbal/AclProvider.php b/Acl/Dbal/AclProvider.php index 1d1cb16..5d45655 100644 --- a/Acl/Dbal/AclProvider.php +++ b/Acl/Dbal/AclProvider.php @@ -40,8 +40,8 @@ class AclProvider implements AclProviderInterface protected $cache; protected $connection; - protected $loadedAces; - protected $loadedAcls; + protected $loadedAces = array(); + protected $loadedAcls = array(); protected $options; private $permissionGrantingStrategy; @@ -57,8 +57,6 @@ class AclProvider implements AclProviderInterface { $this->cache = $cache; $this->connection = $connection; - $this->loadedAces = array(); - $this->loadedAcls = array(); $this->options = $options; $this->permissionGrantingStrategy = $permissionGrantingStrategy; } diff --git a/Acl/Domain/Acl.php b/Acl/Domain/Acl.php index 4665c0e..dd3e8d4 100644 --- a/Acl/Domain/Acl.php +++ b/Acl/Domain/Acl.php @@ -37,14 +37,14 @@ class Acl implements AuditableAclInterface, NotifyPropertyChanged private $parentAcl; private $permissionGrantingStrategy; private $objectIdentity; - private $classAces; - private $classFieldAces; - private $objectAces; - private $objectFieldAces; + private $classAces = array(); + private $classFieldAces = array(); + private $objectAces = array(); + private $objectFieldAces = array(); private $id; private $loadedSids; private $entriesInheriting; - private $listeners; + private $listeners = array(); /** * Constructor @@ -62,12 +62,6 @@ class Acl implements AuditableAclInterface, NotifyPropertyChanged $this->permissionGrantingStrategy = $permissionGrantingStrategy; $this->loadedSids = $loadedSids; $this->entriesInheriting = $entriesInheriting; - $this->parentAcl = null; - $this->classAces = array(); - $this->classFieldAces = array(); - $this->objectAces = array(); - $this->objectFieldAces = array(); - $this->listeners = array(); } /** diff --git a/Acl/LICENSE b/Acl/LICENSE new file mode 100644 index 0000000..0b3292c --- /dev/null +++ b/Acl/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2014 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Acl/README.md b/Acl/README.md new file mode 100644 index 0000000..87e5092 --- /dev/null +++ b/Acl/README.md @@ -0,0 +1,23 @@ +Security Component - ACL (Access Control List) +============================================== + +Security provides an infrastructure for sophisticated authorization systems, +which makes it possible to easily separate the actual authorization logic from +so called user providers that hold the users credentials. It is inspired by +the Java Spring framework. + +Resources +--------- + +Documentation: + +http://symfony.com/doc/2.4/book/security.html + +Tests +----- + +You can run the unit tests with the following command: + + $ cd path/to/Symfony/Component/Security/Acl/ + $ composer.phar install --dev + $ phpunit diff --git a/Acl/Tests/Dbal/AclProviderBenchmarkTest.php b/Acl/Tests/Dbal/AclProviderBenchmarkTest.php new file mode 100644 index 0000000..8f68f1f --- /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 += 1; + + 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 += 1; + + 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 += 1; + + 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 += 1; + } + } + + 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..717a258 --- /dev/null +++ b/Acl/Tests/Dbal/AclProviderTest.php @@ -0,0 +1,281 @@ +<?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; + +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 $ex) { + $this->assertInstanceOf('Symfony\Component\Security\Acl\Exception\AclNotFoundException', $ex); + $this->assertInstanceOf('Symfony\Component\Security\Acl\Exception\NotAllAclsFoundException', $ex); + + $partialResult = $ex->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() + { + if (!class_exists('PDO') || !in_array('sqlite', \PDO::getAvailableDrivers())) { + self::markTestSkipped('This test requires SQLite support in your environment'); + } + + $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..440f69c --- /dev/null +++ b/Acl/Tests/Dbal/MutableAclProviderTest.php @@ -0,0 +1,537 @@ +<?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; + +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 $notFound) { } + } + + 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 $notFound) { } + } + + 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 $ex) { } + } + + 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); + } + + /** + * 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() + { + if (!class_exists('PDO') || !in_array('sqlite', \PDO::getAvailableDrivers())) { + self::markTestSkipped('This test requires SQLite support in your environment'); + } + + $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); + } +} diff --git a/Acl/Tests/Domain/AclTest.php b/Acl/Tests/Domain/AclTest.php new file mode 100644 index 0000000..2034c21 --- /dev/null +++ b/Acl/Tests/Domain/AclTest.php @@ -0,0 +1,514 @@ +<?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\Domain; + +use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity; + +use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity; +use Symfony\Component\Security\Acl\Domain\PermissionGrantingStrategy; +use Symfony\Component\Security\Acl\Domain\ObjectIdentity; +use Symfony\Component\Security\Acl\Domain\Acl; + +class AclTest extends \PHPUnit_Framework_TestCase +{ + public function testConstructor() + { + $acl = new Acl(1, $oid = new ObjectIdentity('foo', 'foo'), $permissionStrategy = new PermissionGrantingStrategy(), array(), true); + + $this->assertSame(1, $acl->getId()); + $this->assertSame($oid, $acl->getObjectIdentity()); + $this->assertNull($acl->getParentAcl()); + $this->assertTrue($acl->isEntriesInheriting()); + } + + /** + * @expectedException \OutOfBoundsException + * @dataProvider getDeleteAceTests + */ + public function testDeleteAceThrowsExceptionOnInvalidIndex($type) + { + $acl = $this->getAcl(); + $acl->{'delete'.$type.'Ace'}(0); + } + + /** + * @dataProvider getDeleteAceTests + */ + public function testDeleteAce($type) + { + $acl = $this->getAcl(); + $acl->{'insert'.$type.'Ace'}(new RoleSecurityIdentity('foo'), 1); + $acl->{'insert'.$type.'Ace'}(new RoleSecurityIdentity('foo'), 2, 1); + $acl->{'insert'.$type.'Ace'}(new RoleSecurityIdentity('foo'), 3, 2); + + $listener = $this->getListener(array( + $type.'Aces', 'aceOrder', 'aceOrder', $type.'Aces', + )); + $acl->addPropertyChangedListener($listener); + + $this->assertCount(3, $acl->{'get'.$type.'Aces'}()); + + $acl->{'delete'.$type.'Ace'}(0); + $this->assertCount(2, $aces = $acl->{'get'.$type.'Aces'}()); + $this->assertEquals(2, $aces[0]->getMask()); + $this->assertEquals(3, $aces[1]->getMask()); + + $acl->{'delete'.$type.'Ace'}(1); + $this->assertCount(1, $aces = $acl->{'get'.$type.'Aces'}()); + $this->assertEquals(2, $aces[0]->getMask()); + } + + public function getDeleteAceTests() + { + return array( + array('class'), + array('object'), + ); + } + + /** + * @expectedException \OutOfBoundsException + * @dataProvider getDeleteFieldAceTests + */ + public function testDeleteFieldAceThrowsExceptionOnInvalidIndex($type) + { + $acl = $this->getAcl(); + $acl->{'delete'.$type.'Ace'}('foo', 0); + } + + /** + * @dataProvider getDeleteFieldAceTests + */ + public function testDeleteFieldAce($type) + { + $acl = $this->getAcl(); + $acl->{'insert'.$type.'Ace'}('foo', new RoleSecurityIdentity('foo'), 1, 0); + $acl->{'insert'.$type.'Ace'}('foo', new RoleSecurityIdentity('foo'), 2, 1); + $acl->{'insert'.$type.'Ace'}('foo', new RoleSecurityIdentity('foo'), 3, 2); + + $listener = $this->getListener(array( + $type.'Aces', 'aceOrder', 'aceOrder', $type.'Aces', + )); + $acl->addPropertyChangedListener($listener); + + $this->assertCount(3, $acl->{'get'.$type.'Aces'}('foo')); + + $acl->{'delete'.$type.'Ace'}(0, 'foo'); + $this->assertCount(2, $aces = $acl->{'get'.$type.'Aces'}('foo')); + $this->assertEquals(2, $aces[0]->getMask()); + $this->assertEquals(3, $aces[1]->getMask()); + + $acl->{'delete'.$type.'Ace'}(1, 'foo'); + $this->assertCount(1, $aces = $acl->{'get'.$type.'Aces'}('foo')); + $this->assertEquals(2, $aces[0]->getMask()); + } + + public function getDeleteFieldAceTests() + { + return array( + array('classField'), + array('objectField'), + ); + } + + /** + * @dataProvider getInsertAceTests + */ + public function testInsertAce($property, $method) + { + $acl = $this->getAcl(); + + $listener = $this->getListener(array( + $property, 'aceOrder', $property, 'aceOrder', $property + )); + $acl->addPropertyChangedListener($listener); + + $sid = new RoleSecurityIdentity('foo'); + $acl->$method($sid, 1); + $acl->$method($sid, 2); + $acl->$method($sid, 3, 1, false); + + $this->assertCount(3, $aces = $acl->{'get'.$property}()); + $this->assertEquals(2, $aces[0]->getMask()); + $this->assertEquals(3, $aces[1]->getMask()); + $this->assertEquals(1, $aces[2]->getMask()); + } + + /** + * @expectedException \OutOfBoundsException + * @dataProvider getInsertAceTests + */ + public function testInsertClassAceThrowsExceptionOnInvalidIndex($property, $method) + { + $acl = $this->getAcl(); + $acl->$method(new RoleSecurityIdentity('foo'), 1, 1); + } + + public function getInsertAceTests() + { + return array( + array('classAces', 'insertClassAce'), + array('objectAces', 'insertObjectAce'), + ); + } + + /** + * @dataProvider getInsertFieldAceTests + */ + public function testInsertClassFieldAce($property, $method) + { + $acl = $this->getAcl(); + + $listener = $this->getListener(array( + $property, $property, 'aceOrder', $property, + 'aceOrder', 'aceOrder', $property, + )); + $acl->addPropertyChangedListener($listener); + + $sid = new RoleSecurityIdentity('foo'); + $acl->$method('foo', $sid, 1); + $acl->$method('foo2', $sid, 1); + $acl->$method('foo', $sid, 3); + $acl->$method('foo', $sid, 2); + + $this->assertCount(3, $aces = $acl->{'get'.$property}('foo')); + $this->assertCount(1, $acl->{'get'.$property}('foo2')); + $this->assertEquals(2, $aces[0]->getMask()); + $this->assertEquals(3, $aces[1]->getMask()); + $this->assertEquals(1, $aces[2]->getMask()); + } + + /** + * @expectedException \OutOfBoundsException + * @dataProvider getInsertFieldAceTests + */ + public function testInsertClassFieldAceThrowsExceptionOnInvalidIndex($property, $method) + { + $acl = $this->getAcl(); + $acl->$method('foo', new RoleSecurityIdentity('foo'), 1, 1); + } + + public function getInsertFieldAceTests() + { + return array( + array('classFieldAces', 'insertClassFieldAce'), + array('objectFieldAces', 'insertObjectFieldAce'), + ); + } + + public function testIsFieldGranted() + { + $sids = array(new RoleSecurityIdentity('ROLE_FOO'), new RoleSecurityIdentity('ROLE_IDDQD')); + $masks = array(1, 2, 4); + $strategy = $this->getMock('Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface'); + $acl = new Acl(1, new ObjectIdentity(1, 'foo'), $strategy, array(), true); + + $strategy + ->expects($this->once()) + ->method('isFieldGranted') + ->with($this->equalTo($acl), $this->equalTo('foo'), $this->equalTo($masks), $this->equalTo($sids), $this->isTrue()) + ->will($this->returnValue(true)) + ; + + $this->assertTrue($acl->isFieldGranted('foo', $masks, $sids, true)); + } + + public function testIsGranted() + { + $sids = array(new RoleSecurityIdentity('ROLE_FOO'), new RoleSecurityIdentity('ROLE_IDDQD')); + $masks = array(1, 2, 4); + $strategy = $this->getMock('Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface'); + $acl = new Acl(1, new ObjectIdentity(1, 'foo'), $strategy, array(), true); + + $strategy + ->expects($this->once()) + ->method('isGranted') + ->with($this->equalTo($acl), $this->equalTo($masks), $this->equalTo($sids), $this->isTrue()) + ->will($this->returnValue(true)) + ; + + $this->assertTrue($acl->isGranted($masks, $sids, true)); + } + + public function testSetGetParentAcl() + { + $acl = $this->getAcl(); + $parentAcl = $this->getAcl(); + + $listener = $this->getListener(array('parentAcl')); + $acl->addPropertyChangedListener($listener); + + $this->assertNull($acl->getParentAcl()); + $acl->setParentAcl($parentAcl); + $this->assertSame($parentAcl, $acl->getParentAcl()); + + $acl->setParentAcl(null); + $this->assertNull($acl->getParentAcl()); + } + + public function testSetIsEntriesInheriting() + { + $acl = $this->getAcl(); + + $listener = $this->getListener(array('entriesInheriting')); + $acl->addPropertyChangedListener($listener); + + $this->assertTrue($acl->isEntriesInheriting()); + $acl->setEntriesInheriting(false); + $this->assertFalse($acl->isEntriesInheriting()); + } + + public function testIsSidLoadedWhenAllSidsAreLoaded() + { + $acl = $this->getAcl(); + + $this->assertTrue($acl->isSidLoaded(new UserSecurityIdentity('foo', 'Foo'))); + $this->assertTrue($acl->isSidLoaded(new RoleSecurityIdentity('ROLE_FOO', 'Foo'))); + } + + public function testIsSidLoaded() + { + $acl = new Acl(1, new ObjectIdentity('1', 'foo'), new PermissionGrantingStrategy(), array(new UserSecurityIdentity('foo', 'Foo'), new UserSecurityIdentity('johannes', 'Bar')), true); + + $this->assertTrue($acl->isSidLoaded(new UserSecurityIdentity('foo', 'Foo'))); + $this->assertTrue($acl->isSidLoaded(new UserSecurityIdentity('johannes', 'Bar'))); + $this->assertTrue($acl->isSidLoaded(array( + new UserSecurityIdentity('foo', 'Foo'), + new UserSecurityIdentity('johannes', 'Bar'), + ))); + $this->assertFalse($acl->isSidLoaded(new RoleSecurityIdentity('ROLE_FOO'))); + $this->assertFalse($acl->isSidLoaded(new UserSecurityIdentity('schmittjoh@gmail.com', 'Moo'))); + $this->assertFalse($acl->isSidLoaded(array( + new UserSecurityIdentity('foo', 'Foo'), + new UserSecurityIdentity('johannes', 'Bar'), + new RoleSecurityIdentity('ROLE_FOO'), + ))); + } + + /** + * @dataProvider getUpdateAceTests + * @expectedException \OutOfBoundsException + */ + public function testUpdateAceThrowsOutOfBoundsExceptionOnInvalidIndex($type) + { + $acl = $this->getAcl(); + $acl->{'update'.$type}(0, 1); + } + + /** + * @dataProvider getUpdateAceTests + */ + public function testUpdateAce($type) + { + $acl = $this->getAcl(); + $acl->{'insert'.$type}(new RoleSecurityIdentity('foo'), 1); + + $listener = $this->getListener(array( + 'mask', 'mask', 'strategy', + )); + $acl->addPropertyChangedListener($listener); + + $aces = $acl->{'get'.$type.'s'}(); + $ace = reset($aces); + $this->assertEquals(1, $ace->getMask()); + $this->assertEquals('all', $ace->getStrategy()); + + $acl->{'update'.$type}(0, 3); + $this->assertEquals(3, $ace->getMask()); + $this->assertEquals('all', $ace->getStrategy()); + + $acl->{'update'.$type}(0, 1, 'foo'); + $this->assertEquals(1, $ace->getMask()); + $this->assertEquals('foo', $ace->getStrategy()); + } + + public function getUpdateAceTests() + { + return array( + array('classAce'), + array('objectAce'), + ); + } + + /** + * @dataProvider getUpdateFieldAceTests + * @expectedException \OutOfBoundsException + */ + public function testUpdateFieldAceThrowsExceptionOnInvalidIndex($type) + { + $acl = $this->getAcl(); + $acl->{'update'.$type}(0, 'foo', 1); + } + + /** + * @dataProvider getUpdateFieldAceTests + */ + public function testUpdateFieldAce($type) + { + $acl = $this->getAcl(); + $acl->{'insert'.$type}('foo', new UserSecurityIdentity('foo', 'Foo'), 1); + + $listener = $this->getListener(array( + 'mask', 'mask', 'strategy' + )); + $acl->addPropertyChangedListener($listener); + + $aces = $acl->{'get'.$type.'s'}('foo'); + $ace = reset($aces); + $this->assertEquals(1, $ace->getMask()); + $this->assertEquals('all', $ace->getStrategy()); + + $acl->{'update'.$type}(0, 'foo', 3); + $this->assertEquals(3, $ace->getMask()); + $this->assertEquals('all', $ace->getStrategy()); + + $acl->{'update'.$type}(0, 'foo', 1, 'foo'); + $this->assertEquals(1, $ace->getMask()); + $this->assertEquals('foo', $ace->getStrategy()); + } + + public function getUpdateFieldAceTests() + { + return array( + array('classFieldAce'), + array('objectFieldAce'), + ); + } + + /** + * @dataProvider getUpdateAuditingTests + * @expectedException \OutOfBoundsException + */ + public function testUpdateAuditingThrowsExceptionOnInvalidIndex($type) + { + $acl = $this->getAcl(); + $acl->{'update'.$type.'Auditing'}(0, true, false); + } + + /** + * @dataProvider getUpdateAuditingTests + */ + public function testUpdateAuditing($type) + { + $acl = $this->getAcl(); + $acl->{'insert'.$type.'Ace'}(new RoleSecurityIdentity('foo'), 1); + + $listener = $this->getListener(array( + 'auditFailure', 'auditSuccess', 'auditFailure', + )); + $acl->addPropertyChangedListener($listener); + + $aces = $acl->{'get'.$type.'Aces'}(); + $ace = reset($aces); + $this->assertFalse($ace->isAuditSuccess()); + $this->assertFalse($ace->isAuditFailure()); + + $acl->{'update'.$type.'Auditing'}(0, false, true); + $this->assertFalse($ace->isAuditSuccess()); + $this->assertTrue($ace->isAuditFailure()); + + $acl->{'update'.$type.'Auditing'}(0, true, false); + $this->assertTrue($ace->isAuditSuccess()); + $this->assertFalse($ace->isAuditFailure()); + } + + public function getUpdateAuditingTests() + { + return array( + array('class'), + array('object'), + ); + } + + /** + * @expectedException \InvalidArgumentException + * @dataProvider getUpdateFieldAuditingTests + */ + public function testUpdateFieldAuditingThrowsExceptionOnInvalidField($type) + { + $acl = $this->getAcl(); + $acl->{'update'.$type.'Auditing'}(0, 'foo', true, true); + } + + /** + * @expectedException \OutOfBoundsException + * @dataProvider getUpdateFieldAuditingTests + */ + public function testUpdateFieldAuditingThrowsExceptionOnInvalidIndex($type) + { + $acl = $this->getAcl(); + $acl->{'insert'.$type.'Ace'}('foo', new RoleSecurityIdentity('foo'), 1); + $acl->{'update'.$type.'Auditing'}(1, 'foo', true, false); + } + + /** + * @dataProvider getUpdateFieldAuditingTests + */ + public function testUpdateFieldAuditing($type) + { + $acl = $this->getAcl(); + $acl->{'insert'.$type.'Ace'}('foo', new RoleSecurityIdentity('foo'), 1); + + $listener = $this->getListener(array( + 'auditSuccess', 'auditSuccess', 'auditFailure', + )); + $acl->addPropertyChangedListener($listener); + + $aces = $acl->{'get'.$type.'Aces'}('foo'); + $ace = reset($aces); + $this->assertFalse($ace->isAuditSuccess()); + $this->assertFalse($ace->isAuditFailure()); + + $acl->{'update'.$type.'Auditing'}(0, 'foo', true, false); + $this->assertTrue($ace->isAuditSuccess()); + $this->assertFalse($ace->isAuditFailure()); + + $acl->{'update'.$type.'Auditing'}(0, 'foo', false, true); + $this->assertFalse($ace->isAuditSuccess()); + $this->assertTrue($ace->isAuditFailure()); + } + + public function getUpdateFieldAuditingTests() + { + return array( + array('classField'), + array('objectField'), + ); + } + + protected function getListener($expectedChanges) + { + $aceProperties = array('aceOrder', 'mask', 'strategy', 'auditSuccess', 'auditFailure'); + + $listener = $this->getMock('Doctrine\Common\PropertyChangedListener'); + foreach ($expectedChanges as $index => $property) { + if (in_array($property, $aceProperties)) { + $class = 'Symfony\Component\Security\Acl\Domain\Entry'; + } else { + $class = 'Symfony\Component\Security\Acl\Domain\Acl'; + } + + $listener + ->expects($this->at($index)) + ->method('propertyChanged') + ->with($this->isInstanceOf($class), $this->equalTo($property)) + ; + } + + return $listener; + } + + protected function getAcl() + { + return new Acl(1, new ObjectIdentity(1, 'foo'), new PermissionGrantingStrategy(), array(), true); + } +} diff --git a/Acl/Tests/Domain/AuditLoggerTest.php b/Acl/Tests/Domain/AuditLoggerTest.php new file mode 100644 index 0000000..fe56b8c --- /dev/null +++ b/Acl/Tests/Domain/AuditLoggerTest.php @@ -0,0 +1,83 @@ +<?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\Domain; + +class AuditLoggerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getTestLogData + */ + public function testLogIfNeeded($granting, $audit) + { + $logger = $this->getLogger(); + $ace = $this->getEntry(); + + if (true === $granting) { + $ace + ->expects($this->once()) + ->method('isAuditSuccess') + ->will($this->returnValue($audit)) + ; + + $ace + ->expects($this->never()) + ->method('isAuditFailure') + ; + } else { + $ace + ->expects($this->never()) + ->method('isAuditSuccess') + ; + + $ace + ->expects($this->once()) + ->method('isAuditFailure') + ->will($this->returnValue($audit)) + ; + } + + if (true === $audit) { + $logger + ->expects($this->once()) + ->method('doLog') + ->with($this->equalTo($granting), $this->equalTo($ace)) + ; + } else { + $logger + ->expects($this->never()) + ->method('doLog') + ; + } + + $logger->logIfNeeded($granting, $ace); + } + + public function getTestLogData() + { + return array( + array(true, false), + array(true, true), + array(false, false), + array(false, true), + ); + } + + protected function getEntry() + { + return $this->getMock('Symfony\Component\Security\Acl\Model\AuditableEntryInterface'); + } + + protected function getLogger() + { + return $this->getMockForAbstractClass('Symfony\Component\Security\Acl\Domain\AuditLogger'); + } +} diff --git a/Acl/Tests/Domain/DoctrineAclCacheTest.php b/Acl/Tests/Domain/DoctrineAclCacheTest.php new file mode 100644 index 0000000..128f2c8 --- /dev/null +++ b/Acl/Tests/Domain/DoctrineAclCacheTest.php @@ -0,0 +1,101 @@ +<?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\Domain; + +use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity; +use Symfony\Component\Security\Acl\Domain\ObjectIdentity; +use Symfony\Component\Security\Acl\Domain\PermissionGrantingStrategy; +use Symfony\Component\Security\Acl\Domain\Acl; +use Symfony\Component\Security\Acl\Domain\DoctrineAclCache; +use Doctrine\Common\Cache\ArrayCache; + +class DoctrineAclCacheTest extends \PHPUnit_Framework_TestCase +{ + protected $permissionGrantingStrategy; + + /** + * @expectedException \InvalidArgumentException + * @dataProvider getEmptyValue + */ + public function testConstructorDoesNotAcceptEmptyPrefix($empty) + { + new DoctrineAclCache(new ArrayCache(), $this->getPermissionGrantingStrategy(), $empty); + } + + public function getEmptyValue() + { + return array( + array(null), + array(false), + array(''), + ); + } + + public function test() + { + $cache = $this->getCache(); + + $aclWithParent = $this->getAcl(1); + $acl = $this->getAcl(); + + $cache->putInCache($aclWithParent); + $cache->putInCache($acl); + + $cachedAcl = $cache->getFromCacheByIdentity($acl->getObjectIdentity()); + $this->assertEquals($acl->getId(), $cachedAcl->getId()); + $this->assertNull($acl->getParentAcl()); + + $cachedAclWithParent = $cache->getFromCacheByIdentity($aclWithParent->getObjectIdentity()); + $this->assertEquals($aclWithParent->getId(), $cachedAclWithParent->getId()); + $this->assertNotNull($cachedParentAcl = $cachedAclWithParent->getParentAcl()); + $this->assertEquals($aclWithParent->getParentAcl()->getId(), $cachedParentAcl->getId()); + } + + protected function getAcl($depth = 0) + { + static $id = 1; + + $acl = new Acl($id, new ObjectIdentity($id, 'foo'), $this->getPermissionGrantingStrategy(), array(), $depth > 0); + + // insert some ACEs + $sid = new UserSecurityIdentity('johannes', 'Foo'); + $acl->insertClassAce($sid, 1); + $acl->insertClassFieldAce('foo', $sid, 1); + $acl->insertObjectAce($sid, 1); + $acl->insertObjectFieldAce('foo', $sid, 1); + $id++; + + if ($depth > 0) { + $acl->setParentAcl($this->getAcl($depth - 1)); + } + + return $acl; + } + + protected function getPermissionGrantingStrategy() + { + if (null === $this->permissionGrantingStrategy) { + $this->permissionGrantingStrategy = new PermissionGrantingStrategy(); + } + + return $this->permissionGrantingStrategy; + } + + protected function getCache($cacheDriver = null, $prefix = DoctrineAclCache::PREFIX) + { + if (null === $cacheDriver) { + $cacheDriver = new ArrayCache(); + } + + return new DoctrineAclCache($cacheDriver, $this->getPermissionGrantingStrategy(), $prefix); + } +} diff --git a/Acl/Tests/Domain/EntryTest.php b/Acl/Tests/Domain/EntryTest.php new file mode 100644 index 0000000..6a2aac0 --- /dev/null +++ b/Acl/Tests/Domain/EntryTest.php @@ -0,0 +1,119 @@ +<?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\Domain; + +use Symfony\Component\Security\Acl\Domain\Entry; + +class EntryTest extends \PHPUnit_Framework_TestCase +{ + public function testConstructor() + { + $ace = $this->getAce($acl = $this->getAcl(), $sid = $this->getSid()); + + $this->assertEquals(123, $ace->getId()); + $this->assertSame($acl, $ace->getAcl()); + $this->assertSame($sid, $ace->getSecurityIdentity()); + $this->assertEquals('foostrat', $ace->getStrategy()); + $this->assertEquals(123456, $ace->getMask()); + $this->assertTrue($ace->isGranting()); + $this->assertTrue($ace->isAuditSuccess()); + $this->assertFalse($ace->isAuditFailure()); + } + + public function testSetAuditSuccess() + { + $ace = $this->getAce(); + + $this->assertTrue($ace->isAuditSuccess()); + $ace->setAuditSuccess(false); + $this->assertFalse($ace->isAuditSuccess()); + $ace->setAuditsuccess(true); + $this->assertTrue($ace->isAuditSuccess()); + } + + public function testSetAuditFailure() + { + $ace = $this->getAce(); + + $this->assertFalse($ace->isAuditFailure()); + $ace->setAuditFailure(true); + $this->assertTrue($ace->isAuditFailure()); + $ace->setAuditFailure(false); + $this->assertFalse($ace->isAuditFailure()); + } + + public function testSetMask() + { + $ace = $this->getAce(); + + $this->assertEquals(123456, $ace->getMask()); + $ace->setMask(4321); + $this->assertEquals(4321, $ace->getMask()); + } + + public function testSetStrategy() + { + $ace = $this->getAce(); + + $this->assertEquals('foostrat', $ace->getStrategy()); + $ace->setStrategy('foo'); + $this->assertEquals('foo', $ace->getStrategy()); + } + + public function testSerializeUnserialize() + { + $ace = $this->getAce(); + + $serialized = serialize($ace); + $uAce = unserialize($serialized); + + $this->assertNull($uAce->getAcl()); + $this->assertInstanceOf('Symfony\Component\Security\Acl\Model\SecurityIdentityInterface', $uAce->getSecurityIdentity()); + $this->assertEquals($ace->getId(), $uAce->getId()); + $this->assertEquals($ace->getMask(), $uAce->getMask()); + $this->assertEquals($ace->getStrategy(), $uAce->getStrategy()); + $this->assertEquals($ace->isGranting(), $uAce->isGranting()); + $this->assertEquals($ace->isAuditSuccess(), $uAce->isAuditSuccess()); + $this->assertEquals($ace->isAuditFailure(), $uAce->isAuditFailure()); + } + + protected function getAce($acl = null, $sid = null) + { + if (null === $acl) { + $acl = $this->getAcl(); + } + if (null === $sid) { + $sid = $this->getSid(); + } + + return new Entry( + 123, + $acl, + $sid, + 'foostrat', + 123456, + true, + false, + true + ); + } + + protected function getAcl() + { + return $this->getMock('Symfony\Component\Security\Acl\Model\AclInterface'); + } + + protected function getSid() + { + return $this->getMock('Symfony\Component\Security\Acl\Model\SecurityIdentityInterface'); + } +} diff --git a/Acl/Tests/Domain/FieldEntryTest.php b/Acl/Tests/Domain/FieldEntryTest.php new file mode 100644 index 0000000..735e2e8 --- /dev/null +++ b/Acl/Tests/Domain/FieldEntryTest.php @@ -0,0 +1,74 @@ +<?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\Domain; + +use Symfony\Component\Security\Acl\Domain\FieldEntry; + +class FieldEntryTest extends \PHPUnit_Framework_TestCase +{ + public function testConstructor() + { + $ace = $this->getAce(); + + $this->assertEquals('foo', $ace->getField()); + } + + public function testSerializeUnserialize() + { + $ace = $this->getAce(); + + $serialized = serialize($ace); + $uAce = unserialize($serialized); + + $this->assertNull($uAce->getAcl()); + $this->assertInstanceOf('Symfony\Component\Security\Acl\Model\SecurityIdentityInterface', $uAce->getSecurityIdentity()); + $this->assertEquals($ace->getId(), $uAce->getId()); + $this->assertEquals($ace->getField(), $uAce->getField()); + $this->assertEquals($ace->getMask(), $uAce->getMask()); + $this->assertEquals($ace->getStrategy(), $uAce->getStrategy()); + $this->assertEquals($ace->isGranting(), $uAce->isGranting()); + $this->assertEquals($ace->isAuditSuccess(), $uAce->isAuditSuccess()); + $this->assertEquals($ace->isAuditFailure(), $uAce->isAuditFailure()); + } + + protected function getAce($acl = null, $sid = null) + { + if (null === $acl) { + $acl = $this->getAcl(); + } + if (null === $sid) { + $sid = $this->getSid(); + } + + return new FieldEntry( + 123, + $acl, + 'foo', + $sid, + 'foostrat', + 123456, + true, + false, + true + ); + } + + protected function getAcl() + { + return $this->getMock('Symfony\Component\Security\Acl\Model\AclInterface'); + } + + protected function getSid() + { + return $this->getMock('Symfony\Component\Security\Acl\Model\SecurityIdentityInterface'); + } +} diff --git a/Acl/Tests/Domain/ObjectIdentityRetrievalStrategyTest.php b/Acl/Tests/Domain/ObjectIdentityRetrievalStrategyTest.php new file mode 100644 index 0000000..59fc3bd --- /dev/null +++ b/Acl/Tests/Domain/ObjectIdentityRetrievalStrategyTest.php @@ -0,0 +1,41 @@ +<?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\Domain; + +use Symfony\Component\Security\Acl\Domain\ObjectIdentityRetrievalStrategy; + +class ObjectIdentityRetrievalStrategyTest extends \PHPUnit_Framework_TestCase +{ + public function testGetObjectIdentityReturnsNullForInvalidDomainObject() + { + $strategy = new ObjectIdentityRetrievalStrategy(); + $this->assertNull($strategy->getObjectIdentity('foo')); + } + + public function testGetObjectIdentity() + { + $strategy = new ObjectIdentityRetrievalStrategy(); + $domainObject = new DomainObject(); + $objectIdentity = $strategy->getObjectIdentity($domainObject); + + $this->assertEquals($domainObject->getId(), $objectIdentity->getIdentifier()); + $this->assertEquals(get_class($domainObject), $objectIdentity->getType()); + } +} + +class DomainObject +{ + public function getId() + { + return 'foo'; + } +} diff --git a/Acl/Tests/Domain/ObjectIdentityTest.php b/Acl/Tests/Domain/ObjectIdentityTest.php new file mode 100644 index 0000000..4eab7b2 --- /dev/null +++ b/Acl/Tests/Domain/ObjectIdentityTest.php @@ -0,0 +1,109 @@ +<?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\Domain +{ + use Symfony\Component\Security\Acl\Domain\ObjectIdentity; + + class ObjectIdentityTest extends \PHPUnit_Framework_TestCase + { + public function testConstructor() + { + $id = new ObjectIdentity('fooid', 'footype'); + + $this->assertEquals('fooid', $id->getIdentifier()); + $this->assertEquals('footype', $id->getType()); + } + + // Test that constructor never changes passed type, even with proxies + public function testConstructorWithProxy() + { + $id = new ObjectIdentity('fooid', 'Acme\DemoBundle\Proxy\__CG__\Symfony\Component\Security\Acl\Tests\Domain\TestDomainObject'); + + $this->assertEquals('fooid', $id->getIdentifier()); + $this->assertEquals('Acme\DemoBundle\Proxy\__CG__\Symfony\Component\Security\Acl\Tests\Domain\TestDomainObject', $id->getType()); + } + + public function testFromDomainObjectPrefersInterfaceOverGetId() + { + $domainObject = $this->getMock('Symfony\Component\Security\Acl\Model\DomainObjectInterface'); + $domainObject + ->expects($this->once()) + ->method('getObjectIdentifier') + ->will($this->returnValue('getObjectIdentifier()')) + ; + $domainObject + ->expects($this->never()) + ->method('getId') + ->will($this->returnValue('getId()')) + ; + + $id = ObjectIdentity::fromDomainObject($domainObject); + $this->assertEquals('getObjectIdentifier()', $id->getIdentifier()); + } + + public function testFromDomainObjectWithoutInterface() + { + $id = ObjectIdentity::fromDomainObject(new TestDomainObject()); + $this->assertEquals('getId()', $id->getIdentifier()); + $this->assertEquals('Symfony\Component\Security\Acl\Tests\Domain\TestDomainObject', $id->getType()); + } + + public function testFromDomainObjectWithProxy() + { + $id = ObjectIdentity::fromDomainObject(new \Acme\DemoBundle\Proxy\__CG__\Symfony\Component\Security\Acl\Tests\Domain\TestDomainObject()); + $this->assertEquals('getId()', $id->getIdentifier()); + $this->assertEquals('Symfony\Component\Security\Acl\Tests\Domain\TestDomainObject', $id->getType()); + } + + /** + * @dataProvider getCompareData + */ + public function testEquals($oid1, $oid2, $equal) + { + if ($equal) { + $this->assertTrue($oid1->equals($oid2)); + } else { + $this->assertFalse($oid1->equals($oid2)); + } + } + + public function getCompareData() + { + return array( + array(new ObjectIdentity('123', 'foo'), new ObjectIdentity('123', 'foo'), true), + array(new ObjectIdentity('123', 'foo'), new ObjectIdentity(123, 'foo'), true), + array(new ObjectIdentity('1', 'foo'), new ObjectIdentity('2', 'foo'), false), + array(new ObjectIdentity('1', 'bla'), new ObjectIdentity('1', 'blub'), false), + ); + } + } + + class TestDomainObject + { + public function getObjectIdentifier() + { + return 'getObjectIdentifier()'; + } + + public function getId() + { + return 'getId()'; + } + } +} + +namespace Acme\DemoBundle\Proxy\__CG__\Symfony\Component\Security\Acl\Tests\Domain +{ + class TestDomainObject extends \Symfony\Component\Security\Acl\Tests\Domain\TestDomainObject + { + } +} diff --git a/Acl/Tests/Domain/PermissionGrantingStrategyTest.php b/Acl/Tests/Domain/PermissionGrantingStrategyTest.php new file mode 100644 index 0000000..490d256 --- /dev/null +++ b/Acl/Tests/Domain/PermissionGrantingStrategyTest.php @@ -0,0 +1,185 @@ +<?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\Domain; + +use Symfony\Component\Security\Acl\Domain\ObjectIdentity; +use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity; +use Symfony\Component\Security\Acl\Domain\Acl; +use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity; +use Symfony\Component\Security\Acl\Domain\PermissionGrantingStrategy; +use Symfony\Component\Security\Acl\Exception\NoAceFoundException; + +class PermissionGrantingStrategyTest extends \PHPUnit_Framework_TestCase +{ + public function testIsGrantedObjectAcesHavePriority() + { + $strategy = new PermissionGrantingStrategy(); + $acl = $this->getAcl($strategy); + $sid = new UserSecurityIdentity('johannes', 'Foo'); + + $acl->insertClassAce($sid, 1); + $acl->insertObjectAce($sid, 1, 0, false); + $this->assertFalse($strategy->isGranted($acl, array(1), array($sid))); + } + + public function testIsGrantedFallsBackToClassAcesIfNoApplicableObjectAceWasFound() + { + $strategy = new PermissionGrantingStrategy(); + $acl = $this->getAcl($strategy); + $sid = new UserSecurityIdentity('johannes', 'Foo'); + + $acl->insertClassAce($sid, 1); + $this->assertTrue($strategy->isGranted($acl, array(1), array($sid))); + } + + public function testIsGrantedFavorsLocalAcesOverParentAclAces() + { + $strategy = new PermissionGrantingStrategy(); + $sid = new UserSecurityIdentity('johannes', 'Foo'); + + $acl = $this->getAcl($strategy); + $acl->insertClassAce($sid, 1); + + $parentAcl = $this->getAcl($strategy); + $acl->setParentAcl($parentAcl); + $parentAcl->insertClassAce($sid, 1, 0, false); + + $this->assertTrue($strategy->isGranted($acl, array(1), array($sid))); + } + + public function testIsGrantedFallsBackToParentAcesIfNoLocalAcesAreApplicable() + { + $strategy = new PermissionGrantingStrategy(); + $sid = new UserSecurityIdentity('johannes', 'Foo'); + $anotherSid = new UserSecurityIdentity('ROLE_USER', 'Foo'); + + $acl = $this->getAcl($strategy); + $acl->insertClassAce($anotherSid, 1, 0, false); + + $parentAcl = $this->getAcl($strategy); + $acl->setParentAcl($parentAcl); + $parentAcl->insertClassAce($sid, 1); + + $this->assertTrue($strategy->isGranted($acl, array(1), array($sid))); + } + + /** + * @expectedException \Symfony\Component\Security\Acl\Exception\NoAceFoundException + */ + public function testIsGrantedReturnsExceptionIfNoAceIsFound() + { + $strategy = new PermissionGrantingStrategy(); + $acl = $this->getAcl($strategy); + $sid = new UserSecurityIdentity('johannes', 'Foo'); + + $strategy->isGranted($acl, array(1), array($sid)); + } + + public function testIsGrantedFirstApplicableEntryMakesUltimateDecisionForPermissionIdentityCombination() + { + $strategy = new PermissionGrantingStrategy(); + $acl = $this->getAcl($strategy); + $sid = new UserSecurityIdentity('johannes', 'Foo'); + $aSid = new RoleSecurityIdentity('ROLE_USER'); + + $acl->insertClassAce($aSid, 1); + $acl->insertClassAce($sid, 1, 1, false); + $acl->insertClassAce($sid, 1, 2); + $this->assertFalse($strategy->isGranted($acl, array(1), array($sid, $aSid))); + + $acl->insertObjectAce($sid, 1, 0, false); + $acl->insertObjectAce($aSid, 1, 1); + $this->assertFalse($strategy->isGranted($acl, array(1), array($sid, $aSid))); + } + + public function testIsGrantedCallsAuditLoggerOnGrant() + { + $strategy = new PermissionGrantingStrategy(); + $acl = $this->getAcl($strategy); + $sid = new UserSecurityIdentity('johannes', 'Foo'); + + $logger = $this->getMock('Symfony\Component\Security\Acl\Model\AuditLoggerInterface'); + $logger + ->expects($this->once()) + ->method('logIfNeeded') + ; + $strategy->setAuditLogger($logger); + + $acl->insertObjectAce($sid, 1); + $acl->updateObjectAuditing(0, true, false); + + $this->assertTrue($strategy->isGranted($acl, array(1), array($sid))); + } + + public function testIsGrantedCallsAuditLoggerOnDeny() + { + $strategy = new PermissionGrantingStrategy(); + $acl = $this->getAcl($strategy); + $sid = new UserSecurityIdentity('johannes', 'Foo'); + + $logger = $this->getMock('Symfony\Component\Security\Acl\Model\AuditLoggerInterface'); + $logger + ->expects($this->once()) + ->method('logIfNeeded') + ; + $strategy->setAuditLogger($logger); + + $acl->insertObjectAce($sid, 1, 0, false); + $acl->updateObjectAuditing(0, false, true); + + $this->assertFalse($strategy->isGranted($acl, array(1), array($sid))); + } + + /** + * @dataProvider getAllStrategyTests + */ + public function testIsGrantedStrategies($maskStrategy, $aceMask, $requiredMask, $result) + { + $strategy = new PermissionGrantingStrategy(); + $acl = $this->getAcl($strategy); + $sid = new UserSecurityIdentity('johannes', 'Foo'); + + $acl->insertObjectAce($sid, $aceMask, 0, true, $maskStrategy); + + if (false === $result) { + try { + $strategy->isGranted($acl, array($requiredMask), array($sid)); + $this->fail('The ACE is not supposed to match.'); + } catch (NoAceFoundException $noAce) { } + } else { + $this->assertTrue($strategy->isGranted($acl, array($requiredMask), array($sid))); + } + } + + public function getAllStrategyTests() + { + return array( + array('all', 1 << 0 | 1 << 1, 1 << 0, true), + array('all', 1 << 0 | 1 << 1, 1 << 2, false), + array('all', 1 << 0 | 1 << 10, 1 << 0 | 1 << 10, true), + array('all', 1 << 0 | 1 << 1, 1 << 0 | 1 << 1 || 1 << 2, false), + array('any', 1 << 0 | 1 << 1, 1 << 0, true), + array('any', 1 << 0 | 1 << 1, 1 << 0 | 1 << 2, true), + array('any', 1 << 0 | 1 << 1, 1 << 2, false), + array('equal', 1 << 0 | 1 << 1, 1 << 0, false), + array('equal', 1 << 0 | 1 << 1, 1 << 1, false), + array('equal', 1 << 0 | 1 << 1, 1 << 0 | 1 << 1, true), + ); + } + + protected function getAcl($strategy) + { + static $id = 1; + + return new Acl($id++, new ObjectIdentity(1, 'Foo'), $strategy, array(), true); + } +} diff --git a/Acl/Tests/Domain/RoleSecurityIdentityTest.php b/Acl/Tests/Domain/RoleSecurityIdentityTest.php new file mode 100644 index 0000000..ad5f236 --- /dev/null +++ b/Acl/Tests/Domain/RoleSecurityIdentityTest.php @@ -0,0 +1,55 @@ +<?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\Domain; + +use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity; +use Symfony\Component\Security\Core\Role\Role; +use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity; + +class RoleSecurityIdentityTest extends \PHPUnit_Framework_TestCase +{ + public function testConstructor() + { + $id = new RoleSecurityIdentity('ROLE_FOO'); + + $this->assertEquals('ROLE_FOO', $id->getRole()); + } + + public function testConstructorWithRoleInstance() + { + $id = new RoleSecurityIdentity(new Role('ROLE_FOO')); + + $this->assertEquals('ROLE_FOO', $id->getRole()); + } + + /** + * @dataProvider getCompareData + */ + public function testEquals($id1, $id2, $equal) + { + if ($equal) { + $this->assertTrue($id1->equals($id2)); + } else { + $this->assertFalse($id1->equals($id2)); + } + } + + public function getCompareData() + { + return array( + array(new RoleSecurityIdentity('ROLE_FOO'), new RoleSecurityIdentity('ROLE_FOO'), true), + array(new RoleSecurityIdentity('ROLE_FOO'), new RoleSecurityIdentity(new Role('ROLE_FOO')), true), + array(new RoleSecurityIdentity('ROLE_USER'), new RoleSecurityIdentity('ROLE_FOO'), false), + array(new RoleSecurityIdentity('ROLE_FOO'), new UserSecurityIdentity('ROLE_FOO', 'Foo'), false), + ); + } +} diff --git a/Acl/Tests/Domain/SecurityIdentityRetrievalStrategyTest.php b/Acl/Tests/Domain/SecurityIdentityRetrievalStrategyTest.php new file mode 100644 index 0000000..02fbe67 --- /dev/null +++ b/Acl/Tests/Domain/SecurityIdentityRetrievalStrategyTest.php @@ -0,0 +1,196 @@ +<?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\Domain; + +use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity; +use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity; +use Symfony\Component\Security\Acl\Domain\SecurityIdentityRetrievalStrategy; + +class SecurityIdentityRetrievalStrategyTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getSecurityIdentityRetrievalTests + */ + public function testGetSecurityIdentities($user, array $roles, $authenticationStatus, array $sids) + { + $strategy = $this->getStrategy($roles, $authenticationStatus); + + if ('anonymous' === $authenticationStatus) { + $token = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\AnonymousToken') + ->disableOriginalConstructor() + ->getMock(); + } else { + $class = ''; + if (is_string($user)) { + $class = 'MyCustomTokenImpl'; + } + + $token = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface') + ->setMockClassName($class) + ->getMock(); + } + $token + ->expects($this->once()) + ->method('getRoles') + ->will($this->returnValue(array('foo'))) + ; + if ('anonymous' === $authenticationStatus) { + $token + ->expects($this->never()) + ->method('getUser') + ; + } else { + $token + ->expects($this->once()) + ->method('getUser') + ->will($this->returnValue($user)) + ; + } + + $extractedSids = $strategy->getSecurityIdentities($token); + + foreach ($extractedSids as $index => $extractedSid) { + if (!isset($sids[$index])) { + $this->fail(sprintf('Expected SID at index %d, but there was none.', true)); + } + + if (false === $sids[$index]->equals($extractedSid)) { + $this->fail(sprintf('Index: %d, expected SID "%s", but got "%s".', $index, $sids[$index], $extractedSid)); + } + } + } + + public function getSecurityIdentityRetrievalTests() + { + return array( + array($this->getAccount('johannes', 'FooUser'), array('ROLE_USER', 'ROLE_SUPERADMIN'), 'fullFledged', array( + new UserSecurityIdentity('johannes', 'FooUser'), + new RoleSecurityIdentity('ROLE_USER'), + new RoleSecurityIdentity('ROLE_SUPERADMIN'), + new RoleSecurityIdentity('IS_AUTHENTICATED_FULLY'), + new RoleSecurityIdentity('IS_AUTHENTICATED_REMEMBERED'), + new RoleSecurityIdentity('IS_AUTHENTICATED_ANONYMOUSLY'), + )), + array('johannes', array('ROLE_FOO'), 'fullFledged', array( + new UserSecurityIdentity('johannes', 'MyCustomTokenImpl'), + new RoleSecurityIdentity('ROLE_FOO'), + new RoleSecurityIdentity('IS_AUTHENTICATED_FULLY'), + new RoleSecurityIdentity('IS_AUTHENTICATED_REMEMBERED'), + new RoleSecurityIdentity('IS_AUTHENTICATED_ANONYMOUSLY'), + )), + array(new CustomUserImpl('johannes'), array('ROLE_FOO'), 'fullFledged', array( + new UserSecurityIdentity('johannes', 'Symfony\Component\Security\Acl\Tests\Domain\CustomUserImpl'), + new RoleSecurityIdentity('ROLE_FOO'), + new RoleSecurityIdentity('IS_AUTHENTICATED_FULLY'), + new RoleSecurityIdentity('IS_AUTHENTICATED_REMEMBERED'), + new RoleSecurityIdentity('IS_AUTHENTICATED_ANONYMOUSLY'), + )), + array($this->getAccount('foo', 'FooBarUser'), array('ROLE_FOO'), 'rememberMe', array( + new UserSecurityIdentity('foo', 'FooBarUser'), + new RoleSecurityIdentity('ROLE_FOO'), + new RoleSecurityIdentity('IS_AUTHENTICATED_REMEMBERED'), + new RoleSecurityIdentity('IS_AUTHENTICATED_ANONYMOUSLY'), + )), + array('guest', array('ROLE_FOO'), 'anonymous', array( + new RoleSecurityIdentity('ROLE_FOO'), + new RoleSecurityIdentity('IS_AUTHENTICATED_ANONYMOUSLY'), + )) + ); + } + + protected function getAccount($username, $class) + { + $account = $this->getMock('Symfony\Component\Security\Core\User\UserInterface', array(), array(), $class); + $account + ->expects($this->any()) + ->method('getUsername') + ->will($this->returnValue($username)) + ; + + return $account; + } + + protected function getStrategy(array $roles = array(), $authenticationStatus = 'fullFledged') + { + $roleHierarchy = $this->getMock('Symfony\Component\Security\Core\Role\RoleHierarchyInterface'); + $roleHierarchy + ->expects($this->once()) + ->method('getReachableRoles') + ->with($this->equalTo(array('foo'))) + ->will($this->returnValue($roles)) + ; + + $trustResolver = $this->getMock('Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver', array(), array('', '')); + + $trustResolver + ->expects($this->at(0)) + ->method('isAnonymous') + ->will($this->returnValue('anonymous' === $authenticationStatus)) + ; + + if ('fullFledged' === $authenticationStatus) { + $trustResolver + ->expects($this->once()) + ->method('isFullFledged') + ->will($this->returnValue(true)) + ; + $trustResolver + ->expects($this->never()) + ->method('isRememberMe') + ; + } elseif ('rememberMe' === $authenticationStatus) { + $trustResolver + ->expects($this->once()) + ->method('isFullFledged') + ->will($this->returnValue(false)) + ; + $trustResolver + ->expects($this->once()) + ->method('isRememberMe') + ->will($this->returnValue(true)) + ; + } else { + $trustResolver + ->expects($this->at(1)) + ->method('isAnonymous') + ->will($this->returnValue(true)) + ; + $trustResolver + ->expects($this->once()) + ->method('isFullFledged') + ->will($this->returnValue(false)) + ; + $trustResolver + ->expects($this->once()) + ->method('isRememberMe') + ->will($this->returnValue(false)) + ; + } + + return new SecurityIdentityRetrievalStrategy($roleHierarchy, $trustResolver); + } +} + +class CustomUserImpl +{ + protected $name; + + public function __construct($name) + { + $this->name = $name; + } + + public function __toString() + { + return $this->name; + } +} diff --git a/Acl/Tests/Domain/UserSecurityIdentityTest.php b/Acl/Tests/Domain/UserSecurityIdentityTest.php new file mode 100644 index 0000000..09d3f0d --- /dev/null +++ b/Acl/Tests/Domain/UserSecurityIdentityTest.php @@ -0,0 +1,73 @@ +<?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\Domain; + +use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity; +use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity; + +class UserSecurityIdentityTest extends \PHPUnit_Framework_TestCase +{ + public function testConstructor() + { + $id = new UserSecurityIdentity('foo', 'Foo'); + + $this->assertEquals('foo', $id->getUsername()); + $this->assertEquals('Foo', $id->getClass()); + } + + // Test that constructor never changes the type, even for proxies + public function testConstructorWithProxy() + { + $id = new UserSecurityIdentity('foo', 'Acme\DemoBundle\Proxy\__CG__\Symfony\Component\Security\Acl\Tests\Domain\Foo'); + + $this->assertEquals('foo', $id->getUsername()); + $this->assertEquals('Acme\DemoBundle\Proxy\__CG__\Symfony\Component\Security\Acl\Tests\Domain\Foo', $id->getClass()); + } + + /** + * @dataProvider getCompareData + */ + public function testEquals($id1, $id2, $equal) + { + $this->assertSame($equal, $id1->equals($id2)); + } + + public function getCompareData() + { + $account = $this->getMockBuilder('Symfony\Component\Security\Core\User\UserInterface') + ->setMockClassName('USI_AccountImpl') + ->getMock(); + $account + ->expects($this->any()) + ->method('getUsername') + ->will($this->returnValue('foo')) + ; + + $token = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); + $token + ->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($account)) + ; + + return array( + array(new UserSecurityIdentity('foo', 'Foo'), new UserSecurityIdentity('foo', 'Foo'), true), + array(new UserSecurityIdentity('foo', 'Bar'), new UserSecurityIdentity('foo', 'Foo'), false), + array(new UserSecurityIdentity('foo', 'Foo'), new UserSecurityIdentity('bar', 'Foo'), false), + array(new UserSecurityIdentity('foo', 'Foo'), UserSecurityIdentity::fromAccount($account), false), + array(new UserSecurityIdentity('bla', 'Foo'), new UserSecurityIdentity('blub', 'Foo'), false), + array(new UserSecurityIdentity('foo', 'Foo'), new RoleSecurityIdentity('foo'), false), + array(new UserSecurityIdentity('foo', 'Foo'), UserSecurityIdentity::fromToken($token), false), + array(new UserSecurityIdentity('foo', 'USI_AccountImpl'), UserSecurityIdentity::fromToken($token), true), + ); + } +} diff --git a/Acl/Tests/Permission/BasicPermissionMapTest.php b/Acl/Tests/Permission/BasicPermissionMapTest.php new file mode 100644 index 0000000..2afe588 --- /dev/null +++ b/Acl/Tests/Permission/BasicPermissionMapTest.php @@ -0,0 +1,23 @@ +<?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\Permission; + +use Symfony\Component\Security\Acl\Permission\BasicPermissionMap; + +class BasicPermissionMapTest extends \PHPUnit_Framework_TestCase +{ + public function testGetMasksReturnsNullWhenNotSupportedMask() + { + $map = new BasicPermissionMap(); + $this->assertNull($map->getMasks('IS_AUTHENTICATED_REMEMBERED', null)); + } +} diff --git a/Acl/Tests/Permission/MaskBuilderTest.php b/Acl/Tests/Permission/MaskBuilderTest.php new file mode 100644 index 0000000..8245669 --- /dev/null +++ b/Acl/Tests/Permission/MaskBuilderTest.php @@ -0,0 +1,103 @@ +<?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\Permission; + +use Symfony\Component\Security\Acl\Permission\MaskBuilder; + +class MaskBuilderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @expectedException \InvalidArgumentException + * @dataProvider getInvalidConstructorData + */ + public function testConstructorWithNonInteger($invalidMask) + { + new MaskBuilder($invalidMask); + } + + public function getInvalidConstructorData() + { + return array( + array(234.463), + array('asdgasdf'), + array(array()), + array(new \stdClass()), + ); + } + + public function testConstructorWithoutArguments() + { + $builder = new MaskBuilder(); + + $this->assertEquals(0, $builder->get()); + } + + public function testConstructor() + { + $builder = new MaskBuilder(123456); + + $this->assertEquals(123456, $builder->get()); + } + + public function testAddAndRemove() + { + $builder = new MaskBuilder(); + + $builder + ->add('view') + ->add('eDiT') + ->add('ownEr') + ; + $mask = $builder->get(); + + $this->assertEquals(MaskBuilder::MASK_VIEW, $mask & MaskBuilder::MASK_VIEW); + $this->assertEquals(MaskBuilder::MASK_EDIT, $mask & MaskBuilder::MASK_EDIT); + $this->assertEquals(MaskBuilder::MASK_OWNER, $mask & MaskBuilder::MASK_OWNER); + $this->assertEquals(0, $mask & MaskBuilder::MASK_MASTER); + $this->assertEquals(0, $mask & MaskBuilder::MASK_CREATE); + $this->assertEquals(0, $mask & MaskBuilder::MASK_DELETE); + $this->assertEquals(0, $mask & MaskBuilder::MASK_UNDELETE); + + $builder->remove('edit')->remove('OWner'); + $mask = $builder->get(); + $this->assertEquals(0, $mask & MaskBuilder::MASK_EDIT); + $this->assertEquals(0, $mask & MaskBuilder::MASK_OWNER); + $this->assertEquals(MaskBuilder::MASK_VIEW, $mask & MaskBuilder::MASK_VIEW); + } + + public function testGetPattern() + { + $builder = new MaskBuilder(); + $this->assertEquals(MaskBuilder::ALL_OFF, $builder->getPattern()); + + $builder->add('view'); + $this->assertEquals(str_repeat('.', 31).'V', $builder->getPattern()); + + $builder->add('owner'); + $this->assertEquals(str_repeat('.', 24).'N......V', $builder->getPattern()); + + $builder->add(1 << 10); + $this->assertEquals(str_repeat('.', 21).MaskBuilder::ON.'..N......V', $builder->getPattern()); + } + + public function testReset() + { + $builder = new MaskBuilder(); + $this->assertEquals(0, $builder->get()); + + $builder->add('view'); + $this->assertTrue($builder->get() > 0); + + $builder->reset(); + $this->assertEquals(0, $builder->get()); + } +} diff --git a/Acl/Tests/Voter/AclVoterTest.php b/Acl/Tests/Voter/AclVoterTest.php new file mode 100644 index 0000000..6bec231 --- /dev/null +++ b/Acl/Tests/Voter/AclVoterTest.php @@ -0,0 +1,405 @@ +<?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\Voter; + +use Symfony\Component\Security\Acl\Exception\NoAceFoundException; +use Symfony\Component\Security\Acl\Voter\FieldVote; +use Symfony\Component\Security\Acl\Exception\AclNotFoundException; +use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity; +use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity; +use Symfony\Component\Security\Acl\Domain\ObjectIdentity; +use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; +use Symfony\Component\Security\Acl\Voter\AclVoter; + +class AclVoterTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getSupportsAttributeTests + */ + public function testSupportsAttribute($attribute, $supported) + { + list($voter,, $permissionMap,,) = $this->getVoter(); + + $permissionMap + ->expects($this->once()) + ->method('contains') + ->with($this->identicalTo($attribute)) + ->will($this->returnValue($supported)) + ; + + $this->assertSame($supported, $voter->supportsAttribute($attribute)); + } + + public function getSupportsAttributeTests() + { + return array( + array('foo', true), + array('foo', false), + ); + } + + /** + * @dataProvider getSupportsClassTests + */ + public function testSupportsClass($class) + { + list($voter,,,,) = $this->getVoter(); + + $this->assertTrue($voter->supportsClass($class)); + } + + public function getSupportsClassTests() + { + return array( + array('foo'), + array('bar'), + array('moo'), + ); + } + + public function testVote() + { + list($voter,, $permissionMap,,) = $this->getVoter(); + $permissionMap + ->expects($this->atLeastOnce()) + ->method('getMasks') + ->will($this->returnValue(null)) + ; + + $this->assertSame(VoterInterface::ACCESS_ABSTAIN, $voter->vote($this->getToken(), null, array('VIEW', 'EDIT', 'DELETE'))); + } + + /** + * @dataProvider getTrueFalseTests + */ + public function testVoteWhenNoObjectIsPassed($allowIfObjectIdentityUnavailable) + { + list($voter,, $permissionMap,,) = $this->getVoter($allowIfObjectIdentityUnavailable); + $permissionMap + ->expects($this->once()) + ->method('getMasks') + ->will($this->returnValue(array())) + ; + + if ($allowIfObjectIdentityUnavailable) { + $vote = VoterInterface::ACCESS_GRANTED; + } else { + $vote = VoterInterface::ACCESS_ABSTAIN; + } + + $this->assertSame($vote, $voter->vote($this->getToken(), null, array('VIEW'))); + } + + /** + * @dataProvider getTrueFalseTests + */ + public function testVoteWhenOidStrategyReturnsNull($allowIfUnavailable) + { + list($voter,, $permissionMap, $oidStrategy,) = $this->getVoter($allowIfUnavailable); + $permissionMap + ->expects($this->once()) + ->method('getMasks') + ->will($this->returnValue(array())) + ; + + $oidStrategy + ->expects($this->once()) + ->method('getObjectIdentity') + ->will($this->returnValue(null)) + ; + + if ($allowIfUnavailable) { + $vote = VoterInterface::ACCESS_GRANTED; + } else { + $vote = VoterInterface::ACCESS_ABSTAIN; + } + + $this->assertSame($vote, $voter->vote($this->getToken(), new \stdClass(), array('VIEW'))); + } + + public function getTrueFalseTests() + { + return array(array(true), array(false)); + } + + public function testVoteNoAclFound() + { + list($voter, $provider, $permissionMap, $oidStrategy, $sidStrategy) = $this->getVoter(); + + $permissionMap + ->expects($this->once()) + ->method('getMasks') + ->will($this->returnValue(array())) + ; + + $oidStrategy + ->expects($this->once()) + ->method('getObjectIdentity') + ->will($this->returnValue($oid = new ObjectIdentity('1', 'Foo'))) + ; + + $sidStrategy + ->expects($this->once()) + ->method('getSecurityIdentities') + ->will($this->returnValue($sids = array(new UserSecurityIdentity('johannes', 'Foo'), new RoleSecurityIdentity('ROLE_FOO')))) + ; + + $provider + ->expects($this->once()) + ->method('findAcl') + ->with($this->equalTo($oid), $this->equalTo($sids)) + ->will($this->throwException(new AclNotFoundException('Not found.'))) + ; + + $this->assertSame(VoterInterface::ACCESS_DENIED, $voter->vote($this->getToken(), new \stdClass(), array('VIEW'))); + } + + /** + * @dataProvider getTrueFalseTests + */ + public function testVoteGrantsAccess($grant) + { + list($voter, $provider, $permissionMap, $oidStrategy, $sidStrategy) = $this->getVoter(); + + $permissionMap + ->expects($this->once()) + ->method('getMasks') + ->with($this->equalTo('VIEW')) + ->will($this->returnValue($masks = array(1, 2, 3))) + ; + + $oidStrategy + ->expects($this->once()) + ->method('getObjectIdentity') + ->will($this->returnValue($oid = new ObjectIdentity('1', 'Foo'))) + ; + + $sidStrategy + ->expects($this->once()) + ->method('getSecurityIdentities') + ->will($this->returnValue($sids = array(new UserSecurityIdentity('johannes', 'Foo'), new RoleSecurityIdentity('ROLE_FOO')))) + ; + + $provider + ->expects($this->once()) + ->method('findAcl') + ->with($this->equalTo($oid), $this->equalTo($sids)) + ->will($this->returnValue($acl = $this->getMock('Symfony\Component\Security\Acl\Model\AclInterface'))) + ; + + $acl + ->expects($this->once()) + ->method('isGranted') + ->with($this->identicalTo($masks), $this->equalTo($sids), $this->isFalse()) + ->will($this->returnValue($grant)) + ; + + if ($grant) { + $vote = VoterInterface::ACCESS_GRANTED; + } else { + $vote = VoterInterface::ACCESS_DENIED; + } + + $this->assertSame($vote, $voter->vote($this->getToken(), new \stdClass(), array('VIEW'))); + } + + public function testVoteNoAceFound() + { + list($voter, $provider, $permissionMap, $oidStrategy, $sidStrategy) = $this->getVoter(); + + $permissionMap + ->expects($this->once()) + ->method('getMasks') + ->with($this->equalTo('VIEW')) + ->will($this->returnValue($masks = array(1, 2, 3))) + ; + + $oidStrategy + ->expects($this->once()) + ->method('getObjectIdentity') + ->will($this->returnValue($oid = new ObjectIdentity('1', 'Foo'))) + ; + + $sidStrategy + ->expects($this->once()) + ->method('getSecurityIdentities') + ->will($this->returnValue($sids = array(new UserSecurityIdentity('johannes', 'Foo'), new RoleSecurityIdentity('ROLE_FOO')))) + ; + + $provider + ->expects($this->once()) + ->method('findAcl') + ->with($this->equalTo($oid), $this->equalTo($sids)) + ->will($this->returnValue($acl = $this->getMock('Symfony\Component\Security\Acl\Model\AclInterface'))) + ; + + $acl + ->expects($this->once()) + ->method('isGranted') + ->with($this->identicalTo($masks), $this->equalTo($sids), $this->isFalse()) + ->will($this->throwException(new NoAceFoundException('No ACE'))) + ; + + $this->assertSame(VoterInterface::ACCESS_DENIED, $voter->vote($this->getToken(), new \stdClass(), array('VIEW'))); + } + + /** + * @dataProvider getTrueFalseTests + */ + public function testVoteGrantsFieldAccess($grant) + { + list($voter, $provider, $permissionMap, $oidStrategy, $sidStrategy) = $this->getVoter(); + + $permissionMap + ->expects($this->once()) + ->method('getMasks') + ->with($this->equalTo('VIEW')) + ->will($this->returnValue($masks = array(1, 2, 3))) + ; + + $oidStrategy + ->expects($this->once()) + ->method('getObjectIdentity') + ->will($this->returnValue($oid = new ObjectIdentity('1', 'Foo'))) + ; + + $sidStrategy + ->expects($this->once()) + ->method('getSecurityIdentities') + ->will($this->returnValue($sids = array(new UserSecurityIdentity('johannes', 'Foo'), new RoleSecurityIdentity('ROLE_FOO')))) + ; + + $provider + ->expects($this->once()) + ->method('findAcl') + ->with($this->equalTo($oid), $this->equalTo($sids)) + ->will($this->returnValue($acl = $this->getMock('Symfony\Component\Security\Acl\Model\AclInterface'))) + ; + + $acl + ->expects($this->once()) + ->method('isFieldGranted') + ->with($this->identicalTo('foo'), $this->identicalTo($masks), $this->equalTo($sids), $this->isFalse()) + ->will($this->returnValue($grant)) + ; + + if ($grant) { + $vote = VoterInterface::ACCESS_GRANTED; + } else { + $vote = VoterInterface::ACCESS_DENIED; + } + + $this->assertSame($vote, $voter->vote($this->getToken(), new FieldVote(new \stdClass(), 'foo'), array('VIEW'))); + } + + public function testVoteNoFieldAceFound() + { + list($voter, $provider, $permissionMap, $oidStrategy, $sidStrategy) = $this->getVoter(); + + $permissionMap + ->expects($this->once()) + ->method('getMasks') + ->with($this->equalTo('VIEW')) + ->will($this->returnValue($masks = array(1, 2, 3))) + ; + + $oidStrategy + ->expects($this->once()) + ->method('getObjectIdentity') + ->will($this->returnValue($oid = new ObjectIdentity('1', 'Foo'))) + ; + + $sidStrategy + ->expects($this->once()) + ->method('getSecurityIdentities') + ->will($this->returnValue($sids = array(new UserSecurityIdentity('johannes', 'Foo'), new RoleSecurityIdentity('ROLE_FOO')))) + ; + + $provider + ->expects($this->once()) + ->method('findAcl') + ->with($this->equalTo($oid), $this->equalTo($sids)) + ->will($this->returnValue($acl = $this->getMock('Symfony\Component\Security\Acl\Model\AclInterface'))) + ; + + $acl + ->expects($this->once()) + ->method('isFieldGranted') + ->with($this->identicalTo('foo'), $this->identicalTo($masks), $this->equalTo($sids), $this->isFalse()) + ->will($this->throwException(new NoAceFoundException('No ACE'))) + ; + + $this->assertSame(VoterInterface::ACCESS_DENIED, $voter->vote($this->getToken(), new FieldVote(new \stdClass(), 'foo'), array('VIEW'))); + } + + public function testWhenReceivingAnObjectIdentityInterfaceWeDontRetrieveANewObjectIdentity() + { + list($voter, $provider, $permissionMap, $oidStrategy, $sidStrategy) = $this->getVoter(); + + $oid = new ObjectIdentity('someID','someType'); + + $permissionMap + ->expects($this->once()) + ->method('getMasks') + ->with($this->equalTo('VIEW')) + ->will($this->returnValue($masks = array(1, 2, 3))) + ; + + $oidStrategy + ->expects($this->never()) + ->method('getObjectIdentity') + ; + + $sidStrategy + ->expects($this->once()) + ->method('getSecurityIdentities') + ->will($this->returnValue($sids = array(new UserSecurityIdentity('johannes', 'Foo'), new RoleSecurityIdentity('ROLE_FOO')))) + ; + + $provider + ->expects($this->once()) + ->method('findAcl') + ->with($this->equalTo($oid), $this->equalTo($sids)) + ->will($this->returnValue($acl = $this->getMock('Symfony\Component\Security\Acl\Model\AclInterface'))) + ; + + $acl + ->expects($this->once()) + ->method('isGranted') + ->with($this->identicalTo($masks), $this->equalTo($sids), $this->isFalse()) + ->will($this->throwException(new NoAceFoundException('No ACE'))) + ; + + $voter->vote($this->getToken(), $oid, array('VIEW')); + } + + protected function getToken() + { + return $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); + } + + protected function getVoter($allowIfObjectIdentityUnavailable = true) + { + $provider = $this->getMock('Symfony\Component\Security\Acl\Model\AclProviderInterface'); + $permissionMap = $this->getMock('Symfony\Component\Security\Acl\Permission\PermissionMapInterface'); + $oidStrategy = $this->getMock('Symfony\Component\Security\Acl\Model\ObjectIdentityRetrievalStrategyInterface'); + $sidStrategy = $this->getMock('Symfony\Component\Security\Acl\Model\SecurityIdentityRetrievalStrategyInterface'); + + return array( + new AclVoter($provider, $oidStrategy, $sidStrategy, $permissionMap, null, $allowIfObjectIdentityUnavailable), + $provider, + $permissionMap, + $oidStrategy, + $sidStrategy, + ); + } +} diff --git a/Acl/composer.json b/Acl/composer.json new file mode 100644 index 0000000..0e68d9e --- /dev/null +++ b/Acl/composer.json @@ -0,0 +1,42 @@ +{ + "name": "symfony/security-acl", + "type": "library", + "description": "Symfony Security Component - ACL (Access Control List)", + "keywords": [], + "homepage": "http://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3", + "symfony/security-core": "~2.4" + }, + "require-dev": { + "doctrine/common": "~2.2", + "doctrine/dbal": "~2.2", + "psr/log": "~1.0" + }, + "suggest": { + "symfony/class-loader": "For using the ACL generateSql script", + "symfony/finder": "For using the ACL generateSql script", + "doctrine/dbal": "For using the built-in ACL implementation" + }, + "autoload": { + "psr-0": { "Symfony\\Component\\Security\\Acl\\": "" } + }, + "target-dir": "Symfony/Component/Security/Acl", + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + } +} diff --git a/Acl/phpunit.xml.dist b/Acl/phpunit.xml.dist new file mode 100644 index 0000000..6520948 --- /dev/null +++ b/Acl/phpunit.xml.dist @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<phpunit backupGlobals="false" + backupStaticAttributes="false" + colors="true" + convertErrorsToExceptions="true" + convertNoticesToExceptions="true" + convertWarningsToExceptions="true" + processIsolation="false" + stopOnFailure="false" + syntaxCheck="false" + bootstrap="vendor/autoload.php" +> + <testsuites> + <testsuite name="Symfony Security Component ACL Test Suite"> + <directory>./Tests/</directory> + </testsuite> + </testsuites> + + <filter> + <whitelist> + <directory>./</directory> + <exclude> + <directory>./vendor</directory> + <directory>./Tests</directory> + </exclude> + </whitelist> + </filter> +</phpunit> |