refactor ACL for easy voter. Apply on TaskVoter

This commit is contained in:
Julien Fastré 2021-09-03 12:43:12 +02:00
parent 5b70fb2ee5
commit dc09c9424a
14 changed files with 411 additions and 139 deletions

View File

@ -23,6 +23,7 @@ use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface; use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
use Chill\DocStoreBundle\Entity\PersonDocument; use Chill\DocStoreBundle\Entity\PersonDocument;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\PersonBundle\Security\Authorization\PersonVoter; use Chill\PersonBundle\Security\Authorization\PersonVoter;
@ -42,30 +43,25 @@ class PersonDocumentVoter extends AbstractChillVoter implements ProvideRoleHiera
const UPDATE = 'CHILL_PERSON_DOCUMENT_UPDATE'; const UPDATE = 'CHILL_PERSON_DOCUMENT_UPDATE';
const DELETE = 'CHILL_PERSON_DOCUMENT_DELETE'; const DELETE = 'CHILL_PERSON_DOCUMENT_DELETE';
/** protected AuthorizationHelper $authorizationHelper;
* @var AuthorizationHelper
*/
protected $authorizationHelper;
/** protected AccessDecisionManagerInterface $accessDecisionManager;
* @var AccessDecisionManagerInterface
*/
protected $accessDecisionManager;
/** protected LoggerInterface $logger;
* @var LoggerInterface
*/ protected CenterResolverDispatcher $centerResolverDispatcher;
protected $logger;
public function __construct( public function __construct(
AccessDecisionManagerInterface $accessDecisionManager, AccessDecisionManagerInterface $accessDecisionManager,
AuthorizationHelper $authorizationHelper, AuthorizationHelper $authorizationHelper,
LoggerInterface $logger LoggerInterface $logger//,
//CenterResolverDispatcher $centerResolverDispatcher
) )
{ {
$this->accessDecisionManager = $accessDecisionManager; $this->accessDecisionManager = $accessDecisionManager;
$this->authorizationHelper = $authorizationHelper; $this->authorizationHelper = $authorizationHelper;
$this->logger = $logger; $this->logger = $logger;
//$this->centerResolverDispatcher = $centerResolverDispatcher;
} }
public function getRoles() public function getRoles()
@ -78,17 +74,18 @@ class PersonDocumentVoter extends AbstractChillVoter implements ProvideRoleHiera
self::DELETE self::DELETE
]; ];
} }
protected function supports($attribute, $subject) protected function supports($attribute, $subject)
{ {
if (\in_array($attribute, $this->getRoles()) && $subject instanceof PersonDocument) { if (\in_array($attribute, $this->getRoles()) && $subject instanceof PersonDocument) {
return true; return true;
} }
if ($subject instanceof Person && $attribute === self::CREATE) { if ($subject instanceof Person
&& \in_array($attribute, [self::CREATE, self::SEE])) {
return true; return true;
} }
return false; return false;
} }
@ -107,6 +104,8 @@ class PersonDocumentVoter extends AbstractChillVoter implements ProvideRoleHiera
return false; return false;
} }
$center = $this->centerResolverDispatcher->resolveCenter($subject);
if ($subject instanceof PersonDocument) { if ($subject instanceof PersonDocument) {
return $this->authorizationHelper->userHasAccess($token->getUser(), $subject, $attribute); return $this->authorizationHelper->userHasAccess($token->getUser(), $subject, $attribute);

View File

@ -0,0 +1,36 @@
<?php
namespace Chill\MainBundle\Form\Event;
use Symfony\Component\Form\FormBuilderInterface;
class CustomizeFormEvent extends \Symfony\Component\EventDispatcher\Event
{
const NAME = 'chill_main.customize_form';
protected string $type;
protected FormBuilderInterface $builder;
public function __construct(string $type, FormBuilderInterface $builder)
{
$this->type = $type;
$this->builder = $builder;
}
/**
* @return string
*/
public function getType(): string
{
return $this->type;
}
/**
* @return FormBuilderInterface
*/
public function getBuilder(): FormBuilderInterface
{
return $this->builder;
}
}

View File

@ -23,8 +23,8 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
/** /**
* Voter for Chill software. * Voter for Chill software.
* *
* This abstract Voter provide generic methods to handle object specific to Chill * This abstract Voter provide generic methods to handle object specific to Chill
* *
* *
@ -36,20 +36,20 @@ abstract class AbstractChillVoter extends Voter implements ChillVoterInterface
{ {
@trigger_error('This voter should implements the new `supports` ' @trigger_error('This voter should implements the new `supports` '
. 'methods introduced by Symfony 3.0, and do not rely on ' . 'methods introduced by Symfony 3.0, and do not rely on '
. 'getSupportedAttributes and getSupportedClasses methods.', . 'getSupportedAttributes and getSupportedClasses methods.',
E_USER_DEPRECATED); E_USER_DEPRECATED);
return \in_array($attribute, $this->getSupportedAttributes($attribute)) return \in_array($attribute, $this->getSupportedAttributes($attribute))
&& \in_array(\get_class($subject), $this->getSupportedClasses()); && \in_array(\get_class($subject), $this->getSupportedClasses());
} }
protected function voteOnAttribute($attribute, $subject, TokenInterface $token) protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{ {
@trigger_error('This voter should implements the new `voteOnAttribute` ' @trigger_error('This voter should implements the new `voteOnAttribute` '
. 'methods introduced by Symfony 3.0, and do not rely on ' . 'methods introduced by Symfony 3.0, and do not rely on '
. 'isGranted method', E_USER_DEPRECATED); . 'isGranted method', E_USER_DEPRECATED);
return $this->isGranted($attribute, $subject, $token->getUser()); return $this->isGranted($attribute, $subject, $token->getUser());
} }
} }

View File

@ -23,6 +23,8 @@ use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\HasCenterInterface; use Chill\MainBundle\Entity\HasCenterInterface;
use Chill\MainBundle\Entity\HasScopeInterface; use Chill\MainBundle\Entity\HasScopeInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
use Symfony\Component\Security\Core\Role\Role; use Symfony\Component\Security\Core\Role\Role;
use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\Scope;
@ -32,87 +34,97 @@ use Chill\MainBundle\Entity\GroupCenter;
use Chill\MainBundle\Entity\RoleScope; use Chill\MainBundle\Entity\RoleScope;
/** /**
* Helper for authorizations. * Helper for authorizations.
* *
* Provides methods for user and entities information. * Provides methods for user and entities information.
* *
* @author Julien Fastré <julien.fastre@champs-libres.coop> * @author Julien Fastré <julien.fastre@champs-libres.coop>
*/ */
class AuthorizationHelper class AuthorizationHelper
{ {
protected RoleHierarchyInterface $roleHierarchy;
/** /**
* * The role in a hierarchy, given by the parameter
* @var RoleHierarchyInterface
*/
protected $roleHierarchy;
/**
* The role in a hierarchy, given by the parameter
* `security.role_hierarchy.roles` from the container. * `security.role_hierarchy.roles` from the container.
* *
* @var string[] * @var string[]
*/ */
protected $hierarchy; protected array $hierarchy;
/** protected EntityManagerInterface $em;
*
* @var EntityManagerInterface protected CenterResolverDispatcher $centerResolverDispatcher;
*/
protected $em;
public function __construct( public function __construct(
RoleHierarchyInterface $roleHierarchy, RoleHierarchyInterface $roleHierarchy,
$hierarchy, ParameterBagInterface $parameterBag,
EntityManagerInterface $em EntityManagerInterface $em,
CenterResolverDispatcher $centerResolverDispatcher
) { ) {
$this->roleHierarchy = $roleHierarchy; $this->roleHierarchy = $roleHierarchy;
$this->hierarchy = $hierarchy; $this->hierarchy = $parameterBag->get('security.role_hierarchy.roles');
$this->em = $em; $this->em = $em;
$this->centerResolverDispatcher = $centerResolverDispatcher;
} }
/** /**
* Determines if a user is active on this center * Determines if a user is active on this center
* *
* If
*
* @param User $user * @param User $user
* @param Center $center * @param Center|Center[] $center May be an array of center
* @return bool * @return bool
*/ */
public function userCanReachCenter(User $user, Center $center) public function userCanReachCenter(User $user, $center)
{ {
foreach ($user->getGroupCenters() as $groupCenter) { if ($center instanceof \Traversable) {
if ($center->getId() === $groupCenter->getCenter()->getId()) { foreach ($center as $c) {
if ($c->userCanReachCenter($user, $c)) {
return true; return true;
}
} }
return false;
} elseif ($center instanceof Center) {
foreach ($user->getGroupCenters() as $groupCenter) {
if ($center->getId() === $groupCenter->getCenter()->getId()) {
return true;
}
}
return false;
} }
return false; throw new \UnexpectedValueException(sprintf("The entity given is not an ".
"instance of %s, %s given", Center::class, get_class($center)));
} }
/** /**
* *
* Determines if the user has access to the given entity. * Determines if the user has access to the given entity.
* *
* if the entity implements Chill\MainBundle\Entity\HasScopeInterface, * if the entity implements Chill\MainBundle\Entity\HasScopeInterface,
* the scope is taken into account. * the scope is taken into account.
* *
* @param User $user * @param User $user
* @param HasCenterInterface $entity the entity may also implement HasScopeInterface * @param mixed $entity the entity may also implement HasScopeInterface
* @param string|Role $attribute * @param string|Role $attribute
* @return boolean true if the user has access * @return boolean true if the user has access
*/ */
public function userHasAccess(User $user, HasCenterInterface $entity, $attribute) public function userHasAccess(User $user, $entity, $attribute)
{ {
if (NULL === $center = $this->centerResolverDispatcher->resolveCenter($entity)) {
$center = $entity->getCenter(); return false;
}
if (!$this->userCanReachCenter($user, $center)) { if (!$this->userCanReachCenter($user, $center)) {
return false; return false;
} }
foreach ($user->getGroupCenters() as $groupCenter){ foreach ($user->getGroupCenters() as $groupCenter){
//filter on center //filter on center
if ($groupCenter->getCenter()->getId() === $entity->getCenter()->getId()) { if ($groupCenter->getCenter() === $center) {
$permissionGroup = $groupCenter->getPermissionsGroup(); $permissionGroup = $groupCenter->getPermissionsGroup();
//iterate on roleScopes //iterate on roleScopes
foreach($permissionGroup->getRoleScopes() as $roleScope) { foreach($permissionGroup->getRoleScopes() as $roleScope) {
@ -134,17 +146,17 @@ class AuthorizationHelper
} }
} }
} }
} }
} }
return false; return false;
} }
/** /**
* Get reachable Centers for the given user, role, * Get reachable Centers for the given user, role,
* and optionnaly Scope * and optionnaly Scope
* *
* @param User $user * @param User $user
* @param string|Role $role * @param string|Role $role
* @param null|Scope $scope * @param null|Scope $scope
@ -156,7 +168,7 @@ class AuthorizationHelper
$role = $role->getRole(); $role = $role->getRole();
} }
$centers = array(); $centers = array();
foreach ($user->getGroupCenters() as $groupCenter){ foreach ($user->getGroupCenters() as $groupCenter){
$permissionGroup = $groupCenter->getPermissionsGroup(); $permissionGroup = $groupCenter->getPermissionsGroup();
//iterate on roleScopes //iterate on roleScopes
@ -170,13 +182,13 @@ class AuthorizationHelper
if ($scope->getId() == $roleScope->getScope()->getId()){ if ($scope->getId() == $roleScope->getScope()->getId()){
$centers[] = $groupCenter->getCenter(); $centers[] = $groupCenter->getCenter();
break 1; break 1;
} }
} }
} }
} }
} }
return $centers; return $centers;
} }
@ -203,10 +215,10 @@ class AuthorizationHelper
return $results; return $results;
} }
/** /**
* Return all reachable scope for a given user, center and role * Return all reachable scope for a given user, center and role
* *
* @deprecated Use getReachableCircles * @deprecated Use getReachableCircles
* *
* @param User $user * @param User $user
@ -222,10 +234,10 @@ class AuthorizationHelper
return $this->getReachableCircles($user, $role, $center); return $this->getReachableCircles($user, $role, $center);
} }
/** /**
* Return all reachable circle for a given user, center and role * Return all reachable circle for a given user, center and role
* *
* @param User $user * @param User $user
* @param string|Role $role * @param string|Role $role
* @param Center $center * @param Center $center
@ -237,7 +249,7 @@ class AuthorizationHelper
$role = $role->getRole(); $role = $role->getRole();
} }
$scopes = array(); $scopes = array();
foreach ($user->getGroupCenters() as $groupCenter){ foreach ($user->getGroupCenters() as $groupCenter){
if ($center->getId() === $groupCenter->getCenter()->getId()) { if ($center->getId() === $groupCenter->getCenter()->getId()) {
//iterate on permissionGroup //iterate on permissionGroup
@ -251,12 +263,12 @@ class AuthorizationHelper
} }
} }
} }
return $scopes; return $scopes;
} }
/** /**
* *
* @param Role $role * @param Role $role
* @param Center $center * @param Center $center
* @param Scope $circle * @param Scope $circle
@ -267,7 +279,7 @@ class AuthorizationHelper
$parents = $this->getParentRoles($role); $parents = $this->getParentRoles($role);
$parents[] = $role; $parents[] = $role;
$parentRolesString = \array_map(function(Role $r) { return $r->getRole(); }, $parents); $parentRolesString = \array_map(function(Role $r) { return $r->getRole(); }, $parents);
$qb = $this->em->createQueryBuilder(); $qb = $this->em->createQueryBuilder();
$qb $qb
->select('u') ->select('u')
@ -278,21 +290,21 @@ class AuthorizationHelper
->where('gc.center = :center') ->where('gc.center = :center')
->andWhere($qb->expr()->in('rs.role', $parentRolesString)) ->andWhere($qb->expr()->in('rs.role', $parentRolesString))
; ;
$qb->setParameter('center', $center); $qb->setParameter('center', $center);
if ($circle !== null) { if ($circle !== null) {
$qb->andWhere('rs.scope = :circle') $qb->andWhere('rs.scope = :circle')
->setParameter('circle', $circle) ->setParameter('circle', $circle)
; ;
} }
return $qb->getQuery()->getResult(); return $qb->getQuery()->getResult();
} }
/** /**
* Test if a parent role may give access to a given child role * 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 $childRole The role we want to test if he is reachable
* @param Role $parentRole The role which should give access to $childRole * @param Role $parentRole The role which should give access to $childRole
* @return boolean true if the child role is granted by parent role * @return boolean true if the child role is granted by parent role
@ -301,14 +313,14 @@ class AuthorizationHelper
{ {
$reachableRoles = $this->roleHierarchy $reachableRoles = $this->roleHierarchy
->getReachableRoleNames([$parentRole]); ->getReachableRoleNames([$parentRole]);
return in_array($childRole, $reachableRoles); return in_array($childRole, $reachableRoles);
} }
/** /**
* Return all the role which give access to the given role. Only the role * Return all the role which give access to the given role. Only the role
* 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 Role[] the role which give access to the given $role
*/ */
@ -319,18 +331,18 @@ class AuthorizationHelper
$roles = \array_map( $roles = \array_map(
function($string) { function($string) {
return new Role($string); return new Role($string);
}, },
\array_keys($this->hierarchy) \array_keys($this->hierarchy)
); );
foreach ($roles as $r) { foreach ($roles as $r) {
$childRoles = $this->roleHierarchy->getReachableRoleNames([$r->getRole()]); $childRoles = $this->roleHierarchy->getReachableRoleNames([$r->getRole()]);
if (\in_array($role, $childRoles)) { if (\in_array($role, $childRoles)) {
$parentRoles[] = $r; $parentRoles[] = $r;
} }
} }
return $parentRoles; return $parentRoles;
} }
} }

