feat(core) : add rbac seeder and seed-rbac command for system roles

This commit is contained in:
Matthieu
2026-06-19 17:22:42 +02:00
parent 48c67a5fb9
commit 1a9eba93a0
5 changed files with 118 additions and 0 deletions
+6
View File
@@ -25,6 +25,7 @@ use App\Enum\AbsenceType;
use App\Enum\ContractType;
use App\Enum\RecurrenceType;
use App\Enum\StatusCategory;
use App\Module\Core\Application\Rbac\RbacSeeder;
use App\Module\Core\Domain\Entity\User;
use DateTimeImmutable;
use DateTimeZone;
@@ -36,6 +37,7 @@ class AppFixtures extends Fixture
{
public function __construct(
private readonly UserPasswordHasherInterface $passwordHasher,
private readonly RbacSeeder $rbacSeeder,
) {}
public function load(ObjectManager $manager): void
@@ -751,5 +753,9 @@ class AppFixtures extends Fixture
$manager->persist($pendingMarriage);
$manager->flush();
// Seed des rôles système RBAC (admin, user). Idempotent ; aucune matrice
// métier attachée (cf. Décision 4 : les modules métier arrivent en 2.x).
$this->rbacSeeder->ensureSystemRoles();
}
}
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace App\Module\Core\Application\Rbac;
use App\Module\Core\Domain\Entity\Role;
use App\Module\Core\Domain\Repository\RoleRepositoryInterface;
use App\Module\Core\Domain\Security\SystemRoles;
use Doctrine\ORM\EntityManagerInterface;
final readonly class RbacSeeder
{
public function __construct(
private EntityManagerInterface $em,
private RoleRepositoryInterface $roles,
) {}
/**
* Crée les rôles système s'ils sont absents. Idempotent.
*/
public function ensureSystemRoles(): void
{
$this->ensureRole(SystemRoles::ADMIN_CODE, 'Administrateur', 'Accès complet (bypass RBAC).');
$this->ensureRole(SystemRoles::USER_CODE, 'Utilisateur', 'Rôle de base sans permission spécifique.');
$this->em->flush();
}
private function ensureRole(string $code, string $label, string $description): void
{
if (null !== $this->roles->findByCode($code)) {
return;
}
$this->roles->save(new Role($code, $label, $description, true));
}
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace App\Module\Core\Domain\Security;
final class SystemRoles
{
public const string ADMIN_CODE = 'admin';
public const string USER_CODE = 'user';
}
@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace App\Module\Core\Infrastructure\Console;
use App\Module\Core\Application\Rbac\RbacSeeder;
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;
#[AsCommand(name: 'app:seed-rbac', description: 'Seed les rôles système RBAC (admin, user).')]
final class SeedRbacCommand extends Command
{
public function __construct(private readonly RbacSeeder $seeder)
{
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$this->seeder->ensureSystemRoles();
$io->success('Rôles système RBAC seedés (admin, user).');
return Command::SUCCESS;
}
}
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace App\Tests\Functional\Module\Core;
use App\Module\Core\Domain\Repository\RoleRepositoryInterface;
use App\Module\Core\Domain\Security\SystemRoles;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Console\Tester\CommandTester;
/**
* @internal
*/
final class SeedRbacCommandTest extends KernelTestCase
{
public function testSeedsSystemRolesIdempotently(): void
{
$kernel = self::bootKernel();
$app = new Application($kernel);
$tester = new CommandTester($app->find('app:seed-rbac'));
$tester->execute([]);
$tester->assertCommandIsSuccessful();
$tester->execute([]); // idempotent
$tester->assertCommandIsSuccessful();
$repo = self::getContainer()->get(RoleRepositoryInterface::class);
$admin = $repo->findByCode(SystemRoles::ADMIN_CODE);
self::assertNotNull($admin);
self::assertTrue($admin->isSystem());
self::assertNotNull($repo->findByCode(SystemRoles::USER_CODE));
}
}