X-Git-Url: https://git.rapsys.eu/userbundle/blobdiff_plain/4f7c241cab3374b8fb7a99555d15afa745c78b92..17869e03521ae16d21e3e7d7998ec23fd5da1686:/Controller/DefaultController.php diff --git a/Controller/DefaultController.php b/Controller/DefaultController.php index f364144..5c3c51c 100644 --- a/Controller/DefaultController.php +++ b/Controller/DefaultController.php @@ -1,4 +1,13 @@ - + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ namespace Rapsys\UserBundle\Controller; @@ -10,16 +19,13 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Form\FormError; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\Mailer\Exception\TransportExceptionInterface; use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Mime\Address; -use Symfony\Component\Routing\Exception\MethodNotAllowedException; -use Symfony\Component\Routing\Exception\ResourceNotFoundException; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -use Symfony\Component\Routing\RequestContext; use Symfony\Component\Routing\RouterInterface; -use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; use Symfony\Component\Translation\TranslatorInterface; @@ -38,7 +44,6 @@ class DefaultController extends AbstractController { * Constructor * * @TODO: move all canonical and other view related stuff in an user AbstractController like in RapsysAir render feature !!!! - * @TODO: add resetpassword ? with $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY'); https://symfony.com/doc/current/security/remember_me.html * * @param ContainerInterface $container The containter instance * @param RouterInterface $router The router instance @@ -238,7 +243,7 @@ class DefaultController extends AbstractController { * @param string $hash The hashed password * @return Response The response */ - public function confirm(Request $request, Registry $doctrine, UserPasswordEncoderInterface $encoder, EntityManagerInterface $manager, SluggerUtil $slugger, MailerInterface $mailer, $mail, $hash) { + public function confirm(Request $request, Registry $doctrine, UserPasswordEncoderInterface $encoder, EntityManagerInterface $manager, SluggerUtil $slugger, MailerInterface $mailer, $mail, $hash): Response { //With invalid hash if ($hash != $slugger->hash($mail)) { //Throw bad request @@ -268,9 +273,6 @@ class DefaultController extends AbstractController { //Set active $user->setActive(true); - //Set updated - $user->setUpdated(new \DateTime('now')); - //Persist user $manager->persist($user); @@ -289,13 +291,14 @@ class DefaultController extends AbstractController { * * @param Request $request The request * @param Registry $manager The doctrine registry + * @param UserPasswordEncoderInterface $encoder The password encoder * @param EntityManagerInterface $manager The doctrine entity manager * @param SluggerUtil $slugger The slugger * @param string $mail The shorted mail address * @param string $hash The hashed password * @return Response The response */ - public function edit(Request $request, Registry $doctrine, EntityManagerInterface $manager, SluggerUtil $slugger, $mail, $hash) { + public function edit(Request $request, Registry $doctrine, UserPasswordEncoderInterface $encoder, EntityManagerInterface $manager, SluggerUtil $slugger, $mail, $hash): Response { //With invalid hash if ($hash != $slugger->hash($mail)) { //Throw bad request @@ -313,14 +316,14 @@ class DefaultController extends AbstractController { } //Prevent access when not admin, user is not guest and not currently logged user - if (!$this->isGranted('ROLE_ADMIN') && $user != $this->getUser()) { + if (!$this->isGranted('ROLE_ADMIN') && $user != $this->getUser() || !$this->isGranted('IS_AUTHENTICATED_FULLY')) { //Throw access denied //XXX: prevent slugger reverse engineering by not displaying decoded mail throw $this->createAccessDeniedException($this->translator->trans('Unable to access user: %mail%', ['%mail%' => $smail])); } //Create the RegisterType form and give the proper parameters - $form = $this->createForm($this->config['register']['view']['form'], $user, [ + $editForm = $this->createForm($this->config['register']['view']['form'], $user, [ //Set action to register route name and context 'action' => $this->generateUrl($this->config['route']['edit']['name'], ['mail' => $smail, 'hash' => $slugger->hash($smail)]+$this->config['route']['edit']['context']), //Set civility class @@ -330,19 +333,84 @@ class DefaultController extends AbstractController { //Disable mail 'mail' => $this->isGranted('ROLE_ADMIN'), //Disable password - //XXX: prefer a reset on login to force user unspam action 'password' => false, //Set method 'method' => 'POST' ]); + //Create the RegisterType form and give the proper parameters + $edit = $this->createForm($this->config['edit']['view']['edit'], $user, [ + //Set action to register route name and context + 'action' => $this->generateUrl($this->config['route']['edit']['name'], ['mail' => $smail, 'hash' => $slugger->hash($smail)]+$this->config['route']['edit']['context']), + //Set civility class + 'civility_class' => $this->config['class']['civility'], + //Set civility default + 'civility_default' => $doctrine->getRepository($this->config['class']['civility'])->findOneByTitle($this->config['default']['civility']), + //Disable mail + 'mail' => $this->isGranted('ROLE_ADMIN'), + //Disable password + 'password' => false, + //Set method + 'method' => 'POST' + ]); + + //With admin role + if ($this->isGranted('ROLE_ADMIN')) { + //Create the LoginType form and give the proper parameters + $reset = $this->createForm($this->config['edit']['view']['reset'], $user, [ + //Set action to register route name and context + 'action' => $this->generateUrl($this->config['route']['edit']['name'], ['mail' => $smail, 'hash' => $slugger->hash($smail)]+$this->config['route']['edit']['context']), + //Disable mail + 'mail' => false, + //Set method + 'method' => 'POST' + ]); + + //With post method + if ($request->isMethod('POST')) { + //Refill the fields in case the form is not valid. + $reset->handleRequest($request); + + //With reset submitted and valid + if ($reset->isSubmitted() && $reset->isValid()) { + //Set data + $data = $reset->getData(); + + //Set password + $data->setPassword($encoder->encodePassword($data, $data->getPassword())); + + //Queue snippet save + $manager->persist($data); + + //Flush to get the ids + $manager->flush(); + + //Add notice + $this->addFlash('notice', $this->translator->trans('Account %mail% password updated', ['%mail%' => $mail = $data->getMail()])); + + //Redirect to cleanup the form + return $this->redirectToRoute($this->config['route']['edit']['name'], ['mail' => $smail = $slugger->short($mail), 'hash' => $slugger->hash($smail)]+$this->config['route']['edit']['context']); + } + } + + //Add reset view + $this->config['edit']['view']['context']['reset'] = $reset->createView(); + //Without admin role + //XXX: prefer a reset on login to force user unspam action + } else { + //Add notice + $this->addFlash('notice', $this->translator->trans('To change your password login with your mail and any password then follow the procedure')); + } + + //With post method if ($request->isMethod('POST')) { //Refill the fields in case the form is not valid. - $form->handleRequest($request); + $edit->handleRequest($request); - if ($form->isValid()) { + //With edit submitted and valid + if ($edit->isSubmitted() && $edit->isValid()) { //Set data - $data = $form->getData(); + $data = $edit->getData(); //Queue snippet save $manager->persist($data); @@ -353,16 +421,9 @@ class DefaultController extends AbstractController { //Add notice $this->addFlash('notice', $this->translator->trans('Account %mail% updated', ['%mail%' => $mail = $data->getMail()])); - //Redirect to user view - //TODO: extract referer ??? or useless ??? - return $this->redirectToRoute($this->config['route']['edit']['name'], ['mail' => $smail = $slugger->short($mail), 'hash' => $slugger->hash($smail)]+$this->config['route']['edit']['context']); - //Redirect to cleanup the form - return $this->redirectToRoute('rapsys_air', ['user' => $data->getId()]); + return $this->redirectToRoute($this->config['route']['edit']['name'], ['mail' => $smail = $slugger->short($mail), 'hash' => $slugger->hash($smail)]+$this->config['route']['edit']['context']); } - } else { - //Add notice - $this->addFlash('notice', $this->translator->trans('To change your password relogin with your mail %mail% and any password then follow the procedure', ['%mail%' => $mail])); } //Render view @@ -370,16 +431,13 @@ class DefaultController extends AbstractController { //Template $this->config['edit']['view']['name'], //Context - ['form' => $form->createView(), 'sent' => $request->query->get('sent', 0)]+$this->config['edit']['view']['context'] + ['edit' => $edit->createView(), 'sent' => $request->query->get('sent', 0)]+$this->config['edit']['view']['context'] ); } /** * Login * - * @todo When account is not activated, refuse login and send verification mail ? - * @todo Redirect to referer if route is not connect ? - * * @param Request $request The request * @param AuthenticationUtils $authenticationUtils The authentication utils * @param RouterInterface $router The router instance @@ -388,7 +446,7 @@ class DefaultController extends AbstractController { * @param string $hash The hashed password * @return Response The response */ - public function login(Request $request, AuthenticationUtils $authenticationUtils, RouterInterface $router, SluggerUtil $slugger, $mail, $hash) { + public function login(Request $request, AuthenticationUtils $authenticationUtils, RouterInterface $router, SluggerUtil $slugger, $mail, $hash): Response { //Create the LoginType form and give the proper parameters $login = $this->createForm($this->config['login']['view']['form'], null, [ //Set action to login route name and context @@ -455,7 +513,6 @@ class DefaultController extends AbstractController { $context['recover'] = $recover->createView(); } else { //Add notice - //TODO: drop it if referer route is recover ? $this->addFlash('notice', $this->translator->trans('To change your password login with your mail and any password then follow the procedure')); } @@ -482,7 +539,7 @@ class DefaultController extends AbstractController { * @param string $hash The hashed password * @return Response The response */ - public function recover(Request $request, Registry $doctrine, UserPasswordEncoderInterface $encoder, EntityManagerInterface $manager, SluggerUtil $slugger, MailerInterface $mailer, $mail, $pass, $hash) { + public function recover(Request $request, Registry $doctrine, UserPasswordEncoderInterface $encoder, EntityManagerInterface $manager, SluggerUtil $slugger, MailerInterface $mailer, $mail, $pass, $hash): Response { //Without mail, pass and hash if (empty($mail) && empty($pass) && empty($hash)) { //Create the LoginType form and give the proper parameters @@ -653,9 +710,6 @@ class DefaultController extends AbstractController { //Set user password $user->setPassword($encoded); - //Set updated - $user->setUpdated(new \DateTime('now')); - //Persist user $manager->persist($user); @@ -694,7 +748,7 @@ class DefaultController extends AbstractController { * @param string $hash The hashed serialized field array * @return Response The response */ - public function register(Request $request, Registry $doctrine, UserPasswordEncoderInterface $encoder, EntityManagerInterface $manager, SluggerUtil $slugger, MailerInterface $mailer, LoggerInterface $logger, $mail, $field, $hash) { + public function register(Request $request, Registry $doctrine, UserPasswordEncoderInterface $encoder, EntityManagerInterface $manager, SluggerUtil $slugger, MailerInterface $mailer, LoggerInterface $logger, $mail, $field, $hash): Response { //Init reflection $reflection = new \ReflectionClass($this->config['class']['user']); @@ -723,6 +777,108 @@ class DefaultController extends AbstractController { //Set mail $user->setMail($mail); + + //With existing registrant + if ($existing = $doctrine->getRepository($this->config['class']['user'])->findOneByMail($mail)) { + //With disabled existing + if ($existing->isDisabled()) { + //Render view + return $this->render( + //Template + $this->config['register']['view']['name'], + //Context + ['title' => $this->translator->trans('Access denied'), 'disabled' => 1]+$this->config['register']['view']['context'], + //Set 403 + new Response('', 403) + ); + //With unactivated existing + } elseif (!$existing->isActivated()) { + //Set mail shortcut + //TODO: change for activate ??? + $activateMail =& $this->config['register']['mail']; + + //Generate each route route + foreach($this->config['register']['route'] as $route => $tag) { + //Only process defined routes + if (!empty($this->config['route'][$route])) { + //Process for confirm url + if ($route == 'confirm') { + //Set the url in context + $activateMail['context'][$tag] = $this->get('router')->generate( + $this->config['route'][$route]['name'], + //Prepend subscribe context with tag + [ + 'mail' => $smail = $slugger->short($existing->getMail()), + 'hash' => $slugger->hash($smail) + ]+$this->config['route'][$route]['context'], + UrlGeneratorInterface::ABSOLUTE_URL + ); + } + } + } + + //Set recipient_name + $activateMail['context']['recipient_mail'] = $existing->getMail(); + + //Set recipient name + $activateMail['context']['recipient_name'] = implode(' ', [$existing->getForename(), $existing->getSurname(), $existing->getPseudonym()?'('.$existing->getPseudonym().')':'']); + + //Init subject context + $subjectContext = $slugger->flatten(array_replace_recursive($this->config['register']['view']['context'], $activateMail['context']), null, '.', '%', '%'); + + //Translate subject + $activateMail['subject'] = ucfirst($this->translator->trans($activateMail['subject'], $subjectContext)); + + //Create message + $message = (new TemplatedEmail()) + //Set sender + ->from(new Address($this->config['contact']['mail'], $this->config['contact']['title'])) + //Set recipient + //XXX: remove the debug set in vendor/symfony/mime/Address.php +46 + ->to(new Address($activateMail['context']['recipient_mail'], $activateMail['context']['recipient_name'])) + //Set subject + ->subject($activateMail['subject']) + + //Set path to twig templates + ->htmlTemplate($activateMail['html']) + ->textTemplate($activateMail['text']) + + //Set context + ->context(['subject' => $activateMail['subject']]+$activateMail['context']); + + //Try sending message + //XXX: mail delivery may silently fail + try { + //Send message + $mailer->send($message); + //Catch obvious transport exception + } catch(TransportExceptionInterface $e) { + //Add error message mail unreachable + $this->addFlash('error', $this->translator->trans('Account %mail% tried activate but unable to contact', ['%mail%' => $existing->getMail()])); + } + + //Get route params + $routeParams = $request->get('_route_params'); + + //Remove mail, field and hash from route params + unset($routeParams['mail'], $routeParams['field'], $routeParams['hash']); + + //Redirect on the same route with sent=1 to cleanup form + return $this->redirectToRoute($request->get('_route'), ['sent' => 1]+$routeParams); + } + + //Add error message mail already exists + $this->addFlash('warning', $this->translator->trans('Account %mail% already exists', ['%mail%' => $existing->getMail()])); + + //Redirect to user view + return $this->redirectToRoute( + $this->config['route']['edit']['name'], + [ + 'mail' => $smail = $slugger->short($existing->getMail()), + 'hash' => $slugger->hash($smail) + ]+$this->config['route']['edit']['context'] + ); + } //Without mail } else { //Set smail @@ -750,7 +906,7 @@ class DefaultController extends AbstractController { $smail = $mail; //Set smail - $sfield = $sfield; + $sfield = $field; //Reset field $field = [];