src/Controller/AccountController.php line 489

Open in your IDE?
  1. <?php
  2. /**
  3.  * Pimcore
  4.  *
  5.  * This source file is available under two different licenses:
  6.  * - GNU General Public License version 3 (GPLv3)
  7.  * - Pimcore Enterprise License (PEL)
  8.  * Full copyright and license information is available in
  9.  * LICENSE.md which is distributed with this source code.
  10.  *
  11.  *  @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  12.  *  @license    http://www.pimcore.org/license     GPLv3 and PEL
  13.  */
  14. namespace App\Controller;
  15. use App\EventListener\AuthenticationLoginListener;
  16. use App\Form\LoginFormType;
  17. use App\Form\RegistrationFormHandler;
  18. use App\Form\RegistrationFormType;
  19. use App\Model\Customer;
  20. use App\Services\NewsletterDoubleOptInService;
  21. use App\Services\PasswordRecoveryService;
  22. use CustomerManagementFrameworkBundle\CustomerProvider\CustomerProviderInterface;
  23. use CustomerManagementFrameworkBundle\CustomerSaveValidator\Exception\DuplicateCustomerException;
  24. use CustomerManagementFrameworkBundle\Model\CustomerInterface;
  25. use CustomerManagementFrameworkBundle\Security\Authentication\LoginManagerInterface;
  26. use CustomerManagementFrameworkBundle\Security\OAuth\Exception\AccountNotLinkedException;
  27. use CustomerManagementFrameworkBundle\Security\OAuth\OAuthRegistrationHandler;
  28. use CustomerManagementFrameworkBundle\Security\SsoIdentity\SsoIdentityServiceInterface;
  29. use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
  30. use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
  31. use Pimcore\Bundle\EcommerceFrameworkBundle\Factory;
  32. use Pimcore\Bundle\EcommerceFrameworkBundle\OrderManager\Order\Listing\Filter\CustomerObject;
  33. use Pimcore\DataObject\Consent\Service;
  34. use Pimcore\Model\DataObject;
  35. use Pimcore\Translation\Translator;
  36. use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
  37. use Symfony\Component\HttpFoundation\RedirectResponse;
  38. use Symfony\Component\HttpFoundation\Request;
  39. use Symfony\Component\HttpFoundation\Response;
  40. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  41. use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
  42. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  43. use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
  44. use Symfony\Component\Routing\Annotation\Route;
  45. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  46. use Symfony\Component\Security\Core\User\UserInterface;
  47. use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
  48. use Symfony\Component\Uid\Uuid;
  49. use Symfony\Component\Security\Http\FirewallMapInterface;
  50. /**
  51.  * Class AccountController
  52.  *
  53.  * Controller that handles all account functionality, including register, login and connect to SSO profiles
  54.  */
  55. class AccountController extends BaseController
  56. {
  57.     /**
  58.      * @Route("/account/login", name="account-login")
  59.      *
  60.      * @param AuthenticationUtils $authenticationUtils
  61.      * @param OAuthRegistrationHandler $oAuthHandler
  62.      * @param SessionInterface $session
  63.      * @param Request $request
  64.      * @param UserInterface|null $user
  65.      *
  66.      * @return Response|RedirectResponse
  67.      */
  68.     public function loginAction(
  69.         AuthenticationUtils $authenticationUtils,
  70.         OAuthRegistrationHandler $oAuthHandler,
  71.         SessionInterface $session,
  72.         Request $request,
  73.         UserInterface $user null,
  74.         FirewallMapInterface $firewallMap
  75.     ) {
  76.         //redirect user to index page if logged in
  77.         if ($user && $this->isGranted('ROLE_USER')) {
  78.             return $this->redirect('/workplace/dashboard');
  79.             /* return $this->redirectToRoute('account-index'); */
  80.         }
  81.         // dump($user);
  82.         // get the login error if there is one
  83.         $error $authenticationUtils->getLastAuthenticationError();
  84.         // OAuth handling - the OAuth authenticator is configured to return to the login page on errors
  85.         // (see failure_path configuration) - therefore we can fetch the last authentication error
  86.         // here. If the error is an AccountNotLinkedException (as thrown by our user provider) save the
  87.         // OAuth token to the session and redirect to registration with a special key which can be used
  88.         // to load the token to prepopulate the registration form with account data.
  89.         if ($error instanceof AccountNotLinkedException) {
  90.             // this can be anything - for simplicity we just use an UUID as it is unique and random
  91.             $registrationKey = (string) Uuid::v4()->toRfc4122();
  92.             $oAuthHandler->saveToken($registrationKey$error->getToken());
  93.             return $this->redirectToRoute('account-register', [
  94.                 'registrationKey' => $registrationKey
  95.             ]);
  96.         }
  97.         // last username entered by the user
  98.         $lastUsername $authenticationUtils->getLastUsername();
  99.         $formData = [
  100.             '_username' => $lastUsername
  101.         ];
  102.         $form $this->createForm(LoginFormType::class, $formData, [
  103.             'action' => $this->generateUrl('account-login'),
  104.         ]);
  105.         //store referer in session to get redirected after login
  106.         // if (!$request->get('no-referer-redirect')) {
  107.         //     $session->set('_security.demo_frontend.target_path', $request->headers->get('referer'));
  108.         // }
  109.         // dump($session);
  110.         // die();
  111.         $referer $request->query->get('referer');
  112.         if ($referer) {
  113.             $session->set('_security.demo_frontend.target_path'$referer);
  114.         }
  115.         return $this->render('account/login.html.twig', [
  116.             'form' => $form->createView(),
  117.             'error' => $error,
  118.             'hideBreadcrumbs' => true
  119.         ]);
  120.     }
  121.     /**
  122.      * If registration is called with a registration key, the key will be used to look for an existing OAuth token in
  123.      * the session. This OAuth token will be used to fetch user info which can be used to pre-populate the form and to
  124.      * link a SSO identity to the created customer object.
  125.      *
  126.      * This could be further separated into services, but was kept as single method for demonstration purposes as the
  127.      * registration process is different on every project.
  128.      *
  129.      * @Route("/account/register", name="account-register")
  130.      *
  131.      * @param Request $request
  132.      * @param CustomerProviderInterface $customerProvider
  133.      * @param OAuthRegistrationHandler $oAuthHandler
  134.      * @param LoginManagerInterface $loginManager
  135.      * @param RegistrationFormHandler $registrationFormHandler
  136.      * @param SessionInterface $session
  137.      * @param AuthenticationLoginListener $authenticationLoginListener
  138.      * @param Translator $translator
  139.      * @param Service $consentService
  140.      * @param UrlGeneratorInterface $urlGenerator
  141.      * @param NewsletterDoubleOptInService $newsletterDoubleOptInService
  142.      * @param UserInterface|null $user
  143.      *
  144.      * @return Response|RedirectResponse
  145.      */
  146.     public function registerAction(
  147.         Request $request,
  148.         CustomerProviderInterface $customerProvider,
  149.         OAuthRegistrationHandler $oAuthHandler,
  150.         LoginManagerInterface $loginManager,
  151.         RegistrationFormHandler $registrationFormHandler,
  152.         SessionInterface $session,
  153.         AuthenticationLoginListener $authenticationLoginListener,
  154.         Translator $translator,
  155.         Service $consentService,
  156.         UrlGeneratorInterface $urlGenerator,
  157.         NewsletterDoubleOptInService $newsletterDoubleOptInService,
  158.         UserInterface $user null
  159.     ) {
  160.         //redirect user to index page if logged in
  161.         if ($user && $this->isGranted('ROLE_USER')) {
  162.             return $this->redirectToRoute('account-index');
  163.         }
  164.         $registrationKey $request->get('registrationKey');
  165.         // create a new, empty customer instance
  166.         /** @var CustomerInterface|\Pimcore\Model\DataObject\Customer $customer */
  167.         $customer $customerProvider->create();
  168.         /** @var OAuthToken $oAuthToken */
  169.         $oAuthToken null;
  170.         /** @var UserResponseInterface $oAuthUserInfo */
  171.         $oAuthUserInfo null;
  172.         // load previously stored token from the session and try to load user profile
  173.         // from provider
  174.         if (null !== $registrationKey) {
  175.             $oAuthToken $oAuthHandler->loadToken($registrationKey);
  176.             $oAuthUserInfo $oAuthHandler->loadUserInformation($oAuthToken);
  177.         }
  178.         if (null !== $oAuthUserInfo) {
  179.             // try to load a customer with the given identity from our storage. if this succeeds, we can't register
  180.             // the customer and should either log in the existing identity or show an error. for simplicity, we just
  181.             // throw an exception here.
  182.             // this shouldn't happen as the login would log in the user if found
  183.             if ($oAuthHandler->getCustomerFromUserResponse($oAuthUserInfo)) {
  184.                 throw new \RuntimeException('Customer is already registered');
  185.             }
  186.         }
  187.         // the registration form handler is just a utility class to map pimcore object data to form
  188.         // and vice versa.
  189.         $formData $registrationFormHandler->buildFormData($customer);
  190.         $hidePassword false;
  191.         if (null !== $oAuthToken) {
  192.             $formData $this->mergeOAuthFormData($formData$oAuthUserInfo);
  193.             $hidePassword true;
  194.         }
  195.         // build the registration form and pre-fill it with customer data
  196.         $form $this->createForm(RegistrationFormType::class, $formData, ['hidePassword' => $hidePassword]);
  197.         $form->handleRequest($request);
  198.         $errors = [];
  199.         if ($form->isSubmitted() && $form->isValid()) {
  200.             $registrationFormHandler->updateCustomerFromForm($customer$form);
  201.             $customer->setCustomerLanguage($request->getLocale());
  202.             $customer->setActive(true);
  203.             try {
  204.                 $customer->save();
  205.                 if ($form->getData()['newsletter']) {
  206.                     $consentService->giveConsent($customer'newsletter'$translator->trans('general.newsletter'));
  207.                     $newsletterDoubleOptInService->sendDoubleOptInMail($customer$this->document->getProperty('newsletter_confirm_mail'));
  208.                 }
  209.                 if ($form->getData()['profiling']) {
  210.                     $consentService->giveConsent($customer'profiling'$translator->trans('general.profiling'));
  211.                 }
  212.                 // add SSO identity from OAuth data
  213.                 if (null !== $oAuthUserInfo) {
  214.                     $oAuthHandler->connectSsoIdentity($customer$oAuthUserInfo);
  215.                 }
  216.                 //check if special redirect is necessary
  217.                 if ($session->get('referrer')) {
  218.                     $response $this->redirect($session->get('referrer'));
  219.                     $session->remove('referrer');
  220.                 } else {
  221.                     $response $this->redirectToRoute('account-index');
  222.                 }
  223.                 // log user in manually
  224.                 // pass response to login manager as it adds potential remember me cookies
  225.                 $loginManager->login($customer$request$response);
  226.                 //do ecommerce framework login
  227.                 $authenticationLoginListener->doEcommerceFrameworkLogin($customer);
  228.                 return $response;
  229.             } catch (DuplicateCustomerException $e) {
  230.                 $errors[] = $translator->trans(
  231.                     'account.customer-already-exists',
  232.                     [
  233.                         $customer->getEmail(),
  234.                         $urlGenerator->generate('account-password-send-recovery', ['email' => $customer->getEmail()])
  235.                     ]
  236.                 );
  237.             } catch (\Exception $e) {
  238.                 $errors[] = $e->getMessage();
  239.             }
  240.         }
  241.         if ($form->isSubmitted() && !$form->isValid()) {
  242.             foreach ($form->getErrors() as $error) {
  243.                 $errors[] = $error->getMessage();
  244.             }
  245.         }
  246.         // re-save user info to session as we need it in subsequent requests (e.g. after form errors) or
  247.         // when form is rendered for the first time
  248.         if (null !== $registrationKey && null !== $oAuthToken) {
  249.             $oAuthHandler->saveToken($registrationKey$oAuthToken);
  250.         }
  251.         return $this->render('account/register.html.twig', [
  252.             'customer' => $customer,
  253.             'form' => $form->createView(),
  254.             'errors' => $errors,
  255.             'hideBreadcrumbs' => true,
  256.             'hidePassword' => $hidePassword
  257.         ]);
  258.     }
  259.     /**
  260.      * Special route for connecting to social profiles that saves referrer in session for later
  261.      * redirect to that referrer
  262.      *
  263.      * @param Request $request
  264.      * @param SessionInterface $session
  265.      * @param $service
  266.      *
  267.      * @return Response
  268.      * @Route("/auth/oauth/referrerLogin/{service}", name="app_auth_oauth_login_referrer")
  269.      */
  270.     public function connectAction(Request $requestSessionInterface $session$service)
  271.     {
  272.         // we overwrite this route to store user's referrer in the session
  273.         $session->set('referrer'$request->headers->get('referer'));
  274.         return $this->forward('HWIOAuthBundle:Connect:redirectToService', ['service' => $service]);
  275.     }
  276.     /**
  277.      * Connects an already logged in user to an auth provider
  278.      *
  279.      * @Route("/oauth/connect/{service}", name="app_auth_oauth_connect")
  280.      * @Security("is_granted('ROLE_USER')")
  281.      *
  282.      * @param Request $request
  283.      * @param OAuthRegistrationHandler $oAuthHandler
  284.      * @param UserInterface $user
  285.      * @param string $service
  286.      *
  287.      * @return RedirectResponse
  288.      */
  289.     public function oAuthConnectAction(
  290.         Request $request,
  291.         OAuthRegistrationHandler $oAuthHandler,
  292.         UserInterface $user,
  293.         string $service
  294.     ) {
  295.         $resourceOwner $oAuthHandler->getResourceOwner($service);
  296.         $redirectUrl $this->generateUrl('app_auth_oauth_connect', [
  297.             'service' => $service
  298.         ], UrlGeneratorInterface::ABSOLUTE_URL);
  299.         // redirect to authorization
  300.         if (!$resourceOwner->handles($request)) {
  301.             $authorizationUrl $oAuthHandler->getAuthorizationUrl($request$service$redirectUrl);
  302.             return $this->redirect($authorizationUrl);
  303.         }
  304.         // get access token from URL
  305.         $accessToken $resourceOwner->getAccessToken($request$redirectUrl);
  306.         // e.g. user cancelled auth on provider side
  307.         if (null === $accessToken) {
  308.             return $this->redirectToRoute('account-index');
  309.         }
  310.         $oAuthUserInfo $resourceOwner->getUserInformation($accessToken);
  311.         // we don't want to allow linking an OAuth account to multiple customers
  312.         if ($oAuthHandler->getCustomerFromUserResponse($oAuthUserInfo)) {
  313.             throw new \RuntimeException('There\'s already a customer registered with this provider identity');
  314.         }
  315.         // create a SSO identity object and save it to the user
  316.         $oAuthHandler->connectSsoIdentity($user$oAuthUserInfo);
  317.         // redirect to secure page which should now list the newly linked profile
  318.         return $this->redirectToRoute('account-index');
  319.     }
  320.     /**
  321.      *
  322.      * @param array $formData
  323.      * @param UserResponseInterface $userInformation
  324.      *
  325.      * @return array
  326.      */
  327.     private function mergeOAuthFormData(
  328.         array $formData,
  329.         UserResponseInterface $userInformation
  330.     ): array {
  331.         return array_replace([
  332.             'firstname' => $userInformation->getFirstName(),
  333.             'lastname' => $userInformation->getLastName(),
  334.             'email' => $userInformation->getEmail()
  335.         ], $formData);
  336.     }
  337.     /**
  338.      * Index page for account - it is restricted to ROLE_USER via security annotation
  339.      *
  340.      * @Route("/account/index", name="account-index")
  341.      * @Security("is_granted('ROLE_USER')")
  342.      *
  343.      * @param SsoIdentityServiceInterface $identityService
  344.      * @param UserInterface|null $user
  345.      *
  346.      * @return Response
  347.      */
  348.     public function indexAction(SsoIdentityServiceInterface $identityServiceUserInterface $user null)
  349.     {
  350.         $blacklist = [];
  351.         foreach ($identityService->getSsoIdentities($user) as $identity) {
  352.             $blacklist[] = $identity->getProvider();
  353.         }
  354.         $orderManager Factory::getInstance()->getOrderManager();
  355.         $orderList $orderManager->createOrderList();
  356.         $orderList->addFilter(new CustomerObject($user));
  357.         $orderList->setOrder('orderDate DESC');
  358.         return $this->render('account/index.html.twig', [
  359.             'blacklist' => $blacklist,
  360.             'orderList' => $orderList,
  361.             'hideBreadcrumbs' => true
  362.         ]);
  363.     }
  364.     /**
  365.      * @Route("/account/update-marketing", name="account-update-marketing-permission")
  366.      * @Security("is_granted('ROLE_USER')")
  367.      *
  368.      * @param Request $request
  369.      * @param Service $consentService
  370.      * @param Translator $translator
  371.      * @param NewsletterDoubleOptInService $newsletterDoubleOptInService
  372.      * @param UserInterface|null $user
  373.      *
  374.      * @return RedirectResponse
  375.      *
  376.      * @throws \Exception
  377.      */
  378.     public function updateMarketingPermissionAction(Request $requestService $consentServiceTranslator $translatorNewsletterDoubleOptInService $newsletterDoubleOptInServiceUserInterface $user null)
  379.     {
  380.         if ($user instanceof Customer) {
  381.             $currentNewsletterPermission $user->getNewsletter()->getConsent();
  382.             if (!$currentNewsletterPermission && $request->get('newsletter')) {
  383.                 $consentService->giveConsent($user'newsletter'$translator->trans('general.newsletter'));
  384.                 $newsletterDoubleOptInService->sendDoubleOptInMail($user$this->document->getProperty('newsletter_confirm_mail'));
  385.             } elseif ($currentNewsletterPermission && !$request->get('newsletter')) {
  386.                 $user->setNewsletterConfirmed(false);
  387.                 $consentService->revokeConsent($user'newsletter');
  388.             }
  389.             $currentProfilingPermission $user->getProfiling()->getConsent();
  390.             if (!$currentProfilingPermission && $request->get('profiling')) {
  391.                 $consentService->giveConsent($user'profiling'$translator->trans('general.profiling'));
  392.             } elseif ($currentProfilingPermission && !$request->get('profiling')) {
  393.                 $consentService->revokeConsent($user'profiling');
  394.             }
  395.             $user->save();
  396.             $this->addFlash('success'$translator->trans('account.marketing-permissions-updated'));
  397.         }
  398.         return $this->redirectToRoute('account-index');
  399.     }
  400.     /**
  401.      * @Route("/account/confirm-newsletter", name="account-confirm-newsletter")
  402.      *
  403.      * @param Request $request
  404.      * @param NewsletterDoubleOptInService $newsletterDoubleOptInService
  405.      * @param Translator $translator
  406.      *
  407.      * @return RedirectResponse
  408.      */
  409.     public function confirmNewsletterAction(Request $requestNewsletterDoubleOptInService $newsletterDoubleOptInServiceTranslator $translator)
  410.     {
  411.         $token $request->get('token');
  412.         $customer $newsletterDoubleOptInService->handleDoubleOptInConfirmation($token);
  413.         if ($customer) {
  414.             $this->addFlash('success'$translator->trans('account.marketing-permissions-confirmed-newsletter'));
  415.             return $this->redirectToRoute('account-index');
  416.         } else {
  417.             throw new NotFoundHttpException('Invalid token');
  418.         }
  419.     }
  420.     /**
  421.      * @Route("/account/send-password-recovery", name="account-password-send-recovery")
  422.      *
  423.      * @param Request $request
  424.      * @param PasswordRecoveryService $service
  425.      * @param Translator $translator
  426.      *
  427.      * @return Response
  428.      *
  429.      * @throws \Exception
  430.      */
  431.     public function sendPasswordRecoveryMailAction(Request $requestPasswordRecoveryService $serviceTranslator $translator)
  432.     {
  433.         if ($request->isMethod(Request::METHOD_POST)) {
  434.             $service->sendRecoveryMail($request->get('email'''), $this->document->getProperty('password_reset_mail'));
  435.             $this->addFlash('success'$translator->trans('account.reset-mail-sent-when-possible'));
  436.             return $this->redirectToRoute('account-login', ['no-referer-redirect' => true]);
  437.         }
  438.         return $this->render('account/send_password_recovery_mail.html.twig', [
  439.             'hideBreadcrumbs' => true,
  440.             'emailPrefill' => $request->get('email')
  441.         ]);
  442.     }
  443.     /**
  444.      * @Route("/account/reset-password", name="account-reset-password")
  445.      *
  446.      * @param Request $request
  447.      * @param PasswordRecoveryService $service
  448.      * @param Translator $translator
  449.      *
  450.      * @return Response|RedirectResponse
  451.      */
  452.     public function resetPasswordAction(Request $requestPasswordRecoveryService $serviceTranslator $translator)
  453.     {
  454.         $token $request->get('token');
  455.         $customer $service->getCustomerByToken($token);
  456.         if (!$customer) {
  457.             //TODO render error page
  458.             throw new NotFoundHttpException('Invalid token');
  459.         }
  460.         if ($request->isMethod(Request::METHOD_POST)) {
  461.             $newPassword $request->get('password');
  462.             $service->setPassword($token$newPassword);
  463.             $this->addFlash('success'$translator->trans('account.password-reset-successful'));
  464.             return $this->redirectToRoute('account-login', ['no-referer-redirect' => true]);
  465.         }
  466.         return $this->render('account/reset_password.html.twig', [
  467.             'hideBreadcrumbs' => true,
  468.             'token' => $token,
  469.             'email' => $customer->getEmail()
  470.         ]);
  471.     }
  472.     /**
  473.      * @Route("/api/check-email/{email}", name="check-email", methods="GET")
  474.      */
  475.     public function checkEmailAction($email)
  476.     {
  477.         $loggedUser $this->getUser();
  478.         if (!$loggedUser) {
  479.             throw new UnauthorizedHttpException('Not authorized.');
  480.         }
  481.         $entries = new DataObject\Customer\Listing();
  482.         $entries->setCondition("email LIKE ?", ["%" $email "%"]);
  483.         $entries->load();
  484.         if (!empty($entries->load())) {
  485.             throw new BadRequestHttpException('Email invalid.');
  486.         }
  487.         return new Response('OK'Response::HTTP_OK);
  488.     }
  489. }