mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-12 13:24:25 +00:00
refactor ACL for easy voter. Apply on TaskVoter
This commit is contained in:
parent
5b70fb2ee5
commit
dc09c9424a
@ -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);
|
||||||
|
|
||||||
|
36
src/Bundle/ChillMainBundle/Form/Event/CustomizeFormEvent.php
Normal file
36
src/Bundle/ChillMainBundle/Form/Event/CustomizeFormEvent.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Security\Authorization;
|
||||||
|
|
||||||
|
interface VoterFactoryInterface
|
||||||
|
{
|
||||||
|
}
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Security\Authorization;
|
||||||
|
|
||||||
|
interface VoterGeneratorInterface
|
||||||
|
{
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Security\Authorization;
|
||||||
|
|
||||||
|
interface VoterInterface
|
||||||
|
{
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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:
|
||||||
|
@ -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()
|
||||||
|
@ -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 }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user