From a702124efb03a85685f9808bf3c8e4d4b65ef9b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Fri, 10 Jan 2014 01:58:15 +0100 Subject: [PATCH] [SecurityBundle] added acl:set command --- composer.json | 1 + .../SecurityBundle/Command/SetAclCommand.php | 171 ++++++++++++++++++ .../Functional/Bundle/AclBundle/AclBundle.php | 21 +++ .../Bundle/AclBundle/Entity/Car.php | 22 +++ .../Tests/Functional/SetAclCommandTest.php | 168 +++++++++++++++++ .../Tests/Functional/app/Acl/bundles.php | 8 + .../Tests/Functional/app/Acl/config.yml | 24 +++ .../Bundle/SecurityBundle/composer.json | 3 +- 8 files changed, 417 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Bundle/SecurityBundle/Command/SetAclCommand.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AclBundle/AclBundle.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AclBundle/Entity/Car.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/SetAclCommandTest.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Acl/bundles.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Acl/config.yml diff --git a/composer.json b/composer.json index 3b67c4b3e8ec5..1860fa7b2220a 100644 --- a/composer.json +++ b/composer.json @@ -70,6 +70,7 @@ "doctrine/data-fixtures": "1.0.*", "doctrine/dbal": "~2.2", "doctrine/orm": "~2.2,>=2.2.3", + "doctrine/doctrine-bundle": "~1.2", "monolog/monolog": "~1.3", "propel/propel1": "1.6.*", "ircmaxell/password-compat": "1.0.*", diff --git a/src/Symfony/Bundle/SecurityBundle/Command/SetAclCommand.php b/src/Symfony/Bundle/SecurityBundle/Command/SetAclCommand.php new file mode 100644 index 0000000000000..de01dc6b33d4e --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Command/SetAclCommand.php @@ -0,0 +1,171 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Command; + +use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Security\Acl\Domain\ObjectIdentity; +use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity; +use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity; +use Symfony\Component\Security\Acl\Exception\AclAlreadyExistsException; +use Symfony\Component\Security\Acl\Permission\MaskBuilder; +use Symfony\Component\Security\Acl\Model\MutableAclProviderInterface; + +/** + * Sets ACL for objects + * + * @author Kévin Dunglas + */ +class SetAclCommand extends ContainerAwareCommand +{ + /** + * {@inheritdoc} + */ + public function isEnabled() + { + if (!$this->getContainer()->has('security.acl.provider')) { + return false; + } + + $provider = $this->getContainer()->get('security.acl.provider'); + if (!$provider instanceof MutableAclProviderInterface) { + return false; + } + + return parent::isEnabled(); + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('acl:set') + ->setDescription('Sets ACL for objects') + ->setHelp(<<%command.name% command sets ACL. +The ACL system must have been initialized with the init:acl command. + +To set VIEW and EDIT permissions for the user kevin on the instance of Acme\MyClass having the identifier 42: + +php %command.full_name% --user=Symfony/Component/Security/Core/User/User:kevin VIEW EDIT Acme/MyClass:42 + +Note that you can use / instead of \\ for the namespace delimiter to avoid any +problem. + +To set permissions for a role, use the --role option: + +php %command.full_name% --role=ROLE_USER VIEW Acme/MyClass:1936 + +To set permissions at the class scope, use the --class-scope option: + +php %command.full_name% --class-scope --user=Symfony/Component/Security/Core/User/User:anne OWNER Acme/MyClass:42 +EOF + ) + ->addArgument('arguments', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'A list of permissions and object identities (class name and ID separated by a column)') + ->addOption('user', null, InputOption::VALUE_REQUIRED, 'A list of security identities') + ->addOption('role', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'A list of roles') + ->addOption('class-scope', null, InputOption::VALUE_NONE, 'Use class-scope entries') + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + // Parse arguments + $objectIdentities = array(); + $maskBuilder = $this->getMaskBuilder(); + foreach ($input->getArgument('arguments') as $argument) { + $data = explode(':', $argument, 2); + + if (count($data) > 1) { + $objectIdentities[] = new ObjectIdentity($data[1], strtr($data[0], '/', '\\')); + } else { + $maskBuilder->add($data[0]); + } + } + + // Build permissions mask + $mask = $maskBuilder->get(); + + $userOption = $input->getOption('user'); + $roleOption = $input->getOption('role'); + $classScopeOption = $input->getOption('class-scope'); + + if (empty($userOption) && empty($roleOption)) { + throw new \InvalidArgumentException('A Role or a User must be specified.'); + } + + // Create security identities + $securityIdentities = array(); + + if ($userOption) { + foreach ($userOption as $user) { + $data = explode(':', $user, 2); + + if (count($data) === 1) { + throw new \InvalidArgumentException('The user must follow the format "Acme/MyUser:username".'); + } + + $securityIdentities[] = new UserSecurityIdentity($data[1], strtr($data[0], '/', '\\')); + } + } + + if ($roleOption) { + foreach ($roleOption as $role) { + $securityIdentities[] = new RoleSecurityIdentity($role); + } + } + + /** @var $container \Symfony\Component\DependencyInjection\ContainerInterface */ + $container = $this->getContainer(); + /** @var $aclProvider MutableAclProviderInterface */ + $aclProvider = $container->get('security.acl.provider'); + + // Sets ACL + foreach ($objectIdentities as $objectIdentity) { + // Creates a new ACL if it does not already exist + try { + $aclProvider->createAcl($objectIdentity); + } catch (AclAlreadyExistsException $e) { + } + + $acl = $aclProvider->findAcl($objectIdentity, $securityIdentities); + + foreach ($securityIdentities as $securityIdentity) { + if ($classScopeOption) { + $acl->insertClassAce($securityIdentity, $mask); + } else { + $acl->insertObjectAce($securityIdentity, $mask); + } + } + + $aclProvider->updateAcl($acl); + } + } + + /** + * Gets the mask builder + * + * @return MaskBuilder + */ + protected function getMaskBuilder() + { + return new MaskBuilder(); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AclBundle/AclBundle.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AclBundle/AclBundle.php new file mode 100644 index 0000000000000..1208003bcc2c4 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AclBundle/AclBundle.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AclBundle; + +use Symfony\Component\HttpKernel\Bundle\Bundle; + +/** + * @author Kévin Dunglas + */ +class AclBundle extends Bundle +{ +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AclBundle/Entity/Car.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AclBundle/Entity/Car.php new file mode 100644 index 0000000000000..dd46db2f6459c --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AclBundle/Entity/Car.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AclBundle\Entity; + +/** + * Car + * + * @author Kévin Dunglas + */ +class Car +{ + public $id; +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SetAclCommandTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SetAclCommandTest.php new file mode 100644 index 0000000000000..c476714e88f27 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SetAclCommandTest.php @@ -0,0 +1,168 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +use Symfony\Bundle\FrameworkBundle\Console\Application; +use Symfony\Bundle\SecurityBundle\Command\InitAclCommand; +use Symfony\Bundle\SecurityBundle\Command\SetAclCommand; +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\Security\Acl\Domain\ObjectIdentity; +use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity; +use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity; +use Symfony\Component\Security\Acl\Exception\NoAceFoundException; +use Symfony\Component\Security\Acl\Permission\BasicPermissionMap; + +/** + * Tests SetAclCommand + * + * @author Kévin Dunglas + */ +class SetAclCommandTest extends WebTestCase +{ + const OBJECT_CLASS = 'Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AclBundle\Entity\Car'; + const SECURITY_CLASS = 'Symfony\Component\Security\Core\User\User'; + + public function testSetAclUser() + { + $objectId = 1; + $securityUsername1 = 'kevin'; + $securityUsername2 = 'anne'; + $grantedPermission1 = 'VIEW'; + $grantedPermission2 = 'EDIT'; + + $application = $this->getApplication(); + $application->add(new SetAclCommand()); + + $setAclCommand = $application->find('acl:set'); + $setAclCommandTester = new CommandTester($setAclCommand); + $setAclCommandTester->execute(array( + 'command' => 'acl:set', + 'arguments' => array($grantedPermission1, $grantedPermission2, sprintf('%s:%s', self::OBJECT_CLASS, $objectId)), + '--user' => array(sprintf('%s:%s', self::SECURITY_CLASS, $securityUsername1), sprintf('%s:%s', self::SECURITY_CLASS, $securityUsername2)) + )); + + $objectIdentity = new ObjectIdentity($objectId, self::OBJECT_CLASS); + $securityIdentity1 = new UserSecurityIdentity($securityUsername1, self::SECURITY_CLASS); + $securityIdentity2 = new UserSecurityIdentity($securityUsername2, self::SECURITY_CLASS); + $permissionMap = new BasicPermissionMap(); + + /** @var \Symfony\Component\Security\Acl\Model\AclProviderInterface $aclProvider */ + $aclProvider = $application->getKernel()->getContainer()->get('security.acl.provider'); + $acl = $aclProvider->findAcl($objectIdentity, array($securityIdentity1)); + + $this->assertTrue($acl->isGranted($permissionMap->getMasks($grantedPermission1, null), array($securityIdentity1))); + $this->assertTrue($acl->isGranted($permissionMap->getMasks($grantedPermission1, null), array($securityIdentity2))); + $this->assertTrue($acl->isGranted($permissionMap->getMasks($grantedPermission2, null), array($securityIdentity2))); + + try { + $acl->isGranted($permissionMap->getMasks('OWNER', null), array($securityIdentity1)); + $this->fail('NoAceFoundException not throwed'); + } catch (NoAceFoundException $e) { + } + + try { + $acl->isGranted($permissionMap->getMasks('OPERATOR', null), array($securityIdentity2)); + $this->fail('NoAceFoundException not throwed'); + } catch (NoAceFoundException $e) { + } + } + + public function testSetAclRole() + { + $objectId = 1; + $securityUsername = 'kevin'; + $grantedPermission = 'VIEW'; + $role = 'ROLE_ADMIN'; + + $application = $this->getApplication(); + $application->add(new SetAclCommand()); + + $setAclCommand = $application->find('acl:set'); + $setAclCommandTester = new CommandTester($setAclCommand); + $setAclCommandTester->execute(array( + 'command' => 'acl:set', + 'arguments' => array($grantedPermission, sprintf('%s:%s', strtr(self::OBJECT_CLASS, '\\', '/'), $objectId)), + '--role' => array($role) + )); + + $objectIdentity = new ObjectIdentity($objectId, self::OBJECT_CLASS); + $userSecurityIdentity = new UserSecurityIdentity($securityUsername, self::SECURITY_CLASS); + $roleSecurityIdentity = new RoleSecurityIdentity($role); + $permissionMap = new BasicPermissionMap(); + + /** @var \Symfony\Component\Security\Acl\Model\AclProviderInterface $aclProvider */ + $aclProvider = $application->getKernel()->getContainer()->get('security.acl.provider'); + $acl = $aclProvider->findAcl($objectIdentity, array($roleSecurityIdentity, $userSecurityIdentity)); + + $this->assertTrue($acl->isGranted($permissionMap->getMasks($grantedPermission, null), array($roleSecurityIdentity))); + $this->assertTrue($acl->isGranted($permissionMap->getMasks($grantedPermission, null), array($roleSecurityIdentity))); + + try { + $acl->isGranted($permissionMap->getMasks('VIEW', null), array($userSecurityIdentity)); + $this->fail('NoAceFoundException not throwed'); + } catch (NoAceFoundException $e) { + } + + try { + $acl->isGranted($permissionMap->getMasks('OPERATOR', null), array($userSecurityIdentity)); + $this->fail('NoAceFoundException not throwed'); + } catch (NoAceFoundException $e) { + } + } + + public function testSetAclClassScope() + { + $objectId = 1; + $grantedPermission = 'VIEW'; + $role = 'ROLE_USER'; + + $application = $this->getApplication(); + $application->add(new SetAclCommand()); + + $setAclCommand = $application->find('acl:set'); + $setAclCommandTester = new CommandTester($setAclCommand); + $setAclCommandTester->execute(array( + 'command' => 'acl:set', + 'arguments' => array($grantedPermission, sprintf('%s:%s', self::OBJECT_CLASS, $objectId)), + '--class-scope' => true, + '--role' => array($role) + )); + + $objectIdentity1 = new ObjectIdentity($objectId, self::OBJECT_CLASS); + $objectIdentity2 = new ObjectIdentity(2, self::OBJECT_CLASS); + $roleSecurityIdentity = new RoleSecurityIdentity($role); + $permissionMap = new BasicPermissionMap(); + + /** @var \Symfony\Component\Security\Acl\Model\AclProviderInterface $aclProvider */ + $aclProvider = $application->getKernel()->getContainer()->get('security.acl.provider'); + + $acl1 = $aclProvider->findAcl($objectIdentity1, array($roleSecurityIdentity)); + $this->assertTrue($acl1->isGranted($permissionMap->getMasks($grantedPermission, null), array($roleSecurityIdentity))); + + $acl2 = $aclProvider->createAcl($objectIdentity2); + $this->assertTrue($acl2->isGranted($permissionMap->getMasks($grantedPermission, null), array($roleSecurityIdentity))); + } + + private function getApplication() + { + $kernel = $this->createKernel(array('test_case' => 'Acl')); + $kernel->boot(); + + $application = new Application($kernel); + $application->add(new InitAclCommand()); + + $initAclCommand = $application->find('init:acl'); + $initAclCommandTester = new CommandTester($initAclCommand); + $initAclCommandTester->execute(array('command' => 'init:acl')); + + return $application; + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Acl/bundles.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Acl/bundles.php new file mode 100644 index 0000000000000..0dad9099b493d --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Acl/bundles.php @@ -0,0 +1,8 @@ + pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy