From 5a1a5049f10fafa095428b697869aabdd7620c96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tonon=20Gr=C3=A9gory?= <gtonon@clever-age.com> Date: Mon, 27 Jan 2025 11:21:12 +0100 Subject: [PATCH 1/3] Permissions on users logic --- config/services/controller.yaml | 3 + src/CleverAgeUiProcessBundle.php | 7 ++ .../Admin/LogRecordCrudController.php | 34 +++++- src/Controller/Admin/Process/LaunchAction.php | 4 + .../Admin/ProcessDashboardController.php | 17 ++- .../Admin/ProcessExecutionCrudController.php | 27 ++++- .../Admin/ProcessScheduleCrudController.php | 2 + src/Controller/Admin/UserCrudController.php | 23 +++- src/Controller/ProcessExecuteController.php | 3 + .../SecurityRolesCompilerPass.php | 34 ++++++ templates/admin/process/list.html.twig | 104 +++++++++--------- translations/messages.fr.yaml | 2 + 12 files changed, 193 insertions(+), 67 deletions(-) create mode 100644 src/DependencyInjection/CompilerPass/SecurityRolesCompilerPass.php diff --git a/config/services/controller.yaml b/config/services/controller.yaml index 4cec79e..f37eac3 100644 --- a/config/services/controller.yaml +++ b/config/services/controller.yaml @@ -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' } diff --git a/src/CleverAgeUiProcessBundle.php b/src/CleverAgeUiProcessBundle.php index 8acf1d7..6ede138 100644 --- a/src/CleverAgeUiProcessBundle.php +++ b/src/CleverAgeUiProcessBundle.php @@ -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 @@ -21,4 +23,9 @@ public function getPath(): string { return \dirname(__DIR__); } + + public function build(ContainerBuilder $container) + { + $container->addCompilerPass(new SecurityRolesCompilerPass()); + } } diff --git a/src/Controller/Admin/LogRecordCrudController.php b/src/Controller/Admin/LogRecordCrudController.php index ce515a5..1987d43 100644 --- a/src/Controller/Admin/LogRecordCrudController.php +++ b/src/Controller/Admin/LogRecordCrudController.php @@ -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, ) { } @@ -83,8 +90,14 @@ 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) @@ -92,4 +105,23 @@ public function configureFilters(Filters $filters): Filters ChoiceFilter::new('level')->setChoices(array_combine(Level::NAMES, Level::VALUES)) )->add('message')->add('context')->add('createdAt'); } + + 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; + } } diff --git a/src/Controller/Admin/Process/LaunchAction.php b/src/Controller/Admin/Process/LaunchAction.php index 8f6bed5..7afdede 100644 --- a/src/Controller/Admin/Process/LaunchAction.php +++ b/src/Controller/Admin/Process/LaunchAction.php @@ -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'); diff --git a/src/Controller/Admin/ProcessDashboardController.php b/src/Controller/Admin/ProcessDashboardController.php index 9ec0908..872b8a5 100644 --- a/src/Controller/Admin/ProcessDashboardController.php +++ b/src/Controller/Admin/ProcessDashboardController.php @@ -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( @@ -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 diff --git a/src/Controller/Admin/ProcessExecutionCrudController.php b/src/Controller/Admin/ProcessExecutionCrudController.php index 5e60a60..63cc798 100644 --- a/src/Controller/Admin/ProcessExecutionCrudController.php +++ b/src/Controller/Admin/ProcessExecutionCrudController.php @@ -17,12 +17,17 @@ 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; @@ -30,14 +35,16 @@ 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, ) { } @@ -153,4 +160,22 @@ private function getLogFilePath(ProcessExecution $processExecution): string \DIRECTORY_SEPARATOR.$processExecution->logFilename ; } + + 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( + (string) $qb->expr()->concat($qb->expr()->literal('ROLE_PROCESS_VIEW#'), 'entity.code'), + ':roles' + ) + )->setParameter('roles', $roles); + + return $qb; + } } diff --git a/src/Controller/Admin/ProcessScheduleCrudController.php b/src/Controller/Admin/ProcessScheduleCrudController.php index df5b2f2..4c0edc7 100644 --- a/src/Controller/Admin/ProcessScheduleCrudController.php +++ b/src/Controller/Admin/ProcessScheduleCrudController.php @@ -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) diff --git a/src/Controller/Admin/UserCrudController.php b/src/Controller/Admin/UserCrudController.php index d084fdd..e024018 100644 --- a/src/Controller/Admin/UserCrudController.php +++ b/src/Controller/Admin/UserCrudController.php @@ -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; @@ -31,19 +32,29 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\PasswordHasher\Hasher\Pbkdf2PasswordHasher; use Symfony\Component\Security\Http\Attribute\IsGranted; +use Symfony\Contracts\Translation\TranslatorInterface; -#[IsGranted('ROLE_USER')] +#[IsGranted('ROLE_SUPER_ADMIN')] class UserCrudController extends AbstractCrudController { - /** @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; } @@ -79,7 +90,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'); diff --git a/src/Controller/ProcessExecuteController.php b/src/Controller/ProcessExecuteController.php index 5de49d2..3b070c2 100644 --- a/src/Controller/ProcessExecuteController.php +++ b/src/Controller/ProcessExecuteController.php @@ -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 ?? '', diff --git a/src/DependencyInjection/CompilerPass/SecurityRolesCompilerPass.php b/src/DependencyInjection/CompilerPass/SecurityRolesCompilerPass.php new file mode 100644 index 0000000..e4c049f --- /dev/null +++ b/src/DependencyInjection/CompilerPass/SecurityRolesCompilerPass.php @@ -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); + $container->setParameter('security.role_hierarchy.roles', $roleHierarchy); + $container->getDefinition('security.role_hierarchy')->replaceArgument(0, $roleHierarchy); + } + } + } +} diff --git a/templates/admin/process/list.html.twig b/templates/admin/process/list.html.twig index 06b5cff..2a63136 100644 --- a/templates/admin/process/list.html.twig +++ b/templates/admin/process/list.html.twig @@ -14,7 +14,7 @@ <th><span>{{ 'Status'|trans }}</span></th> <th><span>{{ 'Source'|trans }}</span></th> <th><span>{{ 'Target'|trans }}</span></th> - <th class="text-center"><span>{{ 'Actions'|trans }}</span></th> + <th class="text-right"><span>{{ 'Actions'|trans }}</span></th> </tr> {% endblock %} </thead> @@ -22,57 +22,61 @@ {% block table_body %} {# @var process \CleverAge\ProcessBundle\Configuration\ProcessConfiguration #} {% for process in processes %} - {% set lastExecution = get_last_execution_date(process.code) %} - {% set uiOptions = resolve_ui_options(process.code) %} - {% set statusClass = '' %} - {% if lastExecution is not null %} - {% set statusClass = lastExecution.status.value == 'failed' ? 'danger' : 'success' %} - {% endif %} - <tr> - <td>{{ process.code }}</td> - <td>{% if lastExecution is not null %}{{ lastExecution.startDate|date('Y/m/d H:i:s') }}{% endif %}</td> - <td><span class="badge badge-{{ statusClass }}">{% if lastExecution is not null %}{{ lastExecution.status.value }}{% endif %}</span></td> - <td>{% if process.options.ui.source is defined %}{{ process.options.ui.source }}{% endif %}</td> - <td>{% if process.options.ui.target is defined %}{{ process.options.ui.target }}{% endif %}</td> - <td class="text-center"> - {% if ('modal' == uiOptions.ui_launch_mode) %} - <a class="px-1" data-toggle="tooltip" data-placement="top" title="{{ 'Launch'|trans }}" data-bs-toggle="modal" data-bs-target="#{{ process.code }}"> - <i class="fas fa-rocket"></i> - </a> - {% else %} - <a class="px-1" data-toggle="tooltip" data-placement="top" title="{{ 'Launch'|trans }}" href="{{ url('process', {routeName: 'process_launch', process: process.code}) }}"> - <i class="fas fa-rocket"></i> - </a> - {% endif %} - <a - class="px-1" - data-toggle="tooltip" - data-placement="top" - title="{{ 'View executions'|trans }}" - href="{{ url( - 'process', - { - crudAction: 'index', - crudControllerFqcn: 'CleverAge\\UiProcessBundle\\Controller\\Admin\\ProcessExecutionCrudController', - filters: { - code: { - comparison: '=', - value: process.code, + {% if is_granted("ROLE_PROCESS_VIEW##{process.code}", process) %} + {% set lastExecution = get_last_execution_date(process.code) %} + {% set uiOptions = resolve_ui_options(process.code) %} + {% set statusClass = '' %} + {% if lastExecution is not null %} + {% set statusClass = lastExecution.status.value == 'failed' ? 'danger' : 'success' %} + {% endif %} + <tr> + <td>{{ process.code }}</td> + <td>{% if lastExecution is not null %}{{ lastExecution.startDate|date('Y/m/d H:i:s') }}{% endif %}</td> + <td><span class="badge badge-{{ statusClass }}">{% if lastExecution is not null %}{{ lastExecution.status.value }}{% endif %}</span></td> + <td>{% if process.options.ui.source is defined %}{{ process.options.ui.source }}{% endif %}</td> + <td>{% if process.options.ui.target is defined %}{{ process.options.ui.target }}{% endif %}</td> + <td class="text-right"> + {% if is_granted("ROLE_PROCESS_EXECUTE##{process.code}", process) %} + {% if ('modal' == uiOptions.ui_launch_mode) %} + <a class="px-1" data-toggle="tooltip" data-placement="top" title="{{ 'Launch'|trans }}" data-bs-toggle="modal" data-bs-target="#{{ process.code }}" role="button"> + <i class="fas fa-rocket"></i> + </a> + {% else %} + <a class="px-1" data-toggle="tooltip" data-placement="top" title="{{ 'Launch'|trans }}" href="{{ url('process', {routeName: 'process_launch', process: process.code}) }}" role="button"> + <i class="fas fa-rocket"></i> + </a> + {% endif %} + {% endif %} + <a + class="px-1" + data-toggle="tooltip" + data-placement="top" + title="{{ 'View executions'|trans }}" + href="{{ url( + 'process', + { + crudAction: 'index', + crudControllerFqcn: 'CleverAge\\UiProcessBundle\\Controller\\Admin\\ProcessExecutionCrudController', + filters: { + code: { + comparison: '=', + value: process.code, + }, }, }, - }, - ) }}" - > - <i class="fas fa-eye"></i> - </a> - </td> - </tr> - <twig:ui:BootstrapModal - id="{{ process.code }}" - title="{{ 'Run process'|trans }} {{ process.code }}" - message="{{ 'Do you really want to run process %process% in background'|trans({'%process%': process.code}) }} ?" - confirmUrl="{{ url('process', {routeName: 'process_launch', process: process.code}) }}" - /> + ) }}" + > + <i class="fas fa-eye"></i> + </a> + </td> + </tr> + <twig:ui:BootstrapModal + id="{{ process.code }}" + title="{{ 'Run process'|trans }} {{ process.code }}" + message="{{ 'Do you really want to run process %process% in background'|trans({'%process%': process.code}) }} ?" + confirmUrl="{{ url('process', {routeName: 'process_launch', process: process.code}) }}" + /> + {% endif %} {% endfor %} {% endblock %} </tbody> diff --git a/translations/messages.fr.yaml b/translations/messages.fr.yaml index 4d276e3..e9126d0 100644 --- a/translations/messages.fr.yaml +++ b/translations/messages.fr.yaml @@ -52,3 +52,5 @@ key: clé value: valeur Context Key: Context clé Context Value: Context valeur +View process: Voir le process +Execute process: Exécuter le process From 77d847a6ab974ebe11053d9dd10d2713f8c62830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tonon=20Gr=C3=A9gory?= <gtonon@clever-age.com> Date: Tue, 28 Jan 2025 10:27:17 +0100 Subject: [PATCH 2/3] Add impersonate action --- src/Controller/Admin/UserCrudController.php | 6 +++++- src/DependencyInjection/CleverAgeUiProcessExtension.php | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Controller/Admin/UserCrudController.php b/src/Controller/Admin/UserCrudController.php index e024018..0aa2f70 100644 --- a/src/Controller/Admin/UserCrudController.php +++ b/src/Controller/Admin/UserCrudController.php @@ -31,6 +31,7 @@ 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; @@ -106,7 +107,10 @@ 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(function (User $user) { + return $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 diff --git a/src/DependencyInjection/CleverAgeUiProcessExtension.php b/src/DependencyInjection/CleverAgeUiProcessExtension.php index 2b7b302..c1a39ca 100644 --- a/src/DependencyInjection/CleverAgeUiProcessExtension.php +++ b/src/DependencyInjection/CleverAgeUiProcessExtension.php @@ -150,6 +150,7 @@ public function prepend(ContainerBuilder $container): void 'target' => 'process_login', 'clear_site_data' => '*', ], + 'switch_user' => true, ], ], ] From 312804102825fc5ccf43ad37ddca3c5cc4bc9210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tonon=20Gr=C3=A9gory?= <gtonon@clever-age.com> Date: Tue, 28 Jan 2025 10:33:16 +0100 Subject: [PATCH 3/3] Add impersonate action quality fix --- src/Controller/Admin/UserCrudController.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Controller/Admin/UserCrudController.php b/src/Controller/Admin/UserCrudController.php index 0aa2f70..21da6e1 100644 --- a/src/Controller/Admin/UserCrudController.php +++ b/src/Controller/Admin/UserCrudController.php @@ -108,9 +108,7 @@ public function configureActions(Actions $actions): Actions ->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')) - ->add(Crud::PAGE_INDEX, Action::new('ConnectAs')->linkToUrl(function (User $user) { - return $this->generateUrl('process', ['_switch_user' => $user->getEmail()], UrlGenerator::ABSOLUTE_URL); - })->setLabel(false)->setIcon('fa-solid fa-right-to-bracket'))->setPermission('ConnectAs', 'ROLE_SUPER_ADMIN'); + ->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