fix(security) : harden auth, session, document access and health endpoint
- Remove orphaned PUBLIC_ACCESS rule for deleted /api/test route - Remove JWT login firewall (app is session-based only) - Set APP_SECRET placeholder (real value must be in .env.local) - Remove JWT env vars from .env - Add session regeneration on login (prevent session fixation) - Remove Document.path from API serialization groups (prevent path leak) - Restrict health check details to ROLE_ADMIN (anonymes get status only) - Add path traversal guard in DocumentStorageService - Convert CreateProfileCommand password to interactive hidden prompt - Restrict Profile Get endpoint to ROLE_ADMIN - Change api firewall to stateless: false (matches session-based auth) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
104
src/Command/CreateProfileCommand.php
Normal file
104
src/Command/CreateProfileCommand.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use App\Entity\Profile;
|
||||
use App\Repository\ProfileRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
|
||||
use function in_array;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'app:create-profile',
|
||||
description: 'Create a new profile with the given credentials',
|
||||
)]
|
||||
class CreateProfileCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ProfileRepository $profiles,
|
||||
private readonly EntityManagerInterface $em,
|
||||
private readonly UserPasswordHasherInterface $passwordHasher,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->addArgument('firstName', InputArgument::REQUIRED, 'First name')
|
||||
->addArgument('lastName', InputArgument::REQUIRED, 'Last name')
|
||||
->addOption('email', null, InputOption::VALUE_REQUIRED, 'Email address')
|
||||
->addOption('role', null, InputOption::VALUE_REQUIRED, 'Role (ROLE_ADMIN, ROLE_GESTIONNAIRE, ROLE_VIEWER)', 'ROLE_VIEWER')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
$firstName = $input->getArgument('firstName');
|
||||
$lastName = $input->getArgument('lastName');
|
||||
$email = $input->getOption('email');
|
||||
|
||||
$password = $io->askHidden('Password');
|
||||
if (null === $password || '' === $password) {
|
||||
$io->error('Le mot de passe est requis.');
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
$role = $input->getOption('role');
|
||||
|
||||
$allowedRoles = ['ROLE_ADMIN', 'ROLE_GESTIONNAIRE', 'ROLE_VIEWER', 'ROLE_USER'];
|
||||
if (!in_array($role, $allowedRoles, true)) {
|
||||
$io->error('Role invalide. Roles autorisés : '.implode(', ', $allowedRoles));
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
if (null !== $email && '' !== $email) {
|
||||
$existing = $this->profiles->findOneBy(['email' => $email]);
|
||||
if (null !== $existing) {
|
||||
$io->error('Un profil avec cet email existe déjà.');
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
$profile = new Profile();
|
||||
$profile->setFirstName($firstName);
|
||||
$profile->setLastName($lastName);
|
||||
$profile->setRoles([$role]);
|
||||
$profile->setIsActive(true);
|
||||
|
||||
if (null !== $email && '' !== $email) {
|
||||
$profile->setEmail($email);
|
||||
}
|
||||
|
||||
$profile->setPassword(
|
||||
$this->passwordHasher->hashPassword($profile, $password)
|
||||
);
|
||||
|
||||
$this->em->persist($profile);
|
||||
$this->em->flush();
|
||||
|
||||
$io->success(sprintf(
|
||||
'Profil créé : %s %s (ID: %s, Role: %s)',
|
||||
$firstName,
|
||||
$lastName,
|
||||
$profile->getId(),
|
||||
$role,
|
||||
));
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user