diff --git a/src/Bundle/ChillMainBundle/Controller/ExportIndexController.php b/src/Bundle/ChillMainBundle/Controller/ExportIndexController.php
index d65a6c55e..4f3de7aef 100644
--- a/src/Bundle/ChillMainBundle/Controller/ExportIndexController.php
+++ b/src/Bundle/ChillMainBundle/Controller/ExportIndexController.php
@@ -14,6 +14,7 @@ namespace Chill\MainBundle\Controller;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Export\ExportManager;
use Chill\MainBundle\Repository\ExportGenerationRepository;
+use Chill\MainBundle\Security\Authorization\ChillExportVoter;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Annotation\Route;
@@ -40,6 +41,10 @@ final readonly class ExportIndexController
throw new AccessDeniedHttpException('Only regular user can see this page');
}
+ if (!$this->security->isGranted(ChillExportVoter::COMPOSE_EXPORT)) {
+ throw new AccessDeniedHttpException(sprintf('Require the %s role', ChillExportVoter::COMPOSE_EXPORT));
+ }
+
$exports = $this->exportManager->getExportsGrouped(true);
$lastExecutions = [];
diff --git a/src/Bundle/ChillMainBundle/Controller/SavedExportController.php b/src/Bundle/ChillMainBundle/Controller/SavedExportController.php
index 8e08f67d6..b7e38f7f4 100644
--- a/src/Bundle/ChillMainBundle/Controller/SavedExportController.php
+++ b/src/Bundle/ChillMainBundle/Controller/SavedExportController.php
@@ -20,6 +20,7 @@ use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\MainBundle\Form\SavedExportType;
use Chill\MainBundle\Repository\ExportGenerationRepository;
use Chill\MainBundle\Repository\SavedExportRepositoryInterface;
+use Chill\MainBundle\Security\Authorization\ChillExportVoter;
use Chill\MainBundle\Security\Authorization\ExportGenerationVoter;
use Chill\MainBundle\Security\Authorization\SavedExportVoter;
use Doctrine\ORM\EntityManagerInterface;
@@ -170,8 +171,8 @@ final readonly class SavedExportController
{
$user = $this->security->getUser();
- if (!$this->security->isGranted('ROLE_USER') || !$user instanceof User) {
- throw new AccessDeniedHttpException();
+ if (!$this->security->isGranted(ChillExportVoter::GENERATE_SAVED_EXPORT) || !$user instanceof User) {
+ throw new AccessDeniedHttpException(sprintf('Missing role: %s', ChillExportVoter::GENERATE_SAVED_EXPORT));
}
$exports = array_filter(
diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php b/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php
index 9fa3b0f42..6b33b998d 100644
--- a/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php
+++ b/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php
@@ -78,6 +78,7 @@ use Chill\MainBundle\Form\RegroupmentType;
use Chill\MainBundle\Form\UserGroupType;
use Chill\MainBundle\Form\UserJobType;
use Chill\MainBundle\Form\UserType;
+use Chill\MainBundle\Security\Authorization\ChillExportVoter;
use Misd\PhoneNumberBundle\Doctrine\DBAL\Types\PhoneNumberType;
use Ramsey\Uuid\Doctrine\UuidType;
use Symfony\Component\Config\FileLocator;
@@ -332,6 +333,9 @@ class ChillMainExtension extends Extension implements
'strategy' => 'unanimous',
'allow_if_all_abstain' => false,
],
+ 'role_hierarchy' => [
+ ChillExportVoter::COMPOSE_EXPORT => ChillExportVoter::GENERATE_SAVED_EXPORT,
+ ],
]);
// add crud api
diff --git a/src/Bundle/ChillMainBundle/Resources/views/Export/_navbar.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Export/_navbar.html.twig
index ae62a6313..6ae68f240 100644
--- a/src/Bundle/ChillMainBundle/Resources/views/Export/_navbar.html.twig
+++ b/src/Bundle/ChillMainBundle/Resources/views/Export/_navbar.html.twig
@@ -1,9 +1,11 @@
- -
-
- {{ 'Exports list'|trans }}
-
-
+ {% if is_granted('CHILL_MAIN_COMPOSE_EXPORT') %}
+ -
+
+ {{ 'Exports list'|trans }}
+
+
+ {% endif %}
-
{{ 'saved_export.Saved exports'|trans }}
diff --git a/src/Bundle/ChillMainBundle/Routing/MenuBuilder/SectionMenuBuilder.php b/src/Bundle/ChillMainBundle/Routing/MenuBuilder/SectionMenuBuilder.php
index ab73932b5..0bfea342c 100644
--- a/src/Bundle/ChillMainBundle/Routing/MenuBuilder/SectionMenuBuilder.php
+++ b/src/Bundle/ChillMainBundle/Routing/MenuBuilder/SectionMenuBuilder.php
@@ -49,9 +49,9 @@ class SectionMenuBuilder implements LocalMenuBuilderInterface
);
}
- if ($this->authorizationChecker->isGranted(ChillExportVoter::EXPORT)) {
+ if ($this->authorizationChecker->isGranted(ChillExportVoter::GENERATE_SAVED_EXPORT)) {
$menu->addChild($this->translator->trans('Export Menu'), [
- 'route' => 'chill_main_export_index',
+ 'route' => 'chill_main_export_saved_list_my',
])
->setExtras([
'icons' => ['upload'],
diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/ChillExportVoter.php b/src/Bundle/ChillMainBundle/Security/Authorization/ChillExportVoter.php
index a3db10580..4e6719d0c 100644
--- a/src/Bundle/ChillMainBundle/Security/Authorization/ChillExportVoter.php
+++ b/src/Bundle/ChillMainBundle/Security/Authorization/ChillExportVoter.php
@@ -11,35 +11,34 @@ declare(strict_types=1);
namespace Chill\MainBundle\Security\Authorization;
-use Chill\MainBundle\Export\DirectExportInterface;
-use Chill\MainBundle\Export\ExportInterface;
-use Chill\MainBundle\Export\ExportManager;
+use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
-class ChillExportVoter extends Voter
+class ChillExportVoter extends Voter implements ProvideRoleHierarchyInterface
{
- final public const EXPORT = 'chill_export';
+ /**
+ * Role which give access to the creation of new export from the export itself.
+ */
+ final public const COMPOSE_EXPORT = 'CHILL_MAIN_COMPOSE_EXPORT';
+
+ /**
+ * Role which give access to the execution and edition to the saved exports, but not for creating new ones.
+ */
+ final public const GENERATE_SAVED_EXPORT = 'CHILL_MAIN_GENERATE_SAVED_EXPORT';
private readonly VoterHelperInterface $helper;
- public function __construct(VoterHelperFactoryInterface $voterHelperFactory, ExportManager $exportManager)
+ public function __construct(VoterHelperFactoryInterface $voterHelperFactory)
{
$this->helper = $voterHelperFactory
->generate(self::class)
- ->addCheckFor(null, [self::EXPORT])
+ ->addCheckFor(null, [self::COMPOSE_EXPORT, self::GENERATE_SAVED_EXPORT])
->build();
}
protected function supports($attribute, $subject): bool
{
- if (
- ($subject instanceof ExportInterface or $subject instanceof DirectExportInterface)
- && $attribute === $subject->requiredRole()
- ) {
- return true;
- }
-
return $this->helper->supports($attribute, $subject);
}
@@ -47,4 +46,21 @@ class ChillExportVoter extends Voter
{
return $this->helper->voteOnAttribute($attribute, $subject, $token);
}
+
+ public function getRolesWithHierarchy(): array
+ {
+ return ['export.role.export_role' => [
+ self::COMPOSE_EXPORT, self::GENERATE_SAVED_EXPORT,
+ ]];
+ }
+
+ public function getRoles(): array
+ {
+ return [self::COMPOSE_EXPORT, self::GENERATE_SAVED_EXPORT];
+ }
+
+ public function getRolesWithoutScope(): array
+ {
+ return $this->getRoles();
+ }
}
diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/SavedExportVoter.php b/src/Bundle/ChillMainBundle/Security/Authorization/SavedExportVoter.php
index 8c4db8a60..d84b5a700 100644
--- a/src/Bundle/ChillMainBundle/Security/Authorization/SavedExportVoter.php
+++ b/src/Bundle/ChillMainBundle/Security/Authorization/SavedExportVoter.php
@@ -35,7 +35,7 @@ class SavedExportVoter extends Voter
self::SHARE,
];
- public function __construct(private ExportManager $exportManager, private Security $security) {}
+ public function __construct(private ExportManager $exportManager) {}
protected function supports($attribute, $subject): bool
{
diff --git a/src/Bundle/ChillMainBundle/migrations/Version20250417135712.php b/src/Bundle/ChillMainBundle/migrations/Version20250417135712.php
new file mode 100644
index 000000000..2ba6e2d82
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/migrations/Version20250417135712.php
@@ -0,0 +1,67 @@
+addSql(
+ <<<'SQL'
+ CREATE TEMPORARY TABLE to_create AS (
+ SELECT DISTINCT permissionsgroup_rolescope.permissionsgroup_id, 'CHILL_MAIN_COMPOSE_EXPORT' AS role
+ FROM permissionsgroup_rolescope
+ JOIN public.role_scopes rs on permissionsgroup_rolescope.rolescope_id = rs.id
+ WHERE role LIKE '%STATS%' or role LIKE '%LIST%'
+ )
+ SQL
+ );
+
+ $this->addSql(
+ <<<'SQL'
+ INSERT INTO role_scopes(id, scope_id, role)
+ SELECT nextval('role_scopes_id_seq'), null, 'CHILL_MAIN_COMPOSE_EXPORT'
+ WHERE NOT EXISTS (SELECT 1 FROM role_scopes s WHERE role like 'CHILL_MAIN_COMPOSE_EXPORT')
+ SQL
+ );
+
+ $this->addSql('ALTER TABLE to_create ADD COLUMN rolescope_id INT');
+
+ $this->addSql(
+ <<<'SQL'
+ UPDATE to_create SET rolescope_id = (
+ SELECT id FROM role_scopes
+ WHERE to_create.role = role_scopes.role)
+ SQL
+ );
+
+ $this->addSql(
+ <<<'SQL'
+ INSERT INTO permissionsgroup_rolescope (permissionsgroup_id, rolescope_id)
+ SELECT to_create.permissionsgroup_id, to_create.rolescope_id FROM to_create
+ SQL
+ );
+ }
+
+ public function down(Schema $schema): void
+ {
+ $this->throwIrreversibleMigrationException();
+ }
+}
diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml
index c608f1fd6..e2c5bdda1 100644
--- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml
+++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml
@@ -714,8 +714,12 @@ notification:
mark_as_read: Marquer comme lu
mark_as_unread: Marquer comme non-lu
+CHILL_MAIN_COMPOSE_EXPORT: Exécuter des exports et les sauvegarder
+CHILL_MAIN_GENERATE_SAVED_EXPORT: Exécuter et modifier des exports préalablement sauvegardés
export:
+ role:
+ export_role: Exports
generation:
Export generation is pending: La génération de l'export est en cours
Export generation is pending_short: En cours
diff --git a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php
index 40393f79e..ffff789c2 100644
--- a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php
+++ b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php
@@ -12,7 +12,6 @@ declare(strict_types=1);
namespace Chill\PersonBundle\DependencyInjection;
use Chill\MainBundle\DependencyInjection\MissingBundleException;
-use Chill\MainBundle\Security\Authorization\ChillExportVoter;
use Chill\PersonBundle\Controller\AccompanyingPeriodCommentApiController;
use Chill\PersonBundle\Controller\AccompanyingPeriodResourceApiController;
use Chill\PersonBundle\Controller\AdministrativeStatusController;
@@ -1027,8 +1026,6 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
'role_hierarchy' => [
PersonVoter::UPDATE => [PersonVoter::SEE],
PersonVoter::CREATE => [PersonVoter::SEE],
- PersonVoter::LISTS => [ChillExportVoter::EXPORT],
- PersonVoter::STATS => [ChillExportVoter::EXPORT],
// accompanying period
AccompanyingPeriodVoter::SEE_DETAILS => [AccompanyingPeriodVoter::SEE],
AccompanyingPeriodVoter::CREATE => [AccompanyingPeriodVoter::SEE_DETAILS],