Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Permissions on users logic #39

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions config/services/controller.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,8 @@ services:
$context: '@EasyCorp\Bundle\EasyAdminBundle\Factory\AdminContextFactory'
$logDirectory: '%kernel.logs_dir%'
$processExecutionRepository: '@cleverage_ui_process.repository.process_execution'
CleverAge\ProcessBundle\Registry\ProcessConfigurationRegistry $processConfigurationRegistry: '@cleverage_process.registry.process_configuration'
Symfony\Component\Security\Core\Role\RoleHierarchy $roleHierarchy: '@security.role_hierarchy'
Symfony\Contracts\Translation\TranslatorInterface $translator: '@translator'
tags:
- { name: 'controller.service_arguments' }
7 changes: 7 additions & 0 deletions src/CleverAgeUiProcessBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

namespace CleverAge\UiProcessBundle;

use CleverAge\UiProcessBundle\DependencyInjection\CompilerPass\SecurityRolesCompilerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;

class CleverAgeUiProcessBundle extends Bundle
Expand All @@ -21,4 +23,9 @@ public function getPath(): string
{
return \dirname(__DIR__);
}

public function build(ContainerBuilder $container)
{
$container->addCompilerPass(new SecurityRolesCompilerPass());
}
}
34 changes: 33 additions & 1 deletion src/Controller/Admin/LogRecordCrudController.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,32 @@
use CleverAge\UiProcessBundle\Admin\Filter\LogProcessFilter;
use CleverAge\UiProcessBundle\Entity\LogRecord;
use CleverAge\UiProcessBundle\Manager\ProcessConfigurationsManager;
use Doctrine\ORM\QueryBuilder;
use EasyCorp\Bundle\EasyAdminBundle\Collection\FieldCollection;
use EasyCorp\Bundle\EasyAdminBundle\Collection\FilterCollection;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Config\Filters;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto;
use EasyCorp\Bundle\EasyAdminBundle\Dto\SearchDto;
use EasyCorp\Bundle\EasyAdminBundle\Field\BooleanField;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
use EasyCorp\Bundle\EasyAdminBundle\Filter\ChoiceFilter;
use Monolog\Level;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Role\RoleHierarchy;
use Symfony\Component\Security\Http\Attribute\IsGranted;

