From eb1157054128da539ef5afb316a41b1d75e1795e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 3 Jul 2015 21:34:48 +0200 Subject: [PATCH] add acl to activity + bootstrap php --- DataFixtures/ORM/LoadReportACL.php | 84 +++++++++ .../ChillActivityExtension.php | 18 +- Entity/Activity.php | 16 +- Resources/config/services.yml | 9 +- Security/Authorization/ActivityVoter.php | 66 +++++++ Test/Fixtures/bootstrap.php | 8 + Test/PrepareActivityTrait.php | 49 +++++ .../Authorization/ActivityVoterTest.php | 176 ++++++++++++++++++ phpunit.xml.dist | 23 +++ 9 files changed, 446 insertions(+), 3 deletions(-) create mode 100644 DataFixtures/ORM/LoadReportACL.php create mode 100644 Security/Authorization/ActivityVoter.php create mode 100644 Test/Fixtures/bootstrap.php create mode 100644 Test/PrepareActivityTrait.php create mode 100644 Tests/Security/Authorization/ActivityVoterTest.php create mode 100644 phpunit.xml.dist diff --git a/DataFixtures/ORM/LoadReportACL.php b/DataFixtures/ORM/LoadReportACL.php new file mode 100644 index 000000000..b20f29b97 --- /dev/null +++ b/DataFixtures/ORM/LoadReportACL.php @@ -0,0 +1,84 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\ActivityBundle\DataFixtures\ORM; + +use Doctrine\Common\DataFixtures\AbstractFixture; +use Doctrine\Common\DataFixtures\OrderedFixtureInterface; +use Doctrine\Common\Persistence\ObjectManager; +use Chill\MainBundle\DataFixtures\ORM\LoadPermissionsGroup; +use Chill\MainBundle\Entity\RoleScope; +use Chill\MainBundle\DataFixtures\ORM\LoadScopes; + +/** + * Add a role CHILL_ACTIVITY_UPDATE & CHILL_ACTIVITY_CREATE for all groups except administrative, + * and a role CHILL_ACTIVITY_SEE for administrative + * + * @author Julien Fastré + */ +class LoadActivitytACL extends AbstractFixture implements OrderedFixtureInterface +{ + public function getOrder() + { + return 17999; + } + + + public function load(ObjectManager $manager) + { + foreach (LoadPermissionsGroup::$refs as $permissionsGroupRef) { + $permissionsGroup = $this->getReference($permissionsGroupRef); + foreach (LoadScopes::$references as $scopeRef){ + $scope = $this->getReference($scopeRef); + //create permission group + switch ($permissionsGroup->getName()) { + case 'social': + if ($scope->getName()['en'] === 'administrative') { + break 2; // we do not want any power on administrative + } + break; + case 'administrative': + case 'direction': + if (in_array($scope->getName()['en'], array('administrative', 'social'))) { + break 2; // we do not want any power on social or administrative + } + break; + } + + printf("Adding CHILL_ACTIVITY_UPDATE & CHILL_ACTIVITY_CREATE to %s " + . "permission group, scope '%s' \n", + $permissionsGroup->getName(), $scope->getName()['en']); + $roleScopeUpdate = (new RoleScope()) + ->setRole('CHILL_ACTIVITY_UPDATE') + ->setScope($scope); + $permissionsGroup->addRoleScope($roleScopeUpdate); + $roleScopeCreate = (new RoleScope()) + ->setRole('CHILL_ACTIVITY_CREATE') + ->setScope($scope); + $permissionsGroup->addRoleScope($roleScopeCreate); + $manager->persist($roleScopeUpdate); + $manager->persist($roleScopeCreate); + } + + } + + $manager->flush(); + } + +} diff --git a/DependencyInjection/ChillActivityExtension.php b/DependencyInjection/ChillActivityExtension.php index de0aa0de2..262925889 100644 --- a/DependencyInjection/ChillActivityExtension.php +++ b/DependencyInjection/ChillActivityExtension.php @@ -46,11 +46,17 @@ class ChillActivityExtension extends Extension implements PrependExtensionInterf $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services.yml'); } + + public function prepend(ContainerBuilder $container) + { + $this->prependRoutes($container); + $this->prependAuthorization($container); + } /* (non-PHPdoc) * @see \Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface::prepend() */ - public function prepend(ContainerBuilder $container) + public function prependRoutes(ContainerBuilder $container) { //add routes for custom bundle $container->prependExtensionConfig('chill_main', array( @@ -61,4 +67,14 @@ class ChillActivityExtension extends Extension implements PrependExtensionInterf ) )); } + + public function prependAuthorization(ContainerBuilder $container) + { + $container->prependExtensionConfig('security', array( + 'role_hierarchy' => array( + 'CHILL_ACTIVITY_UPDATE' => array('CHILL_ACTIVITY_SEE'), + 'CHILL_ACTIVITY_CREATE' => array('CHILL_ACTIVITY_SEE') + ) + )); + } } diff --git a/Entity/Activity.php b/Entity/Activity.php index 084f1a067..e7ca954a7 100644 --- a/Entity/Activity.php +++ b/Entity/Activity.php @@ -25,11 +25,13 @@ use Chill\MainBundle\Entity\User; use Chill\ActivityBundle\Entity\ActivityReason; use Chill\ActivityBundle\Entity\ActivityType; use Chill\PersonBundle\Entity\Person; +use Chill\MainBundle\Entity\HasCenterInterface; +use Chill\MainBundle\Entity\HasScopeInterface; /** * Activity */ -class Activity +class Activity implements HasCenterInterface, HasScopeInterface { /** * @var integer @@ -307,5 +309,17 @@ class Activity { return $this->person; } + + /** + * get the center + * + * center is extracted from person + * + * @return \Chill\MainBundle\Entity\Center + */ + public function getCenter() + { + return $this->person->getCenter(); + } } diff --git a/Resources/config/services.yml b/Resources/config/services.yml index 81d68773c..2a24edf02 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -28,4 +28,11 @@ services: - "@doctrine.orm.entity_manager" - "@chill.main.helper.translatable_string" tags: - - { name: form.type, alias: chill_activitybundle_activity } \ No newline at end of file + - { name: form.type, alias: chill_activitybundle_activity } + + chill.activity.security.authorization.activity_voter: + class: Chill\ActivityBundle\Security\Authorization\ActivityVoter + arguments: + - "@chill.main.security.authorization.helper" + tags: + - { name: security.voter } \ No newline at end of file diff --git a/Security/Authorization/ActivityVoter.php b/Security/Authorization/ActivityVoter.php new file mode 100644 index 000000000..b6f82c1ed --- /dev/null +++ b/Security/Authorization/ActivityVoter.php @@ -0,0 +1,66 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\ActivityBundle\Security\Authorization; + +use Chill\MainBundle\Security\Authorization\AbstractChillVoter; +use Chill\MainBundle\Security\Authorization\AuthorizationHelper; + +/** + * + * + * @author Julien Fastré + */ +class ActivityVoter extends AbstractChillVoter +{ + const CREATE = 'CHILL_ACTIVITY_CREATE'; + const SEE = 'CHILL_ACTIVITY_SEE'; + const UPDATE = 'CHILL_ACTIVITY_UPDATE'; + + /** + * + * @var AuthorizationHelper + */ + protected $helper; + + public function __construct(AuthorizationHelper $helper) + { + $this->helper = $helper; + } + + protected function getSupportedAttributes() + { + return array(self::CREATE, self::SEE, self::UPDATE); + } + + protected function getSupportedClasses() + { + return array('Chill\ActivityBundle\Entity\Activity'); + } + + protected function isGranted($attribute, $report, $user = null) + { + if (! $user instanceof \Chill\MainBundle\Entity\User){ + + return false; + } + + return $this->helper->userHasAccess($user, $report, $attribute); + } +} diff --git a/Test/Fixtures/bootstrap.php b/Test/Fixtures/bootstrap.php new file mode 100644 index 000000000..0279b4ee6 --- /dev/null +++ b/Test/Fixtures/bootstrap.php @@ -0,0 +1,8 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\ActivityBundle\Test; + +use Chill\ActivityBundle\Entity\Activity; +use Chill\MainBundle\Entity\Scope; +use Chill\PersonBundle\Entity\Person; + +/** + * Prepare entities useful in tests + * + * + * @author Julien Fastré + */ +trait PrepareActivityTrait +{ + /** + * Return an activity with a scope and a person inside + * + * @param Scope $scope + * @param Person $person + * @return Activity + */ + public function prepareActivity(Scope $scope, Person $person) + { + return (new Activity()) + ->setScope($scope) + ->setPerson($person) + ; + } + +} diff --git a/Tests/Security/Authorization/ActivityVoterTest.php b/Tests/Security/Authorization/ActivityVoterTest.php new file mode 100644 index 000000000..2a9cd6fae --- /dev/null +++ b/Tests/Security/Authorization/ActivityVoterTest.php @@ -0,0 +1,176 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\ActivityBundle\Tests\Security\Authorization; + +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; +use Chill\MainBundle\Entity\Center; +use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Entity\Scope; +use Chill\MainBundle\Test\PrepareUserTrait; +use Chill\MainBundle\Test\PrepareCenterTrait; +use Chill\MainBundle\Test\PrepareScopeTrait; +use Chill\PersonBundle\Test\PreparePersonTrait; +use Chill\ActivityBundle\Test\PrepareActivityTrait; + +/** + * + * + * @author Julien Fastré + */ +class ActivityVoterTest extends KernelTestCase +{ + use PrepareUserTrait, PrepareCenterTrait, PrepareScopeTrait, + PreparePersonTrait, PrepareActivityTrait; + + /** + * + * @var \Chill\PersonBundle\Security\Authorization\PersonVoter + */ + protected $voter; + + /** + * + * @var \Prophecy\Prophet + */ + protected $prophet; + + public function setUp() + { + static::bootKernel(); + $this->voter = static::$kernel->getContainer() + ->get('chill.activity.security.authorization.activity_voter'); + $this->prophet = new \Prophecy\Prophet(); + } + + public function testNullUser() + { + $token = $this->prepareToken(); + $center = $this->prepareCenter(1, 'center'); + $person = $this->preparePerson($center); + $scope = $this->prepareScope(1, 'default'); + $activity = $this->prepareActivity($scope, $person); + + $this->assertEquals( + VoterInterface::ACCESS_DENIED, + $this->voter->vote($token, $activity, array('CHILL_ACTIVITY_SEE')), + "assert that a null user is not allowed to see" + ); + } + + /** + * + * @dataProvider dataProvider_testVoteAction + * @param type $expectedResult + * @param User $user + * @param Scope $scope + * @param Center $center + * @param string $attribute + * @param string $message + */ + public function testVoteAction($expectedResult, User $user, Scope $scope, + Center $center, $attribute, $message) + { + $token = $this->prepareToken($user); + $activity = $this->prepareActivity($scope, $this->preparePerson($center)); + + $this->assertEquals( + $expectedResult, + $this->voter->vote($token, $activity, array($attribute)), + $message + ); + } + + public function dataProvider_testVoteAction() + { + $centerA = $this->prepareCenter(1, 'center A'); + $centerB = $this->prepareCenter(2, 'center B'); + $scopeA = $this->prepareScope(1, 'scope default'); + $scopeB = $this->prepareScope(2, 'scope B'); + $scopeC = $this->prepareScope(3, 'scope C'); + + $userA = $this->prepareUser(array( + array( + 'center' => $centerA, + 'permissionsGroup' => array( + ['scope' => $scopeB, 'role' => 'CHILL_ACTIVITY_CREATE'], + ['scope' => $scopeA, 'role' => 'CHILL_ACTIVITY_SEE'] + ) + ), + array( + 'center' => $centerB, + 'permissionsGroup' => array( + ['scope' => $scopeA, 'role' => 'CHILL_ACTIVITY_CREATE'], + ['scope' => $scopeC, 'role' => 'CHILL_ACTIVITY_CREATE'] + ) + ) + + )); + + return array( + array( + VoterInterface::ACCESS_GRANTED, + $userA, + $scopeB, + $centerA, + 'CHILL_ACTIVITY_CREATE', + 'assert that a user granted with same rights' + ), + array( + VoterInterface::ACCESS_GRANTED, + $userA, + $scopeB, + $centerA, + 'CHILL_ACTIVITY_SEE', + 'assert that a user granted with inheritance rights' + ), + array( + VoterInterface::ACCESS_DENIED, + $userA, + $scopeC, + $centerA, + 'CHILL_ACTIVITY_SEE', + 'assert that a suer is denied if he is not granted right on this center' + + ) + ); + } + + /** + * prepare a token interface with correct rights + * + * if $permissions = null, user will be null (no user associated with token + * + * @return \Symfony\Component\Security\Core\Authentication\Token\TokenInterface + */ + protected function prepareToken(User $user = null) + { + $token = $this->prophet->prophesize(); + $token + ->willImplement('\Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); + if ($user === NULL) { + $token->getUser()->willReturn(null); + } else { + $token->getUser()->willReturn($user); + } + + return $token->reveal(); + } +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 000000000..6592949a7 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,23 @@ + + + + + + ./Tests + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + + + + +