diff --git a/DataFixtures/ORM/LoadUsers.php b/DataFixtures/ORM/LoadUsers.php
index 6bed52f5e..16d02ba4a 100644
--- a/DataFixtures/ORM/LoadUsers.php
+++ b/DataFixtures/ORM/LoadUsers.php
@@ -32,31 +32,54 @@ class LoadUsers extends AbstractFixture implements OrderedFixtureInterface, Cont
return 1000;
}
- public static $refs = array();
+ public static $refs = array(
+ 'center a_social' => array(
+ 'groupCenterRefs' => ['centerA_permission_group_social']
+ ),
+ 'center a_administrative' => array(
+ 'groupCenterRefs' => ['centerA_permission_group_administrative']
+ ),
+ 'center a_direction' => array(
+ 'groupCenterRefs' => ['centerA_permission_group_direction']
+ ),
+ 'center b_social' => array(
+ 'groupCenterRefs' => ['centerB_permission_group_social']
+ ),
+ 'center b_administrative' => array(
+ 'groupCenterRefs' => ['centerB_permission_group_administrative']
+ ),
+ 'center b_direction' => array(
+ 'groupCenterRefs' => ['centerB_permission_group_direction']
+ ),
+ 'multi_center' => array(
+ 'groupCenterRefs' => ['centerA_permission_group_social',
+ 'centerB_permission_group_social']
+ )
+
+ );
public function load(ObjectManager $manager)
{
- foreach(LoadCenters::$refs as $centerRef) {
- foreach(LoadPermissionsGroup::$refs as $permissionGroupRef) {
- $user = new User();
-
- $permissionGroup = $this->getReference($permissionGroupRef);
- $center = $this->getReference($centerRef);
- $username = strtolower($center->getName().'_'.$permissionGroup->getName());
-
- $user->setUsername($username)
- ->setPassword($this->container->get('security.encoder_factory')
+ foreach (self::$refs as $username => $params) {
+
+ $user = new User();
+
+ $user->setUsername($username)
+ ->setPassword(
+ $this->container->get('security.encoder_factory')
->getEncoder($user)
- ->encodePassword('password', $user->getSalt()));
- $user->addGroupCenter($this->getReference($centerRef.'_'.$permissionGroupRef));
-
- $manager->persist($user);
- $this->addReference($username, $user);
- static::$refs[] = $user->getUsername();
- echo "Creating user with username '".$user->getUsername()."' and password 'password'.. \n";
+ ->encodePassword('password', $user->getSalt())
+ );
+
+ foreach ($params['groupCenterRefs'] as $groupCenterRef) {
+ $user->addGroupCenter($this->getReference($groupCenterRef));
}
+
+ echo 'Creating user ' . $username ."... \n";
+ $manager->persist($user);
+ $this->addReference($username, $user);
}
-
+
$manager->flush();
}
diff --git a/DependencyInjection/ChillMainExtension.php b/DependencyInjection/ChillMainExtension.php
index 2a310b38e..b66425ec4 100644
--- a/DependencyInjection/ChillMainExtension.php
+++ b/DependencyInjection/ChillMainExtension.php
@@ -58,8 +58,7 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface
'name' => $config['installation_name']),
'available_languages' => $config['available_languages']
),
- 'form' => array(
- 'resources' => array('ChillMainBundle:Form:fields.html.twig'))
+ 'form_themes' => array('ChillMainBundle:Form:fields.html.twig')
);
$container->prependExtensionConfig('twig', $twigConfig);
diff --git a/DependencyInjection/TimelineCompilerClass.php b/DependencyInjection/TimelineCompilerClass.php
index 1cb06e0c7..92aaaa003 100644
--- a/DependencyInjection/TimelineCompilerClass.php
+++ b/DependencyInjection/TimelineCompilerClass.php
@@ -21,7 +21,6 @@ namespace Chill\MainBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
-use Symfony\Component\DependencyInjection\Reference;
/**
* Add services taggued with `name: chill.timeline` to
diff --git a/Entity/Center.php b/Entity/Center.php
index fddb54441..a526c2468 100644
--- a/Entity/Center.php
+++ b/Entity/Center.php
@@ -76,6 +76,11 @@ class Center
$this->groupCenters->add($groupCenter);
return $this;
}
+
+ public function __toString()
+ {
+ return $this->getName();
+ }
}
diff --git a/Entity/Country.php b/Entity/Country.php
index 96e50b4e3..016e6ad09 100644
--- a/Entity/Country.php
+++ b/Entity/Country.php
@@ -2,8 +2,6 @@
namespace Chill\MainBundle\Entity;
-use Doctrine\ORM\Mapping as ORM;
-
/**
* Country
*/
diff --git a/Entity/HasCenterInterface.php b/Entity/HasCenterInterface.php
new file mode 100644
index 000000000..8d6d541d8
--- /dev/null
+++ b/Entity/HasCenterInterface.php
@@ -0,0 +1,36 @@
+
+ *
+ * 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\MainBundle\Entity;
+
+/**
+ * Interface for entities which may be linked to a center
+ *
+ *
+ * @author Julien Fastré
+ */
+interface HasCenterInterface
+{
+ /**
+ * the linked center
+ *
+ * @return Center
+ */
+ public function getCenter();
+}
diff --git a/Entity/HasScopeInterface.php b/Entity/HasScopeInterface.php
new file mode 100644
index 000000000..344d58557
--- /dev/null
+++ b/Entity/HasScopeInterface.php
@@ -0,0 +1,35 @@
+
+ *
+ * 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\MainBundle\Entity;
+
+/**
+ * Interface for entities which have a scop
+ *
+ * @author Julien Fastré
+ */
+interface HasScopeInterface
+{
+ /**
+ * Return the linked scope
+ *
+ * @return Scope
+ */
+ public function getScope();
+}
diff --git a/Form/Type/AppendScopeChoiceTypeTrait.php b/Form/Type/AppendScopeChoiceTypeTrait.php
new file mode 100644
index 000000000..ddf13a7ba
--- /dev/null
+++ b/Form/Type/AppendScopeChoiceTypeTrait.php
@@ -0,0 +1,153 @@
+
+ *
+ * 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\MainBundle\Form\Type;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Component\Form\FormBuilderInterface;
+use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
+use Chill\MainBundle\Templating\TranslatableStringHelper;
+use Doctrine\Common\Persistence\ObjectManager;
+use Symfony\Component\Form\FormEvents;
+use Symfony\Component\Form\FormEvent;
+use Chill\MainBundle\Entity\User;
+use Chill\MainBundle\Entity\Center;
+use Symfony\Component\Security\Core\Role\Role;
+use Chill\MainBundle\Form\Type\DataTransformer\ScopeTransformer;
+
+/**
+ * Trait to add an input with reachable scope for a given center and role.
+ *
+ * Example usage :
+ *
+ * ```
+ * class AbcType extends Symfony\Component\Form\AbstractType
+ * {
+ * use AppendScopeChoiceTypeTrait;
+ * protected $authorizationHelper;
+ * protected $translatableStringHelper;
+ * protected $user;
+ * protected $om;
+ *
+ * public function __construct(AuthorizationHelper $helper,
+ * TokenStorageInterface $tokenStorage,
+ * TranslatableStringHelper $translatableStringHelper,
+ * ObjectManager $om)
+ * {
+ * $this->authorizationHelper = $helper;
+ * $this->user = $tokenStorage->getToken()->getUser();
+ * $this->translatableStringHelper = $translatableStringHelper;
+ * $this->om = $om;
+ * }
+ *
+ * public function buildForm(FormBuilder $builder, array $options)
+ * {
+ * // ... add your form there
+ *
+ * // append the scope using FormEvents: PRE_SET_DATA
+ * $this->appendScopeChoices($builder, $options['role'],
+ * $options['center'], $this->user, $this->authorizationHelper,
+ * $this->translatableStringHelper, $this->om);
+ * }
+ *
+ * public function configureOptions(OptionsResolver $resolver)
+ * {
+ * // ... add your options
+ *
+ * // add an option 'role' and 'center' to your form (optional)
+ * $this->appendScopeChoicesOptions($resolver);
+ * }
+ *
+ * }
+ * ```
+ *
+ * @author Julien Fastré
+ * @author Champs Libres
+ */
+trait AppendScopeChoiceTypeTrait
+{
+ /**
+ * Append a scope choice field, with the scopes reachable by given
+ * user for the given role and center.
+ *
+ * The field is added on event FormEvents::PRE_SET_DATA
+ *
+ * @param FormBuilderInterface $builder
+ * @param Role $role
+ * @param Center $center
+ * @param User $user
+ * @param AuthorizationHelper $authorizationHelper
+ * @param TranslatableStringHelper $translatableStringHelper
+ * @param string $name
+ */
+ protected function appendScopeChoices(FormBuilderInterface $builder,
+ Role $role, Center $center, User $user,
+ AuthorizationHelper $authorizationHelper,
+ TranslatableStringHelper $translatableStringHelper,
+ ObjectManager $om, $name = 'scope')
+ {
+ $reachableScopes = $authorizationHelper
+ ->getReachableScopes($user, $role, $center);
+
+ $choices = array();
+ foreach($reachableScopes as $scope) {
+ $choices[$scope->getId()] = $translatableStringHelper
+ ->localize($scope->getName());
+ }
+
+ $dataTransformer = new ScopeTransformer($om);
+
+ $builder->addEventListener(FormEvents::PRE_SET_DATA,
+ function (FormEvent $event) use ($choices, $name, $dataTransformer, $builder) {
+ $form = $event->getForm();
+ $form->add(
+ $builder
+ ->create($name, 'choice', array(
+ 'choices' => $choices,
+ 'auto_initialize' => false
+ )
+ )
+ ->addModelTransformer($dataTransformer)
+ ->getForm()
+ );
+ });
+ }
+
+ /**
+ * Append a `role` and `center` option to the form.
+ *
+ * The allowed types are :
+ * - Chill\MainBundle\Entity\Center for center
+ * - Symfony\Component\Security\Core\Role\Role for role
+ *
+ * @param OptionsResolver $resolver
+ */
+ public function appendScopeChoicesOptions(OptionsResolver $resolver)
+ {
+ $resolver
+ ->setRequired(array('center', 'role'))
+ ->setAllowedTypes(array(
+ 'center' => 'Chill\MainBundle\Entity\Center',
+ 'role' => 'Symfony\Component\Security\Core\Role\Role'
+ ))
+ ;
+ }
+
+}
diff --git a/Form/Type/CenterType.php b/Form/Type/CenterType.php
new file mode 100644
index 000000000..19ca1f41d
--- /dev/null
+++ b/Form/Type/CenterType.php
@@ -0,0 +1,138 @@
+
+ *
+ * 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\MainBundle\Form\Type;
+
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
+use Chill\MainBundle\Entity\Center;
+use Chill\MainBundle\Form\Type\DataTransformer\CenterTransformer;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class CenterType extends AbstractType
+{
+ /**
+ * The user linked with this type.
+ *
+ * @var \Chill\MainBundle\Entity\User
+ */
+ protected $user;
+
+ /**
+ * associative array where keys are center.id and
+ * value are center objects
+ *
+ * @var Center[]
+ */
+ protected $reachableCenters = array();
+
+ /**
+ *
+ * @var CenterTransformer
+ */
+ protected $transformer;
+
+ public function __construct(TokenStorage $tokenStorage,
+ CenterTransformer $transformer)
+ {
+ $this->user = $tokenStorage->getToken()->getUser();
+ $this->transformer = $transformer;
+ $this->prepareReachableCenterByUser();
+ }
+
+ public function getName()
+ {
+ return 'center';
+ }
+
+ /**
+ * return a 'hidden' field if only one center is available.
+ *
+ * Return a 'choice' field if more than one center is available.
+ *
+ * @return string
+ * @throws \RuntimeException if the user is not associated with any center
+ */
+ public function getParent()
+ {
+ $nbReachableCenters = count($this->reachableCenters);
+
+ if ($nbReachableCenters === 0) {
+ throw new \RuntimeException("The user is not associated with "
+ . "any center. Associate user with a center");
+ } elseif ($nbReachableCenters === 1) {
+ return 'hidden';
+ } else {
+ return 'entity';
+ }
+ }
+
+ /**
+ * configure default options, i.e. add choices if user can reach multiple
+ * centers.
+ *
+ * @param OptionsResolver $resolver
+ */
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ if (count($this->reachableCenters) > 1) {
+ $resolver->setDefault('class', 'Chill\MainBundle\Entity\Center');
+ }
+ }
+
+ /**
+ * add a data transformer if user can reach only one center
+ *
+ * @param FormBuilderInterface $builder
+ * @param array $options
+ */
+ public function buildForm(FormBuilderInterface $builder, array $options)
+ {
+ if ($this->getParent() === 'hidden') {
+ $builder->addModelTransformer($this->transformer);
+ }
+ }
+
+ /**
+ * populate reachableCenters as an associative array where
+ * keys are center.id and value are center entities.
+ *
+ */
+ private function prepareReachableCenterByUser()
+ {
+ $groupCenters = $this->user->getGroupCenters();
+
+ foreach ($groupCenters as $groupCenter) {
+
+ $center = $groupCenter->getCenter();
+
+ if (!array_key_exists($center->getId(),
+ $this->reachableCenters)) {
+ $this->reachableCenters[$center->getId()] = $center;
+ }
+ }
+ }
+
+}
diff --git a/Form/Type/DataTransformer/CenterTransformer.php b/Form/Type/DataTransformer/CenterTransformer.php
new file mode 100644
index 000000000..a1930cf85
--- /dev/null
+++ b/Form/Type/DataTransformer/CenterTransformer.php
@@ -0,0 +1,70 @@
+
+ *
+ * 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\MainBundle\Form\Type\DataTransformer;
+
+use Symfony\Component\Form\DataTransformerInterface;
+use Doctrine\Common\Persistence\ObjectManager;
+use Symfony\Component\Form\Exception\TransformationFailedException;
+
+/**
+ * Transform a center object to his id, and vice-versa
+ *
+ * @author Julien Fastré
+ */
+class CenterTransformer implements DataTransformerInterface
+{
+ /**
+ *
+ * @var ObjectManager
+ */
+ private $om;
+
+ public function __construct(ObjectManager $om)
+ {
+ $this->om = $om;
+ }
+
+ public function reverseTransform($id)
+ {
+ if ($id === NULL) {
+ return NULL;
+ }
+
+ $center = $this->om->getRepository('ChillMainBundle:Center')
+ ->find($id);
+
+ if ($center === NULL) {
+ throw new TransformationFailedException(sprintf(
+ 'No center found with id %d', $id));
+ }
+
+ return $center;
+ }
+
+ public function transform($center)
+ {
+ if ($center === NULL) {
+ return '';
+ }
+
+ return $center->getId();
+ }
+
+}
diff --git a/Form/Type/DataTransformer/ScopeTransformer.php b/Form/Type/DataTransformer/ScopeTransformer.php
new file mode 100644
index 000000000..ca53b85b0
--- /dev/null
+++ b/Form/Type/DataTransformer/ScopeTransformer.php
@@ -0,0 +1,77 @@
+
+ *
+ * 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\MainBundle\Form\Type\DataTransformer;
+
+use Symfony\Component\Form\DataTransformerInterface;
+use Doctrine\Common\Persistence\ObjectManager;
+use Symfony\Component\Form\Exception\TransformationFailedException;
+use Chill\MainBundle\Templating\TranslatableStringHelper;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class ScopeTransformer implements DataTransformerInterface
+{
+ /**
+ *
+ * @var ObjectManager
+ */
+ protected $om;
+
+ /**
+ *
+ * @var TranslatableStringHelper
+ */
+ protected $helper;
+
+ public function __construct(ObjectManager $om)
+ {
+ $this->om = $om;
+ }
+
+ public function transform($scope)
+ {
+ if ($scope === NULL) {
+ return NULL;
+ }
+
+ return $scope->getId();
+ }
+
+ public function reverseTransform($id)
+ {
+ if ($id == NULL) {
+ return NULL;
+ }
+
+ $scope = $this->om->getRepository('ChillMainBundle:Scope')
+ ->find($id);
+
+ if ($scope === NULL) {
+ throw new TransformationFailedException(sprintf("The scope with id "
+ . "'%d' were not found", $id));
+ }
+
+ return $scope;
+ }
+
+}
diff --git a/Form/Type/Select2ChoiceType.php b/Form/Type/Select2ChoiceType.php
index 263a6f24c..3c1903539 100644
--- a/Form/Type/Select2ChoiceType.php
+++ b/Form/Type/Select2ChoiceType.php
@@ -21,7 +21,7 @@
namespace Chill\MainBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
-use Symfony\Component\OptionsResolver\OptionsResolverInterface;
+use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Extends choice to allow adding select2 library on widget
@@ -40,9 +40,9 @@ class Select2ChoiceType extends AbstractType
return 'choice';
}
- public function setDefaultOptions(OptionsResolverInterface $resolver)
+ public function configureOptions(OptionsResolver $resolver)
{
- $resolver->replaceDefaults(
+ $resolver->setDefaults(
array('attr' => array('class' => 'select2 '))
);
}
diff --git a/Form/Type/Select2CountryType.php b/Form/Type/Select2CountryType.php
index cdd449739..b29fbbcb6 100644
--- a/Form/Type/Select2CountryType.php
+++ b/Form/Type/Select2CountryType.php
@@ -22,7 +22,6 @@ namespace Chill\MainBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
-use Chill\MainBundle\Entity\Country;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Form\FormBuilderInterface;
use Chill\MainBundle\Form\Type\DataTransformer\ObjectToIdTransformer;
diff --git a/Form/Type/Select2LanguageType.php b/Form/Type/Select2LanguageType.php
index 6b0b88ce5..51edc44ca 100644
--- a/Form/Type/Select2LanguageType.php
+++ b/Form/Type/Select2LanguageType.php
@@ -22,7 +22,6 @@ namespace Chill\MainBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
-use Chill\MainBundle\Entity\Language;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Form\FormBuilderInterface;
use Chill\MainBundle\Form\Type\DataTransformer\MultipleObjectsToIdTransformer;
diff --git a/Resources/config/routing.yml b/Resources/config/routing.yml
index b59abfb66..438c7cccf 100644
--- a/Resources/config/routing.yml
+++ b/Resources/config/routing.yml
@@ -1,16 +1,16 @@
root:
- pattern: /
+ path: /
defaults:
_controller: FrameworkBundle:Redirect:urlRedirect
path: /homepage
permanent: true
chill_main_homepage_without_locale:
- pattern: /homepage
+ path: /homepage
defaults: { _controller: ChillMainBundle:Default:indexWithoutLocale }
chill_main_homepage:
- pattern: /{_locale}/homepage
+ path: /{_locale}/homepage
defaults: { _controller: ChillMainBundle:Default:index }
options:
menus:
@@ -20,7 +20,7 @@ chill_main_homepage:
icons: [home]
chill_main_export_index:
- pattern: /{_locale}/export
+ path: /{_locale}/export
defaults: { _controller: ChillMainBundle:Export:index }
options:
menus:
@@ -30,7 +30,7 @@ chill_main_export_index:
icons: [upload]
chill_main_admin_central:
- pattern: /{_locale}/admin
+ path: /{_locale}/admin
defaults: { _controller: ChillMainBundle:Admin:index }
options:
menus:
@@ -40,7 +40,7 @@ chill_main_admin_central:
icons: [gears]
chill_main_search:
- pattern: /{_locale}/search
+ path: /{_locale}/search
defaults: { _controller: ChillMainBundle:Search:search }
login:
diff --git a/Resources/config/services.yml b/Resources/config/services.yml
index 87523bd26..6f1d6fb73 100644
--- a/Resources/config/services.yml
+++ b/Resources/config/services.yml
@@ -81,6 +81,19 @@ services:
- "@doctrine.orm.entity_manager"
tags:
- { name: form.type, alias: select2_chill_language }
+
+ chill.main.form.type.center:
+ class: Chill\MainBundle\Form\Type\CenterType
+ arguments:
+ - "@security.token_storage"
+ - "@chill.main.form.data_transformer.center_transformer"
+ tags:
+ - { name: form.type, alias: center }
+
+ chill.main.form.data_transformer.center_transformer:
+ class: Chill\MainBundle\Form\Type\DataTransformer\CenterTransformer
+ arguments:
+ - "@doctrine.orm.entity_manager"
chill.main.search_provider:
class: Chill\MainBundle\Search\SearchProvider
@@ -91,3 +104,8 @@ services:
- "@doctrine.orm.entity_manager"
calls:
- [ setContainer, ["@service_container"]]
+
+ chill.main.security.authorization.helper:
+ class: Chill\MainBundle\Security\Authorization\AuthorizationHelper
+ arguments:
+ - "@security.role_hierarchy"
diff --git a/Routing/MenuComposer.php b/Routing/MenuComposer.php
index 0df3e9551..4143231ad 100644
--- a/Routing/MenuComposer.php
+++ b/Routing/MenuComposer.php
@@ -2,7 +2,6 @@
namespace Chill\MainBundle\Routing;
-use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
diff --git a/Security/Authorization/AbstractChillVoter.php b/Security/Authorization/AbstractChillVoter.php
new file mode 100644
index 000000000..ac291c673
--- /dev/null
+++ b/Security/Authorization/AbstractChillVoter.php
@@ -0,0 +1,36 @@
+
+ *
+ * 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\MainBundle\Security\Authorization;
+
+use Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter;
+
+/**
+ * Voter for Chill software.
+ *
+ * This abstract Voter provide generic methods to handle object specific to Chill
+ *
+ *
+ * @author Julien Fastré
+ */
+abstract class AbstractChillVoter extends AbstractVoter implements ChillVoterInterface
+{
+
+
+}
diff --git a/Security/Authorization/AuthorizationHelper.php b/Security/Authorization/AuthorizationHelper.php
new file mode 100644
index 000000000..51e2e196a
--- /dev/null
+++ b/Security/Authorization/AuthorizationHelper.php
@@ -0,0 +1,209 @@
+
+ *
+ * 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\MainBundle\Security\Authorization;
+
+use Chill\MainBundle\Entity\User;
+use Chill\MainBundle\Entity\Center;
+use Chill\MainBundle\Entity\HasCenterInterface;
+use Chill\MainBundle\Entity\HasScopeInterface;
+use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
+use Symfony\Component\Security\Core\Role\Role;
+use Chill\MainBundle\Entity\Scope;
+
+/**
+ * Helper for authorizations.
+ *
+ * Provides methods for user and entities information.
+ *
+ * @author Julien Fastré
+ */
+class AuthorizationHelper
+{
+ /**
+ *
+ * @var RoleHierarchyInterface
+ */
+ protected $roleHierarchy;
+
+ protected $existingRoles = array('CHILL_MASTER_ROLE', 'CHILL_PERSON_SEE',
+ 'CHILL_PERSON_UPDATE',);
+
+ public function __construct(RoleHierarchyInterface $roleHierarchy)
+ {
+ $this->roleHierarchy = $roleHierarchy;
+ }
+
+ /**
+ * Determines if a user is active on this center
+ *
+ * @param User $user
+ * @param Center $center
+ * @return bool
+ */
+ public function userCanReachCenter(User $user, Center $center)
+ {
+ foreach ($user->getGroupCenters() as $groupCenter) {
+ if ($center->getId() === $groupCenter->getCenter()->getId()) {
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ *
+ * Determines if the user has access to the given entity.
+ *
+ * if the entity implements Chill\MainBundle\Entity\HasScopeInterface,
+ * the scope is taken into account.
+ *
+ * @param User $user
+ * @param HasCenterInterface $entity the entity may also implement HasScopeInterface
+ * @param string|Role $attribute
+ * @return boolean true if the user has access
+ */
+ public function userHasAccess(User $user, HasCenterInterface $entity, $attribute)
+ {
+
+ $center = $entity->getCenter();
+
+ if (!$this->userCanReachCenter($user, $center)) {
+ return false;
+ }
+
+ $role = ($attribute instanceof Role) ? $attribute : new Role($attribute);
+
+ foreach ($user->getGroupCenters() as $groupCenter){
+ //filter on center
+ if ($groupCenter->getCenter()->getId() === $entity->getCenter()->getId()) {
+ //iterate on permissionGroup
+ foreach($groupCenter->getPermissionGroups() as $permissionGroup) {
+ //iterate on roleScopes
+ foreach($permissionGroup->getRoleScopes() as $roleScope) {
+ //check that the role allow to reach the required role
+ if ($this->isRoleReached($role,
+ new Role($roleScope->getRole()))){
+ //if yes, we have a right on something...
+ // perform check on scope if necessary
+ if ($entity instanceof HasScopeInterface) {
+ $scope = $entity->getScope();
+ if ($scope->getId() === $roleScope
+ ->getScope()->getId()) {
+ return true;
+ }
+ } else {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get reachable Centers for the given user, role,
+ * and optionnaly Scope
+ *
+ * @param User $user
+ * @param Role $role
+ * @param null|Scope $scope
+ * @return Center[]
+ */
+ public function getReachableCenters(User $user, Role $role, Scope $scope = null)
+ {
+ $centers = array();
+
+ foreach ($user->getGroupCenters() as $groupCenter){
+ //iterate on permissionGroup
+ foreach($groupCenter->getPermissionGroups() as $permissionGroup) {
+ //iterate on roleScopes
+ foreach($permissionGroup->getRoleScopes() as $roleScope) {
+ //check that the role is in the reachable roles
+ if ($this->isRoleReached($role,
+ new Role($roleScope->getRole()))) {
+ if ($scope === null) {
+ $centers[] = $groupCenter->getCenter();
+ break 2;
+ } else {
+ if ($scope->getId() == $roleScope->getScope()->getId()){
+ $centers[] = $groupCenter->getCenter();
+ break 2;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return $centers;
+ }
+
+ /**
+ * Return all reachable scope for a given user, center and role
+ *
+ * @param User $user
+ * @param Role $role
+ * @param Center $center
+ * @return Scope[]
+ */
+ public function getReachableScopes(User $user, Role $role, Center $center)
+ {
+ $scopes = array();
+
+ foreach ($user->getGroupCenters() as $groupCenter){
+ if ($center->getId() === $groupCenter->getCenter()->getId()) {
+ //iterate on permissionGroup
+ foreach($groupCenter->getPermissionGroups() as $permissionGroup) {
+ //iterate on roleScopes
+ foreach($permissionGroup->getRoleScopes() as $roleScope) {
+ //check that the role is in the reachable roles
+ if ($this->isRoleReached($role,
+ new Role($roleScope->getRole()))) {
+
+ $scopes[] = $roleScope->getScope();
+ }
+ }
+ }
+ }
+ }
+
+ return $scopes;
+ }
+
+ /**
+ * Test if a parent role may give access to a given child role
+ *
+ * @param Role $childRole The role we want to test if he is reachable
+ * @param Role $parentRole The role which should give access to $childRole
+ * @return boolean true if the child role is granted by parent role
+ */
+ protected function isRoleReached(Role $childRole, Role $parentRole)
+ {
+ $reachableRoles = $this->roleHierarchy
+ ->getReachableRoles([$parentRole]);
+
+ return in_array($childRole, $reachableRoles);
+ }
+}
diff --git a/Security/Authorization/ChillVoterInterface.php b/Security/Authorization/ChillVoterInterface.php
new file mode 100644
index 000000000..3e89d2595
--- /dev/null
+++ b/Security/Authorization/ChillVoterInterface.php
@@ -0,0 +1,30 @@
+
+ *
+ * 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\MainBundle\Security\Authorization;
+
+/**
+ * Provides methods for compiling voter and build admin role fields.
+ *
+ * @author Julien Fastré
+ */
+interface ChillVoterInterface
+{
+
+}
diff --git a/Test/PrepareCenterTrait.php b/Test/PrepareCenterTrait.php
new file mode 100644
index 000000000..39d530a29
--- /dev/null
+++ b/Test/PrepareCenterTrait.php
@@ -0,0 +1,59 @@
+
+ *
+ * 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\MainBundle\Test;
+
+
+/**
+ * A trait to prepare center
+ *
+ * **Usage :** You must set up trait with `setUpTrait` before use
+ * and use tearDownTrait after usage.
+ *
+ * @author Julien Fastré
+ * @codeCoverageIgnore
+ */
+trait PrepareCenterTrait
+{
+
+ private $centerProphet;
+
+ /**
+ * prepare a mocked center, with and id and name given
+ *
+ * @param int $id
+ * @param string $name
+ * @return \Chill\MainBundle\Entity\Center
+ */
+ protected function prepareCenter($id, $name)
+ {
+
+ if ($this->centerProphet === NULL) {
+ $this->centerProphet = new \Prophecy\Prophet();
+ }
+
+ $center = $this->centerProphet->prophesize();
+ $center->willExtend('\Chill\MainBundle\Entity\Center');
+ $center->getId()->willReturn($id);
+ $center->getName()->willReturn($name);
+
+ return $center->reveal();
+ }
+
+}
diff --git a/Test/PrepareScopeTrait.php b/Test/PrepareScopeTrait.php
new file mode 100644
index 000000000..288e0c39b
--- /dev/null
+++ b/Test/PrepareScopeTrait.php
@@ -0,0 +1,57 @@
+
+ *
+ * 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\MainBundle\Test;
+
+/**
+ * A trait to prepare center
+ *
+ * **Usage :** You must set up trait with `setUpTrait` before use
+ * and use tearDownTrait after usage.
+ *
+ *
+ * @author Julien Fastré
+ * @codeCoverageIgnore
+ */
+trait PrepareScopeTrait
+{
+ private $scopeProphet;
+
+ /**
+ * prepare a mocked center, with and id and name given
+ *
+ * @param int $id
+ * @param string $name
+ * @return \Chill\MainBundle\Entity\Center
+ */
+ protected function prepareScope($id, $name)
+ {
+
+ if ($this->scopeProphet === NULL) {
+ $this->scopeProphet = new \Prophecy\Prophet();
+ }
+
+ $scope = $this->scopeProphet->prophesize();
+ $scope->willExtend('\Chill\MainBundle\Entity\Scope');
+ $scope->getId()->willReturn($id);
+ $scope->getName()->willReturn($name);
+
+ return $scope->reveal();
+ }
+}
diff --git a/Test/PrepareUserTrait.php b/Test/PrepareUserTrait.php
new file mode 100644
index 000000000..83a18d2b2
--- /dev/null
+++ b/Test/PrepareUserTrait.php
@@ -0,0 +1,85 @@
+
+ *
+ * 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\MainBundle\Test;
+
+use Chill\MainBundle\Entity\User;
+use Chill\MainBundle\Entity\GroupCenter;
+use Chill\MainBundle\Entity\RoleScope;
+use Chill\MainBundle\Entity\PermissionsGroup;
+
+/**
+ * A trait to prepare user with permission. May be used
+ * within tests.
+ *
+ * **Usage : ** You must set up trait with `setUpTrait` before use
+ * and use tearDownTrait after usage.
+ *
+ *
+ * @author Julien Fastré
+ * @codeCoverageIgnore
+ */
+trait PrepareUserTrait
+{
+
+ /**
+ * prepare a user with correct permissions
+ *
+ * Example of permissions:
+ * ```
+ * array(
+ * array( 'center' => $centerA, 'permissionsGroup' => array(
+ * [ 'role' => 'CHILL_REPORT_SEE', 'scope' => $scopeA]
+ * ),
+ * array( 'center' => $centerB, 'permissionsGroup' => array(
+ * [ 'role' => 'CHILL_ACTIVITY_UPDATE', 'scope' => $scopeB]
+ * )
+ * )
+ * ```
+ * Scope must be an int. Scope created have this int as id, and the
+ * int converted to string as name.
+ *
+ *
+ * @param array $permissions an array of permissions, with key 'center' for the center and key 'attrs' for an array of ['role' => (string), 'scope' => (int)]
+ * @return User
+ * @throws \LogicException if the trait is not set up
+ */
+ protected function prepareUser(array $permissions)
+ {
+ $user = new User();
+
+ foreach ($permissions as $permission) {
+ $groupCenter = (new GroupCenter())
+ ->setCenter($permission['center']);
+ $permissionGroup = new PermissionsGroup();
+ foreach ($permission['permissionsGroup'] as $pg) {
+
+ $roleScope = (new RoleScope())
+ ->setRole($pg['role'])
+ ->setScope($pg['scope']);
+ ;
+ $permissionGroup->addRoleScope($roleScope);
+ $groupCenter->addPermissionGroup($permissionGroup);
+ }
+ $user->addGroupCenter($groupCenter);
+ }
+
+ return $user;
+ }
+}
diff --git a/Test/ProphecyTrait.php b/Test/ProphecyTrait.php
new file mode 100644
index 000000000..d471dd624
--- /dev/null
+++ b/Test/ProphecyTrait.php
@@ -0,0 +1,53 @@
+
+ *
+ * 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\MainBundle\Test;
+
+/**
+ * a trait to prepare prophecy
+ *
+ * **Usage : ** You must set up trait with `setUpTrait` before use
+ * and use tearDownTrait after usage.
+ *
+ * @author Julien Fastré
+ * @codeCoverageIgnore
+ */
+trait ProphecyTrait
+{
+
+ /**
+ *
+ * @var \Prophecy\Prophet()
+ */
+ private $prophet;
+
+ /**
+ *
+ * @return \Prophecy\Prophet
+ */
+ public function getProphet()
+ {
+ if ($this->prophet === NULL) {
+ $this->prophet = new \Prophecy\Prophet();
+ }
+
+ return $this->prophet;
+ }
+
+}
diff --git a/Tests/Fixtures/App/config/config_test.yml b/Tests/Fixtures/App/config/config_test.yml
index 652a26975..18856b3ac 100644
--- a/Tests/Fixtures/App/config/config_test.yml
+++ b/Tests/Fixtures/App/config/config_test.yml
@@ -7,6 +7,8 @@ framework:
storage_id: session.storage.filesystem
security:
+ role_hierarchy:
+ CHILL_MASTER_ROLE: [CHILL_INHERITED_ROLE_1]
providers:
users:
entity:
diff --git a/Tests/Fixtures/App/config/routing.yml b/Tests/Fixtures/App/config/routing.yml
index 2333ea6e3..2da2876a4 100644
--- a/Tests/Fixtures/App/config/routing.yml
+++ b/Tests/Fixtures/App/config/routing.yml
@@ -2,7 +2,7 @@ hello_bundle:
resource: "@ChillMainBundle/Resources/config/routing.yml"
chill_main_dummy_0:
- pattern: /dummy
+ path: /dummy
defaults: { _controller: ChillMainBundle:Default:index }
options:
menus:
@@ -15,7 +15,7 @@ chill_main_dummy_0:
label: test dummy 1
chill_main_dummy_1:
- pattern: /dummy1
+ path: /dummy1
defaults: { _controller: ChillMainBundle:Default:index }
options:
menus:
@@ -25,7 +25,7 @@ chill_main_dummy_1:
helper: 'great helper'
chill_main_dummy_2:
- pattern: /dummy2/{param}
+ path: /dummy2/{param}
defaults: {_controller: ChillMainBundle:Default:index }
options:
menus:
diff --git a/Tests/Form/Type/CenterTypeTest.php b/Tests/Form/Type/CenterTypeTest.php
new file mode 100644
index 000000000..8c3d2f290
--- /dev/null
+++ b/Tests/Form/Type/CenterTypeTest.php
@@ -0,0 +1,153 @@
+
+ *
+ * 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\MainBundle\Form\Type;
+
+use Symfony\Component\Form\Test\TypeTestCase;
+use Chill\MainBundle\Form\Type\CenterType;
+use Chill\MainBundle\Entity\User;
+use Chill\MainBundle\Entity\GroupCenter;
+
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class CenterTypeTest extends TypeTestCase
+{
+ /**
+ * Test that a user which can reach only one center
+ * render as an hidden field
+ */
+ public function testUserCanReachSingleCenter()
+ {
+ //prepare user
+ $center = $this->prepareCenter(1, 'center');
+ $groupCenter = (new GroupCenter())
+ ->setCenter($center)
+ ;
+ $user = (new User())
+ ->addGroupCenter($groupCenter);
+
+ $type = $this->prepareType($user);
+
+ $this->assertEquals('hidden', $type->getParent());
+ }
+
+ /**
+ * Test that a user which can reach only one center
+ * render as an hidden field
+ */
+ public function testUserCanReachMultipleSameCenter()
+ {
+ //prepare user
+ $center = $this->prepareCenter(1, 'center');
+ $groupCenterA = (new GroupCenter())
+ ->setCenter($center)
+ ;
+ $groupCenterB = (new GroupCenter())
+ ->setCenter($center)
+ ;
+ $user = (new User())
+ ->addGroupCenter($groupCenterA)
+ ->addGroupCenter($groupCenterB);
+
+ $type = $this->prepareType($user);
+
+ $this->assertEquals('hidden', $type->getParent());
+ }
+
+ /**
+ * Test that a user which can reach multiple center
+ * make CenterType render as "entity" type.
+ */
+ public function testUserCanReachMultipleCenters()
+ {
+ //prepare user
+ $centerA = $this->prepareCenter(1, 'centerA');
+ $centerB = $this->prepareCenter(2, 'centerB');
+ $groupCenterA = (new GroupCenter())
+ ->setCenter($centerA)
+ ;
+ $groupCenterB = (new GroupCenter())
+ ->setCenter($centerB)
+ ;
+ $user = (new User())
+ ->addGroupCenter($groupCenterA)
+ ->addGroupCenter($groupCenterB)
+ ;
+
+ $type = $this->prepareType($user);
+
+ $this->assertEquals('entity', $type->getParent());
+ }
+
+ /**
+ * prepare a mocked center, with and id and name given
+ *
+ * @param int $id
+ * @param string $name
+ * @return \Chill\MainBundle\Entity\Center
+ */
+ private function prepareCenter($id, $name)
+ {
+ $prophet = new \Prophecy\Prophet;
+
+ $prophecyCenter = $prophet->prophesize();
+ $prophecyCenter->willExtend('\Chill\MainBundle\Entity\Center');
+ $prophecyCenter->getId()->willReturn($id);
+ $prophecyCenter->getName()->willReturn($name);
+
+ return $prophecyCenter->reveal();
+ }
+
+
+ /**
+ * prepare the type with mocked center transformer and token storage
+ *
+ * @param User $user the user for wich the form will be prepared
+ * @return CenterType
+ */
+ private function prepareType(User $user)
+ {
+ $prophet = new \Prophecy\Prophet;
+
+ //create a center transformer
+ $centerTransformerProphecy = $prophet->prophesize();
+ $centerTransformerProphecy
+ ->willExtend('Chill\MainBundle\Form\Type\DataTransformer\CenterTransformer');
+ $transformer = $centerTransformerProphecy->reveal();
+
+ $tokenProphecy = $prophet->prophesize();
+ $tokenProphecy
+ ->willImplement('\Symfony\Component\Security\Core\Authentication\Token\TokenInterface');
+ $tokenProphecy->getUser()->willReturn($user);
+ $token = $tokenProphecy->reveal();
+
+ $tokenStorageProphecy = $prophet->prophesize();
+ $tokenStorageProphecy
+ ->willExtend('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage');
+ $tokenStorageProphecy->getToken()->willReturn($token);
+ $tokenStorage = $tokenStorageProphecy->reveal();
+
+ return new CenterType($tokenStorage, $transformer);
+ }
+
+}
diff --git a/Tests/Security/Authorization/AuthorizationHelperTest.php b/Tests/Security/Authorization/AuthorizationHelperTest.php
new file mode 100644
index 000000000..abfc3c754
--- /dev/null
+++ b/Tests/Security/Authorization/AuthorizationHelperTest.php
@@ -0,0 +1,448 @@
+
+ *
+ * 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\MainBundle\Tests\Security\Authorization;
+
+use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
+use Chill\MainBundle\Test\PrepareUserTrait;
+use Chill\MainBundle\Test\PrepareCenterTrait;
+use Chill\MainBundle\Test\PrepareScopeTrait;
+use Chill\MainBundle\Test\ProphecyTrait;
+use Chill\MainBundle\Entity\User;
+use Symfony\Component\Security\Core\Role\Role;
+use Chill\MainBundle\Entity\Scope;
+use Chill\MainBundle\Entity\Center;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class AuthorizationHelperTest extends KernelTestCase
+{
+
+ use PrepareUserTrait, PrepareCenterTrait, PrepareScopeTrait, ProphecyTrait;
+
+ public function setUp()
+ {
+ static::bootKernel();
+ }
+
+ /**
+ *
+ * @return \Chill\MainBundle\Security\Authorization\AuthorizationHelper
+ */
+ private function getAuthorizationHelper()
+ {
+ return static::$kernel->getContainer()
+ ->get('chill.main.security.authorization.helper')
+ ;
+ }
+
+ /**
+ * Test function userCanReach of helper.
+ *
+ * A user can reach center => the function should return true.
+ */
+ public function testUserCanReachCenter_UserShouldReach()
+ {
+ $center = $this->prepareCenter(1, 'center');
+ $scope = $this->prepareScope(1, 'default');
+ $user = $this->prepareUser(array(
+ array(
+ 'center' => $center, 'permissionsGroup' => array(
+ ['scope' => $scope, 'role' => 'ANY_ROLE']
+ )
+ )
+ ));
+ $helper = $this->getAuthorizationHelper();
+
+ $this->assertTrue($helper->userCanReachCenter($user, $center));
+ }
+
+ /**
+ * Test function userCanReach of helper
+ *
+ * A user can not reachcenter =>W the function should return false
+ */
+ public function testUserCanReachCenter_UserShouldNotReach()
+ {
+ $centerA = $this->prepareCenter(1, 'center');
+ $centerB = $this->prepareCenter(2, 'centerB');
+ $scope = $this->prepareScope(1, 'default');
+ $user = $this->prepareUser(array(
+ array(
+ 'center' => $centerA, 'permissionsGroup' => array(
+ ['scope' => $scope, 'role' => 'ANY_ROLE']
+ )
+ )
+ ));
+ $helper = $this->getAuthorizationHelper();
+
+ $this->assertFalse($helper->userCanReachCenter($user, $centerB));
+
+ }
+
+ public function testUserHasAccess_shouldHaveAccess_EntityWithoutScope()
+ {
+ $center = $this->prepareCenter(1, 'center');
+ $scope = $this->prepareScope(1, 'default');
+ $user = $this->prepareUser(array(
+ array(
+ 'center' => $center, 'permissionsGroup' => array(
+ ['scope' => $scope, 'role' => 'CHILL_ROLE']
+ )
+ )
+ ));
+ $helper = $this->getAuthorizationHelper();
+ $entity = $this->getProphet()->prophesize();
+ $entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface');
+ $entity->getCenter()->willReturn($center);
+
+ $this->assertTrue($helper->userHasAccess($user, $entity->reveal(),
+ 'CHILL_ROLE'));
+ }
+
+ public function testUserHasAccess_ShouldHaveAccessWithInheritance_EntityWithoutScope()
+ {
+ $center = $this->prepareCenter(1, 'center');
+ $scope = $this->prepareScope(1, 'default');
+ $user = $this->prepareUser(array(
+ array(
+ 'center' => $center, 'permissionsGroup' => array(
+ ['scope' => $scope, 'role' => 'CHILL_MASTER_ROLE']
+ )
+ )
+ ));
+
+ $helper = $this->getAuthorizationHelper();
+ $entity = $this->getProphet()->prophesize();
+ $entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface');
+ $entity->getCenter()->willReturn($center);
+
+ $this->assertTrue($helper->userHasAccess($user, $entity->reveal(),
+ 'CHILL_INHERITED_ROLE_1'));
+ }
+
+
+ public function testuserHasAccess_UserHasNoRole_EntityWithoutScope()
+ {
+ $center = $this->prepareCenter(1, 'center');
+ $scope = $this->prepareScope(1, 'default');
+ $user = $this->prepareUser(array(
+ array(
+ 'center' => $center, 'permissionsGroup' => array(
+ ['scope' => $scope, 'role' => 'ANY_ROLE']
+ )
+ )
+ ));
+ $helper = $this->getAuthorizationHelper();
+ $entity = $this->getProphet()->prophesize();
+ $entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface');
+ $entity->getCenter()->willReturn($center);
+
+ $this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE'));
+ }
+
+ /**
+ * test that a user has no access on a entity, but is granted on the same role
+ * on another center
+ */
+ public function testUserHasAccess_userHasNoRole_UserHasRoleOnAnotherCenter_EntityWithoutScope()
+ {
+ $centerA = $this->prepareCenter(1, 'center');
+ $centerB = $this->prepareCenter(2, 'centerB');
+ $scope = $this->prepareScope(1, 'default');
+ $user = $this->prepareUser(array(
+ array(
+ 'center' => $centerA, 'permissionsGroup' => array(
+ ['scope' => $scope, 'role' => 'ANY_ROLE']
+ ),
+ array(
+ 'centerB' => $centerB, 'permissionsGroup' => array(
+ ['scope' => $scope, 'role' => 'ANY_ROLE'],
+ ['scope' => $scope, 'role' => 'CHILL_ROLE']
+ )
+ )
+ )
+ ));
+ $helper = $this->getAuthorizationHelper();
+ $entity = $this->getProphet()->prophesize();
+ $entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface');
+ $entity->getCenter()->willReturn($centerA);
+
+ $this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE'));
+ }
+
+ public function testtestUserHasAccess_UserShouldHaveAccess_EntityWithScope()
+ {
+ $center = $this->prepareCenter(1, 'center');
+ $scope = $this->prepareScope(1, 'default');
+ $user = $this->prepareUser(array(
+ array(
+ 'center' => $center, 'permissionsGroup' => array(
+ ['scope' => $scope, 'role' => 'CHILL_ROLE']
+ )
+ )
+ ));
+ $helper = $this->getAuthorizationHelper();
+ $entity = $this->getProphet()->prophesize();
+ $entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface');
+ $entity->willImplement('\Chill\MainBundle\Entity\HasScopeInterface');
+ $entity->getCenter()->willReturn($center);
+ $entity->getScope()->willReturn($scope);
+
+ $this->assertTrue($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE'));
+ }
+
+ public function testUserHasAccess_UserHasNoRole_EntityWithScope()
+ {
+ $center = $this->prepareCenter(1, 'center');
+ $scope = $this->prepareScope(1, 'default');
+ $user = $this->prepareUser(array(
+ array(
+ 'center' => $center, 'permissionsGroup' => array(
+ ['scope' => $scope, 'role' => 'CHILL_ROLE']
+ )
+ )
+ ));
+ $helper = $this->getAuthorizationHelper();
+ $entity = $this->getProphet()->prophesize();
+ $entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface');
+ $entity->willImplement('\Chill\MainBundle\Entity\HasScopeInterface');
+ $entity->getCenter()->willReturn($center);
+ $entity->getScope()->willReturn($scope);
+
+ $this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'ANOTHER_ROLE'));
+ }
+
+ public function testUserHasAccess_UserHasNoCenter_EntityWithScope()
+ {
+ $centerA = $this->prepareCenter(1, 'center'); //the user will have this center
+ $centerB = $this->prepareCenter(2, 'centerB'); //the entity will have another center
+ $scope = $this->prepareScope(1, 'default');
+ $user = $this->prepareUser(array(
+ array(
+ 'center' => $centerA, 'permissionsGroup' => array(
+ ['scope' => $scope, 'role' => 'CHILL_ROLE']
+ )
+ )
+ ));
+ $helper = $this->getAuthorizationHelper();
+ $entity = $this->getProphet()->prophesize();
+ $entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface');
+ $entity->willImplement('\Chill\MainBundle\Entity\HasScopeInterface');
+ $entity->getCenter()->willReturn($centerB);
+ $entity->getScope()->willReturn($scope);
+
+ $this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE'));
+ }
+
+ public function testUserHasAccess_UserHasNoScope_EntityWithScope()
+ {
+ $center = $this->prepareCenter(1, 'center');
+ $scopeA = $this->prepareScope(1, 'default'); //the entity will have this scope
+ $scopeB = $this->prepareScope(2, 'other'); //the user will be granted this scope
+ $user = $this->prepareUser(array(
+ array(
+ 'center' => $center, 'permissionsGroup' => array(
+ ['scope' => $scopeB, 'role' => 'CHILL_ROLE']
+ )
+ )
+ ));
+ $helper = $this->getAuthorizationHelper();
+ $entity = $this->getProphet()->prophesize();
+ $entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface');
+ $entity->willImplement('\Chill\MainBundle\Entity\HasScopeInterface');
+ $entity->getCenter()->willReturn($center);
+ $entity->getScope()->willReturn($scopeA);
+
+ $this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE'));
+ }
+
+ /**
+ *
+ * @dataProvider dataProvider_getReachableCenters
+ * @param Center $shouldHaveCenter
+ * @param User $user
+ * @param Role $role
+ * @param Scope $scope
+ */
+ public function testGetReachableCenters($test, $result, $msg)
+ {
+ $this->assertEquals($test, $result, $msg);
+ }
+
+ public function dataProvider_getReachableCenters()
+ {
+ $this->setUp();
+ $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_ROLE_1'],
+ ['scope' => $scopeA, 'role' => 'CHILL_ROLE_2']
+ )
+ ),
+ array(
+ 'center' => $centerB,
+ 'permissionsGroup' => array(
+ ['scope' => $scopeA, 'role' => 'CHILL_ROLE_2'],
+ ['scope' => $scopeC, 'role' => 'CHILL_ROLE_2']
+ )
+ )
+
+ ));
+
+ $ah = $this->getAuthorizationHelper();
+
+ return array(
+ // without scopes
+ array(
+ true,
+ in_array($centerA, $ah->getReachableCenters($userA,
+ new Role('CHILL_ROLE_1'), null)),
+ 'center A should be available for userA, with role 1 '
+ ),
+ array(
+ true,
+ in_array($centerA, $ah->getReachableCenters($userA,
+ new Role('CHILL_ROLE_2'), null)),
+ 'center A should be available for userA, with role 2 '
+ ),
+ array(
+ true,
+ in_array($centerB, $ah->getReachableCenters($userA,
+ new Role('CHILL_ROLE_2'), null)),
+ 'center A should be available for userA, with role 2 '
+ ),
+ array(
+ false,
+ in_array($centerB, $ah->getReachableCenters($userA,
+ new Role('CHILL_ROLE_1'), null)),
+ 'center B should NOT be available for userA, with role 1 '
+ ),
+ // with scope
+ array(
+ true,
+ in_array($centerA, $ah->getReachableCenters($userA,
+ new Role('CHILL_ROLE_1'), $scopeB)),
+ 'center A should be available for userA, with role 1, scopeC '
+ ),
+ array(
+ false,
+ in_array($centerA, $ah->getReachableCenters($userA,
+ new Role('CHILL_ROLE_2'), $scopeC)),
+ 'center A should NOT be available for userA, with role 2, scopeA '
+ ),
+ array(
+ true,
+ in_array($centerB, $ah->getReachableCenters($userA,
+ new Role('CHILL_ROLE_2'), $scopeA)),
+ 'center B should be available for userA, with role 2, scopeA '
+ ),
+ );
+
+ }
+
+ /**
+ *
+ * @dataProvider dataProvider_getReachableScopes
+ * @param boolean $expectedResult
+ * @param Scope $testedScope
+ * @param User $user
+ * @param Role $role
+ * @param Center $center
+ * @param string $message
+ */
+ public function testGetReachableScopes($expectedResult, Scope $testedScope,
+ User $user, Role $role, Center $center, $message)
+ {
+ $reachableScopes = $this->getAuthorizationHelper()
+ ->getReachableScopes($user, $role, $center);
+
+ $this->assertEquals($expectedResult, in_array($testedScope, $reachableScopes),
+ $message);
+ }
+
+ public function dataProvider_getReachableScopes()
+ {
+ $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_ROLE_1'],
+ ['scope' => $scopeA, 'role' => 'CHILL_ROLE_2']
+ )
+ ),
+ array(
+ 'center' => $centerB,
+ 'permissionsGroup' => array(
+ ['scope' => $scopeA, 'role' => 'CHILL_ROLE_2'],
+ ['scope' => $scopeC, 'role' => 'CHILL_ROLE_2'],
+ ['scope' => $scopeB, 'role' => 'CHILL_ROLE_2']
+ )
+ )
+
+ ));
+
+ return array(
+ array(
+ true,
+ $scopeA,
+ $userA,
+ new Role('CHILL_ROLE_2'),
+ $centerA,
+ "Assert that a scope is found within accessible scopes"
+ ),
+ array(
+ false,
+ $scopeB,
+ $userA,
+ new Role('CHILL_ROLE_2'),
+ $centerA,
+ "Assert that a scope not reachable is NOT found within accessible scopes"
+ ),
+ array(
+ false,
+ $scopeB,
+ $userA,
+ new Role('CHILL_ROLE_1'),
+ $centerB,
+ "Assert that a scope not reachable is not found within accessible scopes."
+ . " Trying on filter centering"
+ )
+ );
+ }
+
+
+
+}
diff --git a/Tests/TestHelper.php b/Tests/TestHelper.php
index a9ca90e71..a969eeeb3 100644
--- a/Tests/TestHelper.php
+++ b/Tests/TestHelper.php
@@ -36,11 +36,12 @@ class TestHelper
* @param WebTestCase $testCase
* @return \Symfony\Component\BrowserKit\Client authenticated client
*/
- public static function getAuthenticatedClientOptions()
+ public static function getAuthenticatedClientOptions($username = 'center a_social',
+ $password = 'password')
{
return array(
- 'PHP_AUTH_USER' => 'center a_social',
- 'PHP_AUTH_PW' => 'password',
+ 'PHP_AUTH_USER' => $username,
+ 'PHP_AUTH_PW' => $password,
);
}
}
diff --git a/Timeline/TimelineBuilder.php b/Timeline/TimelineBuilder.php
index 2923c13a1..3e534a280 100644
--- a/Timeline/TimelineBuilder.php
+++ b/Timeline/TimelineBuilder.php
@@ -150,8 +150,8 @@ class TimelineBuilder implements ContainerAwareInterface
$data = $provider->fetchQuery($context, $args);
return sprintf(
- 'SELECT "%s" AS id, '
- . '"%s" AS "date", '
+ 'SELECT %s AS id, '
+ . '%s AS "date", '
. "'%s' AS type "
. 'FROM %s '
. 'WHERE %s',
diff --git a/composer.json b/composer.json
index 5d22ebbf1..b88eb9799 100644
--- a/composer.json
+++ b/composer.json
@@ -3,8 +3,14 @@
"license": "AGPL-3.0",
"type": "symfony-bundle",
"description": "The main bundle for the Chill App",
- "keywords" : ["chill", "social work"],
- "homepage" : "https://github.com/Chill-project/Main",
+ "keywords" : ["chill", "social work", "software for social service"],
+ "homepage" : "http://chill.social",
+ "support": {
+ "email": "dev@lists.chill.social",
+ "issues": "https://redmine.champs-libres.coop/projects/chillmain/issues",
+ "sources": "https://github.com/chill-project/main",
+ "docs": "http://chill.readthedocs.org"
+ },
"autoload": {
"psr-4": { "Chill\\MainBundle\\": "" }
},
@@ -20,9 +26,9 @@
"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",
@@ -34,7 +40,8 @@
"require-dev": {
"symfony/dom-crawler": "2.5",
"doctrine/doctrine-fixtures-bundle": "~2.2",
- "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 3358647e3..360d89ccd 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -18,5 +18,6 @@
+