apply new role on accompanying period

This commit is contained in:
Julien Fastré 2021-09-17 13:57:45 +02:00
parent cf40f38463
commit 74598ee926
19 changed files with 281 additions and 195 deletions

View File

@ -146,14 +146,7 @@ class ScopePickerType extends AbstractType
->setParameter('center', $center->getId()) ->setParameter('center', $center->getId())
// role constraints // role constraints
->andWhere($qb->expr()->in('rs.role', ':roles')) ->andWhere($qb->expr()->in('rs.role', ':roles'))
->setParameter( ->setParameter('roles', $roles)
'roles', \array_map(
function (Role $role) {
return $role->getRole();
},
$roles
)
)
// user contraint // user contraint
->andWhere(':user MEMBER OF gc.users') ->andWhere(':user MEMBER OF gc.users')
->setParameter('user', $this->tokenStorage->getToken()->getUser()); ->setParameter('user', $this->tokenStorage->getToken()->getUser());

View File

@ -269,16 +269,12 @@ class AuthorizationHelper
/** /**
* *
* @param Role $role * @return User[]
* @param Center $center
* @param Scope $circle
* @return Users
*/ */
public function findUsersReaching(Role $role, Center $center, Scope $circle = null) public function findUsersReaching(string $role, Center $center, Scope $circle = null): array
{ {
$parents = $this->getParentRoles($role); $parents = $this->getParentRoles($role);
$parents[] = $role; $parents[] = $role;
$parentRolesString = \array_map(function(Role $r) { return $r->getRole(); }, $parents);
$qb = $this->em->createQueryBuilder(); $qb = $this->em->createQueryBuilder();
$qb $qb
@ -288,7 +284,7 @@ class AuthorizationHelper
->join('gc.permissionsGroup', 'pg') ->join('gc.permissionsGroup', 'pg')
->join('pg.roleScopes', 'rs') ->join('pg.roleScopes', 'rs')
->where('gc.center = :center') ->where('gc.center = :center')
->andWhere($qb->expr()->in('rs.role', $parentRolesString)) ->andWhere($qb->expr()->in('rs.role', $parents))
; ;
$qb->setParameter('center', $center); $qb->setParameter('center', $center);
@ -322,21 +318,16 @@ class AuthorizationHelper
* which are registered into Chill are taken into account. * which are registered into Chill are taken into account.
* *
* @param Role $role * @param Role $role
* @return Role[] the role which give access to the given $role * @return string[] the role which give access to the given $role
*/ */
public function getParentRoles(Role $role) public function getParentRoles($role): array
{ {
$parentRoles = []; $parentRoles = [];
// transform the roles from role hierarchy from string to Role // transform the roles from role hierarchy from string to Role
$roles = \array_map( $roles = \array_keys($this->hierarchy);
function($string) {
return new Role($string);
},
\array_keys($this->hierarchy)
);
foreach ($roles as $r) { foreach ($roles as $r) {
$childRoles = $this->roleHierarchy->getReachableRoleNames([$r->getRole()]); $childRoles = $this->roleHierarchy->getReachableRoleNames([$r]);
if (\in_array($role, $childRoles)) { if (\in_array($role, $childRoles)) {
$parentRoles[] = $r; $parentRoles[] = $r;

View File

@ -5,7 +5,7 @@ namespace Chill\MainBundle\Security\Authorization;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher; use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
class DefaultVoterHelper implements VoterHelperInterface final class DefaultVoterHelper implements VoterHelperInterface
{ {
protected AuthorizationHelper $authorizationHelper; protected AuthorizationHelper $authorizationHelper;

View File

@ -18,7 +18,7 @@ final class DefaultVoterHelperGenerator implements VoterGeneratorInterface
$this->centerResolverDispatcher = $centerResolverDispatcher; $this->centerResolverDispatcher = $centerResolverDispatcher;
} }
public function addCheckFor($subject, $attributes): self public function addCheckFor(?string $subject, array $attributes): self
{ {
$this->configuration[] = [$attributes, $subject]; $this->configuration[] = [$attributes, $subject];

View File

@ -9,7 +9,7 @@ interface VoterGeneratorInterface
* @param array $attributes an array of attributes * @param array $attributes an array of attributes
* @return $this * @return $this
*/ */
public function addCheckFor(string $class, array $attributes): self; public function addCheckFor(?string $class, array $attributes): self;
public function build(): VoterHelperInterface; public function build(): VoterHelperInterface;
} }

View File

@ -30,22 +30,22 @@ use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Center;
/** /**
* *
* *
* @author Julien Fastré <julien.fastre@champs-libres.coop> * @author Julien Fastré <julien.fastre@champs-libres.coop>
*/ */
class AuthorizationHelperTest extends KernelTestCase class AuthorizationHelperTest extends KernelTestCase
{ {
use PrepareUserTrait, PrepareCenterTrait, PrepareScopeTrait, ProphecyTrait; use PrepareUserTrait, PrepareCenterTrait, PrepareScopeTrait, ProphecyTrait;
public function setUp() public function setUp()
{ {
static::bootKernel(); static::bootKernel();
} }
/** /**
* *
* @return \Chill\MainBundle\Security\Authorization\AuthorizationHelper * @return \Chill\MainBundle\Security\Authorization\AuthorizationHelper
*/ */
private function getAuthorizationHelper() private function getAuthorizationHelper()
@ -54,13 +54,13 @@ class AuthorizationHelperTest extends KernelTestCase
->get('chill.main.security.authorization.helper') ->get('chill.main.security.authorization.helper')
; ;
} }
/** /**
* Test function userCanReach of helper. * Test function userCanReach of helper.
* *
* A user can reach center => the function should return true. * A user can reach center => the function should return true.
*/ */
public function testUserCanReachCenter_UserShouldReach() public function testUserCanReachCenter_UserShouldReach()
{ {
$center = $this->prepareCenter(1, 'center'); $center = $this->prepareCenter(1, 'center');
$scope = $this->prepareScope(1, 'default'); $scope = $this->prepareScope(1, 'default');
@ -72,16 +72,16 @@ class AuthorizationHelperTest extends KernelTestCase
) )
)); ));
$helper = $this->getAuthorizationHelper(); $helper = $this->getAuthorizationHelper();
$this->assertTrue($helper->userCanReachCenter($user, $center)); $this->assertTrue($helper->userCanReachCenter($user, $center));
} }
/** /**
* Test function userCanReach of helper * Test function userCanReach of helper
* *
* A user can not reachcenter =>W the function should return false * A user can not reachcenter =>W the function should return false
*/ */
public function testUserCanReachCenter_UserShouldNotReach() public function testUserCanReachCenter_UserShouldNotReach()
{ {
$centerA = $this->prepareCenter(1, 'center'); $centerA = $this->prepareCenter(1, 'center');
$centerB = $this->prepareCenter(2, 'centerB'); $centerB = $this->prepareCenter(2, 'centerB');
@ -94,11 +94,11 @@ class AuthorizationHelperTest extends KernelTestCase
) )
)); ));
$helper = $this->getAuthorizationHelper(); $helper = $this->getAuthorizationHelper();
$this->assertFalse($helper->userCanReachCenter($user, $centerB)); $this->assertFalse($helper->userCanReachCenter($user, $centerB));
} }
public function testUserHasAccess_shouldHaveAccess_EntityWithoutScope() public function testUserHasAccess_shouldHaveAccess_EntityWithoutScope()
{ {
$center = $this->prepareCenter(1, 'center'); $center = $this->prepareCenter(1, 'center');
@ -114,11 +114,11 @@ class AuthorizationHelperTest extends KernelTestCase
$entity = $this->getProphet()->prophesize(); $entity = $this->getProphet()->prophesize();
$entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface'); $entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface');
$entity->getCenter()->willReturn($center); $entity->getCenter()->willReturn($center);
$this->assertTrue($helper->userHasAccess($user, $entity->reveal(), $this->assertTrue($helper->userHasAccess($user, $entity->reveal(),
'CHILL_ROLE')); 'CHILL_ROLE'));
} }
public function testUserHasAccess_ShouldHaveAccessWithInheritance_EntityWithoutScope() public function testUserHasAccess_ShouldHaveAccessWithInheritance_EntityWithoutScope()
{ {
$center = $this->prepareCenter(1, 'center'); $center = $this->prepareCenter(1, 'center');
@ -130,17 +130,17 @@ class AuthorizationHelperTest extends KernelTestCase
) )
) )
)); ));
$helper = $this->getAuthorizationHelper(); $helper = $this->getAuthorizationHelper();
$entity = $this->getProphet()->prophesize(); $entity = $this->getProphet()->prophesize();
$entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface'); $entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface');
$entity->getCenter()->willReturn($center); $entity->getCenter()->willReturn($center);
$this->assertTrue($helper->userHasAccess($user, $entity->reveal(), $this->assertTrue($helper->userHasAccess($user, $entity->reveal(),
'CHILL_INHERITED_ROLE_1')); 'CHILL_INHERITED_ROLE_1'));
} }
public function testuserHasAccess_UserHasNoRole_EntityWithoutScope() public function testuserHasAccess_UserHasNoRole_EntityWithoutScope()
{ {
$center = $this->prepareCenter(1, 'center'); $center = $this->prepareCenter(1, 'center');
@ -156,10 +156,10 @@ class AuthorizationHelperTest extends KernelTestCase
$entity = $this->getProphet()->prophesize(); $entity = $this->getProphet()->prophesize();
$entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface'); $entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface');
$entity->getCenter()->willReturn($center); $entity->getCenter()->willReturn($center);
$this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE')); $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 * test that a user has no access on a entity, but is granted on the same role
* on another center * on another center
@ -186,10 +186,10 @@ class AuthorizationHelperTest extends KernelTestCase
$entity = $this->getProphet()->prophesize(); $entity = $this->getProphet()->prophesize();
$entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface'); $entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface');
$entity->getCenter()->willReturn($centerA); $entity->getCenter()->willReturn($centerA);
$this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE')); $this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE'));
} }
public function testtestUserHasAccess_UserShouldHaveAccess_EntityWithScope() public function testtestUserHasAccess_UserShouldHaveAccess_EntityWithScope()
{ {
$center = $this->prepareCenter(1, 'center'); $center = $this->prepareCenter(1, 'center');
@ -207,10 +207,10 @@ class AuthorizationHelperTest extends KernelTestCase
$entity->willImplement('\Chill\MainBundle\Entity\HasScopeInterface'); $entity->willImplement('\Chill\MainBundle\Entity\HasScopeInterface');
$entity->getCenter()->willReturn($center); $entity->getCenter()->willReturn($center);
$entity->getScope()->willReturn($scope); $entity->getScope()->willReturn($scope);
$this->assertTrue($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE')); $this->assertTrue($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE'));
} }
public function testUserHasAccess_UserHasNoRole_EntityWithScope() public function testUserHasAccess_UserHasNoRole_EntityWithScope()
{ {
$center = $this->prepareCenter(1, 'center'); $center = $this->prepareCenter(1, 'center');
@ -228,10 +228,10 @@ class AuthorizationHelperTest extends KernelTestCase
$entity->willImplement('\Chill\MainBundle\Entity\HasScopeInterface'); $entity->willImplement('\Chill\MainBundle\Entity\HasScopeInterface');
$entity->getCenter()->willReturn($center); $entity->getCenter()->willReturn($center);
$entity->getScope()->willReturn($scope); $entity->getScope()->willReturn($scope);
$this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'ANOTHER_ROLE')); $this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'ANOTHER_ROLE'));
} }
public function testUserHasAccess_UserHasNoCenter_EntityWithScope() public function testUserHasAccess_UserHasNoCenter_EntityWithScope()
{ {
$centerA = $this->prepareCenter(1, 'center'); //the user will have this center $centerA = $this->prepareCenter(1, 'center'); //the user will have this center
@ -250,10 +250,10 @@ class AuthorizationHelperTest extends KernelTestCase
$entity->willImplement('\Chill\MainBundle\Entity\HasScopeInterface'); $entity->willImplement('\Chill\MainBundle\Entity\HasScopeInterface');
$entity->getCenter()->willReturn($centerB); $entity->getCenter()->willReturn($centerB);
$entity->getScope()->willReturn($scope); $entity->getScope()->willReturn($scope);
$this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE')); $this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE'));
} }
public function testUserHasAccess_UserHasNoScope_EntityWithScope() public function testUserHasAccess_UserHasNoScope_EntityWithScope()
{ {
$center = $this->prepareCenter(1, 'center'); $center = $this->prepareCenter(1, 'center');
@ -272,12 +272,12 @@ class AuthorizationHelperTest extends KernelTestCase
$entity->willImplement('\Chill\MainBundle\Entity\HasScopeInterface'); $entity->willImplement('\Chill\MainBundle\Entity\HasScopeInterface');
$entity->getCenter()->willReturn($center); $entity->getCenter()->willReturn($center);
$entity->getScope()->willReturn($scopeA); $entity->getScope()->willReturn($scopeA);
$this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE')); $this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE'));
} }
/** /**
* *
* @dataProvider dataProvider_getReachableCenters * @dataProvider dataProvider_getReachableCenters
* @param Center $shouldHaveCenter * @param Center $shouldHaveCenter
* @param User $user * @param User $user
@ -288,7 +288,7 @@ class AuthorizationHelperTest extends KernelTestCase
{ {
$this->assertEquals($test, $result, $msg); $this->assertEquals($test, $result, $msg);
} }
public function dataProvider_getReachableCenters() public function dataProvider_getReachableCenters()
{ {
$this->setUp(); $this->setUp();
@ -297,10 +297,10 @@ class AuthorizationHelperTest extends KernelTestCase
$scopeA = $this->prepareScope(1, 'scope default'); $scopeA = $this->prepareScope(1, 'scope default');
$scopeB = $this->prepareScope(2, 'scope B'); $scopeB = $this->prepareScope(2, 'scope B');
$scopeC = $this->prepareScope(3, 'scope C'); $scopeC = $this->prepareScope(3, 'scope C');
$userA = $this->prepareUser(array( $userA = $this->prepareUser(array(
array( array(
'center' => $centerA, 'center' => $centerA,
'permissionsGroup' => array( 'permissionsGroup' => array(
['scope' => $scopeB, 'role' => 'CHILL_ROLE_1'], ['scope' => $scopeB, 'role' => 'CHILL_ROLE_1'],
['scope' => $scopeA, 'role' => 'CHILL_ROLE_2'] ['scope' => $scopeA, 'role' => 'CHILL_ROLE_2']
@ -313,62 +313,62 @@ class AuthorizationHelperTest extends KernelTestCase
['scope' => $scopeC, 'role' => 'CHILL_ROLE_2'] ['scope' => $scopeC, 'role' => 'CHILL_ROLE_2']
) )
) )
)); ));
$ah = $this->getAuthorizationHelper(); $ah = $this->getAuthorizationHelper();
return array( return array(
// without scopes // without scopes
array( array(
true, true,
in_array($centerA, $ah->getReachableCenters($userA, in_array($centerA, $ah->getReachableCenters($userA,
new Role('CHILL_ROLE_1'), null)), new Role('CHILL_ROLE_1'), null)),
'center A should be available for userA, with role 1 ' 'center A should be available for userA, with role 1 '
), ),
array( array(
true, true,
in_array($centerA, $ah->getReachableCenters($userA, in_array($centerA, $ah->getReachableCenters($userA,
new Role('CHILL_ROLE_2'), null)), new Role('CHILL_ROLE_2'), null)),
'center A should be available for userA, with role 2 ' 'center A should be available for userA, with role 2 '
), ),
array( array(
true, true,
in_array($centerB, $ah->getReachableCenters($userA, in_array($centerB, $ah->getReachableCenters($userA,
new Role('CHILL_ROLE_2'), null)), new Role('CHILL_ROLE_2'), null)),
'center A should be available for userA, with role 2 ' 'center A should be available for userA, with role 2 '
), ),
array( array(
false, false,
in_array($centerB, $ah->getReachableCenters($userA, in_array($centerB, $ah->getReachableCenters($userA,
new Role('CHILL_ROLE_1'), null)), new Role('CHILL_ROLE_1'), null)),
'center B should NOT be available for userA, with role 1 ' 'center B should NOT be available for userA, with role 1 '
), ),
// with scope // with scope
array( array(
true, true,
in_array($centerA, $ah->getReachableCenters($userA, in_array($centerA, $ah->getReachableCenters($userA,
new Role('CHILL_ROLE_1'), $scopeB)), new Role('CHILL_ROLE_1'), $scopeB)),
'center A should be available for userA, with role 1, scopeC ' 'center A should be available for userA, with role 1, scopeC '
), ),
array( array(
false, false,
in_array($centerA, $ah->getReachableCenters($userA, in_array($centerA, $ah->getReachableCenters($userA,
new Role('CHILL_ROLE_2'), $scopeC)), new Role('CHILL_ROLE_2'), $scopeC)),
'center A should NOT be available for userA, with role 2, scopeA ' 'center A should NOT be available for userA, with role 2, scopeA '
), ),
array( array(
true, true,
in_array($centerB, $ah->getReachableCenters($userA, in_array($centerB, $ah->getReachableCenters($userA,
new Role('CHILL_ROLE_2'), $scopeA)), new Role('CHILL_ROLE_2'), $scopeA)),
'center B should be available for userA, with role 2, scopeA ' 'center B should be available for userA, with role 2, scopeA '
), ),
); );
} }
/** /**
* *
* @dataProvider dataProvider_getReachableScopes * @dataProvider dataProvider_getReachableScopes
* @param boolean $expectedResult * @param boolean $expectedResult
* @param Scope $testedScope * @param Scope $testedScope
@ -382,11 +382,11 @@ class AuthorizationHelperTest extends KernelTestCase
{ {
$reachableScopes = $this->getAuthorizationHelper() $reachableScopes = $this->getAuthorizationHelper()
->getReachableScopes($user, $role, $center); ->getReachableScopes($user, $role, $center);
$this->assertEquals($expectedResult, in_array($testedScope, $reachableScopes), $this->assertEquals($expectedResult, in_array($testedScope, $reachableScopes),
$message); $message);
} }
public function dataProvider_getReachableScopes() public function dataProvider_getReachableScopes()
{ {
$centerA = $this->prepareCenter(1, 'center A'); $centerA = $this->prepareCenter(1, 'center A');
@ -394,10 +394,10 @@ class AuthorizationHelperTest extends KernelTestCase
$scopeA = $this->prepareScope(1, 'scope default'); $scopeA = $this->prepareScope(1, 'scope default');
$scopeB = $this->prepareScope(2, 'scope B'); $scopeB = $this->prepareScope(2, 'scope B');
$scopeC = $this->prepareScope(3, 'scope C'); $scopeC = $this->prepareScope(3, 'scope C');
$userA = $this->prepareUser(array( $userA = $this->prepareUser(array(
array( array(
'center' => $centerA, 'center' => $centerA,
'permissionsGroup' => array( 'permissionsGroup' => array(
['scope' => $scopeB, 'role' => 'CHILL_ROLE_1'], ['scope' => $scopeB, 'role' => 'CHILL_ROLE_1'],
['scope' => $scopeA, 'role' => 'CHILL_ROLE_2'] ['scope' => $scopeA, 'role' => 'CHILL_ROLE_2']
@ -411,9 +411,9 @@ class AuthorizationHelperTest extends KernelTestCase
['scope' => $scopeB, 'role' => 'CHILL_ROLE_2'] ['scope' => $scopeB, 'role' => 'CHILL_ROLE_2']
) )
) )
)); ));
return array( return array(
array( array(
true, true,
@ -442,37 +442,30 @@ class AuthorizationHelperTest extends KernelTestCase
) )
); );
} }
public function testGetParentRoles() public function testGetParentRoles()
{ {
$parentRoles = $this->getAuthorizationHelper() $parentRoles = $this->getAuthorizationHelper()
->getParentRoles(new Role('CHILL_INHERITED_ROLE_1')); ->getParentRoles('CHILL_INHERITED_ROLE_1');
$this->assertContains( $this->assertContains('CHILL_MASTER_ROLE', $parentRoles,
'CHILL_MASTER_ROLE',
\array_map(
function(Role $role) {
return $role->getRole();
},
$parentRoles
),
"Assert that `CHILL_MASTER_ROLE` is a parent of `CHILL_INHERITED_ROLE_1`"); "Assert that `CHILL_MASTER_ROLE` is a parent of `CHILL_INHERITED_ROLE_1`");
} }
public function testFindUsersReaching() public function testFindUsersReaching()
{ {
$centerA = static::$kernel->getContainer() $centerA = static::$kernel->getContainer()
->get('doctrine.orm.entity_manager') ->get('doctrine.orm.entity_manager')
->getRepository(Center::class) ->getRepository(Center::class)
->findOneByName('Center A'); ->findOneByName('Center A');
$users = $this->getAuthorizationHelper() $users = $this->getAuthorizationHelper()
->findUsersReaching(new Role('CHILL_PERSON_SEE'), ->findUsersReaching(new Role('CHILL_PERSON_SEE'),
$centerA); $centerA);
$usernames = \array_map(function(User $u) { return $u->getUsername(); }, $users); $usernames = \array_map(function(User $u) { return $u->getUsername(); }, $users);
$this->assertContains('center a_social', $usernames); $this->assertContains('center a_social', $usernames);
} }
} }

View File

@ -19,11 +19,11 @@ services:
autowire: true autowire: true
# do not autowire the directory Security/Resolver # do not autowire the directory Security/Resolver
Chill\MainBundle\Security\Authorization\DefaultVoterFactory: Chill\MainBundle\Security\Authorization\DefaultVoterHelperFactory:
autowire: true autowire: true
# do not autowire the directory Security/Resolver # do not autowire the directory Security/Resolver
Chill\MainBundle\Security\Authorization\VoterFactoryInterface: '@Chill\MainBundle\Security\Authorization\DefaultVoterFactory' Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface: '@Chill\MainBundle\Security\Authorization\DefaultVoterHelperFactory'
chill.main.security.authorization.helper: chill.main.security.authorization.helper:
class: Chill\MainBundle\Security\Authorization\AuthorizationHelper class: Chill\MainBundle\Security\Authorization\AuthorizationHelper

View File

@ -73,7 +73,7 @@ class AccompanyingCourseController extends Controller
} }
} }
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $period); $this->denyAccessUnlessGranted(AccompanyingPeriodVoter::CREATE, $period);
$em->persist($period); $em->persist($period);
$em->flush(); $em->flush();
@ -92,6 +92,8 @@ class AccompanyingCourseController extends Controller
*/ */
public function indexAction(AccompanyingPeriod $accompanyingCourse): Response public function indexAction(AccompanyingPeriod $accompanyingCourse): Response
{ {
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $accompanyingCourse);
// compute some warnings // compute some warnings
// get persons without household // get persons without household
$withoutHousehold = []; $withoutHousehold = [];
@ -131,6 +133,8 @@ class AccompanyingCourseController extends Controller
*/ */
public function editAction(AccompanyingPeriod $accompanyingCourse): Response public function editAction(AccompanyingPeriod $accompanyingCourse): Response
{ {
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $accompanyingCourse);
return $this->render('@ChillPerson/AccompanyingCourse/edit.html.twig', [ return $this->render('@ChillPerson/AccompanyingCourse/edit.html.twig', [
'accompanyingCourse' => $accompanyingCourse 'accompanyingCourse' => $accompanyingCourse
]); ]);
@ -146,6 +150,8 @@ class AccompanyingCourseController extends Controller
*/ */
public function historyAction(AccompanyingPeriod $accompanyingCourse): Response public function historyAction(AccompanyingPeriod $accompanyingCourse): Response
{ {
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $accompanyingCourse);
return $this->render('@ChillPerson/AccompanyingCourse/history.html.twig', [ return $this->render('@ChillPerson/AccompanyingCourse/history.html.twig', [
'accompanyingCourse' => $accompanyingCourse 'accompanyingCourse' => $accompanyingCourse
]); ]);

View File

@ -23,7 +23,10 @@
namespace Chill\PersonBundle\Controller; namespace Chill\PersonBundle\Controller;
use Chill\PersonBundle\Privacy\PrivacyEvent; use Chill\PersonBundle\Privacy\PrivacyEvent;
use Chill\PersonBundle\Repository\AccompanyingPeriodACLAwareRepository;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Form\AccompanyingPeriodType; use Chill\PersonBundle\Form\AccompanyingPeriodType;
@ -53,21 +56,24 @@ class AccompanyingPeriodController extends AbstractController
*/ */
protected $validator; protected $validator;
/** protected AccompanyingPeriodACLAwareRepository $accompanyingPeriodACLAwareRepository;
* AccompanyingPeriodController constructor.
* public function __construct(
* @param EventDispatcherInterface $eventDispatcher AccompanyingPeriodACLAwareRepository $accompanyingPeriodACLAwareRepository,
* @param ValidatorInterface $validator EventDispatcherInterface $eventDispatcher,
*/ ValidatorInterface $validator
public function __construct(EventDispatcherInterface $eventDispatcher, ValidatorInterface $validator) ) {
{ $this->accompanyingPeriodACLAwareRepository = $accompanyingPeriodACLAwareRepository;
$this->eventDispatcher = $eventDispatcher; $this->eventDispatcher = $eventDispatcher;
$this->validator = $validator; $this->validator = $validator;
} }
public function listAction(int $person_id): Response /**
* @ParamConverter("person", options={"id"="person_id"})
*/
public function listAction(Person $person): Response
{ {
$person = $this->_getPerson($person_id); $this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $person);
$event = new PrivacyEvent($person, [ $event = new PrivacyEvent($person, [
'element_class' => AccompanyingPeriod::class, 'element_class' => AccompanyingPeriod::class,
@ -75,9 +81,10 @@ class AccompanyingPeriodController extends AbstractController
]); ]);
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event); $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
$accompanyingPeriods = $person->getAccompanyingPeriodsOrdered(); $accompanyingPeriods = $this->accompanyingPeriodACLAwareRepository
->findByPerson($person, AccompanyingPeriodVoter::SEE);
return $this->render('ChillPersonBundle:AccompanyingPeriod:list.html.twig', [ return $this->render('@ChillPerson/AccompanyingPeriod/list.html.twig', [
'accompanying_periods' => $accompanyingPeriods, 'accompanying_periods' => $accompanyingPeriods,
'person' => $person 'person' => $person
]); ]);

View File

@ -18,6 +18,7 @@
namespace Chill\PersonBundle\DependencyInjection; namespace Chill\PersonBundle\DependencyInjection;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\HttpKernel\DependencyInjection\Extension;
@ -258,14 +259,26 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
*/ */
protected function prependRoleHierarchy(ContainerBuilder $container) protected function prependRoleHierarchy(ContainerBuilder $container)
{ {
$container->prependExtensionConfig('security', array( $container->prependExtensionConfig('security', [
'role_hierarchy' => array( 'role_hierarchy' => [
'CHILL_PERSON_UPDATE' => array('CHILL_PERSON_SEE'), PersonVoter::UPDATE => [PersonVoter::SEE],
'CHILL_PERSON_CREATE' => array('CHILL_PERSON_SEE'), PersonVoter::CREATE => [PersonVoter::SEE],
PersonVoter::LISTS => [ ChillExportVoter::EXPORT ], PersonVoter::LISTS => [ChillExportVoter::EXPORT],
PersonVoter::STATS => [ ChillExportVoter::EXPORT ] PersonVoter::STATS => [ChillExportVoter::EXPORT],
) // accompanying period
)); AccompanyingPeriodVoter::SEE_DETAILS => [AccompanyingPeriodVoter::SEE],
AccompanyingPeriodVoter::CREATE => [AccompanyingPeriodVoter::SEE_DETAILS],
AccompanyingPeriodVoter::DELETE => [AccompanyingPeriodVoter::SEE_DETAILS],
AccompanyingPeriodVoter::EDIT => [AccompanyingPeriodVoter::SEE_DETAILS],
// give all ACL for FULL
AccompanyingPeriodVoter::FULL => [
AccompanyingPeriodVoter::SEE_DETAILS,
AccompanyingPeriodVoter::CREATE,
AccompanyingPeriodVoter::EDIT,
AccompanyingPeriodVoter::DELETE
]
]
]);
} }
/** /**

View File

@ -18,14 +18,17 @@
namespace Chill\PersonBundle\Menu; namespace Chill\PersonBundle\Menu;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface; use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Knp\Menu\MenuItem; use Knp\Menu\MenuItem;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
/** /**
* Add menu entrie to person menu. * Add menu entrie to person menu.
* *
* Menu entries added : * Menu entries added :
* *
* - person details ; * - person details ;
* - accompanying period (if `visible`) * - accompanying period (if `visible`)
* *
@ -37,21 +40,25 @@ class PersonMenuBuilder implements LocalMenuBuilderInterface
* @var string 'visible' or 'hidden' * @var string 'visible' or 'hidden'
*/ */
protected $showAccompanyingPeriod; protected $showAccompanyingPeriod;
/** /**
* *
* @var TranslatorInterface * @var TranslatorInterface
*/ */
protected $translator; protected $translator;
private Security $security;
public function __construct( public function __construct(
$showAccompanyingPeriod, ParameterBagInterface $parameterBag,
Security $security,
TranslatorInterface $translator TranslatorInterface $translator
) { ) {
$this->showAccompanyingPeriod = $showAccompanyingPeriod; $this->showAccompanyingPeriod = $parameterBag->get('chill_person.accompanying_period');
$this->security = $security;
$this->translator = $translator; $this->translator = $translator;
} }
public function buildMenu($menuId, MenuItem $menu, array $parameters) public function buildMenu($menuId, MenuItem $menu, array $parameters)
{ {
$menu->addChild($this->translator->trans('Person details'), [ $menu->addChild($this->translator->trans('Person details'), [
@ -83,8 +90,10 @@ class PersonMenuBuilder implements LocalMenuBuilderInterface
->setExtras([ ->setExtras([
'order' => 99999 'order' => 99999
]); ]);
if ($this->showAccompanyingPeriod === 'visible') { if ($this->showAccompanyingPeriod === 'visible'
&& $this->security->isGranted(AccompanyingPeriodVoter::SEE, $parameters['person'])
) {
$menu->addChild($this->translator->trans('Accompanying period list'), [ $menu->addChild($this->translator->trans('Accompanying period list'), [
'route' => 'chill_person_accompanying_period_list', 'route' => 'chill_person_accompanying_period_list',
'routeParameters' => [ 'routeParameters' => [

View File

@ -0,0 +1,58 @@
<?php
namespace Chill\PersonBundle\Repository;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
use Chill\PersonBundle\Entity\Person;
use Symfony\Component\Security\Core\Security;
final class AccompanyingPeriodACLAwareRepository
{
private AccompanyingPeriodRepository $accompanyingPeriodRepository;
private Security $security;
private AuthorizationHelper $authorizationHelper;
private CenterResolverDispatcher $centerResolverDispatcher;
public function __construct(AccompanyingPeriodRepository $accompanyingPeriodRepository, Security $security, AuthorizationHelper $authorizationHelper, CenterResolverDispatcher $centerResolverDispatcher)
{
$this->accompanyingPeriodRepository = $accompanyingPeriodRepository;
$this->security = $security;
$this->authorizationHelper = $authorizationHelper;
$this->centerResolverDispatcher = $centerResolverDispatcher;
}
public function findByPerson(
Person $person,
string $role,
?array $orderBy = [],
int $limit = null,
int $offset = null
): array {
dump(__METHOD__);
$qb = $this->accompanyingPeriodRepository->createQueryBuilder('ap');
$scopes = $this->authorizationHelper
->getReachableCircles($this->security->getUser(), $role,
$this->centerResolverDispatcher->resolveCenter($person));
if (0 === count($scopes)) {
return [];
}
$qb
->join('ap.participations', 'participation')
->where($qb->expr()->eq('participation.person', ':person'))
->setParameter('person', $person)
;
// add join condition for scopes
$orx = $qb->expr()->orX();
foreach ($scopes as $key => $scope) {
$orx->add($qb->expr()->in('ap.scopes', ':scope_'.$key));
$qb->setParameter('scope_'.$key, $scope);
}
$qb->andWhere($orx);
return $qb->getQuery()->getResult();
}
}

View File

@ -59,6 +59,11 @@ final class AccompanyingPeriodRepository implements ObjectRepository
return $this->findOneBy($criteria); return $this->findOneBy($criteria);
} }
public function createQueryBuilder(string $alias, ?string $indexBy = null): QueryBuilder
{
return $this->repository->createQueryBuilder($alias, $indexBy);
}
public function getClassName() public function getClassName()
{ {
return AccompanyingPeriod::class; return AccompanyingPeriod::class;

View File

@ -60,7 +60,7 @@ final class PersonACLAwareRepository implements PersonACLAwareRepositoryInterfac
$countryCode); $countryCode);
$this->addACLClauses($qb, 'p'); $this->addACLClauses($qb, 'p');
return $this->getQueryResult($qb, $simplify, $limit, $start); return $this->getQueryResult($qb, 'p', $simplify, $limit, $start);
} }
/** /**
@ -119,7 +119,7 @@ final class PersonACLAwareRepository implements PersonACLAwareRepositoryInterfac
$countryCode); $countryCode);
$this->addACLClauses($qb, 'p'); $this->addACLClauses($qb, 'p');
return $this->getCountQueryResult($qb); return $this->getCountQueryResult($qb,'p');
} }
/** /**

View File

@ -4,73 +4,101 @@ namespace Chill\PersonBundle\Security\Authorization;
use Chill\MainBundle\Security\Authorization\AbstractChillVoter; use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface;
use Chill\MainBundle\Security\Authorization\VoterHelperInterface;
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface; use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\MainBundle\Entity\Center;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Role\Role; use Symfony\Component\Security\Core\Security;
class AccompanyingPeriodVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface class AccompanyingPeriodVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface
{ {
protected AuthorizationHelper $helper;
public const SEE = 'CHILL_PERSON_ACCOMPANYING_PERIOD_SEE'; public const SEE = 'CHILL_PERSON_ACCOMPANYING_PERIOD_SEE';
/**
* details are for seeing:
*
* * SocialIssues
*/
public const SEE_DETAILS = 'CHILL_PERSON_ACCOMPANYING_PERIOD_SEE_DETAILS';
public const CREATE = 'CHILL_PERSON_ACCOMPANYING_PERIOD_CREATE';
public const EDIT = 'CHILL_PERSON_ACCOMPANYING_PERIOD_UPDATE';
public const DELETE = 'CHILL_PERSON_ACCOMPANYING_PERIOD_DELETE';
/** /**
* @param AuthorizationHelper $helper * Give all the right above
*/ */
public function __construct(AuthorizationHelper $helper) public const FULL = 'CHILL_PERSON_ACCOMPANYING_PERIOD_FULL';
{
$this->helper = $helper; public const ALL = [
self::SEE,
self::SEE_DETAILS,
self::CREATE,
self::EDIT,
self::DELETE,
self::FULL,
];
private VoterHelperInterface $voterHelper;
private Security $security;
public function __construct(
Security $security,
VoterHelperFactoryInterface $voterHelperFactory
) {
$this->security = $security;
$this->voterHelper = $voterHelperFactory
->generate(self::class)
->addCheckFor(null, [self::CREATE])
->addCheckFor(AccompanyingPeriod::class, self::ALL)
->addCheckFor(Person::class, [self::SEE])
->build();
} }
protected function supports($attribute, $subject) protected function supports($attribute, $subject)
{ {
return $subject instanceof AccompanyingPeriod; return $this->voterHelper->supports($attribute, $subject);
} }
protected function voteOnAttribute($attribute, $subject, TokenInterface $token) protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool
{ {
if (!$token->getUser() instanceof User) { if (!$token->getUser() instanceof User) {
return false; return false;
} }
// TODO take scopes into account if ($subject instanceof AccompanyingPeriod) {
if (count($subject->getPersons()) === 0) { if (AccompanyingPeriod::STEP_DRAFT === $subject->getStep()) {
return true; // only creator can see, edit, delete, etc.
} if ($subject->getCreatedBy() === $token->getUser()
foreach ($subject->getPersons() as $person) { || NULL === $subject->getCreatedBy()) {
// give access as soon as on center is reachable return true;
if ($this->helper->userHasAccess($token->getUser(), $person->getCenter(), $attribute)) { }
return true;
return false;
} }
return false; // if confidential, only the referent can see it
if ($subject->isConfidential()) {
return $token->getUser() === $subject->getUser();
}
} }
}
private function getAttributes() return $this->voterHelper->voteOnAttribute($attribute, $subject, $token);
{
return [
self::SEE
];
} }
public function getRoles() public function getRoles()
{ {
return $this->getAttributes(); return self::ALL;
} }
public function getRolesWithoutScope() public function getRolesWithoutScope()
{ {
return []; return [];
} }
public function getRolesWithHierarchy() public function getRolesWithHierarchy()
{ {
return [ 'Person' => $this->getRoles() ]; return [ 'Accompanying period' => $this->getRoles() ];
} }
} }

View File

@ -40,19 +40,11 @@ class PersonVoter extends AbstractChillVoter implements ProvideRoleHierarchyInte
const LISTS = 'CHILL_PERSON_LISTS'; const LISTS = 'CHILL_PERSON_LISTS';
const DUPLICATE = 'CHILL_PERSON_DUPLICATE'; const DUPLICATE = 'CHILL_PERSON_DUPLICATE';
protected AuthorizationHelper $helper;
protected CenterResolverDispatcher $centerResolverDispatcher;
protected VoterHelperInterface $voter; protected VoterHelperInterface $voter;
public function __construct( public function __construct(
AuthorizationHelper $helper,
CenterResolverDispatcher $centerResolverDispatcher,
VoterHelperFactoryInterface $voterFactory VoterHelperFactoryInterface $voterFactory
) { ) {
$this->helper = $helper;
$this->centerResolverDispatcher = $centerResolverDispatcher;
$this->voter = $voterFactory $this->voter = $voterFactory
->generate(self::class) ->generate(self::class)
->addCheckFor(Center::class, [self::STATS, self::LISTS, self::DUPLICATE]) ->addCheckFor(Center::class, [self::STATS, self::LISTS, self::DUPLICATE])

View File

@ -12,9 +12,8 @@ services:
tags: ['controller.service_arguments'] tags: ['controller.service_arguments']
Chill\PersonBundle\Controller\AccompanyingPeriodController: Chill\PersonBundle\Controller\AccompanyingPeriodController:
arguments: autowire: true
$eventDispatcher: '@Symfony\Component\EventDispatcher\EventDispatcherInterface' autoconfigure: true
$validator: '@Symfony\Component\Validator\Validator\ValidatorInterface'
tags: ['controller.service_arguments'] tags: ['controller.service_arguments']
Chill\PersonBundle\Controller\PersonAddressController: Chill\PersonBundle\Controller\PersonAddressController:

View File

@ -19,14 +19,7 @@ services:
# - { name: 'chill.menu_builder' } # - { name: 'chill.menu_builder' }
# #
Chill\PersonBundle\Menu\PersonMenuBuilder: Chill\PersonBundle\Menu\PersonMenuBuilder:
arguments: autowire: true
$showAccompanyingPeriod: '%chill_person.accompanying_period%' autoconfigure: true
$translator: '@Symfony\Contracts\Translation\TranslatorInterface'
tags: tags:
- { name: 'chill.menu_builder' } - { name: 'chill.menu_builder' }
# Chill\PersonBundle\Menu\AccompanyingCourseMenuBuilder:
# arguments:
# $translator: '@Symfony\Contracts\Translation\TranslatorInterface'
# tags:
# - { name: 'chill.menu_builder' }

View File

@ -7,8 +7,7 @@ services:
- { name: chill.role } - { name: chill.role }
Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter: Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter:
arguments: autowire: true
- "@chill.main.security.authorization.helper"
tags: tags:
- { name: security.voter } - { name: security.voter }
- { name: chill.role } - { name: chill.role }