feat(core) : aggregate module permissions and add sync-permissions command
This commit is contained in:
@@ -24,16 +24,18 @@ final class CoreModule implements ModuleInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Permissions posées pour le RBAC fin (1.2). Inertes tant que 1.2 n'est pas livré.
|
||||
* Permissions RBAC fin du Module Core (1.2).
|
||||
*
|
||||
* @return list<array{code: string, label: string}>
|
||||
*/
|
||||
public static function permissions(): array
|
||||
{
|
||||
return [
|
||||
['code' => 'core.user.read', 'label' => 'Consulter les utilisateurs'],
|
||||
['code' => 'core.user.manage', 'label' => 'Gérer les utilisateurs'],
|
||||
['code' => 'core.notification.read', 'label' => 'Consulter ses notifications'],
|
||||
['code' => 'core.users.view', 'label' => 'Voir les utilisateurs'],
|
||||
['code' => 'core.users.manage', 'label' => 'Gérer les utilisateurs (créer, éditer, supprimer)'],
|
||||
['code' => 'core.roles.view', 'label' => 'Voir les rôles RBAC'],
|
||||
['code' => 'core.roles.manage', 'label' => 'Gérer les rôles et permissions'],
|
||||
['code' => 'core.permissions.view', 'label' => 'Consulter le catalogue des permissions RBAC'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Module\Core\Infrastructure\Console;
|
||||
|
||||
use App\Module\Core\Domain\Entity\Permission;
|
||||
use App\Module\Core\Domain\Repository\PermissionRepositoryInterface;
|
||||
use App\Shared\Domain\Module\ModuleRegistry;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
|
||||
use function count;
|
||||
|
||||
#[AsCommand(name: 'app:sync-permissions', description: 'Synchronise le catalogue des permissions depuis les modules actifs.')]
|
||||
final class SyncPermissionsCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $em,
|
||||
private readonly PermissionRepositoryInterface $permissions,
|
||||
#[Autowire('%kernel.project_dir%')]
|
||||
private readonly string $projectDir,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
/** @var list<class-string> $moduleClasses */
|
||||
$moduleClasses = require $this->projectDir.'/config/modules.php';
|
||||
|
||||
// Phase 1 : permissions désirées (code => {code,label,module}).
|
||||
$desired = [];
|
||||
foreach (ModuleRegistry::permissions($moduleClasses) as $perm) {
|
||||
$desired[$perm['code']] = $perm;
|
||||
}
|
||||
|
||||
// Phase 2 : upsert.
|
||||
$existing = [];
|
||||
foreach ($this->permissions->findAll() as $permission) {
|
||||
$existing[$permission->getCode()] = $permission;
|
||||
}
|
||||
|
||||
$added = $updated = $revived = 0;
|
||||
foreach ($desired as $code => $perm) {
|
||||
$entity = $existing[$code] ?? null;
|
||||
if (null === $entity) {
|
||||
$this->permissions->save(new Permission($perm['code'], $perm['label'], $perm['module']));
|
||||
++$added;
|
||||
|
||||
continue;
|
||||
}
|
||||
if ($entity->isOrphan()) {
|
||||
$entity->revive($perm['label'], $perm['module']);
|
||||
++$revived;
|
||||
} elseif ($entity->getLabel() !== $perm['label'] || $entity->getModule() !== $perm['module']) {
|
||||
$entity->updateMetadata($perm['label'], $perm['module']);
|
||||
++$updated;
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 3 : orphelines (existantes absentes des désirées).
|
||||
$orphaned = 0;
|
||||
foreach ($existing as $code => $entity) {
|
||||
if (!isset($desired[$code]) && !$entity->isOrphan()) {
|
||||
$entity->markOrphan();
|
||||
++$orphaned;
|
||||
}
|
||||
}
|
||||
|
||||
$this->em->flush();
|
||||
|
||||
$io->success(sprintf('Permissions synchronisées : %d ajoutées, %d mises à jour, %d réactivées, %d orphelines. Total désirées : %d.', $added, $updated, $revived, $orphaned, count($desired)));
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Shared\Domain\Module;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
final class ModuleRegistry
|
||||
{
|
||||
/**
|
||||
@@ -22,4 +24,29 @@ final class ModuleRegistry
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<class-string> $moduleClasses
|
||||
*
|
||||
* @return list<array{code: string, label: string, module: string}>
|
||||
*/
|
||||
public static function permissions(array $moduleClasses): array
|
||||
{
|
||||
$out = [];
|
||||
foreach ($moduleClasses as $moduleClass) {
|
||||
if (!is_a($moduleClass, ModuleInterface::class, true)) {
|
||||
continue;
|
||||
}
|
||||
$moduleId = $moduleClass::id();
|
||||
foreach ($moduleClass::permissions() as $perm) {
|
||||
$code = $perm['code'];
|
||||
if (!str_starts_with($code, $moduleId.'.')) {
|
||||
throw new InvalidArgumentException(sprintf('Permission "%s" du module "%s" doit être préfixée par "%s.".', $code, $moduleId, $moduleId));
|
||||
}
|
||||
$out[] = ['code' => $code, 'label' => $perm['label'], 'module' => $moduleId];
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user