X-Git-Url: https://git.rapsys.eu/userbundle/blobdiff_plain/15c15d969c9161e0a10515ff91857e687ab6f60a..0811e272814cf05d6fc8bc76646a0be99bf79bb9:/Handler/AuthenticationFailureHandler.php

diff --git a/Handler/AuthenticationFailureHandler.php b/Handler/AuthenticationFailureHandler.php
index a8ac5a4..7e7c132 100644
--- a/Handler/AuthenticationFailureHandler.php
+++ b/Handler/AuthenticationFailureHandler.php
@@ -12,7 +12,14 @@
 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();
-	}
 }