Cleanup
[userbundle] / Handler / AuthenticationFailureHandler.php
index a8ac5a42492733dbf831087677b5cc888c914ed3..7e7c132ad9b857fa6b8cb85fe574c9e768a51e18 100644 (file)
 namespace Rapsys\UserBundle\Handler;
 
 use Doctrine\Persistence\ManagerRegistry;
+
 use Psr\Log\LoggerInterface;
+
+use Rapsys\PackBundle\Util\SluggerUtil;
+
+use Rapsys\UserBundle\Exception\UnactivatedException;
+use Rapsys\UserBundle\RapsysUserBundle;
+
 use Symfony\Bridge\Twig\Mime\TemplatedEmail;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\RedirectResponse;
@@ -29,14 +36,12 @@ use Symfony\Component\Routing\RequestContext;
 use Symfony\Component\Routing\RouterInterface;
 use Symfony\Component\Security\Core\Exception\AuthenticationException;
 use Symfony\Component\Security\Core\Exception\BadCredentialsException;
+use Symfony\Component\Security\Core\Exception\DisabledException;
 use Symfony\Component\Security\Core\Exception\UserNotFoundException;
 use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler;
 use Symfony\Component\Security\Http\HttpUtils;
 use Symfony\Contracts\Translation\TranslatorInterface;
 
-use Rapsys\PackBundle\Util\SluggerUtil;
-use Rapsys\UserBundle\RapsysUserBundle;
-
 /**
  * {@inheritdoc}
  */
