src/Controller/ResetPasswordController.php line 119

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use App\Entity\Interfaces\SpotHitCampaignInterface;
  4. use App\Entity\SmsSpotHitCampaign;
  5. use App\Entity\User;
  6. use App\Event\PortalUserEvent;
  7. use App\Factory\Platform\MailerFactory;
  8. use App\Form\Type\ChangePasswordFormType;
  9. use App\Form\Type\ResetPasswordRequestFormType;
  10. use App\Services\Common\Email\MailTypes;
  11. use App\Services\Common\MailerService;
  12. use App\Services\Common\Sms\SmsTypes;
  13. use App\Services\Common\User\WorkflowUser;
  14. use App\Services\DTV\YamlConfig\YamlReader;
  15. use App\Services\Portal\PortalService;
  16. use App\Services\SpotHitService;
  17. use DateTime;
  18. use Doctrine\ORM\EntityManagerInterface;
  19. use Exception;
  20. use Psr\Log\LoggerInterface;
  21. use ReflectionException;
  22. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  23. use Symfony\Component\HttpFoundation\RedirectResponse;
  24. use Symfony\Component\HttpFoundation\Request;
  25. use Symfony\Component\HttpFoundation\Response;
  26. use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
  27. use Symfony\Component\Routing\Annotation\Route;
  28. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  29. use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
  30. use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
  31. use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
  32. use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
  33. use Symfony\Contracts\Translation\TranslatorInterface;
  34. use SymfonyCasts\Bundle\ResetPassword\Controller\ResetPasswordControllerTrait;
  35. use SymfonyCasts\Bundle\ResetPassword\Exception\ResetPasswordExceptionInterface;
  36. use SymfonyCasts\Bundle\ResetPassword\ResetPasswordHelperInterface;
  37. /**
  38. * Controller pour la gestion de la réinitialisation du mot de passe
  39. *
  40. * @Route("/reset-password")
  41. */
  42. class ResetPasswordController extends AbstractController
  43. {
  44. use ResetPasswordControllerTrait;
  45. private ResetPasswordHelperInterface $resetPasswordHelper;
  46. private LoggerInterface $logger;
  47. private MailerService $mailerService;
  48. private YamlReader $yamlReader;
  49. private EntityManagerInterface $em;
  50. private PortalService $portalService;
  51. private EventDispatcherInterface $dispatcher;
  52. private TranslatorInterface $translator;
  53. private WorkflowUser $workflowUser;
  54. private SpotHitService $spotHitService;
  55. public function __construct(
  56. ResetPasswordHelperInterface $resetPasswordHelper,
  57. LoggerInterface $logger,
  58. MailerService $mailerService,
  59. YamlReader $yamlReader,
  60. EntityManagerInterface $em,
  61. PortalService $portalService,
  62. EventDispatcherInterface $dispatcher,
  63. TranslatorInterface $translator,
  64. WorkflowUser $workflowUser,
  65. SpotHitService $spotHitService
  66. ) {
  67. $this->resetPasswordHelper = $resetPasswordHelper;
  68. $this->logger = $logger;
  69. $this->mailerService = $mailerService;
  70. $this->yamlReader = $yamlReader;
  71. $this->em = $em;
  72. $this->portalService = $portalService;
  73. $this->dispatcher = $dispatcher;
  74. $this->translator = $translator;
  75. $this->workflowUser = $workflowUser;
  76. $this->spotHitService = $spotHitService;
  77. }
  78. /**
  79. * Affiche et traite le formulaire de demande de réinitialisation du mot de passe.
  80. *
  81. * @Route("", name="app_forgot_password_request")
  82. *
  83. * @param Request $request
  84. *
  85. * @return Response
  86. *
  87. * @throws ClientExceptionInterface
  88. * @throws RedirectionExceptionInterface
  89. * @throws ReflectionException
  90. * @throws ServerExceptionInterface
  91. * @throws TransportExceptionInterface
  92. * @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface
  93. */
  94. public function request(Request $request): Response
  95. {
  96. $form = $this->createForm(ResetPasswordRequestFormType::class);
  97. $form->handleRequest($request);
  98. if ($form->isSubmitted() && $form->isValid()) {
  99. $sendResetPasswordRequestBySms = false;
  100. if ($form->has('sendResetPasswordRequestBySms')) {
  101. $sendResetPasswordRequestBySms = $form->get('sendResetPasswordRequestBySms')->getData();
  102. }
  103. return $this->processSendingPasswordResetEmail(
  104. $form->get('email')
  105. ->getData(),
  106. $sendResetPasswordRequestBySms
  107. );
  108. }
  109. return $this->render('security/request.html.twig', [
  110. 'requestForm' => $form->createView(),
  111. ]);
  112. }
  113. /**
  114. * Redirige vers la page de vérification de l'e-mail.
  115. *
  116. * @param string $emailFormData
  117. * @param bool $sendResetPasswordRequestBySms
  118. *
  119. * @return RedirectResponse
  120. *
  121. * @throws ReflectionException
  122. * @throws ClientExceptionInterface
  123. * @throws RedirectionExceptionInterface
  124. * @throws ServerExceptionInterface
  125. * @throws Exception
  126. * @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface|TransportExceptionInterface
  127. * @throws ResetPasswordExceptionInterface
  128. */
  129. private function processSendingPasswordResetEmail(
  130. string $emailFormData,
  131. bool $sendResetPasswordRequestBySms = false
  132. ): RedirectResponse {
  133. $user = $this->em->getRepository(User::class)->findOneBy([
  134. 'email' => $emailFormData,
  135. ],);
  136. // Do not reveal whether a user account was found or not.
  137. if ($user === null) {
  138. return $this->redirectToRoute('app_check_email');
  139. }
  140. $mailer = $this->yamlReader->getMailer();
  141. // Check les CGU
  142. if (is_null($user->getCguAt()) && $user->getStatus() === User::STATUS_CGU_PENDING) {
  143. $this->workflowUser->resendCGU($user);
  144. $this->addFlash(
  145. 'danger',
  146. $this->translator->trans(
  147. "cgu_pending_message %contact_email%",
  148. ["%contact_email%" => $mailer['contact_email']]
  149. )
  150. );
  151. return $this->redirectToRoute('app_login');
  152. }
  153. try {
  154. $resetToken = $this->resetPasswordHelper->generateResetToken($user);
  155. } catch (ResetPasswordExceptionInterface $e) {
  156. // If you want to tell the user why a reset email was not sent, uncomment
  157. // the lines below and change the redirect to 'app_forgot_password_request'.
  158. // Caution: This may reveal if a user is registered or not.
  159. $this->addFlash(
  160. 'reset_password_error',
  161. 'il y a eu un problème lors du traitement de votre demande de réinitialisation du mot de passe - ' . $e->getReason(
  162. )
  163. );
  164. return $this->redirectToRoute('app_check_email');
  165. }
  166. $this->mailerService
  167. ->createApiMailRequest(MailTypes::RESET_PASSWORD)
  168. ->addRecipientToRequest($user, MailerFactory::buildResetPassword($resetToken->getToken()))
  169. ->send();
  170. if ($sendResetPasswordRequestBySms) {
  171. $smsCampaign = $this->spotHitService->createSmsCampaign(
  172. SmsSpotHitCampaign::CAMPAIGN_TYPE_RESET_PASSWORD,
  173. $this->yamlReader->getMailer()['transactional_sms'][SmsTypes::RESET_PASSWORD],
  174. $user,
  175. 'SMS_' . SmsTypes::RESET_PASSWORD . '_' . $user->getId() . '_' . (new DateTime())->format('YmdHis'),
  176. ['resetToken' => $resetToken->getToken()]
  177. );
  178. if (!$smsCampaign) {
  179. $this->addFlash(
  180. 'danger',
  181. "Une erreur est survenue lors de l'envoi du sms, le numéro fourni est invalide"
  182. );
  183. // Store the token object in session for retrieval in check-email route.
  184. $this->setTokenObjectInSession($resetToken);
  185. return $this->redirectToRoute('app_check_email');
  186. }
  187. $smsCampaign->setStatut(SpotHitCampaignInterface::STATUT_EN_COURS);
  188. $this->em->flush();
  189. $return = $this->spotHitService->sendCampaign($smsCampaign);
  190. if (is_string($return)) {
  191. $smsCampaign->setStatut(SpotHitCampaignInterface::STATUT_EN_ERREUR);
  192. $smsCampaign->setErreur($return);
  193. $this->addFlash('danger', "Une erreur est survenue lors de l'envoi du sms");
  194. } else {
  195. $smsCampaign->setStatut(SpotHitCampaignInterface::STATUT_ENVOYEE);
  196. $this->addFlash('success', "Le sms de rénitialisation a bien été envoyé");
  197. }
  198. $this->em->flush();
  199. }
  200. // Store the token object in session for retrieval in check-email route.
  201. $this->setTokenObjectInSession($resetToken);
  202. return $this->redirectToRoute('app_check_email');
  203. }
  204. /**
  205. * Page de confirmation après qu'un utilisateur a demandé une réinitialisation du mot de passe.
  206. *
  207. * @Route("/check-email", name="app_check_email")
  208. */
  209. public function checkEmail(): Response
  210. {
  211. return $this->render('security/check_email.html.twig', [
  212. 'resetToken' => $this->getTokenObjectFromSession(),
  213. ]);
  214. }
  215. /**
  216. * Valide et traite l'URL de réinitialisation que l'utilisateur a cliqué dans son e-mail.
  217. *
  218. * @Route("/reset/{token}", name="app_reset_password")
  219. */
  220. public function reset(
  221. Request $request,
  222. UserPasswordHasherInterface $passwordEncoder,
  223. string $token = null
  224. ): Response {
  225. if ($token) {
  226. // We store the token in session and remove it from the URL, to avoid the URL being
  227. // loaded in a browser and potentially leaking the token to 3rd party JavaScript.
  228. $this->storeTokenInSession($token);
  229. return $this->redirectToRoute('app_reset_password');
  230. }
  231. $token = $this->getTokenFromSession();
  232. if (null === $token) {
  233. throw $this->createNotFoundException('No reset password token found in the URL or in the session.');
  234. }
  235. try {
  236. /** @var User $user */
  237. $user = $this->resetPasswordHelper->validateTokenAndFetchUser($token);
  238. } catch (ResetPasswordExceptionInterface $e) {
  239. $this->addFlash(
  240. 'reset_password_error',
  241. $this->translator->trans(
  242. 'il y a eu un problème lors de la validation de votre demande de réinitialisation - %s %reason%',
  243. ['%reason%' => $e->getReason()]
  244. )
  245. );
  246. return $this->redirectToRoute('app_forgot_password_request');
  247. }
  248. // The token is valid; allow the user to change their password.
  249. $form = $this->createForm(ChangePasswordFormType::class);
  250. $form->handleRequest($request);
  251. if ($form->isSubmitted() && $form->isValid()) {
  252. $isPortal = $this->portalService->isOnPortal();
  253. // A password reset token should be used only once, remove it.
  254. $this->resetPasswordHelper->removeResetRequest($token);
  255. // Encode the plain password, and set it.
  256. $encodedPassword = $passwordEncoder->hashPassword($user, $form->get('plainPassword')->getData());
  257. // Si on est sur un portail (parent), on propage le nouveau mot de passe sur tous les sites enfants
  258. if ($isPortal && $this->portalService->isAParent()) {
  259. $user->setPlainPassword($form->get('plainPassword')->getData());
  260. $portalUserEvent = new PortalUserEvent($user);
  261. $this->dispatcher->dispatch($portalUserEvent, $portalUserEvent::NAME);
  262. }
  263. $user->setPassword($encodedPassword);
  264. $user->setFailedAttempts(0);
  265. $user->setLastFailedAttempt(null);
  266. $this->em->flush();
  267. // The session is cleaned up after the password has been changed.
  268. $this->cleanSessionAfterReset();
  269. $this->addFlash('success', $this->translator->trans('votre nouveau mot de passe a bien été enregistré !'));
  270. return $this->redirectToRoute('app_login');
  271. }
  272. return $this->render('security/reset.html.twig', [
  273. 'resetForm' => $form->createView(),
  274. ]);
  275. }
  276. /**
  277. * Indique si on doit mettre à jour le mot de passe de l'utilisateur
  278. *
  279. * @Route("/{id}", name="app_need_refresh_password")
  280. *
  281. *
  282. * @param User $user
  283. *
  284. * @return Response
  285. *
  286. * @throws ClientExceptionInterface
  287. * @throws RedirectionExceptionInterface
  288. * @throws ReflectionException
  289. * @throws ServerExceptionInterface
  290. * @throws Exception
  291. * @throws Exception
  292. * @throws TransportExceptionInterface
  293. */
  294. public function needRefreshPassword(User $user): Response
  295. {
  296. try {
  297. $resetToken = $this->resetPasswordHelper->generateResetToken($user);
  298. } catch (ResetPasswordExceptionInterface $e) {
  299. return $this->render('security/need_refresh_password.html.twig');
  300. }
  301. $this->mailerService
  302. ->createApiMailRequest(MailTypes::RESET_PASSWORD)
  303. ->addRecipientToRequest($user, MailerFactory::buildResetPassword($resetToken->getToken()))
  304. ->send();
  305. // Store the token object in session for retrieval in check-email route.
  306. $this->setTokenObjectInSession($resetToken);
  307. return $this->render('security/need_refresh_password.html.twig', [
  308. 'resetToken' => $resetToken,
  309. ]);
  310. }
  311. }