src/Controller/Security/SecurityController.php line 576

Open in your IDE?
  1. <?php
  2. //----------------------------------------------------------------------
  3. // src/Controller/Security/SecurityController.php
  4. //----------------------------------------------------------------------
  5. namespace App\Controller\Security;
  6. use Doctrine\Persistence\ManagerRegistry;
  7. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  8. use Symfony\Component\Form\Extension\Core\Type\EmailType;
  9. use Symfony\Component\Form\Extension\Core\Type\HiddenType;
  10. use Symfony\Component\Form\Extension\Core\Type\PasswordType;
  11. use Symfony\Component\HttpFoundation\Request;
  12. use Symfony\Component\HttpFoundation\Response;
  13. use Symfony\Component\HttpFoundation\JsonResponse;
  14. use Symfony\Component\HttpFoundation\RedirectResponse;
  15. use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
  16. use Symfony\Component\Security\Core\Security;
  17. use Symfony\Component\Validator\Constraints\NotBlank;
  18. use Symfony\Component\Validator\Constraints\NotNull;
  19. use Symfony\Component\Security\Core\Exception\AccessDeniedException;
  20. use Symfony\Contracts\Translation\TranslatorInterface;
  21. use App\Entity\Access;
  22. use App\Entity\AccessClient\AccessClient;
  23. use App\Entity\TOS\TermCond;
  24. use App\Entity\TOS\TermCondAccept;
  25. use App\Services\AccessTools;
  26. use App\Services\Communication\Email\AccessEmails;
  27. use App\Services\Communication\Email\AccessClientEmails;
  28. use App\Services\Dashboard\LoginTools as DashboardLoginTools;
  29. use App\Services\Security\IpTools;
  30. use App\Services\Security\PasswordTools;
  31. use App\Services\SocietyGroupGenerator;
  32. use App\Entity\Config\Config;
  33. use App\Services\AccessClient\AccessClientTools;
  34. use App\Services\LogTools;
  35. use App\Services\Security\SecurityTools;
  36. use App\Validator\ActivationCode;
  37. use App\Validator\LostPwd;
  38. use App\Validator\LostPwdCode;
  39. use App\Validator\Password;
  40. class SecurityController extends AbstractController
  41. {
  42.     public function __construct(
  43.         ManagerRegistry $doctrineLogTools $logToolsSecurity $securityTranslatorInterface $translatorAccessTools $accessTools,
  44.         PasswordTools $passwordToolsAccessEmails $accessEmailsAccessClientEmails $accessClientEmailsSocietyGroupGenerator $societyGroupGenerator,
  45.         DashboardLoginTools $dashboardLoginToolsIpTools $ipToolsSecurityTools $securityToolsAccessClientTools $accessClientTools
  46.     )
  47.     {
  48.         $this->em $doctrine->getManager();
  49.         $this->logTools $logTools;
  50.         $this->security $security;
  51.         $this->translator $translator;
  52.         $this->accessTools $accessTools;
  53.         $this->passwordTools $passwordTools;
  54.         $this->accessEmails $accessEmails;
  55.         $this->accessClientEmails $accessClientEmails;
  56.         $this->societyGroupGenerator $societyGroupGenerator;
  57.         $this->dashboardLoginTools $dashboardLoginTools;
  58.         $this->ipTools $ipTools;
  59.         $this->securityTools $securityTools;
  60.         $this->accessClientTools $accessClientTools;
  61.     }
  62.     public function kickOut(Request $request)
  63.     {
  64.         $this->securityTools->logout($request->getSession());
  65.         return $this->redirectToRoute('login');
  66.     }
  67.     public function loginRedirect(Request $requestAccessTools $accessTools): Response
  68.     {
  69.         if ($this->security->isGranted('IS_AUTHENTICATED_FULLY'))
  70.         {
  71.             // $this->logTools->plooplog("[SecurityController][loginRedirect] ".$this->getUser()->displayForLog());
  72.             // Language
  73.             if ($this->getUser() !== null && $this->getUser()->getLanguage() !== null)
  74.             {
  75.                 $request->getSession()->set('_locale'$this->getUser()->getLanguage());
  76.             }
  77.             if ($this->isGranted('ROLE_USER'))
  78.             {
  79.                 // Plan.io Task #3710 : Check that the user who passed the firewall has a SocietyGroup
  80.                 $societyGroup $this->getUser()->getSocietyGroup();
  81.                 if ($societyGroup === null)
  82.                 {
  83.                     // This should not happen
  84.                     $this->logTools->errorlog("societyGroup is null in loginRedirect in SecurityController");
  85.                     $this->securityTools->logout($request->getSession());
  86.                     return $this->redirectToRoute('login');
  87.                 }
  88.                 // Check that the society group is active
  89.                 if ($societyGroup->isInactive())
  90.                 {
  91.                     $this->logTools->errorlog("societyGroup is inactive in loginRedirect in SecurityController");
  92.                     $this->securityTools->logout($request->getSession());
  93.                     return $this->redirectToRoute('authentication_error');
  94.                 }
  95.                 // Check if the Terms and Conditions have been accepted
  96.                 // Get latest terms and conditions chart
  97.                 // Plan.io Task #4327 : Get that of Rekapp Accesses, not AccessClients
  98.                 $term $this->em->getRepository(TermCond::class)->findOneBy(array(
  99.                     'clientPlatform'     => 0,
  100.                 ), array(
  101.                     'creationDate'         =>    'DESC',
  102.                 ));
  103.                 if ($term !== null)
  104.                 {
  105.                     $acceptation $this->em->getRepository(TermCondAccept::class)
  106.                         ->findOneBy(array(
  107.                             'access'        =>    $this->getUser(),
  108.                             'termCond'        =>    $term,
  109.                         ));
  110.                     if ($acceptation === null)
  111.                     {
  112.                         // Not yet accepted => Redirect to acceptation page
  113.                         return $this->redirectToRoute('icod_terms_and_conditions', array(
  114.                             'id'    =>    $term->getId(),
  115.                         ));
  116.                     }
  117.                 }
  118.                 // Plan.io Task #3275
  119.                 // Log successfull login
  120.                 $this->dashboardLoginTools->logSuccessfulLogin($this->getUser());
  121.                 // Plan.io Task #4453 #TODO one day (or not)
  122.                 // If we want to call it on user login this is the place [Update Permission Cache]
  123.                 // Redirect
  124.                 if ($this->getUser() !== null)
  125.                 {
  126.                     if (!empty($this->getUser()->getReferer()))
  127.                     {
  128.                         // Get the redirect data stored into the access
  129.                         $referer json_decode($this->getUser()->getReferer(), true);
  130.                         $url $referer['url'];
  131.                         $redirect = !$referer['dont_redirect'];
  132.                         // Erase data in all cases, since a redirect will happen anyhow
  133.                         $this->getUser()->setReferer(null);
  134.                         try
  135.                         {
  136.                             $this->em->flush();
  137.                         }
  138.                         catch (\Exception $e)
  139.                         {
  140.                             $this->logTools->errorlog($e->getMessage());
  141.                         }
  142.                         // Redirect or not :)
  143.                         if (!empty($url))
  144.                         {
  145.                             if ($redirect)
  146.                             {
  147.                                 return new RedirectResponse($referer['url']);
  148.                             }
  149.                         }
  150.                     }
  151.                 }
  152.                 // If we are here it means that nothing else was found
  153.                 // Just go the usual way
  154.                 return $this->redirectToRoute('icod_platform_menu');
  155.             }
  156.             else
  157.             {
  158.                 if ($this->isGranted('ROLE_ADMIN'))
  159.                 {
  160.                     // Plan.io Task #4001
  161.                     // Each user should go to its own happy place
  162.                     // if ($this->isGranted('open_sesame'))
  163.                     // {
  164.                     //     return $this->redirectToRoute('icod_admin_dashboard_open_sesame');
  165.                     // }
  166.                     if ($this->security->isGranted('is_admin'))
  167.                     {
  168.                         return $this->redirectToRoute('icod_admin_dashboard');
  169.                     }
  170.                 }
  171.             }
  172.             return $this->redirectToRoute('test');
  173.         }
  174.         return $this->redirectToRoute('login');
  175.     }
  176.     public function handleRedirectUrl(Request $request): JsonResponse
  177.     {
  178.         if (!$request->isXmlHttpRequest())
  179.         {
  180.             return new JsonResponse(array('status' => 'Error'),400);
  181.         }
  182.         if (!isset($request->request))
  183.         {
  184.             return new JsonResponse(array('status' => 'Error'),400);
  185.         }
  186.         // Get data
  187.         $id intval($request->request->get('id'));
  188.         $route strip_tags($request->request->get('route'));
  189.         $dontRedirect intval($request->request->get('dont_redirect'));
  190.         $username strip_tags($request->request->get('username'));
  191.         $access $this->em->getRepository(Access::class)
  192.             ->findOneByUsername($username);
  193.         if ($access === null)
  194.         {
  195.             return new JsonResponse(array('status' => 'Error'),200);
  196.         }
  197.         $url null;
  198.         if ($id == 0)
  199.         {
  200.             try
  201.             {
  202.                 $url $this->generateUrl($route);
  203.             }
  204.             catch (\Exception $e)
  205.             {
  206.                 return new JsonResponse(array('status' => 'Error'),200);
  207.             }
  208.         }
  209.         else
  210.         {
  211.             try
  212.             {
  213.                 $url $this->generateUrl($route, array('id' => $id));
  214.             }
  215.             catch (\Exception $e)
  216.             {
  217.                 return new JsonResponse(array('status' => 'Error'),200);
  218.             }
  219.         }
  220.         $config $this->em->getRepository(Config::class)
  221.             ->findOneByName(Config::URL);
  222.         if ($config === null)
  223.         {
  224.             return new JsonResponse(array('status' => 'Error'),200);
  225.         }
  226.         $url $config->getValue() . $url;
  227.         $access->setReferer(json_encode(array(
  228.             'route'            =>    $route,
  229.             'id'            =>    $id,
  230.             'url'            =>    $url,
  231.             'dont_redirect'    =>    $dontRedirect,
  232.         )));
  233.         try
  234.         {
  235.             $this->em->flush();
  236.         }
  237.         catch (\Exception $e)
  238.         {
  239.             $this->logTools->errorlog($e->getMessage());
  240.             $this->logTools->errorlog_db($e);
  241.             return new JsonResponse(array('status' => 'Error'),200);
  242.         }
  243.         return new JsonResponse(array('status' => 'Done'),200);
  244.     }
  245.     public function termsAndConditions(Request $requestTermCond $terms): Response
  246.     {
  247.         if ($this->security->isGranted('IS_AUTHENTICATED_FULLY'))
  248.         {
  249.             $form $this->createFormBuilder([])->getForm();
  250.             if ($request->isMethod('POST'))
  251.             {
  252.                 $form->handleRequest($request);
  253.                 if ($form->isSubmitted() && $form->isValid())
  254.                 {
  255.                     $acceptation = new TermCondAccept();
  256.                     $acceptation->setAccess($this->getUser());
  257.                     $acceptation->setTermCond($terms);
  258.                     $this->em->persist($acceptation);
  259.                     try
  260.                     {
  261.                         $this->em->flush();
  262.                     }
  263.                     catch (\Exception $e)
  264.                     {
  265.                         $this->logTools->errorlog($e->getMessage());
  266.                         $this->logTools->errorlog_db($e);
  267.                     }
  268.                     // After that form is submited, redirect to route : login_redirect
  269.                     return $this->redirectToRoute('login_redirect');
  270.                 }
  271.             }
  272.             else
  273.             {
  274.                 // Kick the user out
  275.                 $this->redirect($this->generateUrl('logout'));
  276.             }
  277.             return $this->render('security/terms_and_conditions.html.twig', array(
  278.                 'form'       =>     $form->createView(),
  279.                 'terms'     =>     $terms,
  280.             ));
  281.         }
  282.         return $this->redirectToRoute('login');
  283.     }
  284.     public function maintenance(Request $request): Response
  285.     {
  286.         return $this->render('security/maintenance.html.twig');
  287.     }
  288.     // This is called when a new access follows the activation link sent in an email
  289.     // Since we identify the user using a unique activation code,
  290.     // we can decide whether to activate an Access or an AccessClient using only the code.
  291.     // Thus, we can use the same method, validator, etc ...
  292.     public function activation(Request $requestUserPasswordHasherInterface $hasher$code): Response
  293.     {
  294.         // IpBan ?
  295.         $ip $request->getClientIp();
  296.         if ($this->ipTools->isBanned($ip))
  297.         {
  298.             throw new AccessDeniedException('');
  299.         }
  300.         // Before anything else : Check that the code exists
  301.         $accessRepository $this->em->getRepository(Access::class);
  302.         $accessForCode $accessRepository->findOneBy(array(
  303.             'activationCode'    =>    $code,
  304.         ));
  305.         if ($accessForCode === null)
  306.         {
  307.             // Plan.io Task #4327
  308.             // If not an Access, maybe an AccessClient ?
  309.             $accessClientRepository $this->em->getRepository(AccessClient::class);
  310.             $accessForCode $accessClientRepository->findOneBy(array(
  311.                 'activationCode'    =>    $code,
  312.             ));
  313.         }
  314.         if ($accessForCode === null)
  315.         {
  316.             // Nope, inform user
  317.             $flashBag $this->translator->trans('invalid_credentials', array(), 'validators');
  318.             $this->addFlash('error'$flashBag);
  319.             // Fail2Ban, just in case
  320.             $this->ipTools->fail2ban(array(
  321.                 'error'            =>    "invalid_code : ".$code,
  322.             ));
  323.             // Redirect to activation error page
  324.             return $this->redirectToRoute('activation_error');
  325.         }
  326.         // Plan.io Task #4327
  327.         $loginRoute "login";
  328.         $isAccessClient false;
  329.         if ($accessForCode instanceof AccessClient)
  330.         {
  331.             $loginRoute "access_client_login";
  332.             $isAccessClient true;
  333.         }
  334.         if ($accessForCode->getActivationDate() !== null)
  335.         {
  336.             // User already activated, redirect to login, with message
  337.             $msg $this->translator->trans('access_already_activated');
  338.             return $this->redirectToRoute($loginRoute, array(
  339.                 'username'                =>    $accessForCode->getUsername(),
  340.                 'activation_message'    =>    $msg,
  341.             ));
  342.         }
  343.         // If we are here it means we have an active activation code
  344.         // Plan.io Task #3389, modified by Plan.io Task #4327
  345.         if ($accessForCode instanceof Access)
  346.         {
  347.             if ($accessForCode->getSocietyGroupRequest() !== null)
  348.             {
  349.                 // This is an account creation for an external user
  350.                 // So no need to test for society group
  351.             }
  352.             else
  353.             {
  354.                 // However, if the society group of the access is inactive, do not go any further
  355.                 if ($accessForCode->getSocietyGroup() !== null && $accessForCode->getSocietyGroup()->isInactive())
  356.                 {
  357.                     return $this->redirectToRoute('authentication_error');
  358.                 }
  359.             }
  360.         }
  361.         // Ask the user for password
  362.         $form $this->createFormBuilder()
  363.             ->add('password'PasswordType::class, array(
  364.                 'label'     =>     false,
  365.                 'required'    =>    true,
  366.                 'constraints' => array(
  367.                     new Password(),
  368.                     new NotBlank(),
  369.                     new NotNull(),
  370.                 )
  371.             ))
  372.             ->add('passwordRepeat'PasswordType::class, array(
  373.                 'label'     =>     false,
  374.                 'required'    =>    true,
  375.                 'constraints' => array(
  376.                     new NotBlank(),
  377.                     new NotNull(),
  378.                 )
  379.             ))
  380.             ->add('code'HiddenType::class, array(
  381.                 'label'             =>     false,
  382.                 'required'            =>    false,
  383.                 'data'                =>    $code,
  384.                 'error_bubbling'    =>    true,
  385.                 'constraints'         =>     array(
  386.                     new ActivationCode()
  387.                 )
  388.             ));
  389.         $form $form->getForm();
  390.         if ($request->isMethod('POST'))
  391.         {
  392.             $form->handleRequest($request);
  393.             if ($form->isSubmitted() && $form->isValid())
  394.             {
  395.                 // Step ONE : Check that the code exists and has an access
  396.                 // Done by ActivationCode Validator
  397.                 // Step TWO : Check that the access has not already been activated
  398.                 // Done by ActivationCode Validator
  399.                 // Step THREE : Check if the access has email address (this should always be true)
  400.                 // Done by ActivationCode Validator
  401.                 // Step FOUR : Check if the passwords match and none is null
  402.                 // Checked by Password Validator
  403.                 // If we are here it means we have a valid code and two matching passwords
  404.                 // Get the password
  405.                 $form_pwd $form['password']->getData();
  406.                 // Encode the password
  407.                 $encoded_password $hasher->hashPassword($accessForCode$form_pwd);
  408.                 // Update access
  409.                 $accessForCode->setPassword($encoded_password);
  410.                 $accessForCode->setActivationDate(new \DateTime());
  411.                 // Activate access
  412.                 $accessForCode->activate();
  413.                 // Persist
  414.                 $this->em->persist($accessForCode);
  415.                 // Plan.io Task #3389, modified by Plan.io Task #4327
  416.                 if ($accessForCode instanceof Access)
  417.                 {
  418.                     if ($accessForCode->getSocietyGroupRequest() !== null)
  419.                     {
  420.                         // Create SocietyGroup
  421.                         $societyGroupRequest $accessForCode->getSocietyGroupRequest();
  422.                         $societyGroup $this->societyGroupGenerator->createSocietyGroupForRequest($societyGroupRequest$accessForCode);
  423.                         if ($societyGroup === null)
  424.                         {
  425.                             $this->logTools->errorLog("SocietyGroup could not be created for SocietyGroupRequest ".$societyGroupRequest->displayForLog());
  426.                             $flashBag $this->translator->trans('error_try_again_later');
  427.                             $this->addFlash('error'$flashBag);
  428.                             return $this->render('security/access_activation/activation.html.twig', array(
  429.                                 'form'       =>     $form->createView(),
  430.                                 'email'        =>    $accessForCode->getEmail(),
  431.                                 'today'        =>    new \DateTime(),
  432.                             ));
  433.                         }
  434.                     }
  435.                 }
  436.                 // Plan.io Task #4408
  437.                 if ($accessForCode instanceof AccessClient)
  438.                 {
  439.                     $this->accessClientTools->activateCommercialConsentForAccessClient($accessForCode);
  440.                 }
  441.                 // Logging
  442.                 $sourceInfo $this->translator->trans('activation_source');
  443.                 // TODO #4327 : Add Logging for AccessClient also
  444.                 if ($accessForCode instanceof Access)
  445.                 {
  446.                     // Plan.io Task #3922
  447.                     $accessForCode->setLoggingData([
  448.                         "info" => $sourceInfo,
  449.                     ]);
  450.                 }
  451.                 try
  452.                 {
  453.                     $this->em->flush();
  454.                 }
  455.                 catch (\Exception $e)
  456.                 {
  457.                     $this->logTools->errorLog($e->getMessage());
  458.                     $this->logTools->errorlog_db($e);
  459.                     $flashBag $this->translator->trans('error_try_again_later');
  460.                     $this->addFlash('error'$flashBag);
  461.                     return $this->render('security/access_activation/activation.html.twig', array(
  462.                         'form'       =>     $form->createView(),
  463.                         'email'        =>    $accessForCode->getEmail(),
  464.                         'today'        =>    new \DateTime(),
  465.                     ));
  466.                 }
  467.                 $flashBag $this->translator->trans('activation_success');
  468.                 $this->addFlash('activation_message'$flashBag);
  469.                 return $this->redirectToRoute($loginRoute, array(
  470.                     'username'    =>    $accessForCode->getUsername(),
  471.                 ));
  472.             }
  473.             else
  474.             {
  475.                 // The form is invalid
  476.                 // Fail2Ban, just in case
  477.                 $this->ipTools->fail2ban(array(
  478.                     'form_errors'    =>    $form->getErrors(truetrue),
  479.                     'username'        =>    $accessForCode->getUsername(),
  480.                 ));
  481.             }
  482.         }
  483.         return $this->render('security/access_activation/activation.html.twig', array(
  484.             'form'               =>     $form->createView(),
  485.             'email'                =>    $accessForCode->getEmail(),
  486.             'today'                =>    new \DateTime(),
  487.             'isAccessClient'    =>    $isAccessClient,
  488.         ));
  489.     }
  490.     /**
  491.      * Plan.io Task #4327
  492.      * Contrary to account activation, things are trickier for the recovery of lost passwords.
  493.      * Since we only have an email address to work with,
  494.      * we have no way of knowing if we are looking for an Access, or an AccessClient.
  495.      * Two solutions
  496.      *        1. Duplicate the code and have two routes and two controllers
  497.      *        2. Handle the case where the same email address is used
  498.      *        by both an Access and an AccessClient (which will represent a small minority, ...)
  499.      * Go with solution number two ;)
  500.      *      If the email is associated with both an Access and an AccessClient
  501.      *      the method will generate two reset codes and send two emails ;)
  502.     */
  503.     public function lostPassword(Request $request$userEmail null): Response
  504.     {
  505.         // // IpBan ?
  506.         $ip $request->getClientIp();
  507.         if ($this->ipTools->isBanned($ip))
  508.         {
  509.             throw new AccessDeniedException('');
  510.         }
  511.         $form $this->createFormBuilder()
  512.             ->add('email',     EmailType::class, array(
  513.                     'required'        =>    true,
  514.                     'label'            =>    false,
  515.                     'data'            =>    $userEmail,
  516.                     'constraints'     => array(
  517.                         new NotBlank(),
  518.                         new NotNull(),
  519.                         new LostPwd(),
  520.                     ),
  521.                     'attr'            =>    array(
  522.                         'class'        =>    'login-form',
  523.                     )
  524.                 ))
  525.             ->getForm();
  526.         if ($request->isMethod('POST'))
  527.         {
  528.             $form->handleRequest($request);
  529.             if ($form->isSubmitted() && $form->isValid())
  530.             {
  531.                 // #2641 If Casto -> do not do lost password process
  532.                 if (strpos($form->getData()['email'], '@castorama.fr') !== false)
  533.                     return $this->redirectToRoute('activation_error');
  534.                 // Get the access
  535.                 // We know that it exists and it is active thanks to the validator - Not any more :)
  536.                 // edit @ 21/12/2020 : If we receive a lost password request for an inactive user
  537.                 // we send an activation email instead of a lost password email
  538.                 // and inform the user
  539.                 // edit @ 09/03/2021 : If we receive a lost password request for an inactive user
  540.                 // AND the user has never been activated before,
  541.                 // we send an activation email instead of a lost password email
  542.                 // and inform the user
  543.                 // This ensures that users who have been manually deactivated
  544.                 // cannot reclaim their accounts
  545.                 // See below @ if ($form->isValid()) else {...}
  546.                 $access $this->em->getRepository(Access::class)
  547.                     ->findOneByEmail($form->getData()['email']);
  548.                 // Plan.io Task #4327
  549.                 $accessClient $this->em->getRepository(AccessClient::class)
  550.                     ->findOneByEmail($form->getData()['email']);
  551.                 if ($access !== null)
  552.                 {
  553.                     // If the society group of the access is inactive, do not go any further
  554.                     if ($access->getSocietyGroup() !== null && $access->getSocietyGroup()->isInactive())
  555.                     {
  556.                         return $this->redirectToRoute('authentication_error');
  557.                     }
  558.                     // If we are here it means the access is not null and active
  559.                     // Generate code
  560.                     $code $this->passwordTools->generateUniqueActivationCode();
  561.                     $access->setLostPwdCode($code);
  562.                 }
  563.                 // Since both Access and AccessClient could be not null, handle both
  564.                 if ($accessClient !== null)
  565.                 {
  566.                     // Generate code
  567.                     // Yes, another one :)
  568.                     // Even if both are not null, I want different codes
  569.                     $code $this->passwordTools->generateUniqueActivationCode();
  570.                     $accessClient->setLostPwdCode($code);
  571.                 }
  572.                 $allGood true;
  573.                 try
  574.                 {
  575.                     $this->em->flush();
  576.                 }
  577.                 catch (\Exception $e)
  578.                 {
  579.                     $allGood false;
  580.                     $this->logTools->errorLog($e->getMessage());
  581.                     $this->logTools->errorlog_db($e);
  582.                 }
  583.                 if ($allGood)
  584.                 {
  585.                     if ($access !== null)
  586.                     {
  587.                         // Send mail
  588.                         $this->accessEmails->sendResetPwdEmail($access);
  589.                     }
  590.                     if ($accessClient !== null)
  591.                     {
  592.                         // Logging
  593.                         // TODO #4327 : Add Logging for AccessClient also
  594.                         // Send mail
  595.                         $this->accessClientEmails->sendResetPwdEmail($accessClient);
  596.                     }
  597.                     // Inform user
  598.                     $flashBag $this->translator->trans('reset_link_sent_success');
  599.                     $this->addFlash('activation_message'$flashBag);
  600.                     return $this->redirectToRoute('login', array(
  601.                         'username'    => $userEmail,
  602.                     ));
  603.                 }
  604.                 else
  605.                 {
  606.                     // Something went wrong, Inform user
  607.                     $flashBag $this->translator->trans('unknown_error');
  608.                     $this->addFlash('error'$flashBag);
  609.                     return $this->redirectToRoute('activation_error');
  610.                 }
  611.             }
  612.             else
  613.             {
  614.                 // The form is invalid
  615.                 // Fail2Ban, just in case
  616.                 $this->ipTools->fail2ban(array(
  617.                     'form_errors'    =>    $form->getErrors(truetrue),
  618.                     'username'        =>    $form->getData()['email'],
  619.                 ));
  620.                 // We only need to know if the Violation is inactive_access
  621.                 foreach ($form->getErrors(truetrue) as $key => $error)
  622.                 {
  623.                     if ($error->getCause() !== null)
  624.                     {
  625.                         if ($error->getCause()->getMessageTemplate())
  626.                         {
  627.                             if ($error->getCause()->getMessageTemplate() == 'inactive_access')
  628.                             {
  629.                                 // If we are here it means the access is inactive
  630.                                 // #2641 If Casto -> do not do lost password process
  631.                                 if (strpos($form->getData()['email'], '@castorama.fr') !== false)
  632.                                     return $this->redirectToRoute('activation_error');
  633.                                 $access $this->em->getRepository(Access::class)
  634.                                     ->findOneByEmail($form->getData()['email']);
  635.                                 $accessClient $this->em->getRepository(AccessClient::class)
  636.                                     ->findOneByEmail($form->getData()['email']);
  637.                                 if ($access !== null)
  638.                                 {
  639.                                     // If the society group of the access is inactive, do not go any further
  640.                                     if ($access->getSocietyGroup() !== null && $access->getSocietyGroup()->isInactive())
  641.                                     {
  642.                                         return $this->redirectToRoute('authentication_error');
  643.                                     }
  644.                                 }
  645.                                 $flush false;
  646.                                 // Only sending activation link if all below conditions are true :
  647.                                 //         - the access is inactive
  648.                                 //        - the society group of the access is active (checked above)
  649.                                 //        - the access has never been activated before
  650.                                 if ($access !== null && $access->isInactive() && $access->getActivationDate() === null)
  651.                                 {
  652.                                     // Activation code for access
  653.                                     $code $this->passwordTools->generateUniqueActivationCode();
  654.                                     $access->setActivationCode($code);
  655.                                     $access->setActivationDate(null);
  656.                                     $flush true;
  657.                                 }
  658.                                 if ($accessClient !== null && $accessClient->isInactive() && $accessClient->getActivationDate() === null)
  659.                                 {
  660.                                     // Activation code for access
  661.                                     $code $this->passwordTools->generateUniqueActivationCode();
  662.                                     $accessClient->setActivationCode($code);
  663.                                     $accessClient->setActivationDate(null);
  664.                                     $flush true;
  665.                                 }
  666.                                 if ($flush)
  667.                                 {
  668.                                     $allGood true;
  669.                                     try
  670.                                     {
  671.                                         $this->em->flush();
  672.                                     }
  673.                                     catch (\Exception $e)
  674.                                     {
  675.                                         $allGood false;
  676.                                         $this->logTools->errorLog($e->getMessage());
  677.                                         $this->logTools->errorlog_db($e);
  678.                                     }
  679.                                     if ($allGood)
  680.                                     {
  681.                                         // Send mail
  682.                                         if ($access !== null)
  683.                                         {
  684.                                             $this->accessEmails->sendActivationRequiredEmail($accesstrue);
  685.                                         }
  686.                                         if ($accessClient !== null)
  687.                                         {
  688.                                             $this->accessClientEmails->sendActivationRequiredEmail($accessClientnulltrue);
  689.                                         }
  690.                                         return $this->render('security/access_lost_pwd/lost.html.twig', array(
  691.                                             'form'       =>     $form->createView(),
  692.                                         ));
  693.                                     }
  694.                                     else
  695.                                     {
  696.                                         // Something went wrong, Inform user
  697.                                         $flashBag $this->translator->trans('unknown_error');
  698.                                         $this->addFlash('error'$flashBag);
  699.                                         return $this->redirectToRoute('activation_error');
  700.                                     }
  701.                                 }
  702.                                 else
  703.                                 {
  704.                                     return $this->redirectToRoute('authentication_error');
  705.                                 }
  706.                             }
  707.                         }
  708.                     }
  709.                 }
  710.             }
  711.         }
  712.         return $this->render('security/access_lost_pwd/lost.html.twig', array(
  713.             'form'       =>     $form->createView(),
  714.         ));
  715.     }
  716.     // This is called when a new access follows the reset link sent in an email
  717.     // Since we identify the user using a unique reset code,
  718.     // we can decide whether to activate an Access or an AccessClient using only the code.
  719.     // Thus, we can use the same method, validator, etc ...
  720.     public function resetPassword(Request $requestUserPasswordHasherInterface $hasher$code): Response
  721.     {
  722.         // IpBan ?
  723.         $ip $request->getClientIp();
  724.         if ($this->ipTools->isBanned($ip))
  725.         {
  726.             throw new AccessDeniedException('');
  727.         }
  728.         // Before anything else : Check that the code exists
  729.         $accessRepository $this->em->getRepository(Access::class);
  730.         $accessForCode $accessRepository->findOneBy(array(
  731.             'lostPwdCode'    =>    $code,
  732.         ));
  733.         if ($accessForCode === null)
  734.         {
  735.             // Plan.io Task #4327
  736.             // If not an Access, maybe an AccessClient ?
  737.             $accessClientRepository $this->em->getRepository(AccessClient::class);
  738.             $accessForCode $accessClientRepository->findOneBy(array(
  739.                 'lostPwdCode'    =>    $code,
  740.             ));
  741.         }
  742.         if ($accessForCode === null)
  743.         {
  744.             // Nope, inform user
  745.             $flashBag $this->translator->trans('invalid_credentials', array(), 'validators');
  746.             $this->addFlash('error'$flashBag);
  747.             // Fail2Ban, just in case
  748.             $this->ipTools->fail2ban(array(
  749.                 'error'            =>    "invalid_code : ".$code,
  750.             ));
  751.             // Redirect to activation error page
  752.             return $this->redirectToRoute('activation_error');
  753.         }
  754.         // Plan.io Task #4327
  755.         $loginRoute "login";
  756.         if ($accessForCode instanceof AccessClient)
  757.         {
  758.             $loginRoute "access_client_login";
  759.         }
  760.         // If we are here it means we have an active code for a forgotten password
  761.         // Ask the user for password
  762.         $form $this->createFormBuilder()
  763.         ->add('password'PasswordType::class, array(
  764.             'label'     =>     false,
  765.             'required'    =>    true,
  766.             'constraints' => array(
  767.                 new Password(),
  768.                 new NotBlank(),
  769.                 new NotNull(),
  770.             )
  771.         ))
  772.         ->add('passwordRepeat'PasswordType::class, array(
  773.             'label'     =>     false,
  774.             'required'    =>    true,
  775.             'constraints' => array(
  776.                 new NotBlank(),
  777.                 new NotNull(),
  778.             )
  779.         ))
  780.         ->add('code'HiddenType::class, array(
  781.             'label'             =>     false,
  782.             'required'            =>    false,
  783.             'data'                =>    $code,
  784.             'error_bubbling'    =>    true,
  785.             'constraints'         =>     array(
  786.                 new LostPwdCode()
  787.             )
  788.         ));
  789.         $form $form->getForm();
  790.         if ($request->isMethod('POST'))
  791.         {
  792.             $form->handleRequest($request);
  793.             if ($form->isSubmitted() && $form->isValid())
  794.             {
  795.                 // Step ONE : Check that the code exists and has an access
  796.                 // Done by LostPwdCode Validator
  797.                 // Step TWO : Check if the access has email address (this should always be true)
  798.                 // Done by LostPwdCode Validator
  799.                 // Step THREE : Check if the passwords match and none is null
  800.                 // Checked by Password Validator
  801.                 // If we are here it means we have a valid code and two matching passwords
  802.                 // Get the password
  803.                 $form_pwd $form['password']->getData();
  804.                 // Encode the password
  805.                 $encoded_password $hasher->hashPassword($accessForCode$form_pwd);
  806.                 // Update access
  807.                 $accessForCode->setPassword($encoded_password);
  808.                 $accessForCode->setActivationDate(new \DateTime());
  809.                 // Remove code
  810.                 $accessForCode->setLostPwdCode(null);
  811.                 // Persist
  812.                 $this->em->persist($accessForCode);
  813.                 // Logging
  814.                 $sourceInfo $this->translator->trans('lost_pwd_source');
  815.                 // Plan.io Task #3922
  816.                 $accessForCode->setLoggingData([
  817.                     "info" => $sourceInfo,
  818.                 ]);
  819.                 // TODO #4327 : Add Logging for AccessClient also
  820.                 try
  821.                 {
  822.                     $this->em->flush();
  823.                 }
  824.                 catch (\Exception $e)
  825.                 {
  826.                     $this->logTools->errorLog($e->getMessage());
  827.                     $this->logTools->errorlog_db($e);
  828.                     $flashBag $this->translator->trans('unknown_error');
  829.                     $this->addFlash('error'$flashBag);
  830.                     return $this->render('security/access_lost_pwd/reset.html.twig', array(
  831.                         'form'       =>     $form->createView(),
  832.                         'email'        =>    $accessForCode->getEmail(),
  833.                         'today'        =>    new \DateTime(),
  834.                     ));
  835.                 }
  836.                 $flashBag $this->translator->trans('password_changed_success');
  837.                 $this->addFlash('activation_message'$flashBag);
  838.                 return $this->redirectToRoute($loginRoute, array(
  839.                     'username'    =>    $accessForCode->getUsername(),
  840.                 ));
  841.             }
  842.             else
  843.             {
  844.                 // The form is invalid
  845.                 // Fail2Ban, just in case
  846.                 $this->ipTools->fail2ban(array(
  847.                     'form_errors'    =>    $form->getErrors(truetrue),
  848.                     'username'        =>    $accessForCode->getUsername(),
  849.                 ));
  850.             }
  851.         }
  852.         return $this->render('security/access_lost_pwd/reset.html.twig', array(
  853.             'form'       =>     $form->createView(),
  854.             'email'        =>    $accessForCode->getEmail(),
  855.             'today'        =>    new \DateTime(),
  856.         ));
  857.     }
  858.     public function authenticationError(Request $request): Response
  859.     {
  860.         // If the user is already logged in, redirect
  861.         if ($this->isGranted('IS_AUTHENTICATED_FULLY'))
  862.         {
  863.             return $this->redirectToRoute('login_redirect');
  864.         }
  865.         $today = new \DateTime();
  866.         $flashBag $this->translator->trans("invalid_credentials", array(), "validators");
  867.         $this->addFlash('login_error'$flashBag);
  868.         return $this->render('security/login.html.twig', array(
  869.             'last_username'     =>     null,
  870.             'username'            =>    null,
  871.             'error'                =>    null,
  872.             'today'                =>    $today,
  873.         ));
  874.     }
  875.     public function activationError(Request $request): Response
  876.     {
  877.         return $this->render('security/access_activation/activation_error.html.twig', array(
  878.         ));
  879.     }
  880. }