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
+
+
+
+
+
+
+
+