* * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ namespace Chill\MainBundle\Security\Authorization; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\HasCenterInterface; use Chill\MainBundle\Entity\HasScopeInterface; use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; use Symfony\Component\Security\Core\Role\Role; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Security\RoleProvider; use Doctrine\ORM\EntityManagerInterface; use Chill\MainBundle\Entity\GroupCenter; use Chill\MainBundle\Entity\RoleScope; /** * Helper for authorizations. * * Provides methods for user and entities information. * * @author Julien Fastré */ class AuthorizationHelper { protected RoleHierarchyInterface $roleHierarchy; /** * The role in a hierarchy, given by the parameter * `security.role_hierarchy.roles` from the container. * * @var string[] */ protected array $hierarchy; protected EntityManagerInterface $em; protected CenterResolverDispatcher $centerResolverDispatcher; public function __construct( RoleHierarchyInterface $roleHierarchy, ParameterBagInterface $parameterBag, EntityManagerInterface $em, CenterResolverDispatcher $centerResolverDispatcher ) { $this->roleHierarchy = $roleHierarchy; $this->hierarchy = $parameterBag->get('security.role_hierarchy.roles'); $this->em = $em; $this->centerResolverDispatcher = $centerResolverDispatcher; } /** * Determines if a user is active on this center * * If * * @param User $user * @param Center|Center[] $center May be an array of center * @return bool */ public function userCanReachCenter(User $user, $center) { if ($center instanceof \Traversable) { foreach ($center as $c) { if ($c->userCanReachCenter($user, $c)) { return true; } } return false; } elseif ($center instanceof Center) { foreach ($user->getGroupCenters() as $groupCenter) { if ($center->getId() === $groupCenter->getCenter()->getId()) { return true; } } 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. * * if the entity implements Chill\MainBundle\Entity\HasScopeInterface, * the scope is taken into account. * * @param User $user * @param mixed $entity the entity may also implement HasScopeInterface * @param string|Role $attribute * @return boolean true if the user has access */ public function userHasAccess(User $user, $entity, $attribute) { if (NULL === $center = $this->centerResolverDispatcher->resolveCenter($entity)) { return false; } if (!$this->userCanReachCenter($user, $center)) { return false; } foreach ($user->getGroupCenters() as $groupCenter){ //filter on center if ($groupCenter->getCenter() === $center) { $permissionGroup = $groupCenter->getPermissionsGroup(); //iterate on roleScopes foreach($permissionGroup->getRoleScopes() as $roleScope) { //check that the role allow to reach the required role if ($this->isRoleReached($attribute, $roleScope->getRole())) { //if yes, we have a right on something... // perform check on scope if necessary if ($entity instanceof HasScopeInterface) { $scope = $entity->getScope(); if ($scope === NULL) { return true; } if ($scope->getId() === $roleScope ->getScope()->getId()) { return true; } } else { return true; } } } } } return false; } /** * Get reachable Centers for the given user, role, * and optionnaly Scope * * @param User $user * @param string|Role $role * @param null|Scope $scope * @return Center[] */ public function getReachableCenters(User $user, $role, Scope $scope = null) { if ($role instanceof Role) { $role = $role->getRole(); } $centers = array(); foreach ($user->getGroupCenters() as $groupCenter){ $permissionGroup = $groupCenter->getPermissionsGroup(); //iterate on roleScopes foreach($permissionGroup->getRoleScopes() as $roleScope) { //check that the role is in the reachable roles if ($this->isRoleReached($role, $roleScope->getRole())) { if ($scope === null) { $centers[] = $groupCenter->getCenter(); break 1; } else { if ($scope->getId() == $roleScope->getScope()->getId()){ $centers[] = $groupCenter->getCenter(); break 1; } } } } } return $centers; } /** * Filter an array of centers, return only center which are reachable * * @param User $user The user * @param array $centers a list of centers which are going to be filtered * @param string|Center $role */ public function filterReachableCenters(User $user, array $centers, $role): array { $results = []; if ($role instanceof Role) { $role = $role->getRole(); } foreach ($centers as $center) { if ($this->userCanReachCenter($user, $center, $role)) { $results[] = $center; } } return $results; } /** * Return all reachable scope for a given user, center and role * * @deprecated Use getReachableCircles * * @param User $user * @param Role $role * @param Center $center * @return Scope[] */ public function getReachableScopes(User $user, $role, Center $center) { if ($role instanceof Role) { $role = $role->getRole(); } return $this->getReachableCircles($user, $role, $center); } /** * Return all reachable circle for a given user, center and role * * @param User $user * @param string|Role $role * @param Center $center * @return Scope[] */ public function getReachableCircles(User $user, $role, Center $center) { if ($role instanceof Role) { $role = $role->getRole(); } $scopes = array(); foreach ($user->getGroupCenters() as $groupCenter){ if ($center->getId() === $groupCenter->getCenter()->getId()) { //iterate on permissionGroup $permissionGroup = $groupCenter->getPermissionsGroup(); //iterate on roleScopes foreach($permissionGroup->getRoleScopes() as $roleScope) { //check that the role is in the reachable roles if ($this->isRoleReached($role, $roleScope->getRole())) { $scopes[] = $roleScope->getScope(); } } } } return $scopes; } /** * * @param Role $role * @param Center $center * @param Scope $circle * @return Users */ public function findUsersReaching(Role $role, Center $center, Scope $circle = null) { $parents = $this->getParentRoles($role); $parents[] = $role; $parentRolesString = \array_map(function(Role $r) { return $r->getRole(); }, $parents); $qb = $this->em->createQueryBuilder(); $qb ->select('u') ->from(User::class, 'u') ->join('u.groupCenters', 'gc') ->join('gc.permissionsGroup', 'pg') ->join('pg.roleScopes', 'rs') ->where('gc.center = :center') ->andWhere($qb->expr()->in('rs.role', $parentRolesString)) ; $qb->setParameter('center', $center); if ($circle !== null) { $qb->andWhere('rs.scope = :circle') ->setParameter('circle', $circle) ; } return $qb->getQuery()->getResult(); } /** * Test if a parent role may give access to a given child role * * @param Role $childRole The role we want to test if he is reachable * @param Role $parentRole The role which should give access to $childRole * @return boolean true if the child role is granted by parent role */ protected function isRoleReached($childRole, $parentRole) { $reachableRoles = $this->roleHierarchy ->getReachableRoleNames([$parentRole]); return in_array($childRole, $reachableRoles); } /** * Return all the role which give access to the given role. Only the role * which are registered into Chill are taken into account. * * @param Role $role * @return Role[] the role which give access to the given $role */ public function getParentRoles(Role $role) { $parentRoles = []; // transform the roles from role hierarchy from string to Role $roles = \array_map( function($string) { return new Role($string); }, \array_keys($this->hierarchy) ); foreach ($roles as $r) { $childRoles = $this->roleHierarchy->getReachableRoleNames([$r->getRole()]); if (\in_array($role, $childRoles)) { $parentRoles[] = $r; } } return $parentRoles; } }