#[IsGranted('ROLE_USER')]
#[IsGranted('ROLE_ADMIN')]
class LogRecordCrudController extends AbstractCrudController
{
public function __construct(
private readonly ProcessConfigurationsManager $processConfigurationsManager,
private readonly RequestStack $requestStack,
private readonly RoleHierarchy $roleHierarchy,
) {
}

Expand Down Expand Up @@ -83,13 +90,38 @@ public function configureActions(Actions $actions): Actions
public function configureFilters(Filters $filters): Filters
{
$id = $this->requestStack->getMainRequest()?->query->all('filters')['process']['value'] ?? null;
$roles = $this->roleHierarchy->getReachableRoleNames($this->getUser()?->getRoles() ?? []);
$processList = $this->processConfigurationsManager->getPublicProcesses();
$processList = array_map(fn (ProcessConfiguration $cfg) => $cfg->getCode(), $processList);
$processList = array_filter(
$processList, fn (string $code) => \in_array('ROLE_PROCESS_VIEW#'.$code,
$roles
)
);

return $filters->add(
LogProcessFilter::new('process', $processList, $id)
)->add(
ChoiceFilter::new('level')->setChoices(array_combine(Level::NAMES, Level::VALUES))
)->add('message')->add('context')->add('createdAt');
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modification du query builder de la page de listing des logs pour ne pas afficher les rôles sur lesquels l'user connecté n'a pas le role ROLE_PROCESS_VIEW#{process.code}

public function createIndexQueryBuilder(
SearchDto $searchDto,
EntityDto $entityDto,
FieldCollection $fields,
FilterCollection $filters,
): QueryBuilder {
$roles = $this->roleHierarchy->getReachableRoleNames($this->getUser()?->getRoles() ?? []);
$qb = parent::createIndexQueryBuilder($searchDto, $entityDto, $fields, $filters);
$qb->join('entity.processExecution', 'processExecution');
$qb->andWhere(
$qb->expr()->in(
(string) $qb->expr()->concat($qb->expr()->literal('ROLE_PROCESS_VIEW#'), 'processExecution.code'),
':roles'
)
)->setParameter('roles', $roles);

return $qb;
}
}
4 changes: 4 additions & 0 deletions src/Controller/Admin/Process/LaunchAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ public function __invoke(
if (null === $processCode) {
throw new MissingProcessException();
}

if (false === $this->isGranted('ROLE_PROCESS_EXECUTE#'.$processCode)) {
throw $this->createAccessDeniedException();
}
$uiOptions = $processConfigurationsManager->getUiOptions($processCode);
if (null === $uiOptions) {
throw new \InvalidArgumentException('Missing UI Options');
Expand Down
17 changes: 8 additions & 9 deletions src/Controller/Admin/ProcessDashboardController.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Component\Translation\LocaleSwitcher;

#[IsGranted('ROLE_USER')]
#[IsGranted('ROLE_ADMIN')]
class ProcessDashboardController extends AbstractDashboardController
{
public function __construct(
Expand Down Expand Up @@ -59,16 +59,15 @@ public function configureMenuItems(): iterable
MenuItem::linkToRoute('Process list', 'fas fa-list', 'process_list'),
MenuItem::linkToCrud('Executions', 'fas fa-rocket', ProcessExecution::class),
MenuItem::linkToCrud('Logs', 'fas fa-pen', LogRecord::class),
MenuItem::linkToCrud('Scheduler', 'fas fa-solid fa-clock', ProcessSchedule::class),
MenuItem::linkToCrud('Scheduler', 'fas fa-solid fa-clock', ProcessSchedule::class)
->setPermission('ROLE_SUPER_ADMIN'),
]
);
if ($this->isGranted('ROLE_ADMIN')) {
yield MenuItem::subMenu('Users', 'fas fa-user')->setSubItems(
[
MenuItem::linkToCrud('User List', 'fas fa-user', User::class),
]
);
}
yield MenuItem::subMenu('Users', 'fas fa-user')->setSubItems(
[
MenuItem::linkToCrud('User List', 'fas fa-user', User::class),
]
)->setPermission('ROLE_SUPER_ADMIN');
}

public function configureCrud(): Crud
Expand Down
27 changes: 26 additions & 1 deletion src/Controller/Admin/ProcessExecutionCrudController.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,34 @@
use CleverAge\UiProcessBundle\Admin\Field\EnumField;
use CleverAge\UiProcessBundle\Entity\ProcessExecution;
use CleverAge\UiProcessBundle\Repository\ProcessExecutionRepository;
use Doctrine\ORM\QueryBuilder;
use EasyCorp\Bundle\EasyAdminBundle\Collection\FieldCollection;
use EasyCorp\Bundle\EasyAdminBundle\Collection\FilterCollection;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Config\Filters;
use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto;
use EasyCorp\Bundle\EasyAdminBundle\Dto\SearchDto;
use EasyCorp\Bundle\EasyAdminBundle\Field\ArrayField;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Security\Core\Role\RoleHierarchy;
use Symfony\Component\Security\Http\Attribute\IsGranted;

#[IsGranted('ROLE_USER')]
#[IsGranted('ROLE_ADMIN')]
class ProcessExecutionCrudController extends AbstractCrudController
{
public function __construct(
private readonly ProcessExecutionRepository $processExecutionRepository,
private readonly string $logDirectory,
private readonly RoleHierarchy $roleHierarchy,
) {
}

Expand Down Expand Up @@ -153,4 +160,22 @@ private function getLogFilePath(ProcessExecution $processExecution): string
\DIRECTORY_SEPARATOR.$processExecution->logFilename
;
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modification du query builder de la page de listing de l'execution des process pour ne pas afficher les rôles sur lesquels l'user connecté n'a pas le role ROLE_PROCESS_VIEW#{process.code}

public function createIndexQueryBuilder(
SearchDto $searchDto,
EntityDto $entityDto,
FieldCollection $fields,
FilterCollection $filters,
): QueryBuilder {
$roles = $this->roleHierarchy->getReachableRoleNames($this->getUser()?->getRoles() ?? []);
$qb = parent::createIndexQueryBuilder($searchDto, $entityDto, $fields, $filters);
$qb->andWhere(
$qb->expr()->in(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Si l'user a accès a ROLE_PROCESS_VIEW alors ne pas faire le filtrage.

(string) $qb->expr()->concat($qb->expr()->literal('ROLE_PROCESS_VIEW#'), 'entity.code'),
':roles'
)
)->setParameter('roles', $roles);

return $qb;
}
}
2 changes: 2 additions & 0 deletions src/Controller/Admin/ProcessScheduleCrudController.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Process\Process;
use Symfony\Component\Scheduler\Trigger\CronExpressionTrigger;
use Symfony\Component\Security\Http\Attribute\IsGranted;

#[IsGranted('ROLE_SUPER_ADMIN')]
class ProcessScheduleCrudController extends AbstractCrudController
{
public function __construct(private readonly ProcessConfigurationsManager $processConfigurationsManager)
Expand Down
27 changes: 20 additions & 7 deletions src/Controller/Admin/UserCrudController.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace CleverAge\UiProcessBundle\Controller\Admin;

use CleverAge\ProcessBundle\Registry\ProcessConfigurationRegistry;
use CleverAge\UiProcessBundle\Entity\User;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
Expand All @@ -30,20 +31,31 @@
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\PasswordHasher\Hasher\Pbkdf2PasswordHasher;
use Symfony\Component\Routing\Generator\UrlGenerator;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Contracts\Translation\TranslatorInterface;

#[IsGranted('ROLE_USER')]
#[IsGranted('ROLE_SUPER_ADMIN')]
class UserCrudController extends AbstractCrudController
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A voir si on fait une gestion des groupes via une admin ou juste via le security.yaml role_hierarchy.

{
/** @param array<string, string> $roles */
public function __construct(private readonly array $roles)
{
/** @param array<string, array<string, string>|string> $roles */
public function __construct(
private array $roles,
private readonly ProcessConfigurationRegistry $processConfigurationRegistry,
private readonly TranslatorInterface $translator,
) {
foreach ($this->processConfigurationRegistry->getProcessConfigurations() as $config) {
$this->roles[$config->getCode()] = [
$this->translator->trans('View process').' '.$config->getCode() => 'ROLE_PROCESS_VIEW#'.$config->getCode(),
$this->translator->trans('Execute process').' '.$config->getCode() => 'ROLE_PROCESS_EXECUTE#'.$config->getCode(),
];
}
}

public function configureCrud(Crud $crud): Crud
{
$crud->showEntityActionsInlined();
$crud->setEntityPermission('ROLE_ADMIN');
$crud->setEntityPermission('ROLE_SUPER_ADMIN');

return $crud;
}
Expand Down Expand Up @@ -79,7 +91,7 @@ public function configureFields(string $pageName): iterable
yield FormField::addTab('Roles')->setIcon('fa fa-theater-masks');
yield ChoiceField::new('roles', false)
->setChoices($this->roles)
->setFormTypeOptions(['multiple' => true, 'expanded' => true]);
->setFormTypeOptions(['multiple' => true, 'expanded' => false]);
yield FormField::addTab('Intl.')->setIcon('fa fa-flag');
yield TimezoneField::new('timezone');
yield LocaleField::new('locale');
Expand All @@ -95,7 +107,8 @@ public function configureActions(Actions $actions): Actions
->addCssClass('text-warning'))->update(Crud::PAGE_INDEX, Action::DELETE, fn (Action $action) => $action->setIcon('fa fa-trash-o')
->setLabel(false)
->addCssClass(''))->update(Crud::PAGE_INDEX, Action::BATCH_DELETE, fn (Action $action) => $action->setLabel('Delete')
->addCssClass(''))->add(Crud::PAGE_EDIT, Action::new('generateToken')->linkToCrudAction('generateToken'));
->addCssClass(''))->add(Crud::PAGE_EDIT, Action::new('generateToken')->linkToCrudAction('generateToken'))
->add(Crud::PAGE_INDEX, Action::new('ConnectAs')->linkToUrl(fn (User $user) => $this->generateUrl('process', ['_switch_user' => $user->getEmail()], UrlGenerator::ABSOLUTE_URL))->setLabel(false)->setIcon('fa-solid fa-right-to-bracket'))->setPermission('ConnectAs', 'ROLE_SUPER_ADMIN');
}

public function generateToken(AdminContext $adminContext, AdminUrlGenerator $adminUrlGenerator): Response
Expand Down
3 changes: 3 additions & 0 deletions src/Controller/ProcessExecuteController.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ public function __invoke(
}
throw new UnprocessableEntityHttpException(implode('. ', $violationsMessages));
}
if (false === $this->isGranted('ROLE_PROCESS_EXECUTE#'.$httpProcessExecution->code)) {
throw $this->createAccessDeniedException();
}
$bus->dispatch(
new ProcessExecuteMessage(
$httpProcessExecution->code ?? '',
Expand Down
1 change: 1 addition & 0 deletions src/DependencyInjection/CleverAgeUiProcessExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ public function prepend(ContainerBuilder $container): void
'target' => 'process_login',
'clear_site_data' => '*',
],
'switch_user' => true,
],
],
]
Expand Down
34 changes: 34 additions & 0 deletions src/DependencyInjection/CompilerPass/SecurityRolesCompilerPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