@@ -45,7 +50,6 @@ class AuthenticationFailureHandler extends DefaultAuthenticationFailureHandler {
         * Config array
         */
        protected array $config;
-       protected array $options;
        protected array $defaultOptions = [
                'failure_path' => null,
                'failure_forward' => false,
@@ -54,36 +58,8 @@ class AuthenticationFailureHandler extends DefaultAuthenticationFailureHandler {
        ];
 
        /**
-        * Doctrine instance
-        */
-       protected ManagerRegistry $doctrine;
-
-       /**
-        * MailerInterface
-        */
-       protected MailerInterface $mailer;
-
-       /**
-        * Router instance
-        */
-       protected RouterInterface $router;
-
-       /**
-        * Slugger instance
-        */
-       protected SluggerUtil $slugger;
-
-       /**
-        * RequestStack instance
-        */
-       protected RequestStack $stack;
-
-       /**
-        * Translator instance
-        */
-       protected TranslatorInterface $translator;
-
-       /**
+        * {@inheritdoc}
+        *
         * @xxx Second argument will be replaced by security.firewalls.main.logout.target
         * @see vendor/symfony/security-bundle/DependencyInjection/SecurityExtension.php +360
         *
@@ -98,33 +74,13 @@ class AuthenticationFailureHandler extends DefaultAuthenticationFailureHandler {
         * @param SluggerUtil $slugger The slugger instance
         * @param RequestStack $stack The stack instance
         * @param TranslatorInterface $translator The translator instance
-        *
-        * {@inheritdoc}
         */
-       public function __construct(HttpKernelInterface $httpKernel, HttpUtils $httpUtils, array $options, LoggerInterface $logger, ContainerInterface $container, ManagerRegistry $doctrine, MailerInterface $mailer, RouterInterface $router, SluggerUtil $slugger, RequestStack $stack, TranslatorInterface $translator) {
-               //Set config
-               $this->config = $container->getParameter(self::getAlias());
-
-               //Set doctrine
-               $this->doctrine = $doctrine;
-
-               //Set mailer
-               $this->mailer = $mailer;
-
-               //Set router
-               $this->router = $router;
-
-               //Set slugger
-               $this->slugger = $slugger;
-
-               //Set stack
-               $this->stack = $stack;
-
-               //Set translator
-               $this->translator = $translator;
-
+       public function __construct(protected HttpKernelInterface $httpKernel, protected HttpUtils $httpUtils, protected array $options, protected ?LoggerInterface $logger, protected ContainerInterface $container, protected ManagerRegistry $doctrine, protected MailerInterface $mailer, protected RouterInterface $router, protected SluggerUtil $slugger, protected RequestStack $stack, protected TranslatorInterface $translator) {
                //Call parent constructor
                parent::__construct($httpKernel, $httpUtils, $options, $logger);
+
+               //Set config
+               $this->config = $container->getParameter(RapsysUserBundle::getAlias());
        }
 
        /**
@@ -147,295 +103,203 @@ class AuthenticationFailureHandler extends DefaultAuthenticationFailureHandler {
        }
 
        /**
-        * This is called when an interactive authentication attempt fails
-        *
-        * User may retrieve mail + field + hash for each unactivated/locked accounts
-        *
         * {@inheritdoc}
+        *
+        * This is called when an interactive authentication attempt fails
         */
        public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response {
                //With bad credential exception
                if ($exception instanceof BadCredentialsException) {
                        //With parent exception
                        if (($parent = $exception->getPrevious()) instanceof UserNotFoundException) {
-                               //Retrieve login
-                               //TODO: check form _token validity ???
-                               if (
-                                       !empty($login = $request->get('login')) &&
-                                       !empty($mail = $login['mail'])
-                               ) {
-                                       //Set extra parameters
-                                       $extra = ['mail' => $smail = $this->slugger->short($mail), 'field' => $sfield = $this->slugger->serialize([]), 'hash' => $this->slugger->hash($smail.$sfield)];
-
-                                       //With failure target path option
-                                       if (!empty($failurePath = $this->options['failure_path'])) {
-                                               //With path
-                                               if ($failurePath[0] == '/') {
-                                                       //Create login path request instance
-                                                       $req = Request::create($failurePath);
-
-                                                       //Get login path pathinfo
-                                                       $path = $req->getPathInfo();
-
-                                                       //Remove script name
-                                                       $path = str_replace($request->getScriptName(), '', $path);
-
-                                                       //Try with login path path
-                                                       try {
-                                                               //Save old context
-                                                               $oldContext = $this->router->getContext();
-
-                                                               //Force clean context
-                                                               //XXX: prevent MethodNotAllowedException on GET only routes because our context method is POST
-                                                               //XXX: see vendor/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php +42
-                                                               $this->router->setContext(new RequestContext());
-
-                                                               //Retrieve route matching path
-                                                               $route = $this->router->match($path);
-
-                                                               //Reset context
-                                                               $this->router->setContext($oldContext);
-
-                                                               //Clear old context
-                                                               unset($oldContext);
-
-                                                               //With route name
-                                                               if ($name = $route['_route']) {
-                                                                       //Remove route and controller from route defaults
-                                                                       unset($route['_route'], $route['_controller'], $route['_canonical_route']);
-
-                                                                       //Generate url
-                                                                       $url = $this->router->generate($name, $extra+$route);
-
-                                                                       //Return redirect to url response
-                                                                       return new RedirectResponse($url, 302);
-                                                               }
-                                                       //No route matched
-                                                       } catch (ResourceNotFoundException $e) {
-                                                               //Unset default path, name and route
-                                                               unset($failurePath, $name, $route);
-                                                       }
-                                               //With route name
-                                               } else {
-                                                       //Try with login path route
-                                                       try {
-                                                               //Retrieve route matching path
-                                                               $url = $this->router->generate($failurePath, $extra);
+                               /** Disabled to prevent user mail + hash retrieval for each unactivated/locked accounts
 
-                                                               //Return redirect to url response
-                                                               return new RedirectResponse($url, 302);
-                                                       //Route not found, missing parameter or invalid parameter
-                                                       } catch (RouteNotFoundException|MissingMandatoryParametersException|InvalidParameterException $e) {
-                                                               //Unset default path and url
-                                                               unset($failurePath, $url);
-                                                       }
-                                               }
-                                       }
+                               //Get user identifier
+                               $mail = $parent->getUserIdentifier();
+
+                               //Set extra parameters
+                               $extra = ['mail' => $smail = $this->slugger->short($mail), 'hash' => $this->slugger->hash($smail)];*/
+
+                               //With failure target path option
+                               if (!empty($failurePath = $this->options['failure_path'])) {
+                                       //With path
+                                       if ($failurePath[0] == '/') {
+                                               //Create login path request instance
+                                               $req = Request::create($failurePath);
+
+                                               //Get login path pathinfo
+                                               $path = $req->getPathInfo();
+
+                                               //Remove script name
+                                               $path = str_replace($request->getScriptName(), '', $path);
+
+                                               //Try with login path path
+                                               try {
+                                                       //Save old context
+                                                       $oldContext = $this->router->getContext();
+
+                                                       //Force clean context
+                                                       //XXX: prevent MethodNotAllowedException on GET only routes because our context method is POST
+                                                       //XXX: see vendor/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php +42
+                                                       $this->router->setContext(new RequestContext());
+
+                                                       //Retrieve route matching path
+                                                       $route = $this->router->match($path);
+
+                                                       //Reset context
+                                                       $this->router->setContext($oldContext);
+
+                                                       //Clear old context
+                                                       unset($oldContext);
+
+                                                       //With route name
+                                                       if ($name = $route['_route']) {
+                                                               //Remove route and controller from route defaults
+                                                               unset($route['_route'], $route['_controller'], $route['_canonical_route']);
 
-                                       //Without user
-                                       if (($user = $this->doctrine->getRepository($this->config['class']['user'])->findOneByMail($mail)) === null) {
-                                               //With index route from config
-                                               if (!empty($name = $this->config['route']['register']['name']) && is_array($context = $this->config['route']['register']['context'])) {
-                                                       //Try index route
-                                                       try {
                                                                //Generate url
-                                                               $url = $this->router->generate($name, $extra+$context);
+                                                               $url = $this->router->generate($name, /*$extra+*/$route);
 
-                                                               //Return generated route
+                                                               //Return redirect to url response
                                                                return new RedirectResponse($url, 302);
-                                                       //No route matched
-                                                       } catch (ResourceNotFoundException $e) {
-                                                               //Unset name and context
-                                                               unset($name, $context);
-                                                       }
-                                               } 
-
-                                               //With login target path option
-                                               if (!empty($loginPath = $this->options['login_path'])) {
-                                                       //With path
-                                                       if ($loginPath[0] == '/') {
-                                                               //Create login path request instance
-                                                               $req = Request::create($loginPath);
-
-                                                               //Get login path pathinfo
-                                                               $path = $req->getPathInfo();
-
-                                                               //Remove script name
-                                                               $path = str_replace($request->getScriptName(), '', $path);
-
-                                                               //Try with login path path
-                                                               try {
-                                                                       //Save old context
-                                                                       $oldContext = $this->router->getContext();
-
-                                                                       //Force clean context
-                                                                       //XXX: prevent MethodNotAllowedException on GET only routes because our context method is POST
-                                                                       //XXX: see vendor/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php +42
-                                                                       $this->router->setContext(new RequestContext());
-
-                                                                       //Retrieve route matching path
-                                                                       $route = $this->router->match($path);
-
-                                                                       //Reset context
-                                                                       $this->router->setContext($oldContext);
-
-                                                                       //Clear old context
-                                                                       unset($oldContext);
-
-                                                                       //With route name
-                                                                       if ($name = $route['_route']) {
-                                                                               //Remove route and controller from route defaults
-                                                                               unset($route['_route'], $route['_controller'], $route['_canonical_route']);
-
-                                                                               //Generate url
-                                                                               $url = $this->router->generate($name, $extra+$route);
-
-                                                                               //Return redirect to url response
-                                                                               return new RedirectResponse($url, 302);
-                                                                       }
-                                                               //No route matched
-                                                               } catch (ResourceNotFoundException $e) {
-                                                                       //Unset default path, name and route
-                                                                       unset($loginPath, $name, $route);
-                                                               }
-                                                       //With route name
-                                                       } else {
-                                                               //Try with login path route
-                                                               try {
-                                                                       //Retrieve route matching path
-                                                                       $url = $this->router->generate($loginPath, $extra);
-
-                                                                       //Return redirect to url response
-                                                                       return new RedirectResponse($url, 302);
-                                                               //Route not found, missing parameter or invalid parameter
-                                                               } catch (RouteNotFoundException|MissingMandatoryParametersException|InvalidParameterException $e) {
-                                                                       //Unset default path and url
-                                                                       unset($loginPath, $url);
-                                                               }
                                                        }
+                                               //No route matched
+                                               } catch (ResourceNotFoundException $e) {
+                                                       //Unset default path, name and route
+                                                       unset($failurePath, $name, $route);
                                                }
-                                       //With not enabled user
-                                       } elseif (empty($user->isEnabled())) {
-                                               //Add error message account is not enabled
-                                               $this->addFlash('error', $this->translator->trans('Your account is not enable'));
-
-                                               //Redirect on the same route with sent=1 to cleanup form
-                                               return new RedirectResponse($this->router->generate($request->get('_route'), $request->get('_route_params')), 302);
-                                       //With unactivated user
-                                       } elseif (empty($user->isActivated())) {
-                                               //Set context
-                                               $context = [
-                                                       'recipient_mail' => $user->getMail(),
-                                                       'recipient_name' => $user->getRecipientName()
-                                               ] + array_replace_recursive(
-                                                       $this->config['context'],
-                                                       $this->config['register']['view']['context'],
-                                                       $this->config['register']['mail']['context']
-                                               );
-
-                                               //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
-                                                                       $context[$tag] = $this->router->generate(
-                                                                               $this->config['route'][$route]['name'],
-                                                                               //Prepend subscribe context with tag
-                                                                               [
-                                                                                       'mail' => $smail = $this->slugger->short($context['recipient_mail']),
-                                                                                       'hash' => $this->slugger->hash($smail)
-                                                                               ]+$this->config['route'][$route]['context'],
-                                                                               UrlGeneratorInterface::ABSOLUTE_URL
-                                                                       );
-                                                               }
-                                                       }
+                                       //With route name
+                                       } else {
+                                               //Try with login path route
+                                               try {
+                                                       //Retrieve route matching path
+                                                       $url = $this->router->generate($failurePath/*, $extra*/);
+
+                                                       //Return redirect to url response
+                                                       return new RedirectResponse($url, 302);
+                                               //Route not found, missing parameter or invalid parameter
+                                               } catch (RouteNotFoundException|MissingMandatoryParametersException|InvalidParameterException $e) {
+                                                       //Unset default path and url
+                                                       unset($failurePath, $url);
+                                               }
+                                       }
+                               }
+                       //With not enabled user
+                       } elseif ($parent instanceof DisabledException) {
+                               //Add error message account is not enabled
+                               $this->addFlash('error', $this->translator->trans('Account not enabled'));
+
+                               //Redirect on the same route with sent=1 to cleanup form
+                               return new RedirectResponse($this->router->generate($request->get('_route'), $request->get('_route_params')), 302);
+                       //With not activated user
+                       } elseif ($parent instanceof UnactivatedException) {
+                               //Set user
+                               $user = $parent->getUser();
+
+                               //Set context
+                               $context = [
+                                       'recipient_mail' => $user->getMail(),
+                                       'recipient_name' => $user->getRecipientName()
+                               ] + array_replace_recursive(
+                                       $this->config['context'],
+                                       $this->config['register']['view']['context'],
+                                       $this->config['register']['mail']['context']
+                               );
+
+                               //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
+                                                       $context[$tag] = $this->router->generate(
+                                                               $this->config['route'][$route]['name'],
+                                                               //Prepend confirm context with tag
+                                                               [
+                                                                       'mail' => $smail = $this->slugger->short($context['recipient_mail']),
+                                                                       'hash' => $this->slugger->hash($smail)
+                                                               ]+$this->config['route'][$route]['context'],
+                                                               UrlGeneratorInterface::ABSOLUTE_URL
+                                                       );
                                                }
+                                       }
+                               }
 
-                                               //Iterate on keys to translate
-                                               foreach($this->config['translate'] as $translate) {
-                                                       //Extract keys
-                                                       $keys = explode('.', $translate);
+                               //Iterate on keys to translate
+                               foreach($this->config['translate'] as $translate) {
+                                       //Extract keys
+                                       $keys = explode('.', $translate);
 
-                                                       //Set current
-                                                       $current =& $context;
+                                       //Set current
+                                       $current =& $context;
 
-                                                       //Iterate on each subkey
-                                                       do {
-                                                               //Skip unset translation keys
-                                                               if (!isset($current[current($keys)])) {
-                                                                       continue(2);
-                                                               }
+                                       //Iterate on each subkey
+                                       do {
+                                               //Skip unset translation keys
+                                               if (!isset($current[current($keys)])) {
+                                                       continue(2);
+                                               }
 
-                                                               //Set current to subkey
-                                                               $current =& $current[current($keys)];
-                                                       } while(next($keys));
+                                               //Set current to subkey
+                                               $current =& $current[current($keys)];
+                                       } while(next($keys));
 
-                                                       //Set translation
-                                                       $current = $this->translator->trans($current);
+                                       //Set translation
+                                       $current = $this->translator->trans($current);
 
-                                                       //Remove reference
-                                                       unset($current);
-                                               }
+                                       //Remove reference
+                                       unset($current);
+                               }
 
-                                               //Translate subject
-                                               $context['subject'] = $subject = ucfirst(
-                                                       $this->translator->trans(
-                                                               $this->config['register']['mail']['subject'],
-                                                               $this->slugger->flatten($context, null, '.', '%', '%')
-                                                       )
-                                               );
-
-                                               //Create message
-                                               $message = (new TemplatedEmail())
-                                                       //Set sender
-                                                       ->from(new Address($this->config['contact']['address'], $this->config['contact']['name']))
-                                                       //Set recipient
-                                                       //XXX: remove the debug set in vendor/symfony/mime/Address.php +46
-                                                       ->to(new Address($context['recipient_mail'], $context['recipient_name']))
-                                                       //Set subject
-                                                       ->subject($context['subject'])
-
-                                                       //Set path to twig templates
-                                                       ->htmlTemplate($this->config['register']['mail']['html'])
-                                                       ->textTemplate($this->config['register']['mail']['text'])
-
-                                                       //Set context
-                                                       ->context($context);
-
-                                               //Try sending message
-                                               //XXX: mail delivery may silently fail
-                                               try {
-                                                       //Send message
-                                                       $this->mailer->send($message);
-                                               //Catch obvious transport exception
-                                               } catch(TransportExceptionInterface $e) {
-                                                       //Add error message mail unreachable
-                                                       $this->addFlash('error', $this->translator->trans('Unable to reach account'));
-                                               }
+                               //Translate subject
+                               $context['subject'] = $subject = ucfirst(
+                                       $this->translator->trans(
+                                               $this->config['register']['mail']['subject'],
+                                               $this->slugger->flatten($context, null, '.', '%', '%')
+                                       )
+                               );
+
+                               //Create message
+                               $message = (new TemplatedEmail())
+                                       //Set sender
+                                       ->from(new Address($this->config['contact']['address'], $this->config['contact']['name']))
+                                       //Set recipient
+                                       //XXX: remove the debug set in vendor/symfony/mime/Address.php +46
+                                       ->to(new Address($context['recipient_mail'], $context['recipient_name']))
+                                       //Set subject
+                                       ->subject($context['subject'])
+
+                                       //Set path to twig templates
+                                       ->htmlTemplate($this->config['register']['mail']['html'])
+                                       ->textTemplate($this->config['register']['mail']['text'])
+
+                                       //Set context
+                                       ->context($context);
+
+                               //Try sending message
+                               //XXX: mail delivery may silently fail
+                               try {
+                                       //Send message
+                                       $this->mailer->send($message);
+                               //Catch obvious transport exception
+                               } catch(TransportExceptionInterface $e) {
+                                       //Add error message mail unreachable
+                                       $this->addFlash('error', $this->translator->trans('Unable to reach account'));
+                               }
 
-                                               //Add notice
-                                               $this->addFlash('notice', $this->translator->trans('Your verification mail has been sent, to activate your account you must follow the confirmation link inside'));
+                               //Add notice
+                               $this->addFlash('notice', $this->translator->trans('Your verification mail has been sent, to activate your account follow the confirmation link inside'));
 
-                                               //Add junk warning
-                                               $this->addFlash('warning', $this->translator->trans('If you did not receive a verification mail, check your Spam or Junk mail folders'));
+                               //Add junk warning
+                               $this->addFlash('warning', $this->translator->trans('If you did not receive a verification mail, check your Spam or Junk mail folder'));
 
-                                               //Redirect on the same route with sent=1 to cleanup form
-                                               return new RedirectResponse($this->router->generate($request->get('_route'), $request->get('_route_params')), 302);
-                                       }
-                               }
+                               //Redirect on the same route with sent=1 to cleanup form
+                               return new RedirectResponse($this->router->generate($request->get('_route'), $request->get('_route_params')), 302);
                        }
                }
 
                //Call parent function
                return parent::onAuthenticationFailure($request, $exception);
        }
-
-       /**
-        * {@inheritdoc}
-        */
-       public function getAlias(): string {
-               return RapsysUserBundle::getAlias();
-       }
 }