added team permissions on user queries (#1815)

This commit is contained in:
Kevin Papst
2020-07-12 01:18:57 +02:00
committed by GitHub
parent 32c1e3258e
commit b97d9f692d
11 changed files with 128 additions and 36 deletions

View File

@@ -11,6 +11,7 @@ namespace App\Controller;
use App\Entity\Role;
use App\Entity\RolePermission;
use App\Entity\User;
use App\Event\PermissionSectionsEvent;
use App\Event\PermissionsEvent;
use App\Form\RoleType;
@@ -157,6 +158,7 @@ final class PermissionController extends AbstractController
'sorted' => $event->getPermissions(),
'manager' => $this->manager,
'system_roles' => $this->roleService->getSystemRoles(),
'always_apply_superadmin' => RolePermissionManager::SUPER_ADMIN_PERMISSIONS,
]);
}
@@ -219,12 +221,16 @@ final class PermissionController extends AbstractController
* @Route(path="/roles/{id}/{name}/{value}", name="admin_user_permission_save", methods={"GET"})
* @Security("is_granted('role_permissions')")
*/
public function savePermission(Role $role, string $name, string $value, RolePermissionRepository $rolePermissionRepository): Response
public function savePermission(Role $role, string $name, bool $value, RolePermissionRepository $rolePermissionRepository): Response
{
if (!$this->manager->isRegisteredPermission($name)) {
throw $this->createNotFoundException('Unknown permission: ' . $name);
}
if (false === $value && $role->getName() === User::ROLE_SUPER_ADMIN && \in_array($name, RolePermissionManager::SUPER_ADMIN_PERMISSIONS)) {
throw $this->createAccessDeniedException(sprintf('Permission "%s" cannot be deactivated for role "%s"', $name, $role->getName()));
}
try {
$permission = $rolePermissionRepository->findRolePermission($role, $name);
if (null === $permission) {

View File

@@ -126,6 +126,13 @@ class User extends BaseUser implements UserInterface
*/
private $auth = self::AUTH_INTERNAL;
/**
* This flag will be initialized in UserEnvironmentSubscriber.
*
* @var bool|null
*/
private $isAllowedToSeeAllData = null;
/**
* User constructor.
*/
@@ -379,7 +386,27 @@ class User extends BaseUser implements UserInterface
public function canSeeAllData(): bool
{
return $this->isSuperAdmin() || $this->isAdmin();
return $this->isSuperAdmin() || true === $this->isAllowedToSeeAllData;
}
/**
* This method should not be called by plugins and returns true on success or false on a failure.
*
* @internal immutable property that cannot be set by plugins
* @param bool $canSeeAllData
* @return bool
* @throws \Exception
*/
public function initCanSeeAllData(bool $canSeeAllData): bool
{
// prevent manipulation from plugins
if (null !== $this->isAllowedToSeeAllData) {
return false;
}
$this->isAllowedToSeeAllData = $canSeeAllData;
return true;
}
public function isTeamlead(): bool

View File

@@ -14,6 +14,7 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
class UserEnvironmentSubscriber implements EventSubscriberInterface
{
@@ -21,21 +22,30 @@ class UserEnvironmentSubscriber implements EventSubscriberInterface
* @var TokenStorageInterface
*/
private $storage;
/**
* @var AuthorizationCheckerInterface
*/
private $auth;
public function __construct(TokenStorageInterface $tokenStorage)
public function __construct(TokenStorageInterface $tokenStorage, AuthorizationCheckerInterface $auth)
{
$this->storage = $tokenStorage;
$this->auth = $auth;
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => ['prepareEnvironment', 100],
KernelEvents::REQUEST => ['prepareEnvironment', -100],
];
}
public function prepareEnvironment(RequestEvent $event)
{
if (!$event->isMasterRequest()) {
return;
}
if (null === $this->storage->getToken()) {
return;
}
@@ -45,6 +55,7 @@ class UserEnvironmentSubscriber implements EventSubscriberInterface
if ($user instanceof User) {
date_default_timezone_set($user->getTimezone());
\Locale::setDefault($user->getLocale());
$user->initCanSeeAllData($this->auth->isGranted('view_all_data'));
}
}
}

View File

@@ -13,16 +13,11 @@ use App\Entity\Role;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\ORMException;
/**
* @method Role[] findAll()
*/
class RoleRepository extends EntityRepository
{
/**
* @return Role[]
*/
public function findAll()
{
return parent::findAll();
}
public function saveRole(Role $role)
{
$entityManager = $this->getEntityManager();

View File

@@ -163,11 +163,30 @@ class UserRepository extends EntityRepository implements UserLoaderInterface
return;
}
$or = $qb->expr()->orX();
// if no explicit team was requested and the user is part of some teams
// then find all members of teams where he is teamlead
if (null !== $user && $user->hasTeamAssignment()) {
$qb->leftJoin('u.teams', 't');
$or->add($qb->expr()->eq('t.teamlead', ':teamlead'));
$qb->setParameter('teamlead', $user);
}
// if teams where requested, then select all team members
if (\count($teams) > 0) {
$or->add($qb->expr()->isMemberOf(':teams', 'u.teams'));
$qb->setParameter('teams', $teams);
}
// and make sure, that the user himself is always returned
if (null !== $user) {
$qb->leftJoin('u.teams', 'teams')
->leftJoin('teams.users', 'users')
->andWhere('teams.teamlead = :id')
->setParameter('id', $user);
$or->add($qb->expr()->eq('u.id', ':user'));
$qb->setParameter('user', $user);
}
if ($or->count() > 0) {
$qb->andWhere($or);
}
}
@@ -203,10 +222,12 @@ class UserRepository extends EntityRepository implements UserLoaderInterface
->orderBy('u.' . $query->getOrderBy(), $query->getOrder())
;
if (UserQuery::SHOW_VISIBLE == $query->getVisibility()) {
$this->addPermissionCriteria($qb, $query->getCurrentUser(), $query->getTeams());
if ($query->isShowVisible()) {
$qb->andWhere($qb->expr()->eq('u.enabled', ':enabled'));
$qb->setParameter('enabled', true, \PDO::PARAM_BOOL);
} elseif (UserQuery::SHOW_HIDDEN == $query->getVisibility()) {
} elseif ($query->isShowHidden()) {
$qb->andWhere($qb->expr()->eq('u.enabled', ':enabled'));
$qb->setParameter('enabled', false, \PDO::PARAM_BOOL);
}

View File

@@ -14,6 +14,17 @@ use App\Repository\RolePermissionRepository;
final class RolePermissionManager
{
/**
* Permissions that are always true for ROLE_SUPER_ADMIN, no matter what is inside the database.
*
* @var string[]
*/
public const SUPER_ADMIN_PERMISSIONS = [
'view_all_data',
'role_permissions',
'view_user'
];
/**
* @var array
*/
@@ -36,23 +47,22 @@ final class RolePermissionManager
foreach ($all as $item) {
$perm = $item['permission'];
$role = strtoupper($item['role']);
$isAllowed = $item['allowed'];
$isAllowed = (bool) $item['allowed'];
// see permissions.html.twig for this special case
if ($role === User::ROLE_SUPER_ADMIN && \in_array($perm, ['role_permissions', 'view_user'])) {
// these permissions may not be revoked at any time, because super admin would loose the ability to reactivate any permission
if ($role === User::ROLE_SUPER_ADMIN && \in_array($perm, self::SUPER_ADMIN_PERMISSIONS)) {
continue;
}
if (!$isAllowed) {
if (\array_key_exists($role, $this->permissions)) {
if (($key = array_search($perm, $this->permissions[$role])) !== false) {
unset($this->permissions[$role][$key]);
}
if (!\array_key_exists($role, $this->permissions)) {
$this->permissions[$role] = [];
}
if (false === $isAllowed) {
if (($key = array_search($perm, $this->permissions[$role])) !== false) {
unset($this->permissions[$role][$key]);
}
} else {
if (!\array_key_exists($role, $this->permissions)) {
$this->permissions[$role] = [];
}
$this->permissions[$role][] = $perm;
}
}