<?php
namespace App\Controller;
use App\Domain\Account\AccountPasswordEmailForgottenService;
use App\Domain\Account\AccountPasswordForgottenService;
use App\Domain\EgeeDataExportation\DataExportationService;
use App\Domain\EgeeDataExportation\EgeeRequest;
use App\Domain\Encryption\EncryptionTrait;
use App\Domain\Encryption\HashingService;
use App\Domain\Notification\Pdf\PdfService;
use App\Repository\AccountRepository;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Routing\Annotation\Route;
class PasswordForgottenController extends AbstractController
{
use EncryptionTrait;
private $hashingService;
private $servicesContainer;
private $filesystem;
private $accountPasswordForgottenService;
private $accountPasswordEmailForgottenService;
private $logger;
public function __construct(
ContainerInterface $servicesContainer,
Filesystem $filesystem,
AccountPasswordForgottenService $accountPasswordForgottenService,
AccountPasswordEmailForgottenService $accountPasswordEmailForgottenService,
LoggerInterface $logger,
HashingService $hashingService
) {
$this->servicesContainer = $servicesContainer;
$this->filesystem = $filesystem;
$this->accountPasswordForgottenService = $accountPasswordForgottenService;
$this->accountPasswordEmailForgottenService = $accountPasswordEmailForgottenService;
$this->logger = $logger;
$this->hashingService = $hashingService;
}
/**
* @Route("/password-forgotten", name="password-forgotten", methods={"GET","POST"})
*
* Formulaire de perte de mot de passe.
* Différentes informations sont à renseigner pour s'assurer de l'identité
* de l'internaute.
*
* Nouvelle gestion de récupération de mot de passe :
* 1. saisie de l'adresse email + référence client ;
* 2. vérification de la correspondance email <=> reférence dans la base de données ;
* 3. envoi d'un message avec lien unique vers un formulaire permettant de réinitialiser le mot de passe (durée de vie 30 minutes) ;
* 4. enregistrement du nouveau mot de passe ;
* 5. flux EGEE.
*/
public function showForm(Request $request): Response
{
if ($request->isMethod('POST')) {
$this->addFlash(
'success',
'<strong>Votre demande a bien été prise en compte.</strong>'
);
$this->addFlash(
'success',
'Un e-mail contenant les instructions à suivre pour réinitialiser votre mot de passe vient de vous être envoyé.'
);
return $this->redirectToRoute('password-forgotten');
}
return $this->render(
'password_forgotten/form.html.twig',
[
'menu' => '',
]
);
}
/**
* @Route("/password-forgotten-action", name="password-forgotten-action", methods={"GET","POST"})
*
**/
public function formProcess(Request $request, AccountRepository $accountRepository)
{
if (!$this->isCsrfTokenValid('password-forgotten', $request->request->get('_token'))) {
return $this->redirectToRoute('password-forgotten');
}
$pathPasswordForgotten = $this->servicesContainer->getParameter('password.directory');
$this->filesystem->mkdir($pathPasswordForgotten);
$dataEmail = $request->request->all();
if (!$dataEmail['reference'] || !$dataEmail['customer_email'] || !filter_var($dataEmail['customer_email'], FILTER_VALIDATE_EMAIL)) {
$this->addFlash(
'danger',
'Attention vous devez fournir une adresse mail valide'
);
return $this->redirectToRoute('password-forgotten');
}
// vérification correspondance ref/email
$account = $accountRepository->findOneBy(['reference' => $dataEmail['reference']]);
if (!$account || !$account->getEmail()) {
$this->addFlash(
'danger',
"Attention l'adresse mail ou la référence est invalide"
);
$this->logger->error(
'Traitement de perte de mot de passe : correspondance email/ref invalide, ref inexistante',
[
'reference' => $dataEmail['reference'],
'email' => $dataEmail['customer_email'],
]
);
return $this->redirectToRoute('password-forgotten');
}
if ($account->getEmail() != $dataEmail['customer_email']) {
$this->addFlash(
'danger',
"Attention l'adresse mail ou la référence est invalide"
);
$this->logger->error(
'Traitement de perte de mot de passe : correspondance email/ref invalide',
[
'reference' => $dataEmail['reference'],
'email' => $dataEmail['customer_email'],
]
);
return $this->redirectToRoute('password-forgotten');
}
$dateRequest = date('Y-m-d H:i:s');
$code = urlencode($this->encrypt($dataEmail['customer_email'].$dataEmail['reference'].$dateRequest));
$ip = $this->getUserIp();
$email = $dataEmail['customer_email'];
$urlDomainSiteEau = $this->servicesContainer->getParameter('url.site.eau');
$baseurl = $request->getScheme().'://'.$request->getHttpHost().$request->getBasePath();
$url = $baseurl.'/password-change?code='.$code.'&reference='.$dataEmail['reference'];
$dataEmail['url'] = $url;
$this->filesystem->dumpFile($pathPasswordForgotten.'/'.$dataEmail['reference'], $code."\n");
$this->filesystem->appendToFile($pathPasswordForgotten.'/'.$dataEmail['reference'], $dateRequest."\n");
$this->filesystem->appendToFile($pathPasswordForgotten.'/'.$dataEmail['reference'], $ip."\n");
$this->filesystem->appendToFile($pathPasswordForgotten.'/'.$dataEmail['reference'], $email."\n");
try {
$this->accountPasswordForgottenService->processUpdatePassword($dataEmail);
$this->addFlash(
'success',
'Un message de confirmation a été retourné à votre adresse e-mail. Si ce dernier n\'apparait pas veuillez vérifier vos courriers indésirables.'
);
$this->logger->info(
'Traitement de perte de mot de passe : envoi email ok',
[
'reference' => $dataEmail['reference'],
'email' => $dataEmail['customer_email'],
]
);
} catch (TransportExceptionInterface $e) {
$this->addFlash(
'danger',
'Une erreur est survenue lors de l’envoi du message.'
);
$this->logger->error(
'Traitement de perte de mot de passe : envoi email en erreur',
[
'reference' => $dataEmail['reference'],
'email' => $dataEmail['customer_email'],
]
);
}
return $this->redirectToRoute('password-forgotten');
}
/**
* @Route("/password-forgottent-email-action", name="password-forgotten-email-action", methods={"GET","POST"})
*
**/
public function passwordChangeEmailFormProcess(Request $request, AccountRepository $accountRepository)
{
if (!$this->isCsrfTokenValid('password-email-forgotten', $request->request->get('_token'))) {
return $this->redirectToRoute('password-forgotten');
}
$dataEmail = $request->request->all();
$account = $accountRepository->findOneBy(['reference' => $dataEmail['reference']]);
if (!$account) {
$this->addFlash(
'danger',
'Cette référence est inconnue'
);
$this->logger->error(
'Traitement de perte de mot de passe email différent : erreur (référence inconnue)'
);
return $this->redirectToRoute('password-forgotten');
}
if (!filter_var($dataEmail['recup_email'], FILTER_VALIDATE_EMAIL)) {
$this->addFlash(
'danger',
'Cette adresse email n\'est pas valide'
);
$this->logger->error(
'Traitement de perte de mot de passe email différent : erreur (adresse email incorrecte)'
);
return $this->redirectToRoute('password-forgotten');
}
if ($dataEmail['recup_nom']
&& $dataEmail['recup_address']
&& $dataEmail['recup_commune']
&& $dataEmail['recup_tel']
&& $dataEmail['recup_numero']
) {
try {
$pdf = new PdfService();
$dataEmail['attachedFiles'] = null;
$dataEmail['generatedDate'] = new \DateTimeImmutable('now', new \DateTimeZone('Europe/Paris'));
$template = $this->renderView('email_notifications/password-email-change-reset-request.html.twig', $dataEmail);
$pdf->setTitle('password-email-change-reset-request');
$pdf->setCliReference($dataEmail['reference']);
$fileName = $pdf->generateFile($template);
$attachments = $request->files->all();
$attachments['attachment'] = $fileName;
$this->accountPasswordEmailForgottenService->process($dataEmail, $attachments);
unlink($fileName);
$this->addFlash(
'success',
"Votre message a été envoyé avec succès.\nUn accusé de réception a été retourné à l’adresse e-mail indiquée."
);
$this->logger->info(
'Mot de passe perdu et adresse mail différente - envoi mail réussi',
[
'email' => $dataEmail['recup_email'],
]
);
} catch (TransportExceptionInterface $e) {
$this->addFlash(
'danger',
'Une erreur est survenue lors de l’envoi du message.'
);
$this->logger->error(
'Mot de passe perdu et adresse mail différente - envoi mail en échec',
[
'email' => $dataEmail['recup_email'],
]
);
}
}
return $this->redirectToRoute('password-forgotten');
}
/**
* @Route("/password-change", name="password-change", methods={"GET","POST"})
*
**/
public function passwordChangeForm(Request $request, AccountRepository $accountRepository)
{
$pathPasswordForgotten = $this->servicesContainer->getParameter('password.directory');
$dateLimit = date('Y-m-d H:m:s', (time() - 1800));
// 1. Mauvais paramètres
if (empty($request->query->get('code')) || empty($request->query->get('reference'))) {
$this->addFlash(
'danger',
'Une erreur est survenue lors du changement de mot de passe.'
);
$this->logger->error(
'Traitement de perte de mot de passe : changement en erreur (aucun code ou ref dans la requête)'
);
return $this->redirectToRoute('password-forgotten');
}
// 2. Demande introuvable.
$code = $ip = $dateRequest = '';
if (file_exists($pathPasswordForgotten.'/'.$request->query->get('reference'))) {
$passwordRequest = file($pathPasswordForgotten.'/'.$request->query->get('reference'));
if (isset($passwordRequest[0]) && isset($passwordRequest[1]) && isset($passwordRequest[2])) {
$code = str_replace(["\n", "\r"], '', $passwordRequest[0]);
$dateRequest = str_replace(["\n", "\r"], '', $passwordRequest[1]);
$ip = str_replace(["\n", "\r"], '', $passwordRequest[2]);
}
//$this->filesystem->remove([$pathPasswordForgotten.'/'.$request->query->get('reference')]);
}
if (empty($code) || empty($ip) || empty($dateRequest)) {
$this->addFlash(
'danger',
'Une erreur est survenue lors du changement de mot de passe.'
);
$this->logger->error(
'Traitement de perte de mot de passe : changement en erreur (demande non trouvée)'
);
return $this->redirectToRoute('password-forgotten');
}
if (urldecode($code) != $request->query->get('code')) {
$this->addFlash(
'danger',
'Une erreur est survenue lors du changement de mot de passe.'
);
$this->logger->error(
'Traitement de perte de mot de passe : changement en erreur (le hash est incorrect)'
);
return $this->redirectToRoute('password-forgotten');
}
// 3. Référence inconnue
$account = $accountRepository->findOneBy(['reference' => $request->query->get('reference')]);
if (!$account) {
$this->addFlash(
'danger',
'Une erreur est survenue lors du changement de mot de passe.'
);
$this->logger->error(
'Traitement de perte de mot de passe : changement en erreur (référence inconnue)'
);
return $this->redirectToRoute('password-forgotten');
}
// 4. Délai d'expiration dépassé.
if ($dateRequest <= $dateLimit) {
$this->addFlash(
'danger',
'Une erreur est survenue lors du changement de mot de passe. La demande est expirée'
);
$this->logger->error(
'Traitement de perte de mot de passe : changement en erreur (demande expirée)'
);
return $this->redirectToRoute('password-forgotten');
}
return $this->render(
'password_forgotten/form-change.html.twig',
[
'menu' => '',
'reference' => $request->query->get('reference'),
]
);
}
/**
* @Route("/password-change-action", name="password-change-action", methods={"GET","POST"})
*
**/
public function passwordChangeFormProcess(Request $request, AccountRepository $accountRepository, DataExportationService $dataExportationService)
{
$dataForm = $request->request->all();
if ($this->isCsrfTokenValid('setup-password'.$dataForm['reference'], $request->request->get('_token'))) {
if ('' != $request->request->get('password')
&& ($request->request->get('password') === $request->request->get('password_confirmation'))) {
$entityManager = $this->getDoctrine()->getManager();
$account = $accountRepository->findOneBy(['reference' => $dataForm['reference']]);
$account->setPassword(
$this->hashingService->hashPassword($request->request->get('password'))
);
$entityManager->flush();
$pathPasswordForgotten = $this->servicesContainer->getParameter('password.directory');
$email = '';
if (file_exists($pathPasswordForgotten.'/'.$dataForm['reference'])) {
$passwordRequest = file($pathPasswordForgotten.'/'.$dataForm['reference']);
$email = str_replace(["\n", "\r"], '', $passwordRequest[3]);
$this->filesystem->remove([$pathPasswordForgotten.'/'.$dataForm['reference']]);
}
// --------------------------------------------------
// On modifie le temoin de changement de mot de passe
$reference = preg_replace('/\s+/', '', $account->getReference());
$dossierParent = substr($reference, 0, 3);
$varPath = getcwd().'/../var';
$filename = $varPath.'/accounts/'.$dossierParent.'/'.$reference;
if (file_exists($filename)) {
$timestamp = time();
$result = file_put_contents($filename, $timestamp);
if (false !== $result) {
$this->logger->info('Modification fichier timestamp', ['customer' => $account->getReference()]);
} else {
$this->logger->info('Impossible d\'écrire dans le fichier timestamp', ['customer' => $account->getReference()]);
}
}
// --------------------------------------------------
$egeeRequest = new EgeeRequest(
'egee_flux/password-change.xml.twig',
[
'DATE_DEMANDE' => date('Ymd'),
'CLI_REFERENCE' => $dataForm['reference'],
'email' => $email,
'phone' => '',
]
);
$dataExportationService->persist($egeeRequest);
$this->addFlash(
'success',
'Votre mot de passe a été changé avec succès.'
);
$this->logger->info(
'Usager - Changement de mot de passe',
[
'reference' => $dataForm['reference'],
]
);
} else {
$this->addFlash(
'danger',
'Attention les mots de passe ne correspondent pas.'
);
$this->logger->error(
'Usager - Changement de mot de passe en erreur les mots de passe ne correspondent pas',
[
'reference' => $dataForm['reference'],
]
);
}
}
return $this->redirectToRoute('login');
}
private function getUserIp()
{
$ip = '';
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} elseif (!empty($_SERVER['REMOTE_ADDR'])) {
$ip = $_SERVER['REMOTE_ADDR'];
}
return $ip;
}
}