diff --git a/ansible/templates/caddy.j2 b/ansible/templates/caddy.j2 index de59716..4b49fd7 100644 --- a/ansible/templates/caddy.j2 +++ b/ansible/templates/caddy.j2 @@ -1,4 +1,4 @@ -intranet.ludikevent.fr, signature.ludikevent.fr, reservation.ludikevent.fr { +etl.ludikevent.fr, intranet.ludikevent.fr, signature.ludikevent.fr, reservation.ludikevent.fr { tls { dns cloudflare KL6pZ-Z_12_zbnM2TtFDIsKM8A-HLPhU5GJJbKTW } diff --git a/config/packages/security.yaml b/config/packages/security.yaml index d326ab1..728f418 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -2,6 +2,7 @@ security: password_hashers: App\Entity\Account: 'auto' App\Entity\Customer: 'auto' + App\Entity\Prestaire: 'auto' providers: app_account_provider: @@ -12,12 +13,33 @@ security: entity: class: App\Entity\Customer property: email - + etl_account_provider: # Provider spécifique Customer + entity: + class: App\Entity\Prestaire + property: email firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false + etl: + pattern: ^/(etl) + lazy: true + provider: etl_account_provider # Force l'entité Account (Admin) ici + user_checker: App\Security\UserChecker + entry_point: App\Security\EtlAuthenticator + form_login: + login_path: etl_home + check_path: etl_home + enable_csrf: true + csrf_token_id: authenticate + + custom_authenticator: + - App\Security\EtlAuthenticator + + logout: + path: elt_logout + target: elt_home intranet: diff --git a/migrations/Version20260129162649.php b/migrations/Version20260129162649.php new file mode 100644 index 0000000..93f6238 --- /dev/null +++ b/migrations/Version20260129162649.php @@ -0,0 +1,32 @@ +addSql('ALTER TABLE prestaire ADD roles JSON NOT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('ALTER TABLE prestaire DROP roles'); + } +} diff --git a/src/Controller/EtlController.php b/src/Controller/EtlController.php new file mode 100644 index 0000000..6a1983d --- /dev/null +++ b/src/Controller/EtlController.php @@ -0,0 +1,65 @@ +getUser()) + return $this->redirectToRoute('etl_login'); + } + #[Route('/etl/connexion', name: 'etl_login')] + public function eltLogin(AuthenticationUtils $authenticationUtils): Response + { + return $this->render('etl/login.twig',[ + 'last_username' => $authenticationUtils->getLastUsername(), + 'error' => $authenticationUtils->getLastAuthenticationError() + + ]); + } + #[Route('/etl/logout', name: 'etl_logout')] + public function eltLogout(): Response + { + return $this->redirectToRoute('etl_home'); + } +} diff --git a/src/Entity/Prestaire.php b/src/Entity/Prestaire.php index f85cd0f..a6a0a4d 100644 --- a/src/Entity/Prestaire.php +++ b/src/Entity/Prestaire.php @@ -6,9 +6,12 @@ use App\Repository\PrestaireRepository; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; - +use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface; +use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; +use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; +use Symfony\Component\Security\Core\User\UserInterface; #[ORM\Entity(repositoryClass: PrestaireRepository::class)] -class Prestaire +class Prestaire implements UserInterface, PasswordAuthenticatedUserInterface { #[ORM\Id] #[ORM\GeneratedValue] @@ -33,6 +36,12 @@ class Prestaire #[ORM\Column(length: 255)] private ?string $phone = null; + /** + * @var list The user roles + */ + #[ORM\Column] + private array $roles = []; + public function __construct() { $this->etatLieuxes = new ArrayCollection(); @@ -120,4 +129,38 @@ class Prestaire return $this; } + + public function getPassword(): ?string + { + // TODO: Implement getPassword() method. + } + + public function getRoles(): array + { + $roles = $this->roles; + // guarantee every user at least has ROLE_USER + $roles[] = 'ROLE_PRESTAIRE'; + + return array_unique($roles); + } + + /** + * @param list $roles + */ + public function setRoles(array $roles): static + { + $this->roles = $roles; + + return $this; + } + + public function eraseCredentials(): void + { + // TODO: Implement eraseCredentials() method. + } + + public function getUserIdentifier(): string + { + return (string) $this->email; + } } diff --git a/src/Security/EtlAuthenticator.php b/src/Security/EtlAuthenticator.php new file mode 100644 index 0000000..a193d51 --- /dev/null +++ b/src/Security/EtlAuthenticator.php @@ -0,0 +1,92 @@ +attributes->get('_route') === 'etl_login' + && $request->isMethod('POST'); + } + + /** + * Crée le passeport de sécurité à partir des données du formulaire. + */ + public function authenticate(Request $request): Passport + { + $email = $request->getPayload()->getString('_username'); + $password = $request->getPayload()->getString('_password'); + $csrfToken = $request->getPayload()->getString('_csrf_token'); + + // Sauvegarde de l'email pour ré-affichage en cas d'erreur (Pratique UX) + $request->getSession()->set(SecurityRequestAttributes::LAST_USERNAME, $email); + + return new Passport( + // On force la recherche dans la table Customer uniquement + new UserBadge($email, function (string $userIdentifier) { + return $this->entityManager->getRepository(Prestaire::class) + ->findOneBy(['email' => $userIdentifier]); + }), + new PasswordCredentials($password), + [ + // Identifiant de jeton spécifique pour la réservation + new CsrfTokenBadge('authenticate_etl', $csrfToken), + // Permet la gestion du "Se souvenir de moi" + new RememberMeBadge(), + ] + ); + } + + /** + * Actions à effectuer après une connexion réussie. + */ + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + // Si l'utilisateur a été intercepté alors qu'il tentait d'accéder à une page protégée + if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) { + return new RedirectResponse($targetPath); + } + + // Redirection par défaut vers le tableau de bord Bento (route 'reservation') + return new RedirectResponse($this->urlGenerator->generate('etl_contrat')); + } + + /** + * URL vers laquelle rediriger si l'utilisateur doit se connecter. + */ + protected function getLoginUrl(Request $request): string + { + return $this->urlGenerator->generate('etl_login'); + } +} diff --git a/src/Security/RedirecListener.php b/src/Security/RedirecListener.php index 2fdb448..9f0458c 100644 --- a/src/Security/RedirecListener.php +++ b/src/Security/RedirecListener.php @@ -49,6 +49,14 @@ class RedirecListener return; } } + if ($pathInfo === "/") { + if ($host === "etl.ludikevent.fr") { + $redirect = new RedirectResponse("https://etl.ludikevent.fr/etl"); + $event->setResponse($redirect); + $event->stopPropagation(); + return; + } + } // --- Logique RESERVATION --- if (str_contains($pathInfo, "/reservation")) {