<?php
namespace App\Controller;
use App\Constants\Setting;
use App\Constants\Setting as SettingConst;
use App\Constants\Sso;
use App\Entity\User;
use App\Exception\EncryptionException;
use App\Factory\Security\SecurityFormFactory;
use App\Form\Type\LoginType;
use App\Services\Common\ModuleSettingService;
use App\Services\Common\SettingService;
use App\Services\Common\User\WorkflowUser;
use App\Services\Common\UserService;
use App\Services\DTV\YamlConfig\YamlReader;
use App\Services\Security\EncryptionManager;
use App\Services\Security\RegisterService;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use LogicException;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Symfony\Component\Translation\TranslatableMessage;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use SymfonyCasts\Bundle\ResetPassword\Exception\ResetPasswordExceptionInterface;
/**
* Controller qui gère la sécurité
*/
class SecurityController extends AbstractController
{
private YamlReader $yamlReader;
private SecurityFormFactory $formFactory;
private SettingService $settingService;
private EntityManagerInterface $em;
private RegisterService $registerService;
private EncryptionManager $encryptionManager;
private WorkflowUser $workflowUser;
private LoggerInterface $logger;
private string $env;
private UserService $userService;
private ModuleSettingService $moduleSettingService;
public function __construct(
YamlReader $yamlReader,
SecurityFormFactory $formFactory,
SettingService $settingService,
EntityManagerInterface $em,
RegisterService $registerService,
EncryptionManager $encryptionManager,
WorkflowUser $workflowUser,
LoggerInterface $logger,
string $env,
UserService $userService,
ModuleSettingService $moduleSettingService
) {
$this->yamlReader = $yamlReader;
$this->formFactory = $formFactory;
$this->settingService = $settingService;
$this->em = $em;
$this->registerService = $registerService;
$this->encryptionManager = $encryptionManager;
$this->workflowUser = $workflowUser;
$this->logger = $logger;
$this->env = $env;
$this->userService = $userService;
$this->moduleSettingService = $moduleSettingService;
}
/**
* Formulaire de connexion
*
* @Route("/login", name="app_login")
*
* @param AuthenticationUtils $authenticationUtils
* @param Request $request
*
* @return Response
*
* @throws EncryptionException
* @throws ResetPasswordExceptionInterface
* @throws TransportExceptionInterface
* @throws \Symfony\Component\Mailer\Exception\TransportExceptionInterface
*/
public function login(AuthenticationUtils $authenticationUtils, Request $request): Response
{
return $this->processLogin($authenticationUtils, $request);
}
/**
* Formulaire de connexion secondaire
*
* @Route("/login-admin", name="app_login_admin")
*
* @param AuthenticationUtils $authenticationUtils
* @param Request $request
*
* @return Response
* @throws EncryptionException
* @throws ResetPasswordExceptionInterface
* @throws TransportExceptionInterface
* @throws \Symfony\Component\Mailer\Exception\TransportExceptionInterface
*/
public function loginAdmin(AuthenticationUtils $authenticationUtils, Request $request): Response
{
if ($this->settingService->isExist(Setting::SSO_SETTINGS) && $this->moduleSettingService->isModuleActive(
Sso::MODULE_NAME
)) {
return $this->processLogin($authenticationUtils, $request, 'security/login_admin.html.twig');
}
throw $this->createNotFoundException();
}
/**
* @throws TransportExceptionInterface
* @throws ResetPasswordExceptionInterface
* @throws \Symfony\Component\Mailer\Exception\TransportExceptionInterface
* @throws EncryptionException
* @throws Exception
*/
protected function processLogin(
AuthenticationUtils $authenticationUtils,
Request $request,
?string $twigPath = null
): Response {
// On gère ici si on est sur un Portail/enfant
$setting = $this->settingService->getSettingFromName(SettingConst::PORTAL_CHILDREN);
$values = $setting !== null ? json_decode($setting->getValue(), true) : [];
$hasParent = !empty($values['parent_url']);
if ($hasParent) {
$header = $request->headers;
if ($header->has('q') || $request->query->get('q')) {
$q = base64_decode($header->get('q') ?? $request->query->get('q'));
try {
$q = $this->encryptionManager->decrypt($q);
} catch (Exception $e) {
$this->addFlash('danger', 'Un problème est survenu lors de la connexion');
$this->logger->error(
'Erreur lors du décryptage du token de connexion',
['error' => $e->getMessage()]
);
return $this->redirectToRoute('app_login');
}
$q = json_decode($q, true);
$user = $this->em->getRepository(User::class)->findOneByEmailOrExtension([
'email' => $q['email'],
'extension1' => $q['extension1'],
'extension2' => $q['extension2'],
]);
if ($user instanceof User) {
if ($q['email'] !== $user->getEmail()) {
$user
->setEmail($q['email'])
->setFirstname($q['firstName'])
->setLastname($q['lastName'])
->setRoles($q['roles']);
$this->registerService->registerReplaceFakeUserBySellerCode($user, $q['extension1'], true);
}
// Gestion du cas où l'utilisateur existe en BDD (pas en fakeUser) mais ne s'est pas encore connecté à la plateforme
if ($q['cguAt'] !== null && $user->getStatus() === 'cgu_pending') {
$this->workflowUser->acceptCGU($user);
$user->setCguAt(new DateTime($q['cguAt']));
$this->em->flush();
}
// Créer un token de connexion
$token = new UsernamePasswordToken($user, 'app', $user->getRoles());
// Stocker le token dans le token storage
try {
$this->container->get('security.token_storage')->setToken($token);
} catch (NotFoundExceptionInterface|ContainerExceptionInterface $e) {
$this->addFlash('danger', 'Un problème est survenu lors de la connexion');
$this->logger->error(
'Erreur lors de la récupération du token storage',
['error' => $e->getMessage()]
);
return $this->redirectToRoute('front_homepage');
}
} else {
// on delog l'user
$this->get('security.token_storage')->setToken(null);
$this->logger->info('L\'utilisateur n\'existe pas en BDD', ['email' => $q['email']]);
$request->getSession()->invalidate();
}
}
}
// Redirige sur la home-page si l'user est connecté
if ($this->getUser()) {
return $this->redirectToRoute('front_homepage');
}
$user = $this->userService->initUser();
$config = $this->yamlReader->getFrontSecurity();
$configLogin = $config['login'];
$globalRegister = $this->yamlReader->getRegister();
$globalRegisterEnabled = $globalRegister['enabled'];
$configRegister = $configLogin['sections']['section_register'] ?? false;
$hasFormRegister = false;
$formRegister = false;
if ($globalRegisterEnabled && is_array($configRegister) && $configRegister['enabled']) {
// Création du formulaire d'inscription
try {
$formRegister = $this->formFactory->generateRegisterForm($user);
$hasFormRegister = true;
} catch (Exception $e) {
throw $this->createNotFoundException($e->getMessage());
}
$formRegister->handleRequest($request);
if ($formRegister->isSubmitted()) {
// Validation spécifique du formulaire d'inscription
try {
$formRegister = $this->formFactory->postValidateRegisterForm($formRegister);
} catch (Exception $e) {
if ($this->env != 'prod') {
throw $e;
}
$this->addFlash(
'danger',
new TranslatableMessage('impossible d\'exécuter la post validation du formulaire', [])
);
$this->logger->error(
'Erreur lors de la post validation du formulaire d\'inscription',
['error' => $e->getMessage()]
);
$referer = $request->headers->get('referer');
return $this->redirect($referer);
}
if ($formRegister->isValid()) {
// Post traitement du formulaire d'inscription
try {
$response = $this->formFactory->postProcessingRegisterForm($formRegister, $user);
} catch (Exception $e) {
if ($this->env != 'prod') {
throw $e;
}
$this->addFlash('danger', 'impossible d\'exécuter le post traitement du formulaire');
$this->logger->error(
'Erreur lors du post traitement du formulaire d\'inscription',
['error' => $e->getMessage()]
);
$referer = $request->headers->get('referer');
return $this->redirect($referer);
} catch (TransportExceptionInterface $e) {
if ($this->env != 'prod') {
throw $e;
}
$this->addFlash('danger', 'impossible d\'exécuter le post traitement du formulaire');
$this->logger->error(
'Erreur lors du post traitement du formulaire d\'inscription',
['error' => $e->getMessage()]
);
$referer = $request->headers->get('referer');
return $this->redirect($referer);
}
if ($response['message'] !== null) {
$this->addFlash('success', $response['message']);
}
return $this->redirectToRoute($response['route']);
}
}
}
$formLogin = $this->createForm(LoginType::class);
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
if (!$twigPath) {
$twigPath = 'security/login.html.twig';
if (isset($configLogin['folder']) && !in_array($configLogin['folder'], [false, '', null], true)) {
$twigPath = 'security/' . $configLogin['folder'] . '/login.html.twig';
}
}
return $this->render($twigPath, [
'last_username' => $lastUsername,
'error' => $error,
'loginForm' => $formLogin->createView(),
'registrationForm' => $hasFormRegister ? $formRegister->createView() : false,
'hasFormRegister' => $hasFormRegister
]);
}
/**
* Déconnexion
*
* @Route("/universal_logout", name="universal_logout")
*
* @return RedirectResponse
*/
public function universalLogout(): RedirectResponse
{
if ($this->isSamlSsoEnabled()) {
return $this->redirectToRoute('saml_logout');
}
return $this->redirectToRoute('app_logout');
}
/**
* Déconnexion
*
* @Route("/logout", name="app_logout")
*
* @return void
*/
public function logout(): void
{
throw new LogicException(
'This method can be blank - it will be intercepted by the logout key on your firewall.',
);
}
private function isSamlSsoEnabled(): bool
{
if (!$this->moduleSettingService->isModuleActive(Sso::MODULE_NAME) || !$this->settingService->isExist(
Setting::SSO_SETTINGS
)) {
return false;
}
$ssoSettings = $this->settingService->getSettingFromName(Setting::SSO_SETTINGS, true, true);
return is_array($ssoSettings) && !empty($ssoSettings['ssoType']) && strtolower(
(string)$ssoSettings['ssoType']
) === Sso::SSO_SAML;
}
}