feat(core) : add rbac seeder and seed-rbac command for system roles
This commit is contained in:
@@ -25,6 +25,7 @@ use App\Enum\AbsenceType;
|
|||||||
use App\Enum\ContractType;
|
use App\Enum\ContractType;
|
||||||
use App\Enum\RecurrenceType;
|
use App\Enum\RecurrenceType;
|
||||||
use App\Enum\StatusCategory;
|
use App\Enum\StatusCategory;
|
||||||
|
use App\Module\Core\Application\Rbac\RbacSeeder;
|
||||||
use App\Module\Core\Domain\Entity\User;
|
use App\Module\Core\Domain\Entity\User;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use DateTimeZone;
|
use DateTimeZone;
|
||||||
@@ -36,6 +37,7 @@ class AppFixtures extends Fixture
|
|||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly UserPasswordHasherInterface $passwordHasher,
|
private readonly UserPasswordHasherInterface $passwordHasher,
|
||||||
|
private readonly RbacSeeder $rbacSeeder,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function load(ObjectManager $manager): void
|
public function load(ObjectManager $manager): void
|
||||||
@@ -751,5 +753,9 @@ class AppFixtures extends Fixture
|
|||||||
$manager->persist($pendingMarriage);
|
$manager->persist($pendingMarriage);
|
||||||
|
|
||||||
$manager->flush();
|
$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));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user