Merge branch add_acl ref #263

Squashed commit of the following:

commit e1236655e1514fd207818aeb57789eca0d949453
Merge: c0b349b fb15bd3
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Tue Jun 30 09:51:35 2015 +0200

    Merge remote-tracking branch 'origin/master' into add_acl

    In order to prepare merging of add_acl to master

    Conflicts:
    	composer.json

commit c0b349bb5f31fe79c84f82d4dd6658c9e90ef728
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Tue Jun 30 00:40:13 2015 +0200

    fix infos in composer.json

    [ci skip]

commit 106bbf56a5060efd2a89232f278692eeb57e3092
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Mon Jun 29 22:58:48 2015 +0200

    add username and password to client auth options

    [ci skip]

commit c4990972711850616aa1426394884223d63b504f
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Mon Jun 29 22:22:20 2015 +0200

    fix quoting in timelinebuilder

commit 1db7cbea5a0fb0e8d396f8c9d8dc01240b47e96f
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Thu Jun 25 22:43:46 2015 +0200

    remove data_class to allow edit form

commit 7c999279310b5e2b9ecef8a9b5001c71910a822d
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Jun 24 22:33:42 2015 +0200

    fix doc for AppendScopeChoiceTypeTrait

commit 839d4c43bf6f463e705b47d5dbc0cdf7853db0b6
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Jun 24 22:30:13 2015 +0200

    refactor: move scope field to a trait

    Example usage :

      ```
      class AbcType extends Symfony\Component\Form\AbstractType
      {
          use AppendScopeChoiceTypeTrait;
          protected $authorizationHelper;
          protected $translatableStringHelper;
          protected $user;

          public function __construct(AuthorizationHelper $helper,
            TokenStorageInterface $tokenStorage,
            TranslatableStringHelper $translatableStringHelper)
          {
              $this->authorizationHelper = $helper;
              $this->user = $tokenStorage->getToken()->getUser();
              $this->translatableStringHelper = $translatableStringHelper;
          }

          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);
           }

           public function configureOptions(OptionsResolver $resolver)
           {
                   // ... add your options

              // add an option 'role' and 'center' to your form (optional)
              $this->appendScopeChoicesOptions($resolver);
           }

       }
      ```

    [ci skip]

commit a1ac530f343146eee12b5982e4e6fceb6dc1da66
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Jun 24 00:24:30 2015 +0200

    remove unused statements

commit 74f0a4ce5dfdfa4f8fc39ce1cfe726d945e9bdec
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Jun 24 00:09:47 2015 +0200

    add missing unused statement

commit b3a49f2de8758b51c57af6ff0437f30b2fdef72e
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Tue Jun 23 23:58:57 2015 +0200

    remove ScopeType strategy and fix autorization helper

commit aaa70b5eeae76b0950110f51b8274e08bef10576
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Tue Jun 23 21:35:06 2015 +0200

    create scope type

commit 8f5b2b23c9448b8c8e752e46bdd7f9f721054a98
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Fri Jun 19 18:13:54 2015 +0200

    add getREachableCenter method + test on Autho.Helper

commit ab2ccb8c287f9aef12912ea9b7f5dc4998209d77
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Fri Jun 19 17:27:22 2015 +0200

    remove debug information [ci skip]

commit 9d481c07966a5d769d1418bb366bd6b1ea9d4f76
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Fri Jun 19 17:24:57 2015 +0200

    fix role hierarchy

    Now we test effectively that a user has access, not that a role may grant access

commit f4b17d0ae398fd6c26d4907377761e9f15d36a90
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Thu Jun 18 02:08:55 2015 +0200

    fix trait conflicts

    traits does not share the same instance of prophet any more

commit baac8ce97ba0acd4c4fe6f03278f7a0d30da28ad
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Thu Jun 18 01:56:39 2015 +0200

    try to fix trait error in zend strict mode (used by travis)

commit 7b9fa4b14bde72c7036a29c03a0c6cca3c7e6c74
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Thu Jun 18 01:24:09 2015 +0200

    fix error on trait hierarchy (should be)

