From e89f5e4713621d8a6a1b25d28fdb2a3a374ca767 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 26 May 2025 12:26:48 +0200 Subject: [PATCH] Add and enforce 'DUPLICATE' permissions for Saved Exports Introduce a new 'DUPLICATE' permission in SavedExportVoter and update related logic in the controller and templates to enforce this rule. Ensure only authorized users can duplicate exports and adjust UI elements accordingly for better permission handling. --- .../Controller/SavedExportController.php | 10 +++++----- .../Resources/views/SavedExport/index.html.twig | 8 +++++++- .../Security/Authorization/SavedExportVoter.php | 7 ++++++- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Controller/SavedExportController.php b/src/Bundle/ChillMainBundle/Controller/SavedExportController.php index cb9d89215..22e4a6ba7 100644 --- a/src/Bundle/ChillMainBundle/Controller/SavedExportController.php +++ b/src/Bundle/ChillMainBundle/Controller/SavedExportController.php @@ -120,15 +120,15 @@ final readonly class SavedExportController #[Route(path: '/exports/saved/duplicate-from-saved-export/{id}/new', name: 'chill_main_export_saved_duplicate')] public function duplicate(SavedExport $previousSavedExport, Request $request): Response { - if (!$this->security->isGranted(SavedExportVoter::GENERATE, $previousSavedExport)) { - throw new AccessDeniedHttpException('Not allowed to see this saved export'); - } - $user = $this->security->getUser(); if (!$user instanceof User) { throw new AccessDeniedHttpException('only regular user can create a saved export'); } + if (!$this->security->isGranted(SavedExportVoter::EDIT, $previousSavedExport)) { + throw new AccessDeniedHttpException('Not allowed to edit this saved export'); + } + $savedExport = new SavedExport(); $savedExport ->setExportAlias($previousSavedExport->getExportAlias()) @@ -209,7 +209,7 @@ final readonly class SavedExportController #[Route(path: '/{_locale}/exports/saved/{savedExport}/edit-options/{exportGeneration}', name: 'chill_main_export_saved_options_edit')] public function updateOptionsFromGeneration(SavedExport $savedExport, ExportGeneration $exportGeneration, Request $request): Response { - if (!$this->security->isGranted(SavedExportVoter::EDIT, $savedExport)) { + if (!$this->security->isGranted(SavedExportVoter::DUPLICATE, $savedExport)) { throw new AccessDeniedHttpException('You are not allowed to access this saved export'); } diff --git a/src/Bundle/ChillMainBundle/Resources/views/SavedExport/index.html.twig b/src/Bundle/ChillMainBundle/Resources/views/SavedExport/index.html.twig index 4a9b9490e..40257a996 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/SavedExport/index.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/SavedExport/index.html.twig @@ -30,6 +30,10 @@

{% if app.user is same as saved.user %}{{ 'saved_export.Owner'|trans }}{% endif %}

+ {% else %} +

+ Partagé par {{ saved.user|chill_entity_render_box }} +

{% endif %}

{{ saved.description|chill_markdown_to_html }}

@@ -63,7 +67,9 @@ {% endif %} {# reminder: the controller already checked that the user can generate saved exports #}
  • {{ 'saved_export.update_filters_aggregators_and_execute'|trans }}
  • -
  • {{ 'saved_export.Duplicate'|trans }}
  • + {% if is_granted('CHILL_MAIN_EXPORT_SAVED_DUPLICATE', saved) %} +
  • {{ 'saved_export.Duplicate'|trans }}
  • + {% endif %} {% if is_granted('CHILL_MAIN_EXPORT_SAVED_DELETE', saved) %}
  • {{ 'Delete'|trans }}
  • {% endif %} diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/SavedExportVoter.php b/src/Bundle/ChillMainBundle/Security/Authorization/SavedExportVoter.php index 1d686dee4..aaf8b8010 100644 --- a/src/Bundle/ChillMainBundle/Security/Authorization/SavedExportVoter.php +++ b/src/Bundle/ChillMainBundle/Security/Authorization/SavedExportVoter.php @@ -15,6 +15,7 @@ use Chill\MainBundle\Entity\SavedExport; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Export\ExportManager; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; use Symfony\Component\Security\Core\Authorization\Voter\Voter; final class SavedExportVoter extends Voter @@ -25,6 +26,8 @@ final class SavedExportVoter extends Voter final public const GENERATE = 'CHILL_MAIN_EXPORT_SAVED_GENERATE'; + final public const DUPLICATE = 'CHILL_MAIN_EXPORT_SAVED_DUPLICATE'; + final public const SHARE = 'CHILL_MAIN_EXPORT_SAVED_SHARE'; private const ALL = [ @@ -32,9 +35,10 @@ final class SavedExportVoter extends Voter self::EDIT, self::GENERATE, self::SHARE, + self::DUPLICATE, ]; - public function __construct(private readonly ExportManager $exportManager) {} + public function __construct(private readonly ExportManager $exportManager, private readonly AccessDecisionManagerInterface $accessDecisionManager) {} protected function supports($attribute, $subject): bool { @@ -52,6 +56,7 @@ final class SavedExportVoter extends Voter return match ($attribute) { self::DELETE, self::EDIT, self::SHARE => $subject->getUser() === $token->getUser(), + self::DUPLICATE => $this->accessDecisionManager->decide($token, [ChillExportVoter::COMPOSE_EXPORT]) && $this->accessDecisionManager->decide($token, [self::EDIT], $subject) , self::GENERATE => $this->canUserGenerate($user, $subject), default => throw new \UnexpectedValueException('attribute not supported: '.$attribute), };