/*
* This file is part of the CleverAge/UiProcessBundle package.
*
* Copyright (c) Clever-Age
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace CleverAge\UiProcessBundle\DependencyInjection\CompilerPass;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class SecurityRolesCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if ($container->hasDefinition('security.role_hierarchy')) {
// For each configured process, add ROLE_PROCESS_VIEW#<code> and ROLE_PROCESS_EXECUTE#<code> under ROLE_SUPER_ADMIN role
$pbExtCfg = $container->getExtensionConfig('clever_age_process');
$processCodes = array_keys(array_merge(...array_column($pbExtCfg, 'configurations')));
$processRoles = array_merge(...array_map(fn ($code) => ['ROLE_PROCESS_VIEW#'.$code, 'ROLE_PROCESS_EXECUTE#'.$code], $processCodes));
$roleHierarchy = $container->getParameter('security.role_hierarchy.roles');
if (\is_array($roleHierarchy)) {
$roleHierarchy['ROLE_SUPER_ADMIN'] = array_merge($roleHierarchy['ROLE_SUPER_ADMIN'] ?? [], $processRoles);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rajouter 2 niveaux ROLE_PROCESS_VIEW et ROLE_PROCESS_EXECUTE qui contiennent leurs enfants respectifs. Du coup ROLE_SUPER_ADMIN, contient [ROLE_PROCESS_VIEW, ROLE_PROCESS_EXECUTE].

Ajouter un bout de doc expliquant qu'il suffit de mettre un role_hierarchy: ROLE_ADMIN: [ROLE_PROCESS_VIEW, ROLE_PROCESS_EXECUTE] si on a pas besoin de cette protection.

$container->setParameter('security.role_hierarchy.roles', $roleHierarchy);
$container->getDefinition('security.role_hierarchy')->replaceArgument(0, $roleHierarchy);
}
}
}
}
Loading
Loading