commit f8b3451089f7017653bf8d280b640356df6a1841
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Thu Jun 18 01:03:04 2015 +0200

    add userHasAccess method to AuthorizationHelper

    This method may be used in voter to check access.

    It supports both hasCenterInterface and HasScopeInterface and check all
    required permission.

commit 9ad9f624a0aa73ca2e639fd68fe4d5559da2bbd7
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Thu Jun 18 01:01:16 2015 +0200

    add utilities to generate prophesized entities

    entities
    - User (with permissions)
    - Center
    - Scope
    may be  generated by trait/methods

    This ease test writing about acl

commit 16008b9e64bb7f551319abea494db1c1c5a12b82
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Fri Jun 12 22:42:46 2015 +0200

    add test to CenterType

commit 55e2c64aba9714caf09df8dbc9595980826b296a
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Fri Jun 12 20:50:05 2015 +0200

    first implementation of test on CenterType

    [TRANSFER][ci skip]

commit 548fb24927cc470794a51d81f1ba6af52237363f
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Jun 10 21:15:52 2015 +0200

    add center type

    the center type is hidden if the current user can reach only one center,
    and is `entity` type if the user can reach multiple centers

commit 024e3ef8d969d25560406864d86768a260ef4402
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Mon Jun 8 00:28:51 2015 +0200

    add first impl of Access Model

    - first classes and interfaces
    - authorizationHelper + test
    - rewrite loadUser to have multi-center

commit bc5ae70c83c39a0a738e78313e33020fc284f456
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Sun Jun 7 22:11:13 2015 +0200

    make deprecations message not fail tests

commit ab9308ed62e45171e9d355ec033b0862b9274e07
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Sat Jun 6 10:19:19 2015 +0200

    introducting phpunit-bridge to handle deprecation warnings

commit 5b7a43c4d058af58578c30120670c5be5b11cfd8
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Fri Jun 5 12:49:23 2015 +0200

    fix options resolver deprecation

commit a5b4e5743f790c16dfd04e5068c5ecca9c2b1583
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Fri Jun 5 12:36:17 2015 +0200

    remove warning about deprecation in phpunit

commit 56621767936df1ea1293c49ae06380d7735c8d6c
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Fri Jun 5 11:59:50 2015 +0200

    fix pattern deprecation in routing/test

    pattern=> path

commit 17d40fc5294b245d7377572a8c918abab2484985
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Fri Jun 5 11:40:59 2015 +0200

    fix deprecation of pattern in routing

    pattern => path in routing

commit 6a33752c6439bd9dbc707df010ce1d8765eeb5fe
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Fri Jun 5 11:21:18 2015 +0200

    fix twig.form.resources deprecation

    the new key is twig.form_themes

commit adf03eb819f2d443f87d7b744f59a5adf51d2b31
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Fri Jun 5 10:56:51 2015 +0200

    switch to symfony 2.7 [ci-skip]
This commit is contained in:
Julien Fastré 2015-06-30 14:38:08 +02:00
parent fb15bd336c
commit 8ac41a15b6
32 changed files with 1739 additions and 50 deletions

View File

@ -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();
}

View File

@ -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);

View File

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

View File

@ -76,6 +76,11 @@ class Center
$this->groupCenters->add($groupCenter);
return $this;
}
public function __toString()
{
return $this->getName();
}
}

View File

@ -2,8 +2,6 @@
namespace Chill\MainBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Country
*/

View File

@ -0,0 +1,36 @@
<?php
/*
* Copyright (C) 2015 Julien Fastré <julien.fastre@champs-libres.coop>
*
* 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 <http://www.gnu.org/licenses/>.
*/
namespace Chill\MainBundle\Entity;
/**
* Interface for entities which may be linked to a center
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
interface HasCenterInterface
{
/**
* the linked center
*
* @return Center
*/
public function getCenter();
}

View File

@ -0,0 +1,35 @@
<?php
/*
* Copyright (C) 2015 Julien Fastré <julien.fastre@champs-libres.coop>
*
* 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 <http://www.gnu.org/licenses/>.
*/
namespace Chill\MainBundle\Entity;
/**
* Interface for entities which have a scop
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
interface HasScopeInterface
{
/**
* Return the linked scope
*
* @return Scope
*/
public function getScope();
}

View File

