feat(core) : RBAC Task 6 - fixtures et CreateUserCommand branches sur les roles systeme

- AppFixtures : rattachement des users aux entites Role via
  RoleRepositoryInterface. Re-seed idempotent des roles systeme dans
  ensureSystemRole() pour compenser le purger Doctrine qui vide la table
  role avant load(), afin que "make db-reset && make fixtures" reste un
  workflow one-shot.
- CreateUserCommand : flag --admin attache au role systeme admin + is_admin,
  sinon au role user. Gestion d'erreur explicite si les roles systeme sont
  absents (FAILURE + message pointant vers la migration).
- CreateUserCommand devient final, descriptions traduites en francais.

Ticket #343 - 6/7 : fixtures et command alignes sur le RBAC relationnel.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthieu
2026-04-14 17:12:09 +02:00
parent d68aa0456a
commit aafe08b6ad
2 changed files with 66 additions and 12 deletions

View File

@@ -5,7 +5,9 @@ declare(strict_types=1);
namespace App\Module\Core\Infrastructure\Console;
use App\Module\Core\Domain\Entity\User;
use App\Module\Core\Domain\Repository\RoleRepositoryInterface;
use App\Module\Core\Domain\Repository\UserRepositoryInterface;
use App\Module\Core\Domain\Security\SystemRoles;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
@@ -17,13 +19,14 @@ use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
#[AsCommand(
name: 'app:create-user',
description: 'Create a new user',
description: 'Cree un utilisateur rattache au role systeme admin ou user.',
)]
class CreateUserCommand extends Command
final class CreateUserCommand extends Command
{
public function __construct(
private readonly UserRepositoryInterface $userRepository,
private readonly UserPasswordHasherInterface $passwordHasher,
private readonly RoleRepositoryInterface $roleRepository,
) {
parent::__construct();
}
@@ -31,9 +34,9 @@ class CreateUserCommand extends Command
protected function configure(): void
{
$this
->addArgument('username', InputArgument::REQUIRED, 'Username')
->addArgument('password', InputArgument::REQUIRED, 'Plain password')
->addOption('admin', null, InputOption::VALUE_NONE, 'Grant ROLE_ADMIN')
->addArgument('username', InputArgument::REQUIRED, 'Nom d\'utilisateur')
->addArgument('password', InputArgument::REQUIRED, 'Mot de passe en clair')
->addOption('admin', null, InputOption::VALUE_NONE, 'Rattache au role systeme admin + active is_admin')
;
}
@@ -43,19 +46,34 @@ class CreateUserCommand extends Command
$username = $input->getArgument('username');
$plainPassword = $input->getArgument('password');
$isAdmin = (bool) $input->getOption('admin');
$roleCode = $isAdmin ? SystemRoles::ADMIN_CODE : SystemRoles::USER_CODE;
$role = $this->roleRepository->findByCode($roleCode);
if (null === $role) {
$io->error(sprintf(
'Le role systeme "%s" est introuvable. Lance "bin/console doctrine:migrations:migrate" pour le seeder.',
$roleCode,
));
return Command::FAILURE;
}
$user = new User();
$user->setUsername($username);
$user->setPassword($this->passwordHasher->hashPassword($user, $plainPassword));
if ($input->getOption('admin')) {
// TODO Task 6 : attacher l'entite Role "admin" en plus du flag is_admin.
$user->setIsAdmin(true);
}
$user->setIsAdmin($isAdmin);
$user->addRbacRole($role);
$this->userRepository->save($user);
$io->success(sprintf('User "%s" created%s.', $username, $input->getOption('admin') ? ' with ROLE_ADMIN' : ''));
$io->success(sprintf(
'Utilisateur "%s" cree, rattache au role systeme "%s"%s.',
$username,
$roleCode,
$isAdmin ? ' (bypass is_admin actif)' : '',
));
return Command::SUCCESS;
}

View File

@@ -4,36 +4,72 @@ declare(strict_types=1);
namespace App\Module\Core\Infrastructure\DataFixtures;
use App\Module\Core\Domain\Entity\Role;
use App\Module\Core\Domain\Entity\User;
use App\Module\Core\Domain\Repository\RoleRepositoryInterface;
use App\Module\Core\Domain\Security\SystemRoles;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
/**
* Fixtures de base du module Core : 3 utilisateurs (1 admin + 2 standards)
* rattaches aux roles systeme RBAC seedes par la migration Version20260414150034.
*
* Note : le purger Doctrine execute avant load() supprime l'ensemble des
* entites managees, ce qui inclut la table role. On re-seede donc les roles
* systeme de maniere idempotente avant de rattacher les utilisateurs, afin
* que le workflow "make db-reset && make fixtures" reste one-shot.
*/
class AppFixtures extends Fixture
{
public function __construct(
private readonly UserPasswordHasherInterface $passwordHasher,
private readonly RoleRepositoryInterface $roleRepository,
) {}
public function load(ObjectManager $manager): void
{
// TODO Task 6 : cette fixture sera refactoree pour attacher les entites Role RBAC.
$adminRole = $this->ensureSystemRole($manager, SystemRoles::ADMIN_CODE, 'Administrateur');
$userRole = $this->ensureSystemRole($manager, SystemRoles::USER_CODE, 'Utilisateur');
$admin = new User();
$admin->setUsername('admin');
$admin->setIsAdmin(true);
$admin->setPassword($this->passwordHasher->hashPassword($admin, 'admin'));
$admin->addRbacRole($adminRole);
$manager->persist($admin);
$alice = new User();
$alice->setUsername('alice');
$alice->setPassword($this->passwordHasher->hashPassword($alice, 'alice'));
$alice->addRbacRole($userRole);
$manager->persist($alice);
$bob = new User();
$bob->setUsername('bob');
$bob->setPassword($this->passwordHasher->hashPassword($bob, 'bob'));
$bob->addRbacRole($userRole);
$manager->persist($bob);
$manager->flush();
}
/**
* Retourne le role systeme correspondant au code donne, en le creant
* s'il n'existe pas encore (le purger Doctrine a pu vider la table role).
*/
private function ensureSystemRole(ObjectManager $manager, string $code, string $label): Role
{
$role = $this->roleRepository->findByCode($code);
if (null !== $role) {
return $role;
}
$role = new Role($code, $label, isSystem: true);
$manager->persist($role);
return $role;
}
}