* * 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 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 { /** * * @var RoleHierarchyInterface */ protected $roleHierarchy; /** * The role in a hierarchy, given by the parameter * `security.role_hierarchy.roles` from the container. * * @var string[] */ protected $hierarchy; /** * * @var EntityManagerInterface */ protected $em; public function __construct( RoleHierarchyInterface $roleHierarchy, $hierarchy, EntityManagerInterface $em ) { $this->roleHierarchy = $roleHierarchy; $this->hierarchy = $hierarchy; $this->em = $em; } /** * Determines if a user is active on this center * * @param User $user * @param Center $center * @return bool */ public function userCanReachCenter(User $user, Center $center) { foreach ($user->getGroupCenters() as $groupCenter) { if ($center->getId() === $groupCenter->getCenter()->getId()) { return true; } } return false; } /** * * Determines if the user has access to the given entity. * * if the entity implements Chill\MainBundle\Entity\HasScopeInterface, * the scope is taken into account. * * @param User $user * @param HasCenterInterface $entity the entity may also implement HasScopeInterface * @param string|Role $attribute * @return boolean true if the user has access */ public function userHasAccess(User $user, HasCenterInterface $entity, $attribute) { $center = $entity->getCenter(); if (!$this->userCanReachCenter($user, $center)) { return false; } $role = ($attribute instanceof Role) ? $attribute : new Role($attribute); foreach ($user->getGroupCenters() as $groupCenter){ //filter on center if ($groupCenter->getCenter()->getId() === $entity->getCenter()->getId()) { $permissionGroup = $groupCenter->getPermissionsGroup(); //iterate on roleScopes foreach($permissionGroup->getRoleScopes() as $roleScope) { //check that the role allow to reach the required role if ($this->isRoleReached($role, new Role($roleScope->getRole()))){ //if yes, we have a right on something... // perform check on scope if necessary if ($entity instanceof HasScopeInterface) { $scope = $entity->getScope(); if ($scope === 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 Role $role * @param null|Scope $scope * @return Center[] */ public function getReachableCenters(User $user, Role $role, Scope $scope = null) { $centers = array(); foreach ($user->getGroupCenters() as $groupCenter){ $permissionGroup = $groupCenter->getPermissionsGroup(); //iterate on roleScopes foreach($permissionGroup->getRoleScopes() as $roleScope) { //check that the role is in the reachable roles if ($this->isRoleReached($role, new Role($roleScope->getRole()))) { if ($scope === null) { $centers[] = $groupCenter->getCenter(); break 1; } else { if ($scope->getId() == $roleScope->getScope()->getId()){ $centers[] = $groupCenter->getCenter(); break 1; } } } } } return $centers; } /** * 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 $role, Center $center) { return $this->getReachableCircles($user, $role, $center); } /** * Return all reachable circle for a given user, center and role * * @param User $user * @param Role $role * @param Center $center * @return Scope[] */ public function getReachableCircles(User $user, Role $role, Center $center) { $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, new 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(Role $childRole, Role $parentRole) { $reachableRoles = $this->roleHierarchy ->getReachableRoles([$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; } }