vendor/boldr/users-bundle/src/Controller/SecurityController.php line 54

Open in your IDE?
  1. <?php
  2. namespace Boldr\Cms\UsersBundle\Controller;
  3. use Boldr\Cms\UsersBundle\Form\{ ForgotPasswordType, ResetPasswordType };
  4. use Boldr\Cms\UsersBundle\Entity\User;
  5. use Boldr\Cms\UsersBundle\EmailFactory;
  6. use Boldr\Cms\UsersBundle\Event\UserPasswordChangedEvent;
  7. use Symfony\Component\HttpFoundation\{ Request, Response };
  8. use Symfony\Component\Routing\Annotation\Route;
  9. use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
  10. use Symfony\Component\Mailer\MailerInterface;
  11. use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
  12. use Symfony\Component\Form\FormError;
  13. use Symfony\Component\Form\Extension\Core\Type\{ FormType, TextType, PasswordType, CheckboxType };
  14. use Symfony\Contracts\Translation\TranslatorInterface;
  15. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  16. use Psr\EventDispatcher\EventDispatcherInterface;
  17. use DateTimeImmutable;
  18. use DateInterval;
  19. class SecurityController extends AbstractController
  20. {
  21. public static function getSubscribedServices(): array
  22. {
  23. return array_merge(parent::getSubscribedServices(), [
  24. MailerInterface::class,
  25. AuthenticationUtils::class,
  26. UserPasswordHasherInterface::class,
  27. EmailFactory::class,
  28. TranslatorInterface::class,
  29. EventDispatcherInterface::class
  30. ]);
  31. }
  32. /**
  33. * @Route({
  34. * "nl": "/inloggen",
  35. * "de": "/anmelden",
  36. * "en": "/login"
  37. * }, name="boldr_users_login")
  38. */
  39. public function login(Request $request): Response
  40. {
  41. if ($this->getUser())
  42. {
  43. return $this->redirectToRoute('cms_home');
  44. }
  45. $form = $this->get('form.factory')->createNamedBuilder('', FormType::class, null, [
  46. 'translation_domain' => 'BoldrUsersBundle',
  47. 'csrf_field_name' => '_csrf_token',
  48. 'csrf_token_id' => 'authenticate'
  49. ])
  50. ->add('_username', TextType::class, [
  51. 'label' => 'username',
  52. 'attr' => [
  53. 'autofocus' => 'autofocus'
  54. ]
  55. ])
  56. ->add('_password', PasswordType::class, [
  57. 'label' => 'password'
  58. ])
  59. ->add('_remember_me', CheckboxType::class, [
  60. 'label' => 'remember_me',
  61. 'required' => false
  62. ])
  63. ->getForm();
  64. $authenticationUtils = $this->get(AuthenticationUtils::class);
  65. $error = $authenticationUtils->getLastAuthenticationError();
  66. $lastUsername = $authenticationUtils->getLastUsername();
  67. $form->get('_username')->setData($lastUsername);
  68. if ($error)
  69. {
  70. $form->addError(new FormError($this->get(TranslatorInterface::class)->trans($error->getMessageKey(), $error->getMessageData(), 'security')));
  71. }
  72. return $this->render('@BoldrUsers/login.html.twig', [
  73. 'error' => $error,
  74. 'form' => $form->createView()
  75. ]);
  76. }
  77. /**
  78. * @Route({
  79. * "nl": "/uitloggen",
  80. * "de": "/abmelden",
  81. * "en": "/logout"
  82. * }, name="boldr_users_logout")
  83. */
  84. public function logout()
  85. {
  86. throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
  87. }
  88. /**
  89. * @Route({
  90. * "en": "/forgot-password",
  91. * "nl": "/wachtwoord-vergeten",
  92. * "de": "/passwort-vergessen"
  93. * }, name="boldr_users_forgot_password")
  94. * @Route("/boldr-api/boldr-users/forgot-password", name="boldr_users_api_forgot_password")
  95. */
  96. public function forgotPassword(Request $request)
  97. {
  98. if ($this->getUser() !== null)
  99. {
  100. return $this->redirectToRoute('cms_home');
  101. }
  102. $form = $this->createForm(ForgotPasswordType::class);
  103. $form->handleRequest($request);
  104. if ($form->isSubmitted() && $form->isValid())
  105. {
  106. $userRepository = $this->getDoctrine()->getRepository(User::class);
  107. $user = $userRepository->createQueryBuilder('u')
  108. ->andWhere('u.username = ?1 OR u.emailAddress = ?1')
  109. ->setMaxResults(1)
  110. ->setParameter(1, $form['usernameOrEmailAddress']->getData())
  111. ->getQuery()
  112. ->getOneOrNullResult();
  113. if ($user !== null)
  114. {
  115. if ($user->getForgotPasswordToken() === null || $user->getTimeForgotPasswordTokenExpires() < new DateTimeImmutable)
  116. {
  117. $forgotPasswordToken = bin2hex(random_bytes(32));
  118. $timeForgotPasswordTokenExpires = (new DateTimeImmutable)->add(new DateInterval('PT24H'));
  119. $user->setForgotPasswordToken($forgotPasswordToken);
  120. $user->setTimeForgotPasswordTokenExpires($timeForgotPasswordTokenExpires);
  121. $this->getDoctrine()->getManager()->flush();
  122. }
  123. $email = $this->get(EmailFactory::class)->createForgotPasswordEmail($user);
  124. try
  125. {
  126. $this->get(MailerInterface::class)->send($email);
  127. }
  128. catch (\Exception $ex)
  129. {
  130. // could not send
  131. }
  132. }
  133. if (in_array('application/json', $request->getAcceptableContentTypes()))
  134. {
  135. return $this->json(['success' => true]);
  136. }
  137. return $this->render('@BoldrUsers/password-link-sent.html.twig');
  138. }
  139. return $this->render('@BoldrUsers/forgot-password.html.twig', [
  140. 'form' => $form->createView()
  141. ]);
  142. }
  143. /**
  144. * @Route({
  145. * "en": "/reset-password/{token}",
  146. * "nl": "/wachtwoord-opnieuw-instellen/{token}",
  147. * "de": "/passwort-zurucksetzen/{token}"
  148. * }, name="boldr_users_reset_password")
  149. * @Route("/boldr-api/boldr-users/reset-password", name="boldr_users_api_reset_password", methods={"POST"})
  150. */
  151. public function resetPassword(Request $request, ?string $token = null)
  152. {
  153. $isJsonRequest = in_array('application/json', $request->getAcceptableContentTypes());
  154. if ($token === null)
  155. {
  156. if (!$request->request->has('token'))
  157. {
  158. return $this->json(['success' => false, 'error' => 'missing_token_parameter']);
  159. }
  160. $token = $request->request->get('token');
  161. }
  162. $userRepository = $this->getDoctrine()->getRepository(User::class);
  163. $user = $userRepository->createQueryBuilder('u')
  164. ->andWhere('u.forgotPasswordToken = :token AND u.timeForgotPasswordTokenExpires >= :now')
  165. ->setParameter('token', $token)
  166. ->setParameter('now', new DateTimeImmutable)
  167. ->setMaxResults(1)
  168. ->getQuery()
  169. ->getOneOrNullResult();
  170. if ($user === null)
  171. {
  172. if ($isJsonRequest)
  173. {
  174. return $this->json(['success' => false, 'error' => 'invalid_or_expired_token']);
  175. }
  176. return $this->render('@BoldrUsers/reset-link-expired.html.twig');
  177. }
  178. $form = $this->createForm(ResetPasswordType::class, null, ['allow_extra_fields' => true]);
  179. $form->handleRequest($request);
  180. $newPassword = null;
  181. if ($form->isSubmitted() && $form->isValid())
  182. {
  183. $newPassword = $form['newPassword']->getData();
  184. }
  185. if ($request->request->has('new_password'))
  186. {
  187. $newPassword = $request->request->get('new_password');
  188. }
  189. if ($newPassword !== null)
  190. {
  191. $passwordHash = $this->get(UserPasswordHasherInterface::class)->hashPassword($user, $newPassword);
  192. $user->setPasswordHash($passwordHash);
  193. $user->setForgotPasswordToken(null);
  194. $user->setTimeForgotPasswordTokenExpires(null);
  195. $this->getDoctrine()->getManager()->flush();
  196. $event = new UserPasswordChangedEvent($user, $newPassword, true);
  197. $this->get(EventDispatcherInterface::class)->dispatch($event);
  198. $email = $this->get(EmailFactory::class)->createPasswordResetEmail($user);
  199. try
  200. {
  201. $this->get(MailerInterface::class)->send($email);
  202. }
  203. catch (\Exception $ex)
  204. {
  205. // could not send email
  206. }
  207. if ($isJsonRequest)
  208. {
  209. return $this->json(['success' => true]);
  210. }
  211. return $this->render('@BoldrUsers/password-reset.html.twig');
  212. }
  213. if ($isJsonRequest)
  214. {
  215. return $this->json(['success' => false, 'error' => 'missing_fields']);
  216. }
  217. return $this->render('@BoldrUsers/reset-password.html.twig', [
  218. 'form' => $form->createView()
  219. ]);
  220. }
  221. /**
  222. * @Route({
  223. * "en": "/confirm-email-address/{token}",
  224. * "nl": "/bevestig-email-adres/{token}",
  225. * "de": "/bestatige-email-adresse/{token}"
  226. * }, name="boldr_users_confirm_email_address")
  227. */
  228. public function confirmEmailAddress(Request $request, string $token): Response
  229. {
  230. $userRepository = $this->getDoctrine()->getRepository(User::class);
  231. $user = $userRepository->findOneBy([
  232. 'emailAddressIsConfirmed' => false,
  233. 'confirmEmailAddressToken' => $token
  234. ]);
  235. if ($user === null)
  236. {
  237. return $this->render('@BoldrUsers/invalid-confirm-token.html.twig');
  238. }
  239. $user->setEmailAddressIsConfirmed(true);
  240. $user->setConfirmEmailAddressToken(null);
  241. $this->getDoctrine()->getManager()->flush();
  242. if (in_array('application/json', $request->getAcceptableContentTypes()))
  243. {
  244. return $this->json(['success' => true]);
  245. }
  246. /** @FIXME Make sure it does not redirect to outside URL */
  247. if ($request->query->has('redirect'))
  248. {
  249. return $this->redirect($request->query->get('redirect'));
  250. }
  251. return $this->render('@BoldrUsers/confirmed.html.twig', [
  252. 'user' => $user
  253. ]);
  254. }
  255. /**
  256. * @Route({
  257. * "en": "/resend-confirmation-email",
  258. * "nl": "/verstuur-bevestigingsemail-opnieuw",
  259. * "de": "/bestatigungsmail-erneut-senden"
  260. * }, name="boldr_users_resend_confirmation_email")
  261. * @Route("/boldr-api/boldr-users/resend-confirmation-email", name="boldr_users_api_resend_confirmation_email")
  262. */
  263. public function resendConfirmationEmail(Request $request): Response
  264. {
  265. $this->denyAccessUnlessGranted('ROLE_USER');
  266. $isJsonRequest = in_array('application/json', $request->getAcceptableContentTypes());
  267. /** @var User */
  268. $user = $this->getUser();
  269. if ($user->getEmailAddressIsConfirmed())
  270. {
  271. if ($isJsonRequest)
  272. {
  273. return $this->json(['success' => false, 'error' => 'already_confirmed']);
  274. }
  275. return $this->render('@BoldrUsers/already-confirmed.html.twig');
  276. }
  277. // Generate a confirmation token if it does not exist yet
  278. if ($user->getConfirmEmailAddressToken() === null)
  279. {
  280. $confirmEmailAddressToken = bin2hex(random_bytes(32));
  281. $user->setConfirmEmailAddressToken($confirmEmailAddressToken);
  282. $this->getDoctrine()->getManager()->flush();
  283. }
  284. $email = $this->get(EmailFactory::class)->createResendConfirmationEmail($user);
  285. try
  286. {
  287. $this->get(MailerInterface::class)->send($email);
  288. }
  289. catch (\Exception $ex)
  290. {
  291. // could not send email
  292. }
  293. if ($isJsonRequest)
  294. {
  295. return $this->json(['success' => true]);
  296. }
  297. /** @FIXME Make sure it does not redirect to outside URL */
  298. if ($request->query->has('redirect'))
  299. {
  300. return $this->redirect($request->query->get('redirect'));
  301. }
  302. return $this->render('@BoldrUsers/confirmation-email-resent.html.twig');
  303. }
  304. }