mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
Add role-based access controls for export functionality
Introduced `CHILL_MAIN_COMPOSE_EXPORT` and `CHILL_MAIN_GENERATE_SAVED_EXPORT` roles for managing export creation and execution permissions. Updated access checks, menu routing, and templates to align with the new roles. Added a migration to extend existing permission groups with the `CHILL_MAIN_COMPOSE_EXPORT` role.
This commit is contained in:
parent
fc8e3789e0
commit
edeb8edbea
@ -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 = [];
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -1,9 +1,11 @@
|
||||
<ul class="nav nav-pills justify-content-center">
|
||||
{% if is_granted('CHILL_MAIN_COMPOSE_EXPORT') %}
|
||||
<li class="nav-item">
|
||||
<a href="{{ chill_path_forward_return_path('chill_main_export_index') }}" class="nav-link {% if current == 'common' %}active{% endif %}">
|
||||
{{ 'Exports list'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
<a href="{{ chill_path_forward_return_path('chill_main_export_saved_list_my') }}" class="nav-link {% if current == 'my' %}active{% endif %}">
|
||||
{{ 'saved_export.Saved exports'|trans }}
|
||||
|
@ -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'],
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\Migrations\Main;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20250417135712 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add the role CHILL_MAIN_COMPOSE_EXPORT to the existing permissions groups which contains already a permission related to list or stat';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->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();
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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],
|
||||
|
Loading…
x
Reference in New Issue
Block a user