View File

@ -0,0 +1,69 @@
<?php
namespace Chill\MainBundle\Security\Authorization;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
class DefaultVoter implements VoterInterface
{
protected AuthorizationHelper $authorizationHelper;
protected CenterResolverDispatcher $centerResolverDispatcher;
protected array $configuration = [];
/**
* @param AuthorizationHelper $authorizationHelper
* @param CenterResolverDispatcher $centerResolverDispatcher
* @param array $configuration
*/
public function __construct(
AuthorizationHelper $authorizationHelper,
CenterResolverDispatcher $centerResolverDispatcher,
array $configuration
) {
$this->authorizationHelper = $authorizationHelper;
$this->centerResolverDispatcher = $centerResolverDispatcher;
$this->configuration = $configuration;
}
public function supports($attribute, $subject): bool
{
foreach ($this->configuration as list($attributes, $subj)) {
if ($subj === null) {
if ($subject === null && \in_array($attribute, $attributes)) {
return true;
}
} elseif ($subject instanceof $subj) {
return \in_array($attribute, $attributes);
}
}
return false;
}
public function voteOnAttribute($attribute, $subject, $token): bool
{
if (!$token->getUser() instanceof User) {
return false;
}
if (NULL === $subject) {
if (NULL === $center = $this->centerResolverDispatcher
->resolveCenter($subject)) {
return false;
}
return $this->authorizationHelper->userCanReachCenter(
$token->getUser(),
$center
);
}
return $this->authorizationHelper->userHasAccess(
$token->getUser(),
$subject,
$attribute
);
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace Chill\MainBundle\Security\Authorization;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
class DefaultVoterFactory implements VoterFactoryInterface
{
protected AuthorizationHelper $authorizationHelper;
protected CenterResolverDispatcher $centerResolverDispatcher;
public function __construct(
AuthorizationHelper $authorizationHelper,
CenterResolverDispatcher $centerResolverDispatcher
) {
$this->authorizationHelper = $authorizationHelper;
$this->centerResolverDispatcher = $centerResolverDispatcher;
}
public function generate($context): VoterGeneratorInterface
{
return new VoterGenerator(
$this->authorizationHelper,
$this->centerResolverDispatcher
);
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace Chill\MainBundle\Security\Authorization;
interface VoterFactoryInterface
{
}

View File

@ -0,0 +1,36 @@
<?php
namespace Chill\MainBundle\Security\Authorization;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
final class VoterGenerator implements VoterGeneratorInterface
{
protected AuthorizationHelper $authorizationHelper;
protected CenterResolverDispatcher $centerResolverDispatcher;
protected array $configuration = [];
public function __construct(
AuthorizationHelper $authorizationHelper,
CenterResolverDispatcher $centerResolverDispatcher
) {
$this->authorizationHelper = $authorizationHelper;
$this->centerResolverDispatcher = $centerResolverDispatcher;
}
public function addCheckFor($subject, $attributes): self
{
$this->configuration[] = [$attributes, $subject];
return $this;
}
public function build(): VoterInterface
{
return new DefaultVoter(
$this->authorizationHelper,
$this->centerResolverDispatcher,
$this->configuration
);
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace Chill\MainBundle\Security\Authorization;
interface VoterGeneratorInterface
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Chill\MainBundle\Security\Authorization;
interface VoterInterface
{
}

View File

@ -0,0 +1,27 @@
<?php
namespace Chill\MainBundle\Tests\Security\Resolver;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class CenterResolverDispatcherTest extends KernelTestCase
{
private CenterResolverDispatcher $dispatcher;
protected function setUp()
{
self::bootKernel();
$this->dispatcher = self::$container->get(CenterResolverDispatcher::class);
}
public function testResolveCenter()
{
$center = new Center();
$resolved = $this->dispatcher->resolveCenter($center);
$this->assertSame($center, $resolved);
}
}

View File

@ -3,12 +3,38 @@ services:
autowire: true autowire: true
autoconfigure: true autoconfigure: true
# do not autowire the directory Security/Resolver
Chill\MainBundle\Security\Resolver\CenterResolverDispatcher:
arguments:
- !tagged_iterator chill_main.center_resolver
# do not autowire the directory Security/Resolver
_instanceof:
Chill\MainBundle\Security\Resolver\CenterResolverInterface:
tags:
- chill_main.center_resolver
# do not autowire the directory Security/Resolver
Chill\MainBundle\Security\Resolver\DefaultCenterResolver:
autoconfigure: true
autowire: true
# do not autowire the directory Security/Resolver
Chill\MainBundle\Security\Resolver\ResolverTwigExtension:
autoconfigure: true
autowire: true
# do not autowire the directory Security/Resolver
Chill\MainBundle\Security\Authorization\DefaultVoterFactory:
autowire: true
# do not autowire the directory Security/Resolver
Chill\MainBundle\Security\Authorization\VoterFactoryInterface: '@Chill\MainBundle\Security\Authorization\DefaultVoterFactory'
chill.main.security.authorization.helper: chill.main.security.authorization.helper:
class: Chill\MainBundle\Security\Authorization\AuthorizationHelper class: Chill\MainBundle\Security\Authorization\AuthorizationHelper
arguments: autowire: true
$roleHierarchy: "@security.role_hierarchy" autoconfigure: true
$hierarchy: "%security.role_hierarchy.roles%"
$em: '@Doctrine\ORM\EntityManagerInterface'
Chill\MainBundle\Security\Authorization\AuthorizationHelper: '@chill.main.security.authorization.helper' Chill\MainBundle\Security\Authorization\AuthorizationHelper: '@chill.main.security.authorization.helper'
chill.main.role_provider: chill.main.role_provider:

View File

@ -18,7 +18,12 @@
namespace Chill\TaskBundle\Security\Authorization; namespace Chill\TaskBundle\Security\Authorization;
use Chill\EventBundle\Entity\Event;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Security\Authorization\AbstractChillVoter; use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
use Chill\MainBundle\Security\Authorization\VoterFactoryInterface;
use Chill\MainBundle\Security\Authorization\VoterInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
use Chill\TaskBundle\Entity\AbstractTask; use Chill\TaskBundle\Entity\AbstractTask;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
@ -32,12 +37,7 @@ use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Chill\TaskBundle\Security\Authorization\AuthorizationEvent; use Chill\TaskBundle\Security\Authorization\AuthorizationEvent;
/** final class TaskVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class TaskVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface
{ {
const CREATE = 'CHILL_TASK_TASK_CREATE'; const CREATE = 'CHILL_TASK_TASK_CREATE';
const UPDATE = 'CHILL_TASK_TASK_UPDATE'; const UPDATE = 'CHILL_TASK_TASK_UPDATE';
@ -51,50 +51,52 @@ class TaskVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterf
self::DELETE self::DELETE
]; ];
/** protected AuthorizationHelper $authorizationHelper;
*
* @var AuthorizationHelper
*/
protected $authorizationHelper;
/** protected AccessDecisionManagerInterface $accessDecisionManager;
*
* @var AccessDecisionManagerInterface
*/
protected $accessDecisionManager;
/** protected LoggerInterface $logger;
*
* @var LoggerInterface protected EventDispatcherInterface $eventDispatcher;
*/
protected $logger; protected CenterResolverDispatcher $centerResolverDispatcher;
/** protected VoterInterface $voter;
*
* @var EventDispatcherInterface
*/
protected $eventDispatcher;
public function __construct( public function __construct(
AccessDecisionManagerInterface $accessDecisionManager, AccessDecisionManagerInterface $accessDecisionManager,
AuthorizationHelper $authorizationHelper, AuthorizationHelper $authorizationHelper,
EventDispatcherInterface $eventDispatcher, EventDispatcherInterface $eventDispatcher,
LoggerInterface $logger LoggerInterface $logger,
CenterResolverDispatcher $centerResolverDispatcher,
VoterFactoryInterface $voterFactory
) { ) {
$this->accessDecisionManager = $accessDecisionManager; $this->accessDecisionManager = $accessDecisionManager;
$this->authorizationHelper = $authorizationHelper; $this->authorizationHelper = $authorizationHelper;
$this->eventDispatcher = $eventDispatcher; $this->eventDispatcher = $eventDispatcher;
$this->logger = $logger; $this->logger = $logger;
$this->centerResolverDispatcher = $centerResolverDispatcher;
$this->voter = $voterFactory
->generate(AbstractTask::class)
->addCheckFor(AbstractTask::class, self::ROLES)
->addCheckFor(Person::class, [self::SHOW])
->addCheckFor(null, [self::SHOW])
->build()
;
} }
public function supports($attribute, $subject) public function supports($attribute, $subject)
{ {
return $this->voter->supports($attribute, $subject);
/*
return ($subject instanceof AbstractTask && in_array($attribute, self::ROLES)) return ($subject instanceof AbstractTask && in_array($attribute, self::ROLES))
|| ||
($subject instanceof Person && \in_array($attribute, [ self::CREATE, self::SHOW ])) ($subject instanceof Person && \in_array($attribute, [ self::CREATE, self::SHOW ]))
|| ||
(NULL === $subject && $attribute === self::SHOW ) (NULL === $subject && $attribute === self::SHOW )
; ;
*/
} }
/** /**
@ -111,40 +113,60 @@ class TaskVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterf
if (!$token->getUser() instanceof User) { if (!$token->getUser() instanceof User) {
return false; return false;
} }
$event = new AuthorizationEvent($subject, $attribute, $token); $event = new AuthorizationEvent($subject, $attribute, $token);
$this->eventDispatcher->dispatch(AuthorizationEvent::VOTE, $event); $this->eventDispatcher->dispatch(AuthorizationEvent::VOTE, $event);
if ($event->hasVote()) { if ($event->hasVote()) {
$this->logger->debug("The TaskVoter is overriding by " $this->logger->debug("The TaskVoter is overriding by "
.AuthorizationEvent::VOTE, [ .AuthorizationEvent::VOTE, [
'vote' => $event->getVote(), 'vote' => $event->getVote(),
'task_id' => $subject->getId() 'task_id' => $subject->getId()
]); ]);
return $event->getVote(); return $event->getVote();
} }
// do pre-flight check, relying on other decision manager
// those pre-flight check concern associated entities
if ($subject instanceof AbstractTask) {
if (NULL !== $person = $subject->getPerson()) {
if (!$this->accessDecisionManager->decide($token, [PersonVoter::SEE], $person)) {
return false;
}
} elseif (false) {
// here will come the test if the task is associated to an accompanying course
}
}
// do regular check.
return $this->voter->voteOnAttribute($attribute, $subject, $token);
/*
if ($subject instanceof AbstractTask) { if ($subject instanceof AbstractTask) {
if ($subject->getPerson() === null) { if ($subject->getPerson() === null) {
throw new \LogicException("You should associate a person with task " throw new \LogicException("You should associate a person with task "
. "in order to check autorizations"); . "in order to check autorizations");
} }
$person = $subject->getPerson(); $person = $subject->getPerson();
} elseif ($subject instanceof Person) { } elseif ($subject instanceof Person) {
$person = $subject; $person = $subject;
} else { } else {
// subject is null. We check that at least one center is reachable // subject is null. We check that at least one center is reachable
$centers = $this->authorizationHelper->getReachableCenters($token->getUser(), new Role($attribute)); $centers = $this->authorizationHelper->getReachableCenters($token->getUser(), new Role($attribute));
return count($centers) > 0; return count($centers) > 0;
} }
if (!$this->accessDecisionManager->decide($token, [PersonVoter::SEE], $person)) { if (!$this->accessDecisionManager->decide($token, [PersonVoter::SEE], $person)) {
return false;
}
$center = $this->centerResolverDispatcher->resolveCenter($subject);
if (NULL === $center) {
return false; return false;
} }
@ -153,6 +175,7 @@ class TaskVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterf
$subject, $subject,
$attribute $attribute
); );
*/
} }
public function getRoles() public function getRoles()

View File

@ -1,11 +1,7 @@
services: services:
chill_task.task_voter: chill_task.task_voter:
class: Chill\TaskBundle\Security\Authorization\TaskVoter class: Chill\TaskBundle\Security\Authorization\TaskVoter
arguments: autowire: true
- "@security.access.decision_manager" autoconfigure: true
- "@chill.main.security.authorization.helper"
- '@Symfony\Component\EventDispatcher\EventDispatcherInterface'
- "@logger"
tags: tags:
- { name: security.voter }
- { name: chill.role } - { name: chill.role }