1 <?php 
declare(strict_types
=1); 
   4  * This file is part of the Rapsys UserBundle package. 
   6  * (c) Raphaël Gertz <symfony@rapsys.eu> 
   8  * For the full copyright and license information, please view the LICENSE 
   9  * file that was distributed with this source code. 
  12 namespace Rapsys\UserBundle\Handler
; 
  14 use Doctrine\Persistence\ManagerRegistry
; 
  15 use Psr\Log\LoggerInterface
; 
  16 use Symfony\Bridge\Twig\Mime\TemplatedEmail
; 
  17 use Symfony\Component\DependencyInjection\ContainerInterface
; 
  18 use Symfony\Component\HttpFoundation\RedirectResponse
; 
  19 use Symfony\Component\HttpFoundation\Request
; 
  20 use Symfony\Component\HttpFoundation\RequestStack
; 
  21 use Symfony\Component\HttpFoundation\Response
; 
  22 use Symfony\Component\HttpFoundation\Session\FlashBagAwareSessionInterface
; 
  23 use Symfony\Component\HttpKernel\HttpKernelInterface
; 
  24 use Symfony\Component\Mailer\MailerInterface
; 
  25 use Symfony\Component\Mime\Address
; 
  26 use Symfony\Component\Routing\Exception\ResourceNotFoundException
; 
  27 use Symfony\Component\Routing\Generator\UrlGeneratorInterface
; 
  28 use Symfony\Component\Routing\RequestContext
; 
  29 use Symfony\Component\Routing\RouterInterface
; 
  30 use Symfony\Component\Security\Core\Exception\AuthenticationException
; 
  31 use Symfony\Component\Security\Core\Exception\BadCredentialsException
; 
  32 use Symfony\Component\Security\Core\Exception\UserNotFoundException
; 
  33 use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler
; 
  34 use Symfony\Component\Security\Http\HttpUtils
; 
  35 use Symfony\Contracts\Translation\TranslatorInterface
; 
  37 use Rapsys\PackBundle\Util\SluggerUtil
; 
  38 use Rapsys\UserBundle\RapsysUserBundle
; 
  43 class AuthenticationFailureHandler 
