275c6ff5b5
- RbacSeeder : source unique des 4 roles metier (bureau/compta/commerciale/usine), de la matrice RBAC § 2.7 (role -> permissions) et des comptes demo. Operations idempotentes et non destructives (ensureRoles / attachMatrix / ensureDemoUsers). - app:seed-rbac : commande applicative presente en build prod (contrairement aux fixtures require-dev). Sans option : roles + matrice. --with-demo-users + --password / RBAC_DEMO_PASSWORD : un compte demo par role. Garde-fou : exit non-zero + invite a lancer app:sync-permissions si les codes manquent. - RbacDemoFixtures (dev/test) : appelle le meme seeder (DRY). La matrice est attachee post-sync par app:seed-rbac (la table permission est purgee au load). - makefile : etape seed-rbac apres sync-permissions (db-reset + test-db-setup). - Doc deploiement (README) + credentials des comptes demo (CLAUDE.md / README).
139 lines
4.8 KiB
PHP
139 lines
4.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Module\Core\Infrastructure\Console;
|
|
|
|
use App\Module\Core\Application\Rbac\RbacSeeder;
|
|
use App\Module\Core\Domain\Exception\RbacSeedException;
|
|
use Symfony\Component\Console\Attribute\AsCommand;
|
|
use Symfony\Component\Console\Command\Command;
|
|
use Symfony\Component\Console\Input\InputInterface;
|
|
use Symfony\Component\Console\Input\InputOption;
|
|
use Symfony\Component\Console\Output\OutputInterface;
|
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
|
|
|
/**
|
|
* Seed RBAC metier idempotent et NON destructif (cf. ERP-74 / spec-back M1
|
|
* § 2.7). Contrairement aux fixtures Doctrine (`require-dev`, absentes du build
|
|
* prod `--no-dev`), cette commande applicative est presente dans l'image prod :
|
|
* elle est donc rejouable en recette/staging/prod.
|
|
*
|
|
* Etape de release : a lancer APRES `doctrine:migrations:migrate` et
|
|
* `app:sync-permissions`.
|
|
* - En prod : `app:seed-rbac` (roles + matrice § 2.7, sans comptes demo).
|
|
* - En recette : `app:seed-rbac --with-demo-users --password=<...>` pour
|
|
* disposer de logins de test.
|
|
*
|
|
* Toute la logique (litteraux des roles, matrice, comptes demo) vit dans
|
|
* RbacSeeder — cette commande n'en est que l'enveloppe CLI.
|
|
*/
|
|
#[AsCommand(
|
|
name: 'app:seed-rbac',
|
|
description: 'Seede les roles metier RBAC + la matrice § 2.7 (idempotent, non destructif).',
|
|
)]
|
|
final class SeedRbacCommand extends Command
|
|
{
|
|
/** Variable d'environnement de repli pour le mot de passe des comptes demo. */
|
|
private const string PASSWORD_ENV = 'RBAC_DEMO_PASSWORD';
|
|
|
|
public function __construct(private readonly RbacSeeder $seeder)
|
|
{
|
|
parent::__construct();
|
|
}
|
|
|
|
protected function configure(): void
|
|
{
|
|
$this
|
|
->addOption(
|
|
'with-demo-users',
|
|
null,
|
|
InputOption::VALUE_NONE,
|
|
'Cree aussi un compte demo par role metier (recette/dev — JAMAIS en prod).',
|
|
)
|
|
->addOption(
|
|
'password',
|
|
null,
|
|
InputOption::VALUE_REQUIRED,
|
|
'Mot de passe des comptes demo (defaut : variable d\'env '.self::PASSWORD_ENV.'). Requis avec --with-demo-users.',
|
|
)
|
|
;
|
|
}
|
|
|
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
|
{
|
|
$io = new SymfonyStyle($input, $output);
|
|
|
|
// 1. Roles metier + matrice § 2.7. attachMatrix() exige que les
|
|
// permissions soient en base : sinon RbacSeedException porteuse de
|
|
// l'invite a lancer `app:sync-permissions`.
|
|
try {
|
|
$createdRoles = $this->seeder->ensureRoles();
|
|
$addedLinks = $this->seeder->attachMatrix();
|
|
} catch (RbacSeedException $e) {
|
|
$io->error($e->getMessage());
|
|
|
|
return Command::FAILURE;
|
|
}
|
|
|
|
$io->text(sprintf(
|
|
'Roles metier : %d cree(s), matrice § 2.7 : %d lien(s) ajoute(s).',
|
|
count($createdRoles),
|
|
$addedLinks,
|
|
));
|
|
|
|
// 2. Comptes demo (optionnel, jamais en prod).
|
|
if ((bool) $input->getOption('with-demo-users')) {
|
|
$password = $this->resolveDemoPassword($input);
|
|
if (null === $password) {
|
|
$io->error(sprintf(
|
|
'--with-demo-users exige un mot de passe : passe --password=<...> ou definis la variable d\'env %s. '
|
|
.'(Aucun mot de passe en dur cote serveur.)',
|
|
self::PASSWORD_ENV,
|
|
));
|
|
|
|
return Command::FAILURE;
|
|
}
|
|
|
|
try {
|
|
$createdUsers = $this->seeder->ensureDemoUsers($password);
|
|
} catch (RbacSeedException $e) {
|
|
$io->error($e->getMessage());
|
|
|
|
return Command::FAILURE;
|
|
}
|
|
|
|
$io->text(sprintf(
|
|
'Comptes demo : %d cree(s)%s.',
|
|
count($createdUsers),
|
|
[] === $createdUsers ? '' : ' ['.implode(', ', $createdUsers).']',
|
|
));
|
|
}
|
|
|
|
$io->success('Seed RBAC metier termine (idempotent).');
|
|
|
|
return Command::SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Resout le mot de passe demo : option `--password` prioritaire, sinon
|
|
* variable d'environnement. Renvoie null si aucun n'est fourni (la commande
|
|
* refuse alors --with-demo-users plutot que d'inventer un mot de passe).
|
|
*/
|
|
private function resolveDemoPassword(InputInterface $input): ?string
|
|
{
|
|
/** @var null|string $option */
|
|
$option = $input->getOption('password');
|
|
if (null !== $option && '' !== $option) {
|
|
return $option;
|
|
}
|
|
|
|
$env = $_SERVER[self::PASSWORD_ENV] ?? $_ENV[self::PASSWORD_ENV] ?? getenv(self::PASSWORD_ENV);
|
|
if (is_string($env) && '' !== $env) {
|
|
return $env;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|