Commit 12eccb0f authored by Thibaut Vallee's avatar Thibaut Vallee
Browse files

Ajout de l'historisation

parent ef5d063a
Loading
Loading
Loading
Loading
+81 −0
Changes for src/Command/CreateUserCommand.php: 81 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

namespace Unicaen\Utilisateur\Command;

use Doctrine\ORM\EntityManagerInterface;
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;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Unicaen\Utilisateur\Entity\Role;
use Unicaen\Utilisateur\Entity\User;

#[AsCommand(name: 'utilisateur:create-user')]
class CreateUserCommand extends Command
{
    private EntityManagerInterface $entityManager;
    private UserPasswordHasherInterface $passwordHasher;

    public function __construct(EntityManagerInterface $entityManager, UserPasswordHasherInterface $passwordHasher)
    {
        $this->entityManager = $entityManager;
        $this->passwordHasher = $passwordHasher;

        parent::__construct();
    }

    protected function configure(): void
    {
        $this
            ->setName('utilisateur:create-user')
            ->setDescription("Création d'un utilisateur");
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        $io->info("Création d'un compte utilisateur local");

        $username = $io->ask('Username');
        $existingUser = $this->entityManager->getRepository(User::class)->findOneBy(['username' => $username]);
        if ($existingUser) {
            $io->error('Un utilisateur avec ce username existe déjà');
            return Command::FAILURE;
        }

        $email = $io->ask('Email');
        // Check if user with same mail exists
        $existingUser = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $email]);
        if ($existingUser) {
            $io->error('Un utilisateur avec cette adresse mail existe déjà');
            return Command::FAILURE;
        }

        $dipslayName = $io->ask('DisplayName');
        $password = $io->askHidden('Password');

        if (strlen($password) < 8) {
            $io->error('Le mot doit contenir 8 caractères minimum');
            return Command::FAILURE;
        }

        $user = new User();
        $user->setUsername($username);
        $user->setDisplayName($dipslayName);
        $user->setEmail($email);
        $user->setPassword($this->passwordHasher->hashPassword($user,$password));
        $roleUser = $this->entityManager->getRepository(Role::class)->findOneBy(['code' => 'ROLE_USER']);
        if ($roleUser) {
            $user->addRole($roleUser);
        }

        $this->entityManager->persist($user);
        $this->entityManager->flush();

        $io->success('Utilisateur créé.');
        return Command::SUCCESS;
    }

}
+22 −0
Changes for src/Entity/AbstractUser.php: 22 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -50,6 +50,8 @@ abstract class AbstractUser implements UnicaenUserInterface, PasswordAuthenticat
    #[ORM\Column(length: 180, unique: true)]
    protected string $email;

    #[Groups(['user:read', 'user:create', 'user:update'])]
    #[ORM\Column(length: 180, nullable: true)]
    protected ?string $displayName = null;

    #[Assert\NotBlank(groups: ['user:create'])]