extends DefaultAuthenticationFailureHandler 
{ 
  47         protected array $config; 
  48         protected array $options; 
  49         protected array $defaultOptions = [ 
  50                 'failure_path' => null, 
  51                 'failure_forward' => false, 
  52                 'login_path' => '/login', 
  53                 'failure_path_parameter' => '_failure_path', 
  59         protected ManagerRegistry 
$doctrine; 
  64         protected MailerInterface 
$mailer; 
  69         protected RouterInterface 
$router; 
  74         protected SluggerUtil 
$slugger; 
  77          * RequestStack instance 
  79         protected RequestStack 
$stack; 
  84         protected TranslatorInterface 
$translator; 
  87          * @xxx Second argument will be replaced by security.firewalls.main.logout.target 
  88          * @see vendor/symfony/security-bundle/DependencyInjection/SecurityExtension.php +360 
  90          * @param HttpKernelInterface $httpKernel The http kernel 
  91          * @param HttpUtils $httpUtils The http utils 
  92          * @param array $options The options 
  93          * @param LoggerInterface $logger The logger instance 
  94          * @param ContainerInterface $container The container instance 
  95          * @param ManagerRegistry $doctrine The doctrine instance 
  96          * @param MailerInterface $mailer The mailer instance 
  97          * @param RouterInterface $router The router instance 
  98          * @param SluggerUtil $slugger The slugger instance 
  99          * @param RequestStack $stack The stack instance 
 100          * @param TranslatorInterface $translator The translator instance 
 104         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) { 
 106                 $this->config 
= $container->getParameter(self
::getAlias()); 
 109                 $this->doctrine 
= $doctrine; 
 112                 $this->mailer 
= $mailer; 
 115                 $this->router 
= $router; 
 118                 $this->slugger 
= $slugger; 
 121                 $this->stack 
= $stack; 
 124                 $this->translator 
= $translator; 
 126                 //Call parent constructor 
 127                 parent
::__construct($httpKernel, $httpUtils, $options, $logger); 
 131          * Adds a flash message to the current session for type. 
 133          * @throws \LogicException 
 135         protected function addFlash(string $type, mixed $message): void { 
 137                         $session = $this->stack
->getSession(); 
 138                 } catch (SessionNotFoundException 
$e) { 
 139                         throw new \
LogicException('You cannot use the addFlash method if sessions are disabled. Enable them in "config/packages/framework.yaml".', 0, $e); 
 142                 if (!$session instanceof FlashBagAwareSessionInterface
) { 
 143                         throw new \
LogicException(sprintf('You cannot use the addFlash method because class "%s" doesn\'t implement "%s".', get_debug_type($session), FlashBagAwareSessionInterface
::class)); 
 146                 $session->getFlashBag()->add($type, $message); 
 150          * This is called when an interactive authentication attempt fails 
 152          * User may retrieve mail + field + hash for each unactivated/locked accounts 
 156         public function onAuthenticationFailure(Request 
$request, AuthenticationException 
$exception): Response 
{ 
 157                 //With bad credential exception 
 158                 if ($exception instanceof BadCredentialsException
) { 
 159                         //With parent exception 
 160                         if (($parent = $exception->getPrevious()) instanceof UserNotFoundException
) { 
 162                                 //TODO: check form _token validity ??? 
 164                                         !empty($login = $request->get('login')) && 
 165                                         !empty($mail = $login['mail']) 
 167                                         //Set extra parameters 
 168                                         $extra = ['mail' => $smail = $this->slugger
->short($mail), 'field' => $sfield = $this->slugger
->serialize([]), 'hash' => $this->slugger
->hash($smail.$sfield)]; 
 170                                         //With failure target path option 
 171                                         if (!empty($failurePath = $this->options
['failure_path'])) { 
 173                                                 if ($failurePath[0] == '/') { 
 174                                                         //Create login path request instance 
 175                                                         $req = Request
::create($failurePath); 
 177                                                         //Get login path pathinfo 
 178                                                         $path = $req->getPathInfo(); 
 181                                                         $path = str_replace($request->getScriptName(), '', $path); 
 183                                                         //Try with login path path 
 186                                                                 $oldContext = $this->router
->getContext(); 
 188                                                                 //Force clean context 
 189                                                                 //XXX: prevent MethodNotAllowedException on GET only routes because our context method is POST 
 190                                                                 //XXX: see vendor/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php +42 
 191                                                                 $this->router
->setContext(new RequestContext()); 
 193                                                                 //Retrieve route matching path 
 194                                                                 $route = $this->router
->match($path); 
 197                                                                 $this->router
->setContext($oldContext); 
 203                                                                 if ($name = $route['_route']) { 
 204                                                                         //Remove route and controller from route defaults 
 205                                                                         unset($route['_route'], $route['_controller'], $route['_canonical_route']); 
 208                                                                         $url = $this->router
->generate($name, $extra+
$route); 
 210                                                                         //Return redirect to url response 
 211                                                                         return new RedirectResponse($url, 302); 
 214                                                         } catch (ResourceNotFoundException 
$e) { 
 215                                                                 //Unset default path, name and route 
 216                                                                 unset($failurePath, $name, $route); 
 220                                                         //Try with login path route 
 222                                                                 //Retrieve route matching path 
 223                                                                 $url = $this->router
->generate($failurePath, $extra); 
 225                                                                 //Return redirect to url response 
 226                                                                 return new RedirectResponse($url, 302); 
 227                                                         //Route not found, missing parameter or invalid parameter 
 228                                                         } catch (RouteNotFoundException
|MissingMandatoryParametersException
|InvalidParameterException 
$e) { 
 229                                                                 //Unset default path and url 
 230                                                                 unset($failurePath, $url); 
 236                                         if (($user = $this->doctrine
->getRepository($this->config
['class']['user'])->findOneByMail($mail)) === null) { 
 237                                                 //With index route from config 
 238                                                 if (!empty($name = $this->config
['route']['register']['name']) && is_array($context = $this->config
['route']['register']['context'])) { 
 242                                                                 $url = $this->router
->generate($name, $extra+
$context); 
 244                                                                 //Return generated route 
 245                                                                 return new RedirectResponse($url, 302); 
 247                                                         } catch (ResourceNotFoundException 
$e) { 
 248                                                                 //Unset name and context 
 249                                                                 unset($name, $context); 
 253                                                 //With login target path option 
 254                                                 if (!empty($loginPath = $this->options
['login_path'])) { 
 256                                                         if ($loginPath[0] == '/') { 
 257                                                                 //Create login path request instance 
 258                                                                 $req = Request
::create($loginPath); 
 260                                                                 //Get login path pathinfo 
 261                                                                 $path = $req->getPathInfo(); 
 264                                                                 $path = str_replace($request->getScriptName(), '', $path); 
 266                                                                 //Try with login path path 
 269                                                                         $oldContext = $this->router
->getContext(); 
 271                                                                         //Force clean context 
 272                                                                         //XXX: prevent MethodNotAllowedException on GET only routes because our context method is POST 
 273                                                                         //XXX: see vendor/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php +42 
 274                                                                         $this->router
->setContext(new RequestContext()); 
 276                                                                         //Retrieve route matching path 
 277                                                                         $route = $this->router
->match($path); 
 280                                                                         $this->router
->setContext($oldContext); 
 286                                                                         if ($name = $route['_route']) { 
 287                                                                                 //Remove route and controller from route defaults 
 288                                                                                 unset($route['_route'], $route['_controller'], $route['_canonical_route']); 
 291                                                                                 $url = $this->router
->generate($name, $extra+
$route); 
 293                                                                                 //Return redirect to url response 
 294                                                                                 return new RedirectResponse($url, 302); 
 297                                                                 } catch (ResourceNotFoundException 
$e) { 
 298                                                                         //Unset default path, name and route 
 299                                                                         unset($loginPath, $name, $route); 
 303                                                                 //Try with login path route 
 305                                                                         //Retrieve route matching path 
 306                                                                         $url = $this->router
->generate($loginPath, $extra); 
 308                                                                         //Return redirect to url response 
 309                                                                         return new RedirectResponse($url, 302); 
 310                                                                 //Route not found, missing parameter or invalid parameter 
 311                                                                 } catch (RouteNotFoundException
|MissingMandatoryParametersException
|InvalidParameterException 
$e) { 
 312                                                                         //Unset default path and url 
 313                                                                         unset($loginPath, $url); 
 317                                         //With not enabled user 
 318                                         } elseif (empty($user->isEnabled())) { 
 319                                                 //Add error message account is not enabled 
 320                                                 $this->addFlash('error', $this->translator
->trans('Your account is not enable')); 
 322                                                 //Redirect on the same route with sent=1 to cleanup form 
 323                                                 return new RedirectResponse($this->router
->generate($request->get('_route'), $request->get('_route_params')), 302); 
 324                                         //With unactivated user 
 325                                         } elseif (empty($user->isActivated())) { 
 328                                                         'recipient_mail' => $user->getMail(), 
 329                                                         'recipient_name' => $user->getRecipientName() 
 330                                                 ] + 
array_replace_recursive( 
 331                                                         $this->config
['context'], 
 332                                                         $this->config
['register']['view']['context'], 
 333                                                         $this->config
['register']['mail']['context'] 
 336                                                 //Generate each route route 
 337                                                 foreach($this->config
['register']['route'] as $route => $tag) { 
 338                                                         //Only process defined routes 
 339                                                         if (!empty($this->config
['route'][$route])) { 
 340                                                                 //Process for confirm url 
 341                                                                 if ($route == 'confirm') { 
 342                                                                         //Set the url in context 
 343                                                                         $context[$tag] = $this->router
->generate( 
 344                                                                                 $this->config
['route'][$route]['name'], 
 345                                                                                 //Prepend subscribe context with tag 
 347                                                                                         'mail' => $smail = $this->slugger
->short($context['recipient_mail']), 
 348                                                                                         'hash' => $this->slugger
->hash($smail) 
 349                                                                                 ]+
$this->config
['route'][$route]['context'], 
 350                                                                                 UrlGeneratorInterface
::ABSOLUTE_URL
 
 356                                                 //Iterate on keys to translate 
 357                                                 foreach($this->config
['translate'] as $translate) { 
 359                                                         $keys = explode('.', $translate); 
 362                                                         $current =& $context; 
 364                                                         //Iterate on each subkey 
 366                                                                 //Skip unset translation keys 
 367                                                                 if (!isset($current[current($keys)])) { 
 371                                                                 //Set current to subkey 
 372                                                                 $current =& $current[current($keys)]; 
 373                                                         } while(next($keys)); 
 376                                                         $current = $this->translator
->trans($current); 
 383                                                 $context['subject'] = $subject = ucfirst( 
 384                                                         $this->translator
->trans( 
 385                                                                 $this->config
['register']['mail']['subject'], 
 386                                                                 $this->slugger
->flatten($context, null, '.', '%', '%') 
 391                                                 $message = (new TemplatedEmail()) 
 393                                                         ->from(new Address($this->config
['contact']['address'], $this->config
['contact']['name'])) 
 395                                                         //XXX: remove the debug set in vendor/symfony/mime/Address.php +46 
 396                                                         ->to(new Address($context['recipient_mail'], $context['recipient_name'])) 
 398                                                         ->subject($context['subject']) 
 400                                                         //Set path to twig templates 
 401                                                         ->htmlTemplate($this->config
['register']['mail']['html']) 
 402                                                         ->textTemplate($this->config
['register']['mail']['text']) 
 407                                                 //Try sending message 
 408                                                 //XXX: mail delivery may silently fail 
 411                                                         $this->mailer
->send($message); 
 412                                                 //Catch obvious transport exception 
 413                                                 } catch(TransportExceptionInterface 
$e) { 
 414                                                         //Add error message mail unreachable 
 415                                                         $this->addFlash('error', $this->translator
->trans('Unable to reach account')); 
 419                                                 $this->addFlash('notice', $this->translator
->trans('Your verification mail has been sent, to activate your account you must follow the confirmation link inside')); 
 422                                                 $this->addFlash('warning', $this->translator
->trans('If you did not receive a verification mail, check your Spam or Junk mail folders')); 
 424                                                 //Redirect on the same route with sent=1 to cleanup form 
 425                                                 return new RedirectResponse($this->router
->generate($request->get('_route'), $request->get('_route_params')), 302); 
 431                 //Call parent function 
 432                 return parent
::onAuthenticationFailure($request, $exception); 
 438         public function getAlias(): string { 
 439                 return RapsysUserBundle
::getAlias();