@ -0,0 +1,153 @@
<?php
/*
* Chill is a software for social workers
* Copyright (C) 2015 Champs Libres <info@champs-libres.coop>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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é <julien.fastre@champs-libres.coop>
* @author Champs Libres <info@champs-libres.coop>
*/
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'
))
;
}
}

138
Form/Type/CenterType.php Normal file
View File

@ -0,0 +1,138 @@
<?php
/*
* Copyright (C) 2015 Julien Fastré <julien.fastre@champs-libres.coop>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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é <julien.fastre@champs-libres.coop>
*/
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;
}
}
}
}

View File

@ -0,0 +1,70 @@
<?php
/*
* Copyright (C) 2015 Julien Fastré <julien.fastre@champs-libres.coop>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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é <julien.fastre@champs-libres.coop>
*/
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();
}
}

View File

@ -0,0 +1,77 @@
<?php
/*
* Copyright (C) 2015 Julien Fastré <julien.fastre@champs-libres.coop>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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é <julien.fastre@champs-libres.coop>
*/
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;
}
}

View File

@ -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 '))
);
}

View File

@ -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;

View File

@ -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;

View File

@ -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:

View File

@ -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"

View File

@ -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;

View File

@ -0,0 +1,36 @@
<?php
/*
* Copyright (C) 2015 Julien Fastré <julien.fastre@champs-libres.coop>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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é <julien.fastre@champs-libres.coop>
*/
abstract class AbstractChillVoter extends AbstractVoter implements ChillVoterInterface
{
}

View File

@ -0,0 +1,209 @@
<?php
/*
* Copyright (C) 2015 Julien Fastré <julien.fastre@champs-libres.coop>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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é <julien.fastre@champs-libres.coop>
*/
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);
}
}

View File

@ -0,0 +1,30 @@
<?php
/*
* Copyright (C) 2015 Julien Fastré <julien.fastre@champs-libres.coop>
*
* 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 <http://www.gnu.org/licenses/>.
*/
namespace Chill\MainBundle\Security\Authorization;
/**
* Provides methods for compiling voter and build admin role fields.
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
interface ChillVoterInterface
{
}

View File

@ -0,0 +1,59 @@
<?php
/*
* Copyright (C) 2015 Julien Fastré <julien.fastre@champs-libres.coop>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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é <julien.fastre@champs-libres.coop>
* @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();
}
}

View File

@ -0,0 +1,57 @@
<?php
/*
* Copyright (C) 2015 Julien Fastré <julien.fastre@champs-libres.coop>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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é <julien.fastre@champs-libres.coop>
* @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();
}
}

85
Test/PrepareUserTrait.php Normal file
View File

@ -0,0 +1,85 @@
<?php
/*
* Copyright (C) 2015 Julien Fastré <julien.fastre@champs-libres.coop>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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é <julien.fastre@champs-libres.coop>
* @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;
}
}

53
Test/ProphecyTrait.php Normal file
View File

@ -0,0 +1,53 @@
<?php
/*
* Copyright (C) 2015 Julien Fastré <julien.fastre@champs-libres.coop>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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é <julien.fastre@champs-libres.coop>
* @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;
}
}

View File

@ -7,6 +7,8 @@ framework:
storage_id: session.storage.filesystem
security:
role_hierarchy:
CHILL_MASTER_ROLE: [CHILL_INHERITED_ROLE_1]
providers:
users:
entity:

View File

@ -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:

View File

@ -0,0 +1,153 @@
<?php
/*
* Copyright (C) 2015 Julien Fastré <julien.fastre@champs-libres.coop>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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é <julien.fastre@champs-libres.coop>
*/
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);
}
}

View File

@ -0,0 +1,448 @@
<?php
/*
* Copyright (C) 2015 Julien Fastré <julien.fastre@champs-libres.coop>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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é <julien.fastre@champs-libres.coop>
*/
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"
)
);
}
}

View File

@ -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,
);
}
}

View File

@ -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',

View File

@ -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": [

View File

@ -18,5 +18,6 @@
</filter>
<php>
<server name="KERNEL_DIR" value="./Tests/Fixtures/App/" />
<env name="SYMFONY_DEPRECATIONS_HELPER" value="weak" />
</php>
</phpunit>