diff --git a/Controller/PersonController.php b/Controller/PersonController.php
index 82b85caa3..7657b43cd 100644
--- a/Controller/PersonController.php
+++ b/Controller/PersonController.php
@@ -51,9 +51,13 @@ class PersonController extends Controller
$person = $this->_getPerson($person_id);
if ($person === null) {
- return $this->createNotFoundException("Person with id $person_id not found on this server");
+ return $this->createNotFoundException("Person with id $person_id not"
+ . " found on this server");
}
+ $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person,
+ "You are not allowed to see this person.");
+
return $this->render('ChillPersonBundle:Person:view.html.twig',
array("person" => $person,
"cFGroup" => $this->getCFGroup()));
@@ -67,6 +71,9 @@ class PersonController extends Controller
return $this->createNotFoundException();
}
+ $this->denyAccessUnlessGranted('CHILL_PERSON_UPDATE', $person,
+ 'You are not allowed to edit this person');
+
$form = $this->createForm(new PersonType(), $person,
array(
"action" => $this->generateUrl('chill_person_general_update',
@@ -87,6 +94,9 @@ class PersonController extends Controller
return $this->createNotFoundException();
}
+ $this->denyAccessUnlessGranted('CHILL_PERSON_UPDATE', $person,
+ 'You are not allowed to edit this person');
+
$form = $this->createForm(new PersonType(), $person,
array("cFGroup" => $this->getCFGroup()));
@@ -120,64 +130,6 @@ class PersonController extends Controller
}
}
- public function searchAction()
- {
- $q = $this->getRequest()->query->getAlnum('q', '');
- $q = trim($q);
-
- if ( $q === '' ) {
- $this->get('session')
- ->getFlashBag()
- ->add('info',
- $this->get('translator')
- ->trans('Your query is empty. Be more explicive')
- );
- }
-
- $em = $this->getDoctrine()->getManager();
-
- $offset = $this->getRequest()->query->getInt('offet', 0);
- $limit = $this->getRequest()->query->getInt('limit', 30);
-
- $dql = 'SELECT p FROM ChillPersonBundle:Person p'
- . ' WHERE'
- . ' LOWER(p.firstName) like LOWER(:q)'
- . ' OR LOWER(p.lastName) like LOWER(:q)';
-
- if ($this->container->getParameter('cl_chill_person.search.use_double_metaphone')) {
- $dql .= ' OR DOUBLEMETAPHONE(p.lastName) = DOUBLEMETAPHONE(:qabsolute)';
- }
-
-
- $query = $em->createQuery($dql)
- ->setParameter('q', '%'.$q.'%');
- if ($this->container->getParameter('cl_chill_person.search.use_double_metaphone')) {
- $query->setParameter('qabsolute', $q);
- }
-
- // ->setOffset($offset)
- // ->setLimit($limit)
- $persons = $query->getResult() ;
-
-
- if (count($persons) === 0 ){
- $this->get('session')
- ->getFlashBag()
- ->add('info',
- $this->get('translator')
- ->trans('Your query %q% gives no results', array(
- '%q%' => $q
- ))
- );
- }
-
- return $this->render('ChillPersonBundle:Person:list.html.twig',
- array(
- 'persons' => $persons,
- 'pattern' => $q
- ));
- }
-
/**
* Return a csv file with all the persons
*
@@ -199,10 +151,22 @@ class PersonController extends Controller
}
public function newAction()
- {
+ {
+ // this is a dummy default center.
+ $defaultCenter = $this->get('security.token_storage')
+ ->getToken()
+ ->getUser()
+ ->getGroupCenters()[0]
+ ->getCenter();
+
+ $person = (new Person())
+ ->setCenter($defaultCenter);
+
$form = $this->createForm(
new CreationPersonType(CreationPersonType::FORM_NOT_REVIEWED),
- null, array('action' => $this->generateUrl('chill_person_review')));
+ array('creation_date' => new \DateTime(), 'center' => $defaultCenter),
+ array('action' => $this->generateUrl('chill_person_review'))
+ );
return $this->_renderNewForm($form);
}
@@ -232,6 +196,7 @@ class PersonController extends Controller
->setLastName($form['lastName']->getData())
->setGenre($form['genre']->getData())
->setDateOfBirth($form['dateOfBirth']->getData())
+ ->setCenter($form['center']->getData())
;
return $person;
@@ -360,6 +325,9 @@ class PersonController extends Controller
$errors = $this->_validatePersonAndAccompanyingPeriod($person);
+ $this->denyAccessUnlessGranted('CHILL_PERSON_CREATE', $person,
+ 'You are not allowed to create this person');
+
if ($errors->count() === 0) {
$em = $this->getDoctrine()->getManager();
diff --git a/Controller/TimelinePersonController.php b/Controller/TimelinePersonController.php
index 0fb0f1015..95c91df8e 100644
--- a/Controller/TimelinePersonController.php
+++ b/Controller/TimelinePersonController.php
@@ -40,6 +40,8 @@ class TimelinePersonController extends Controller
if ($person === NULL) {
throw $this->createNotFoundException();
}
+
+ $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person);
return $this->render('ChillPersonBundle:Timeline:index.html.twig', array
(
diff --git a/DataFixtures/ORM/LoadPeople.php b/DataFixtures/ORM/LoadPeople.php
index 5a143af54..24a88a114 100644
--- a/DataFixtures/ORM/LoadPeople.php
+++ b/DataFixtures/ORM/LoadPeople.php
@@ -118,7 +118,8 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con
'FirstName' => $firstName,
'LastName' => $lastName,
'Genre' => $sex,
- 'Nationality' => (rand(0,100) > 50) ? NULL: 'BE'
+ 'Nationality' => (rand(0,100) > 50) ? NULL: 'BE',
+ 'center' => (rand(0,1) == 0) ? 'centerA': 'centerB'
);
$this->addAPerson($this->fillWithDefault($person), $manager);
@@ -151,18 +152,19 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con
foreach ($person as $key => $value) {
switch ($key) {
case 'CountryOfBirth':
- $p->setCountryOfBirth($this->getCountry($value));
+ $value = $this->getCountry($value);
break;
case 'Nationality':
- $p->setNationality($this->getCountry($value));
+ $value = $this->getCountry($value);
break;
case 'DateOfBirth':
$value = new \DateTime($value);
-
-
- default:
- call_user_func(array($p, 'set'.$key), $value);
+ break;
+ case 'center':
+ $value = $this->getReference($value);
+ break;
}
+ call_user_func(array($p, 'set'.$key), $value);
}
$manager->persist($p);
@@ -210,7 +212,8 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con
'PlaceOfBirth' => "Châteauroux",
'Genre' => Person::GENRE_MAN,
'CountryOfBirth' => 'FR',
- 'Nationality' => 'RU'
+ 'Nationality' => 'RU',
+ 'center' => 'centerA'
),
array(
//to have a person with same firstname as Gérard Depardieu
@@ -218,24 +221,28 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con
'LastName' => "Jean",
'DateOfBirth' => "1960-10-12",
'CountryOfBirth' => 'FR',
- 'Nationality' => 'FR'
+ 'Nationality' => 'FR',
+ 'center' => 'centerA'
),
array(
//to have a person with same birthdate of Gérard Depardieu
'FirstName' => 'Van Snick',
'LastName' => 'Bart',
- 'DateOfBirth' => '1948-12-27'
+ 'DateOfBirth' => '1948-12-27',
+ 'center' => 'centerA'
),
array(
//to have a woman with Depardieu as FirstName
'FirstName' => 'Depardieu',
'LastName' => 'Charline',
- 'Genre' => Person::GENRE_WOMAN
+ 'Genre' => Person::GENRE_WOMAN,
+ 'center' => 'centerA'
),
array(
//to have a special character in lastName
'FirstName' => 'Manço',
- 'LastName' => 'Étienne'
+ 'LastName' => 'Étienne',
+ 'center' => 'centerA'
)
);
}
diff --git a/DataFixtures/ORM/LoadPersonACL.php b/DataFixtures/ORM/LoadPersonACL.php
new file mode 100644
index 000000000..1f4163deb
--- /dev/null
+++ b/DataFixtures/ORM/LoadPersonACL.php
@@ -0,0 +1,79 @@
+
+ *
+ * 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\PersonBundle\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;
+
+/**
+ * Add a role CHILL_PERSON_UPDATE & CHILL_PERSON_CREATE for all groups except administrative,
+ * and a role CHILL_PERSON_SEE for administrative
+ *
+ * @author Julien Fastré
+ */
+class LoadPersonACL extends AbstractFixture implements OrderedFixtureInterface
+{
+ public function getOrder()
+ {
+ return 9600;
+ }
+
+
+ public function load(ObjectManager $manager)
+ {
+ foreach (LoadPermissionsGroup::$refs as $permissionsGroupRef) {
+ $permissionsGroup = $this->getReference($permissionsGroupRef);
+ $scope = $this->getReference('scope_all');
+
+ //create permission group
+ switch ($permissionsGroup->getName()) {
+ case 'social':
+ case 'direction':
+ printf("Adding CHILL_PERSON_UPDATE & CHILL_PERSON_CREATE to %s permission group \n", $permissionsGroup->getName());
+ $roleScopeUpdate = (new RoleScope())
+ ->setRole('CHILL_PERSON_UPDATE')
+ ->setScope($scope);
+ $permissionsGroup->addRoleScope($roleScopeUpdate);
+ $roleScopeCreate = (new RoleScope())
+ ->setRole('CHILL_PERSON_CREATE')
+ ->setScope($scope);
+ $permissionsGroup->addRoleScope($roleScopeCreate);
+ $manager->persist($roleScopeUpdate);
+ $manager->persist($roleScopeCreate);
+ break;
+ case 'administrative':
+ printf("Adding CHILL_PERSON_SEE to %s permission group \n", $permissionsGroup->getName());
+ $roleScopeSee = (new RoleScope())
+ ->setRole('CHILL_PERSON_SEE')
+ ->setScope($scope);
+ $permissionsGroup->addRoleScope($roleScopeSee);
+ $manager->persist($roleScopeSee);
+ break;
+ }
+
+ }
+
+ $manager->flush();
+ }
+
+}
diff --git a/DependencyInjection/ChillPersonExtension.php b/DependencyInjection/ChillPersonExtension.php
index 1dafadcd0..548f3536f 100644
--- a/DependencyInjection/ChillPersonExtension.php
+++ b/DependencyInjection/ChillPersonExtension.php
@@ -49,6 +49,8 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
public function prepend(ContainerBuilder $container)
{
+ $this->prependRoleHierarchy($container);
+
$bundles = $container->getParameter('kernel.bundles');
//add ChillMain to assetic-enabled bundles
if (!isset($bundles['AsseticBundle'])) {
@@ -71,4 +73,14 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
)
));
}
+
+ protected function prependRoleHierarchy(ContainerBuilder $container)
+ {
+ $container->prependExtensionConfig('security', array(
+ 'role_hierarchy' => array(
+ 'CHILL_PERSON_UPDATE' => array('CHILL_PERSON_SEE'),
+ 'CHILL_PERSON_CREATE' => array('CHILL_PERSON_SEE')
+ )
+ ));
+ }
}
diff --git a/Entity/Person.php b/Entity/Person.php
index 25b0a4804..1d748ffe1 100644
--- a/Entity/Person.php
+++ b/Entity/Person.php
@@ -25,11 +25,12 @@ namespace Chill\PersonBundle\Entity;
use Symfony\Component\Validator\ExecutionContextInterface;
use Chill\MainBundle\Entity\Country;
use Doctrine\Common\Collections\ArrayCollection;
+use Chill\MainBundle\Entity\HasCenterInterface;
/**
* Person
*/
-class Person {
+class Person implements HasCenterInterface {
/**
* @var integer
*/
@@ -60,6 +61,12 @@ class Person {
*/
private $genre;
+ /**
+ *
+ * @var \Chill\MainBundle\Entity\Center
+ */
+ private $center;
+
const GENRE_MAN = 'man';
const GENRE_WOMAN = 'woman';
@@ -486,7 +493,30 @@ class Person {
public function getLabel() {
return $this->getFirstName()." ".$this->getLastName();
}
+
+ /**
+ * Get center
+ *
+ * @return \Chill\MainBundle\Entity\Center
+ */
+ public function getCenter()
+ {
+ return $this->center;
+ }
+ /**
+ * Set the center
+ *
+ * @param \Chill\MainBundle\Entity\Center $center
+ * @return \Chill\PersonBundle\Entity\Person
+ */
+ public function setCenter(\Chill\MainBundle\Entity\Center $center)
+ {
+ $this->center = $center;
+ return $this;
+ }
+
+
/**
* Set cFData
*
diff --git a/Form/CreationPersonType.php b/Form/CreationPersonType.php
index 2459e0126..9e878d73a 100644
--- a/Form/CreationPersonType.php
+++ b/Form/CreationPersonType.php
@@ -66,6 +66,7 @@ class CreationPersonType extends AbstractType
->addModelTransformer($dateToStringTransformer)
)
->add('form_status', 'hidden')
+ ->add('center', 'center')
;
} else {
$builder
@@ -80,6 +81,7 @@ class CreationPersonType extends AbstractType
'widget' => 'single_text', 'format' => 'dd-MM-yyyy',
'data' => new \DateTime()))
->add('form_status', 'hidden', array('data' => $this->form_status))
+ ->add('center', 'center')
;
}
}
diff --git a/Resources/config/doctrine/Person.orm.yml b/Resources/config/doctrine/Person.orm.yml
index 1c11cb4cd..bfaab4376 100644
--- a/Resources/config/doctrine/Person.orm.yml
+++ b/Resources/config/doctrine/Person.orm.yml
@@ -51,6 +51,9 @@ Chill\PersonBundle\Entity\Person:
targetEntity: Chill\MainBundle\Entity\Country
inversedBy: nationals
nullable: true
+ center:
+ targetEntity: Chill\MainBundle\Entity\Center
+ nullable: false
oneToMany:
accompanyingPeriods:
targetEntity: AccompanyingPeriod
diff --git a/Resources/config/routing.yml b/Resources/config/routing.yml
index aba288ba7..b4e825dc0 100644
--- a/Resources/config/routing.yml
+++ b/Resources/config/routing.yml
@@ -1,5 +1,5 @@
chill_person_view:
- pattern: /{_locale}/person/{person_id}/general
+ path: /{_locale}/person/{person_id}/general
defaults: { _controller: ChillPersonBundle:Person:view }
options:
menus:
@@ -8,15 +8,15 @@ chill_person_view:
label: Person details
chill_person_general_edit:
- pattern: /{_locale}/person/{person_id}/general/edit
+ path: /{_locale}/person/{person_id}/general/edit
defaults: {_controller: ChillPersonBundle:Person:edit }
chill_person_general_update:
- pattern: /{_locale}/person/{person_id}/general/update
+ path: /{_locale}/person/{person_id}/general/update
defaults: {_controller: ChillPersonBundle:Person:update }
chill_person_new:
- pattern: /{_locale}/person/new
+ path: /{_locale}/person/new
defaults: {_controller: ChillPersonBundle:Person:new }
options:
menus:
@@ -29,15 +29,15 @@ chill_person_new:
icons: [plus, male, female]
chill_person_review:
- pattern: /{_locale}/person/review
+ path: /{_locale}/person/review
defaults: {_controller: ChillPersonBundle:Person:review }
chill_person_create:
- pattern: /{_locale}/person/create
+ path: /{_locale}/person/create
defaults: {_controller: ChillPersonBundle:Person:create }
chill_person_search:
- pattern: /{_locale}/person/search
+ path: /{_locale}/person/search
defaults: { _controller: ChillPersonBundle:Person:search }
options:
menus:
@@ -46,7 +46,7 @@ chill_person_search:
label: Search within persons
chill_person_accompanying_period_list:
- pattern: /{_locale}/person/{person_id}/accompanying-period
+ path: /{_locale}/person/{person_id}/accompanying-period
defaults: { _controller: ChillPersonBundle:AccompanyingPeriod:list }
# options:
# menus:
@@ -55,23 +55,23 @@ chill_person_accompanying_period_list:
# label: menu.person.history
chill_person_accompanying_period_create:
- pattern: /{_locale}/person/{person_id}/accompanying-period/create
+ path: /{_locale}/person/{person_id}/accompanying-period/create
defaults: { _controller: ChillPersonBundle:AccompanyingPeriod:create }
chill_person_accompanying_period_update:
- pattern: /{_locale}/person/{person_id}/accompanying-period/{period_id}/update
+ path: /{_locale}/person/{person_id}/accompanying-period/{period_id}/update
defaults: { _controller: ChillPersonBundle:AccompanyingPeriod:update }
chill_person_accompanying_period_close:
- pattern: /{_locale}/person/{person_id}/accompanying-period/close
+ path: /{_locale}/person/{person_id}/accompanying-period/close
defaults: { _controller: ChillPersonBundle:AccompanyingPeriod:close }
chill_person_accompanying_period_open:
- pattern: /{_locale}/person/{person_id}/accompanying-period/open
+ path: /{_locale}/person/{person_id}/accompanying-period/open
defaults: { _controller: ChillPersonBundle:AccompanyingPeriod:open }
chill_person_admin:
- pattern: /{_locale}/admin/person
+ path: /{_locale}/admin/person
defaults: { _controller: ChillPersonBundle:Admin:index }
options:
menus:
@@ -81,7 +81,7 @@ chill_person_admin:
helper: menu.person.admin.helper
chill_person_export:
- pattern: /{_locale}/person/export/
+ path: /{_locale}/person/export/
defaults: { _controller: ChillPersonBundle:Person:export }
options:
menus:
@@ -90,7 +90,7 @@ chill_person_export:
label: Export persons
chill_person_timeline:
- pattern: /{_locale}/person/{person_id}/timeline
+ path: /{_locale}/person/{person_id}/timeline
defaults: { _controller: ChillPersonBundle:TimelinePerson:person }
options:
menus:
diff --git a/Resources/config/services.yml b/Resources/config/services.yml
index 9f7f61f99..b97cfefd4 100644
--- a/Resources/config/services.yml
+++ b/Resources/config/services.yml
@@ -14,6 +14,8 @@ services:
class: Chill\PersonBundle\Search\PersonSearch
arguments:
- "@doctrine.orm.entity_manager"
+ - "@security.token_storage"
+ - "@chill.main.security.authorization.helper"
calls:
- ['setContainer', ["@service_container"]]
tags:
@@ -32,3 +34,10 @@ services:
- "@doctrine.orm.entity_manager"
tags:
- { name: chill.timeline, context: 'person' }
+
+ chill.person.security.authorization.person:
+ class: Chill\PersonBundle\Security\Authorization\PersonVoter
+ arguments:
+ - "@chill.main.security.authorization.helper"
+ tags:
+ - { name: security.voter }
diff --git a/Resources/migrations/Version20150607231010.php b/Resources/migrations/Version20150607231010.php
new file mode 100644
index 000000000..38abb3e47
--- /dev/null
+++ b/Resources/migrations/Version20150607231010.php
@@ -0,0 +1,100 @@
+container = $container;
+ }
+
+ public function getDescription()
+ {
+ return 'Add a center on the person entity. The default center is the first '
+ . 'recorded.';
+ }
+
+
+ /**
+ * @param Schema $schema
+ */
+ public function up(Schema $schema)
+ {
+ $this->abortIf($this->connection->getDatabasePlatform()->getName() != 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
+
+ // retrieve center for setting a default center
+ $centers = $this->container->get('doctrine.orm.entity_manager')
+ ->getRepository('ChillMainBundle:Center')
+ ->findAll();
+
+
+ if (count($centers) > 0) {
+ $defaultCenterId = $centers[0]->getId();
+ } else { // if no center, performs other checks
+ //check if there are data in person table
+ $nbPeople = $this->container->get('doctrine.orm.entity_manager')
+ ->createQuery('SELECT count(p) FROM ChillPersonBundle:Person p')
+ ->getSingleScalarResult();
+
+ if ($nbPeople > 0) {
+ // we have data ! We have to create a center !
+ $center = new Center();
+ $center->setName('Auto-created center');
+ $this->container->get('doctrine.orm.entity_manager')
+ ->persist($center)
+ ->flush();
+ $defaultCenterId = $center->getId();
+ }
+ }
+
+
+ $this->addSql('ALTER TABLE person ADD center_id INT');
+
+ if (isset($defaultCenterId)) {
+ $this->addSql('UPDATE person SET center_id = :id', array('id' => $defaultCenterId));
+ }
+
+ $this->addSql('ALTER TABLE person '
+ . 'ADD CONSTRAINT FK_person_center FOREIGN KEY (center_id) '
+ . 'REFERENCES centers (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
+ $this->addSql('ALTER TABLE person ALTER center_id SET NOT NULL');
+ $this->addSql('CREATE INDEX IDX_person_center ON person (center_id)');
+ }
+
+ /**
+ * @param Schema $schema
+ */
+ public function down(Schema $schema)
+ {
+ $this->abortIf($this->connection->getDatabasePlatform()->getName() != 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
+
+ $this->addSql('ALTER TABLE Person DROP CONSTRAINT FK_person_center');
+ $this->addSql('DROP INDEX IDX_person_center');
+ $this->addSql('ALTER TABLE Person DROP center_id');
+ }
+
+
+
+}
diff --git a/Resources/views/Person/view.html.twig b/Resources/views/Person/view.html.twig
index 56896403b..13d3e0c3e 100644
--- a/Resources/views/Person/view.html.twig
+++ b/Resources/views/Person/view.html.twig
@@ -35,8 +35,9 @@ This view should receive those arguments:
{% block personcontent %}
-
+{% if is_granted('CHILL_PERSON_UPDATE', person) %}
{{ include(edit_tmp_name, edit_tmp_args) }}
+{% endif %}
{{ 'General information'|trans }}
@@ -83,7 +84,9 @@ This view should receive those arguments:
+{% if is_granted('CHILL_PERSON_UPDATE', person) %}
{{ include(edit_tmp_name, edit_tmp_args) }}
+{% endif %}
{{ 'Administrative information'|trans }}
@@ -112,7 +115,9 @@ This view should receive those arguments:
+{% if is_granted('CHILL_PERSON_UPDATE', person) %}
{{ include(edit_tmp_name, edit_tmp_args) }}
+{% endif %}
{{ 'Contact information'|trans }}
@@ -128,7 +133,9 @@ This view should receive those arguments:
+{% if is_granted('CHILL_PERSON_UPDATE', person) %}
{{ include(edit_tmp_name, edit_tmp_args) }}
+{% endif %}
{% if cFGroup %}
@@ -142,7 +149,9 @@ This view should receive those arguments:
{% endfor %}
- {{ include(edit_tmp_name, edit_tmp_args) }}
+{% if is_granted('CHILL_PERSON_UPDATE', person) %}
+{{ include(edit_tmp_name, edit_tmp_args) }}
+{% endif %}
{% endif %}
diff --git a/Search/PersonSearch.php b/Search/PersonSearch.php
index dda98721c..5d1e69ca7 100644
--- a/Search/PersonSearch.php
+++ b/Search/PersonSearch.php
@@ -23,11 +23,14 @@ use Chill\MainBundle\Search\AbstractSearch;
use Doctrine\ORM\EntityManagerInterface;
use Chill\PersonBundle\Entity\Person;
use Symfony\Component\DependencyInjection\ContainerInterface;
-use Symfony\Component\DependencyInjection\ContainerAware;
+use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Chill\MainBundle\Search\ParsingException;
+use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
+use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
+use Symfony\Component\Security\Core\Role\Role;
-class PersonSearch extends AbstractSearch
+class PersonSearch extends AbstractSearch implements ContainerAwareInterface
{
use ContainerAwareTrait;
@@ -37,10 +40,31 @@ class PersonSearch extends AbstractSearch
*/
private $em;
+ /**
+ *
+ * @var \Chill\MainBundle\Entity\User
+ */
+ private $user;
- public function __construct(EntityManagerInterface $em)
+ /**
+ *
+ * @var AuthorizationHelper
+ */
+ private $helper;
+
+
+ public function __construct(EntityManagerInterface $em,
+ TokenStorage $tokenStorage, AuthorizationHelper $helper)
{
$this->em = $em;
+ $this->user = $tokenStorage->getToken()->getUser();
+ $this->helper = $helper;
+
+ // throw an error if user is not a valid user
+ if (!$this->user instanceof \Chill\MainBundle\Entity\User) {
+ throw new \LogicException('The user provided must be an instance'
+ . ' of Chill\MainBundle\Entity\User');
+ }
}
/*
@@ -189,6 +213,14 @@ class PersonSearch extends AbstractSearch
}
}
+ //restraint center for security
+ $reachableCenters = $this->helper->getReachableCenters($this->user,
+ new Role('CHILL_PERSON_SEE'));
+ $qb->andWhere($qb->expr()
+ ->in('p.center', ':centers'))
+ ->setParameter('centers', $reachableCenters)
+ ;
+
$this->_cacheQuery[$cacheKey] = $qb;
return $qb;
diff --git a/Security/Authorization/PersonVoter.php b/Security/Authorization/PersonVoter.php
new file mode 100644
index 000000000..e373c34a8
--- /dev/null
+++ b/Security/Authorization/PersonVoter.php
@@ -0,0 +1,67 @@
+
+ *
+ * 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\PersonBundle\Security\Authorization;
+
+use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
+use Chill\MainBundle\Entity\User;
+use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class PersonVoter extends AbstractChillVoter
+{
+ const CREATE = 'CHILL_PERSON_CREATE';
+ const UPDATE = 'CHILL_PERSON_UPDATE';
+ const SEE = 'CHILL_PERSON_SEE';
+
+ /**
+ *
+ * @var AuthorizationHelper
+ */
+ protected $helper;
+
+ public function __construct(AuthorizationHelper $helper)
+ {
+ $this->helper = $helper;
+ }
+
+ protected function getSupportedAttributes()
+ {
+ return array(self::CREATE, self::UPDATE, self::SEE);
+ }
+
+ protected function getSupportedClasses()
+ {
+ return array('Chill\PersonBundle\Entity\Person');
+ }
+
+ protected function isGranted($attribute, $person, $user = null)
+ {
+ if (!$user instanceof User) {
+ return false;
+ }
+
+ return $this->helper->userHasAccess($user, $person, $attribute);
+
+ }
+}
diff --git a/Tests/Controller/AccompanyingPeriodControllerTest.php b/Tests/Controller/AccompanyingPeriodControllerTest.php
index df13e282c..6f31c0e29 100644
--- a/Tests/Controller/AccompanyingPeriodControllerTest.php
+++ b/Tests/Controller/AccompanyingPeriodControllerTest.php
@@ -77,9 +77,13 @@ class AccompanyingPeriodControllerTest extends WebTestCase
'PHP_AUTH_PW' => 'password',
));
+ $center = static::$em->getRepository('ChillMainBundle:Center')
+ ->findOneBy(array('name' => 'Center A'));
+
$this->person = (new Person(new \DateTime('2015-01-05')))
->setFirstName('Roland')
->setLastName('Gallorime')
+ ->setCenter($center)
->setGenre(Person::GENRE_MAN);
static::$em->persist($this->person);
diff --git a/Tests/Controller/PersonControllerCreateTest.php b/Tests/Controller/PersonControllerCreateTest.php
index 0496d68c2..8808f5c76 100644
--- a/Tests/Controller/PersonControllerCreateTest.php
+++ b/Tests/Controller/PersonControllerCreateTest.php
@@ -36,6 +36,8 @@ class PersonControllerCreateTest extends WebTestCase
const GENRE_INPUT = "chill_personbundle_person_creation[genre]";
const DATEOFBIRTH_INPUT = "chill_personbundle_person_creation[dateOfBirth]";
const CREATEDATE_INPUT = "chill_personbundle_person_creation[creation_date]";
+ const CENTER_INPUT = "chill_personbundle_person_creation[center]";
+
const LONG_TEXT = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosq.";
/**
@@ -43,10 +45,10 @@ class PersonControllerCreateTest extends WebTestCase
*
* @return \Symfony\Component\BrowserKit\Client
*/
- private function getAuthenticatedClient()
+ private function getAuthenticatedClient($username = 'center a_social')
{
return static::createClient(array(), array(
- 'PHP_AUTH_USER' => 'center a_social',
+ 'PHP_AUTH_USER' => $username,
'PHP_AUTH_PW' => 'password',
));
}
@@ -55,10 +57,11 @@ class PersonControllerCreateTest extends WebTestCase
*
* @param Form $creationForm
*/
- private function fillAValidCreationForm(Form &$creationForm)
+ private function fillAValidCreationForm(Form &$creationForm,
+ $firstname = 'God', $lastname = 'Jesus')
{
- $creationForm->get(self::FIRSTNAME_INPUT)->setValue("God");
- $creationForm->get(self::LASTNAME_INPUT)->setValue("Jesus");
+ $creationForm->get(self::FIRSTNAME_INPUT)->setValue($firstname);
+ $creationForm->get(self::LASTNAME_INPUT)->setValue($lastname);
$creationForm->get(self::GENRE_INPUT)->select("man");
$date = new \DateTime('1947-02-01');
$creationForm->get(self::DATEOFBIRTH_INPUT)->setValue($date->format('d-m-Y'));
@@ -203,13 +206,57 @@ class PersonControllerCreateTest extends WebTestCase
. "/{_locale}/person/{personID}/general");
}
+ /**
+ * test adding a person with a user with multi center
+ * is valid
+ */
+ public function testValidFormWithMultiCenterUser()
+ {
+ $client = $this->getAuthenticatedClient('multi_center');
+
+ $crawler = $client->request('GET', '/fr/person/new');
+
+ $this->assertTrue($client->getResponse()->isSuccessful(),
+ "The page is accessible at the URL /{_locale}/person/new");
+ $form = $crawler->selectButton("Ajouter la personne")->form();
+
+ $this->fillAValidCreationForm($form, 'roger', 'rabbit');
+
+ $this->assertTrue($form->has(self::CENTER_INPUT),
+ 'The page contains a "center" input');
+ $centerInput = $form->get(self::CENTER_INPUT);
+ $availableValues = $centerInput->availableOptionValues();
+ $lastCenterInputValue = end($availableValues);
+ $centerInput->setValue($lastCenterInputValue);
+
+ $client->submit($form);
+
+ $this->assertTrue($client->getResponse()->isRedirect(),
+ "a valid form redirect to url /{_locale}/person/{personId}/general/edit");
+ $client->followRedirect();
+ $this->assertRegExp('|/fr/person/[1-9][0-9]*/general/edit$|',
+ $client->getHistory()->current()->getUri(),
+ "a valid form redirect to url /{_locale}/person/{personId}/general/edit");
+
+ }
+
public static function tearDownAfterClass()
{
static::bootKernel();
$em = static::$kernel->getContainer()->get('doctrine.orm.entity_manager');
+
+ //remove two people created during test
$jesus = $em->getRepository('ChillPersonBundle:Person')
->findOneBy(array('firstName' => 'God'));
- $em->remove($jesus);
+ if ($jesus !== NULL) {
+ $em->remove($jesus);
+ }
+
+ $jesus2 = $em->getRepository('ChillPersonBundle:Person')
+ ->findOneBy(array('firstName' => 'roger'));
+ if ($jesus2 !== NULL) {
+ $em->remove($jesus2);
+ }
$em->flush();
}
}
diff --git a/Tests/Controller/PersonControllerUpdateTest.php b/Tests/Controller/PersonControllerUpdateTest.php
index 3756c8a38..ef933e5e6 100644
--- a/Tests/Controller/PersonControllerUpdateTest.php
+++ b/Tests/Controller/PersonControllerUpdateTest.php
@@ -57,13 +57,18 @@ class PersonControllerUpdateTest extends WebTestCase
{
static::bootKernel();
+ $this->em = static::$kernel->getContainer()
+ ->get('doctrine.orm.entity_manager');
+
+ $center = $this->em->getRepository('ChillMainBundle:Center')
+ ->findOneBy(array('name' => 'Center A'));
+
$this->person = (new Person())
->setLastName("My Beloved")
->setFirstName("Jesus")
+ ->setCenter($center)
->setGenre(Person::GENRE_MAN);
- $this->em = static::$kernel->getContainer()->get('doctrine.orm.entity_manager');
-
$this->em->persist($this->person);
$this->em->flush();
@@ -76,10 +81,6 @@ class PersonControllerUpdateTest extends WebTestCase
));
}
- /**
- *
- * @return Person
- */
protected function refreshPerson()
{
$this->person = $this->em->getRepository('ChillPersonBundle:Person')
@@ -97,6 +98,30 @@ class PersonControllerUpdateTest extends WebTestCase
"The person edit form is accessible");
}
+ public function testEditPageDeniedForUnauthorized_OutsideCenter()
+ {
+ $client = static::createClient(array(), array(
+ 'PHP_AUTH_USER' => 'center b_social',
+ 'PHP_AUTH_PW' => 'password',
+ ));
+
+ $client->request('GET', $this->editUrl);
+
+ $this->assertEquals(403, $client->getResponse()->getStatusCode());
+ }
+
+ public function testEditPageDeniedForUnauthorized_InsideCenter()
+ {
+ $client = static::createClient(array(), array(
+ 'PHP_AUTH_USER' => 'center a_administrative',
+ 'PHP_AUTH_PW' => 'password',
+ ));
+
+ $client->request('GET', $this->editUrl);
+
+ $this->assertEquals(403, $client->getResponse()->getStatusCode());
+ }
+
/**
* test the edition of a field
*
diff --git a/Tests/Controller/PersonControllerViewTest.php b/Tests/Controller/PersonControllerViewTest.php
new file mode 100644
index 000000000..cffa45506
--- /dev/null
+++ b/Tests/Controller/PersonControllerViewTest.php
@@ -0,0 +1,104 @@
+
+ *
+ * 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\PersonBundle\Tests\Controller;
+
+use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
+use Chill\PersonBundle\Entity\Person;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class PersonControllerViewTest extends WebTestCase
+{
+
+ /**
+ *
+ * @var \Doctrine\ORM\EntityManagerInterface
+ */
+ private $em;
+
+ /**
+ *
+ * @var Person
+ */
+ private $person;
+
+ public function setUp()
+ {
+ static::bootKernel();
+
+ $this->em = static::$kernel->getContainer()
+ ->get('doctrine.orm.entity_manager');
+
+ $center = $this->em->getRepository('ChillMainBundle:Center')
+ ->findOneBy(array('name' => 'Center A'));
+
+ $this->person = (new Person())
+ ->setLastName("Tested Person")
+ ->setFirstName("Réginald")
+ ->setCenter($center)
+ ->setGenre(Person::GENRE_MAN);
+
+ $this->em->persist($this->person);
+ $this->em->flush();
+
+ $this->seeUrl = '/en/person/'.$this->person->getId().'/general';
+ }
+
+ public function testViewPerson()
+ {
+ $client = static::createClient(array(), array(
+ 'PHP_AUTH_USER' => 'center a_social',
+ 'PHP_AUTH_PW' => 'password',
+ ));
+
+ $client->request('GET', $this->seeUrl);
+
+ $this->assertTrue($client->getResponse()->isSuccessful());
+ }
+
+ public function testViewPersonAccessDeniedForUnauthorized()
+ {
+ $client = static::createClient(array(), array(
+ 'PHP_AUTH_USER' => 'center b_social',
+ 'PHP_AUTH_PW' => 'password',
+ ));
+
+ $client->request('GET', $this->seeUrl);
+
+ $this->assertEquals(403, $client->getResponse()->getStatusCode());
+ }
+
+ protected function refreshPerson()
+ {
+ $this->person = $this->em->getRepository('ChillPersonBundle:Person')
+ ->find($this->person->getId());
+ }
+
+ public function tearDown()
+ {
+ $this->refreshPerson();
+ $this->em->remove($this->person);
+ $this->em->flush();
+ }
+
+}
diff --git a/Tests/Search/PersonSearchTest.php b/Tests/Search/PersonSearchTest.php
index d8f57b9db..eba78d4be 100644
--- a/Tests/Search/PersonSearchTest.php
+++ b/Tests/Search/PersonSearchTest.php
@@ -194,9 +194,25 @@ class PersonSearchTest extends WebTestCase
$this->assertRegExp('/Étienne/', $crawlerNoSpecial->text());
}
- private function generateCrawlerForSearch($pattern)
+ /**
+ * test that person which a user cannot see are not displayed in results
+ */
+ public function testSearchWithAuthorization()
{
- $client = $this->getAuthenticatedClient();
+ $crawlerCanSee = $this->generateCrawlerForSearch('Gérard', 'center a_social');
+ $crawlerCannotSee = $this->generateCrawlerForSearch('Gérard', 'center b_social');
+
+ $this->assertRegExp('/Gérard/', $crawlerCanSee->text(),
+ 'center a_social may see "Gérard" in center a');
+ $this->assertRegExp('/Aucune personne ne correspond aux termes de recherche "gerard"/',
+ $crawlerCannotSee->text(),
+ 'center b_social may not see any "Gérard" associated to center b');
+
+ }
+
+ private function generateCrawlerForSearch($pattern, $username = 'center a_social')
+ {
+ $client = $this->getAuthenticatedClient($username);
$crawler = $client->request('GET', '/fr/search', array(
'q' => $pattern
@@ -211,10 +227,10 @@ class PersonSearchTest extends WebTestCase
*
* @return \Symfony\Component\BrowserKit\Client
*/
- private function getAuthenticatedClient()
+ private function getAuthenticatedClient($username = 'center a_social')
{
return static::createClient(array(), array(
- 'PHP_AUTH_USER' => 'center a_social',
+ 'PHP_AUTH_USER' => $username,
'PHP_AUTH_PW' => 'password',
));
}
diff --git a/Tests/Security/Authorization/PersonVoterTest.php b/Tests/Security/Authorization/PersonVoterTest.php
new file mode 100644
index 000000000..474fbc103
--- /dev/null
+++ b/Tests/Security/Authorization/PersonVoterTest.php
@@ -0,0 +1,184 @@
+
+ *
+ * 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\PersonBundle\Tests\Security\Authorization;
+
+use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
+use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
+use Chill\PersonBundle\Entity\Person;
+use Chill\MainBundle\Entity\Center;
+use Chill\MainBundle\Entity\User;
+use Chill\MainBundle\Entity\PermissionsGroup;
+use Chill\MainBundle\Entity\GroupCenter;
+use Chill\MainBundle\Entity\RoleScope;
+use Chill\MainBundle\Entity\Scope;
+use Chill\MainBundle\Test\PrepareUserTrait;
+use Chill\MainBundle\Test\PrepareCenterTrait;
+use Chill\MainBundle\Test\PrepareScopeTrait;
+use Chill\MainBundle\Test\ProphecyTrait;
+
+/**
+ * Test PersonVoter
+ *
+ * @author Julien Fastré
+ * @author Champs Libres
+ */
+class PersonVoterTest extends KernelTestCase
+{
+
+ use PrepareUserTrait, PrepareCenterTrait, PrepareScopeTrait;
+
+ /**
+ *
+ * @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.person.security.authorization.person');
+ $this->prophet = new \Prophecy\Prophet();
+ }
+
+ public function testNullUser()
+ {
+ $token = $this->prepareToken();
+ $center = $this->prepareCenter(1, 'center');
+ $person = $this->preparePerson($center);
+
+ $this->assertEquals(
+ VoterInterface::ACCESS_DENIED,
+ $this->voter->vote($token, $person, array('CHILL_PERSON_SEE')),
+ "assert that a null user is not allowed to see"
+ );
+ }
+
+ public function testUserCanNotReachCenter()
+ {
+ $centerA = $this->prepareCenter(1, 'centera');
+ $centerB = $this->prepareCenter(2, 'centerb');
+ $scope = $this->prepareScope(1, 'default');
+ $token = $this->prepareToken(array(
+ array(
+ 'center' => $centerA, 'permissionsGroup' => array(
+ ['scope' => $scope, 'role' => 'CHILL_PERSON_UPDATE']
+ )
+ )
+ ));
+ $person = $this->preparePerson($centerB);
+
+ $this->assertEquals(
+ VoterInterface::ACCESS_DENIED,
+ $this->voter->vote($token, $person, array('CHILL_PERSON_UPDATE')),
+ 'assert that a user with right not in the good center has access denied'
+ );
+ }
+
+ /**
+ * test a user with sufficient right may see the person
+ */
+ public function testUserAllowed()
+ {
+ $center = $this->prepareCenter(1, 'center');
+ $scope = $this->prepareScope(1, 'default');
+ $token = $this->prepareToken(array(
+ array(
+ 'center' => $center, 'permissionsGroup' => array(
+ ['scope' => $scope, 'role' => 'CHILL_PERSON_SEE']
+ )
+ )
+ ));
+ $person = $this->preparePerson($center);
+
+ $this->assertEquals(
+ VoterInterface::ACCESS_GRANTED,
+ $this->voter->vote($token, $person, array('CHILL_PERSON_SEE')),
+ 'assert that a user with correct rights may is granted access'
+ );
+ }
+
+ /**
+ * test a user with sufficient right may see the person.
+ * hierarchy between role is required
+ */
+ public function testUserAllowedWithInheritance()
+ {
+ $center = $this->prepareCenter(1, 'center');
+ $scope = $this->prepareScope(1, 'default');
+ $token = $this->prepareToken(array(
+ array(
+ 'center' => $center, 'permissionsGroup' => array(
+ ['scope' => $scope, 'role' => 'CHILL_PERSON_UPDATE']
+ )
+ )
+ ));
+ $person = $this->preparePerson($center);
+ $this->assertEquals(
+ VoterInterface::ACCESS_GRANTED,
+ $this->voter->vote($token, $person, array('CHILL_PERSON_SEE')),
+ 'assert that a user with correct role is granted on inherited roles'
+ );
+ }
+
+ /**
+ * prepare a person
+ *
+ * The only properties set is the center, others properties are ignored.
+ *
+ * @param Center $center
+ * @return Person
+ */
+ protected function preparePerson(Center $center)
+ {
+ return (new Person())
+ ->setCenter($center)
+ ;
+ }
+
+ /**
+ * prepare a token interface with correct rights
+ *
+ * if $permissions = null, user will be null (no user associated with token
+ *
+ * @param array $permissions an array of permissions, with key 'center' for the center and 'permissions' for an array of permissions
+ * @return \Symfony\Component\Security\Core\Authentication\Token\TokenInterface
+ */
+ protected function prepareToken(array $permissions = null)
+ {
+ $token = $this->prophet->prophesize();
+ $token
+ ->willImplement('\Symfony\Component\Security\Core\Authentication\Token\TokenInterface');
+ if ($permissions === NULL) {
+ $token->getUser()->willReturn(null);
+ } else {
+ $token->getUser()->willReturn($this->prepareUser($permissions));
+ }
+
+ return $token->reveal();
+ }
+}
diff --git a/composer.json b/composer.json
index 8210fcd75..5b884c8e4 100644
--- a/composer.json
+++ b/composer.json
@@ -20,15 +20,15 @@
"twig/extensions": "~1.0",
"symfony/assetic-bundle": "~2.3",
"symfony/monolog-bundle": "~2.4",
- "symfony/framework-bundle": "2.5.*",
- "symfony/yaml": "2.5.*",
- "symfony/symfony": "~2.5",
+ "symfony/framework-bundle": "~2.7",
+ "symfony/yaml": "~2.7",
+ "symfony/symfony": "~2.7",
"doctrine/dbal": "~2.5",
"doctrine/orm": "~2.4",
"doctrine/common": "~2.4",
"doctrine/doctrine-bundle": "~1.2",
- "chill-project/main": "*@dev",
- "chill-project/custom-fields": "*@dev",
+ "chill-project/main": "dev-add_acl@dev",
+ "chill-project/custom-fields": "dev-add_acl@dev",
"doctrine/doctrine-fixtures-bundle": "~2.2",
"champs-libres/composer-bundle-migration": "~1.0",
"doctrine/doctrine-migrations-bundle": "dev-master@dev",
@@ -36,7 +36,8 @@
},
"require-dev": {
"symfony/dom-crawler": "2.5",
- "symfony/security": "~2.5"
+ "symfony/security": "~2.5",
+ "symfony/phpunit-bridge": "^2.7"
},
"scripts": {
"post-install-cmd": [
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index c9d5669ce..c13f031a9 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -19,5 +19,6 @@
+
-
\ No newline at end of file
+