@@ -87,6 +89,26 @@ abstract class AbstractUser implements UnicaenUserInterface, PasswordAuthenticat
        return $this->id;
    }

    public function getUsername(): ?string
    {
        return $this->username;
    }

    public function setUsername(?string $username): void
    {
        $this->username = $username;
    }

    public function getDisplayName(): ?string
    {
        return $this->displayName;
    }

    public function setDisplayName(?string $displayName): void
    {
        $this->displayName = $displayName;
    }

    public function getEmail(): ?string
    {
        return $this->email;
+34 −0
Changes for src/Model/HistoriqueAwareInterface.php: 34 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

namespace Unicaen\Utilisateur\Model;

use DateTime;
use DateTimeInterface;

/**
 * Adaptation de HistoAwareTrait issue de UnicaenUtilisateur pour le passage en API-Plateforme
 * @author Thibaut Vallée <thibaut.vallee at unicaen.fr>
 */
interface HistoriqueAwareInterface
{
    public function setHistoCreation(?DateTimeInterface $histoCreation): HistoriqueAwareInterface;
    public function getHistoCreation(): ?DateTimeInterface;
    public function setHistoDestruction(?DateTimeInterface $histoDestruction): HistoriqueAwareInterface;
    public function getHistoDestruction(): ?DateTimeInterface;
    public function setHistoModification(?DateTimeInterface $histoModification): HistoriqueAwareInterface;
    public function getHistoModification(): ?DateTimeInterface;


    public function setHistoModificateur(?UnicaenUserInterface $histoModificateur = null): HistoriqueAwareInterface;
    public function getHistoModificateur(): ?UnicaenUserInterface;
    public function setHistoDestructeur(?UnicaenUserInterface $histoDestructeur = null): HistoriqueAwareInterface;
    public function getHistoDestructeur(): ?UnicaenUserInterface;
    public function setHistoCreateur(?UnicaenUserInterface $histoCreateur = null): HistoriqueAwareInterface;
    public function getHistoCreateur(): ?UnicaenUserInterface;


    public function estHistorise(?DateTimeInterface $dateObs = null) : bool;
    public function estNonHistorise(?DateTimeInterface $dateObs = null) : bool;
    public function historiser(?UnicaenUserInterface $histoDestructeur, ?DateTime $histoDestruction = null): HistoriqueAwareInterface;
    public function dehistoriser(): HistoriqueAwareInterface;
}
 No newline at end of file
+186 −0
Changes for src/Model/HistoriqueAwareTrait.php: 186 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

namespace Unicaen\Utilisateur\Model;

use DateTime;
use DateTimeInterface;
use Exception;
use RuntimeException;

trait HistoriqueAwareTrait
{
    protected ?DateTimeInterface $histoCreation = null;
    protected ?DateTimeInterface $histoModification = null;
    protected ?DateTimeInterface $histoDestruction = null;
    protected ?UnicaenUserInterface $histoCreateur = null;
    protected ?UnicaenUserInterface $histoModificateur = null;
    protected ?UnicaenUserInterface $histoDestructeur = null;

    public function setHistoCreation(?DateTimeInterface $histoCreation): HistoriqueAwareInterface
    {
        $this->histoCreation = $histoCreation;
        /** @var HistoriqueAwareInterface $this */
        return $this;
    }

    public function getHistoCreation(): ?DateTimeInterface
    {
        return $this->histoCreation;
    }

    public function setHistoDestruction(?DateTimeInterface $histoDestruction): HistoriqueAwareInterface
    {
        $this->histoDestruction = $histoDestruction;
        /** @var HistoriqueAwareInterface $this */
        return $this;
    }

    public function getHistoDestruction(): ?DateTimeInterface
    {
        return $this->histoDestruction;
    }

    public function setHistoModification(?DateTimeInterface $histoModification): HistoriqueAwareInterface
    {
        $this->histoModification = $histoModification;
        /** @var HistoriqueAwareInterface $this */
        return $this;
    }

    public function getHistoModification(): ?DateTimeInterface
    {
        return $this->histoModification;
    }

    public function setHistoModificateur(?UnicaenUserInterface $histoModificateur = null): HistoriqueAwareInterface
    {
        $this->histoModificateur = $histoModificateur;
        /** @var HistoriqueAwareInterface $this */
        return $this;
    }

    public function getHistoModificateur(): ?UnicaenUserInterface
    {
        return $this->histoModificateur;
    }

    public function setHistoDestructeur(?UnicaenUserInterface $histoDestructeur = null): HistoriqueAwareInterface
    {
        $this->histoDestructeur = $histoDestructeur;
        /** @var HistoriqueAwareInterface $this */
        return $this;
    }

    public function getHistoDestructeur(): ?UnicaenUserInterface
    {
        return $this->histoDestructeur;
    }

    public function setHistoCreateur(?UnicaenUserInterface $histoCreateur = null): HistoriqueAwareInterface
    {
        $this->histoCreateur = $histoCreateur;
        /** @var HistoriqueAwareInterface $this */
        return $this;
    }

    public function getHistoCreateur(): ?UnicaenUserInterface
    {
        return $this->histoCreateur;
    }

    /** Historisation / Restauration **********************************************************************************/
//    TODO : a revoir si l'on garde les fonctions  Historiser et desistoriser : c'est des actions d'api qui vont le faire
    /**
     * @param UnicaenUserInterface|null $destructeur Auteur de la suppression
     * @param DateTimeInterface|null $dateDestruction Date éventuelle de la suppression
     * @return HistoriqueAwareInterface
     * @see HistoriqueAwareInterface
     */
    public function historiser(?UnicaenUserInterface $destructeur = null, ?DateTimeInterface $dateDestruction = null): HistoriqueAwareInterface
    {
        if ($destructeur) {
            $this->setHistoDestructeur($destructeur);
        }

        if (empty($dateDestruction)) {
            try {
                $dateDestruction = new DateTime();
            } catch (Exception $e) {
                throw new RuntimeException("Impossible d'instancier un DateTime!", null, $e);
            }
        }

        $this->setHistoDestruction($dateDestruction);

        /** @var HistoriqueAwareInterface $this */
        return $this;
    }

    public function dehistoriser(): HistoriqueAwareInterface
    {
        $this->setHistoDestructeur();
        $this->setHistoDestruction(null);

        /** @var HistoriqueAwareInterface $this */
        return $this;
    }

    /**
     * @param DateTimeInterface|null $dateObs Date d'observation éventuelle
     * @return bool
     */
    public function estHistorise(DateTimeInterface $dateObs = null): bool
    {
        return !$this->estNonHistorise($dateObs);
    }

    /**
     * @param DateTimeInterface|null $dateObs Date d'observation éventuelle
     * @return bool
     */
    public function estNonHistorise(DateTimeInterface $dateObs = null): bool
    {
        if (empty($dateObs)) {
            $dateObs = new DateTime();
        }

        $dObs = $dateObs->format('Y-m-d');
        $dDeb = $this->getHistoCreation()?->format('Y-m-d');
        $dFin = $this->getHistoDestruction()?->format('Y-m-d');

        if ($dDeb && !($dDeb <= $dObs)) return false;
        if ($dFin && !($dObs < $dFin)) return false;

        return true;
    }

    /**
     * @todo : a revoir si utile de garder la fonction
     * @deprecated cette affichage est géré coté front
     * @see useHistorique.js
     * @return string
     */
    public function derniereModification(): string
    {
        if ($this->getHistoDestruction() !== null) {
            return "Historisation " . $this->getHistoDestruction()->format('d/m/Y à H:i:s') . " par " . $this->getHistoDestructeur()->getUserIdentifier();
        }
        if ($this->getHistoModification() !== null) {
            return "Modification " . $this->getHistoModification()->format('d/m/Y à H:i:s') . " par " . $this->getHistoModificateur()->getUserIdentifier();
        }
        if ($this->getHistoCreation() !== null) {
            return "Création " . $this->getHistoCreation()->format('d/m/Y à H:i:s') . " par " . $this->getHistoCreateur()->getUserIdentifier();
        }
        return "Aucune donnée d'historisation";
    }

    public function getDateTime(): ?string
    {
        return $this->getHistoCreation()?->format('d/m/Y à H:i');
    }

    public function getDate(): ?string
    {
        return $this->getHistoCreation()?->format('d/m/Y');
    }
}
 No newline at end of file
+86 −0
Changes for src/Security/HistoriqueVoter.php: 86 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

namespace Unicaen\Utilisateur\Security;

use Unicaen\Utilisateur\Model\HistoriqueAwareInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Vote;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;

/**
 * TODO :
 * Regarder du coté du AccessDeniedHandler | des exceptions ou autre pour que la réponse ne soit pas considéré comme une "Error" et donne le bon message d'info
 */
class HistoriqueVoter extends Voter
{
    /** @var string  */
    const HISTORIQUE = 'HISTORIQUE'; //Générique pour gerer le gros des cas en se basant sur la méthode
    const AFFICHER = 'HISTORIQUE-AFFICHER';
    const CREER = 'HISTORIQUE-CREER';
    const MODIFIER = 'HISTORIQUE-MODIFIER';
    const HISTORISER = 'HISTORIQUE-HISTORISER';
    const RESTAURER = 'HISTORIQUE-RESTAURER';
    const SUPPRIMER = 'HISTORIQUE-SUPPRIMER';

    public function __construct(
        private RequestStack $requestStack)
    {
    }

    protected function supports(string $attribute, mixed $subject): bool
    {
        if(!$subject instanceof HistoriqueAwareInterface){return false;}
        return true;
    }

    public function supportsAttribute(string $attribute): bool
    {
        return str_starts_with($attribute, 'HISTORIQUE');
    }

    /**
     * @param HistoriqueAwareInterface $subject
     */
    protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token,  ?Vote $vote = null): bool
    {
        //au cas ou l'on utilise l'attribue générique, on se base sur la méthode de la requête
        if($attribute == self::HISTORIQUE){
        /** @var Request $request */
        $request = $this->requestStack->getCurrentRequest();
            $attribute = match ($request->getMethod()) {
                'GET' =>  self::AFFICHER,
                'POST' =>  self::CREER,
                'PUT', 'PATCH' =>  self::MODIFIER,
                'DELETE',=>  self::SUPPRIMER,
                default => false,
            };
        }

        return match ($attribute) {
            self::AFFICHER, self::CREER => true,
            self::MODIFIER, self::HISTORISER  => $this->assertIsNonHistoriser($subject, $vote),
            self::RESTAURER, self::SUPPRIMER => $this->assertIsHistoriser($subject, $vote),
            default => $vote?->addReason("Cas non gérer") && false,
        };
    }

    private function assertIsHistoriser(HistoriqueAwareInterface $entity, ?Vote $vote) : bool
    {
        if(!$entity->estHistorise() && isset($vote)){
            $vote?->addReason("L'entité n'est pas historisée");
            $vote->result = self::ACCESS_DENIED;
        }
        return $entity->estHistorise();
    }

    private function assertIsNonHistoriser(HistoriqueAwareInterface $entity, ?Vote $vote) : bool
    {
        if(!$entity->estNonHistorise() && isset($vote)){
            $vote?->addReason("L'entité est historisée");
            $vote->result = self::ACCESS_DENIED;
        }
        return $entity->estNonHistorise();
    }
}
Loading