vendor/pimcore/pimcore/bundles/AdminBundle/Controller/Admin/LoginController.php line 170

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 Commercial License (PCL)
  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 PCL
  13.  */
  14. namespace Pimcore\Bundle\AdminBundle\Controller\Admin;
  15. use Pimcore\Bundle\AdminBundle\Controller\AdminController;
  16. use Pimcore\Bundle\AdminBundle\Controller\BruteforceProtectedControllerInterface;
  17. use Pimcore\Bundle\AdminBundle\Security\Authenticator\AdminLoginAuthenticator;
  18. use Pimcore\Bundle\AdminBundle\Security\BruteforceProtectionHandler;
  19. use Pimcore\Bundle\AdminBundle\Security\CsrfProtectionHandler;
  20. use Pimcore\Config;
  21. use Pimcore\Controller\KernelControllerEventInterface;
  22. use Pimcore\Controller\KernelResponseEventInterface;
  23. use Pimcore\Event\Admin\Login\LoginRedirectEvent;
  24. use Pimcore\Event\Admin\Login\LostPasswordEvent;
  25. use Pimcore\Event\AdminEvents;
  26. use Pimcore\Http\ResponseHelper;
  27. use Pimcore\Logger;
  28. use Pimcore\Model\User;
  29. use Pimcore\Tool;
  30. use Pimcore\Tool\Authentication;
  31. use Symfony\Component\HttpFoundation\RedirectResponse;
  32. use Symfony\Component\HttpFoundation\Request;
  33. use Symfony\Component\HttpFoundation\Response;
  34. use Symfony\Component\HttpKernel\Event\ControllerEvent;
  35. use Symfony\Component\HttpKernel\Event\ResponseEvent;
  36. use Symfony\Component\Routing\Annotation\Route;
  37. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  38. use Symfony\Component\Security\Core\Exception\AuthenticationException;
  39. use Symfony\Component\Security\Core\Security;
  40. use Symfony\Component\Security\Core\User\UserInterface;
  41. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  42. use Symfony\Contracts\Translation\LocaleAwareInterface;
  43. /**
  44.  * @internal
  45.  */
  46. class LoginController extends AdminController implements BruteforceProtectedControllerInterfaceKernelControllerEventInterfaceKernelResponseEventInterface
  47. {
  48.     public function __construct(
  49.         protected ResponseHelper $responseHelper,
  50.     ) {
  51.     }
  52.     /**
  53.      * @param ControllerEvent $event
  54.      */
  55.     public function onKernelControllerEvent(ControllerEvent $event)
  56.     {
  57.         // use browser language for login page if possible
  58.         $locale 'en';
  59.         $availableLocales Tool\Admin::getLanguages();
  60.         foreach ($event->getRequest()->getLanguages() as $userLocale) {
  61.             if (in_array($userLocale$availableLocales)) {
  62.                 $locale $userLocale;
  63.                 break;
  64.             }
  65.         }
  66.         if ($this->getTranslator() instanceof LocaleAwareInterface) {
  67.             $this->getTranslator()->setLocale($locale);
  68.         }
  69.     }
  70.     /**
  71.      * {@inheritdoc}
  72.      */
  73.     public function onKernelResponseEvent(ResponseEvent $event)
  74.     {
  75.         $response $event->getResponse();
  76.         $response->headers->set('X-Frame-Options''deny'true);
  77.         $this->responseHelper->disableCache($responsetrue);
  78.     }
  79.     /**
  80.      * @Route("/login", name="pimcore_admin_login")
  81.      * @Route("/login/", name="pimcore_admin_login_fallback")
  82.      */
  83.     public function loginAction(Request $requestCsrfProtectionHandler $csrfProtectionConfig $config)
  84.     {
  85.         if ($request->get('_route') === 'pimcore_admin_login_fallback') {
  86.             return $this->redirectToRoute('pimcore_admin_login'$request->query->all(), Response::HTTP_MOVED_PERMANENTLY);
  87.         }
  88.         $csrfProtection->regenerateCsrfToken();
  89.         $user $this->getAdminUser();
  90.         if ($user instanceof UserInterface) {
  91.             return $this->redirectToRoute('pimcore_admin_index');
  92.         }
  93.         $params $this->buildLoginPageViewParams($config);
  94.         $session_gc_maxlifetime ini_get('session.gc_maxlifetime');
  95.         if (empty($session_gc_maxlifetime)) {
  96.             $session_gc_maxlifetime 120;
  97.         }
  98.         $params['csrfTokenRefreshInterval'] = ((int)$session_gc_maxlifetime 60) * 1000;
  99.         if ($request->get('auth_failed')) {
  100.             $params['error'] = 'error_auth_failed';
  101.         }
  102.         if ($request->get('session_expired')) {
  103.             $params['error'] = 'error_session_expired';
  104.         }
  105.         if ($request->get('deeplink')) {
  106.             $params['deeplink'] = true;
  107.         }
  108.         $params['browserSupported'] = $this->detectBrowser();
  109.         $params['debug'] = \Pimcore::inDebugMode();
  110.         return $this->render('@PimcoreAdmin/Admin/Login/login.html.twig'$params);
  111.     }
  112.     /**
  113.      * @Route("/login/csrf-token", name="pimcore_admin_login_csrf_token")
  114.      */
  115.     public function csrfTokenAction(Request $requestCsrfProtectionHandler $csrfProtection)
  116.     {
  117.         if (!$this->getAdminUser()) {
  118.             $csrfProtection->regenerateCsrfToken();
  119.         }
  120.         return $this->json([
  121.            'csrfToken' => $csrfProtection->getCsrfToken(),
  122.         ]);
  123.     }
  124.     /**
  125.      * @Route("/logout", name="pimcore_admin_logout" , methods={"POST"})
  126.      */
  127.     public function logoutAction()
  128.     {
  129.         // this route will never be matched, but will be handled by the logout handler
  130.     }
  131.     /**
  132.      * Dummy route used to check authentication
  133.      *
  134.      * @Route("/login/login", name="pimcore_admin_login_check")
  135.      *
  136.      * @see AdminLoginAuthenticator for the security implementation
  137.      * @see AdminAuthenticator for the security implementation (Authenticator Based Security)
  138.      */
  139.     public function loginCheckAction()
  140.     {
  141.         // just in case the authenticator didn't redirect
  142.         return new RedirectResponse($this->generateUrl('pimcore_admin_login'));
  143.     }
  144.     /**
  145.      * @Route("/login/lostpassword", name="pimcore_admin_login_lostpassword")
  146.      */
  147.     public function lostpasswordAction(Request $request, ?BruteforceProtectionHandler $bruteforceProtectionHandlerCsrfProtectionHandler $csrfProtectionConfig $configEventDispatcherInterface $eventDispatcher)
  148.     {
  149.         $params $this->buildLoginPageViewParams($config);
  150.         $error null;
  151.         if ($request->getMethod() === 'POST' && $username $request->get('username')) {
  152.             $user User::getByName($username);
  153.             if ($user instanceof User) {
  154.                 if (!$user->isActive()) {
  155.                     $error 'user_inactive';
  156.                 }
  157.                 if (!$user->getEmail()) {
  158.                     $error 'user_no_email_address';
  159.                 }
  160.                 if (!$user->getPassword()) {
  161.                     $error 'user_no_password';
  162.                 }
  163.             } else {
  164.                 $error 'user_unknown';
  165.             }
  166.             if (!$error && $user instanceof User) {
  167.                 $token Authentication::generateToken($user->getName());
  168.                 $loginUrl $this->generateUrl('pimcore_admin_login_check', [
  169.                     'token' => $token,
  170.                     'reset' => 'true',
  171.                 ], UrlGeneratorInterface::ABSOLUTE_URL);
  172.                 try {
  173.                     $event = new LostPasswordEvent($user$loginUrl);
  174.                     $eventDispatcher->dispatch($eventAdminEvents::LOGIN_LOSTPASSWORD);
  175.                     // only send mail if it wasn't prevented in event
  176.                     if ($event->getSendMail()) {
  177.                         $mail Tool::getMail([$user->getEmail()], 'Pimcore lost password service');
  178.                         $mail->setIgnoreDebugMode(true);
  179.                         $mail->text("Login to pimcore and change your password using the following link. This temporary login link will expire in 24 hours: \r\n\r\n" $loginUrl);
  180.                         $mail->send();
  181.                     }
  182.                     // directly return event response
  183.                     if ($event->hasResponse()) {
  184.                         return $event->getResponse();
  185.                     }
  186.                 } catch (\Exception $e) {
  187.                     Logger::error('Error sending password recovery email: ' $e->getMessage());
  188.                     $error 'lost_password_email_error';
  189.                 }
  190.             }
  191.             if ($error) {
  192.                 Logger::error('Lost password service: ' $error);
  193.                 $bruteforceProtectionHandler?->addEntry($request->get('username'), $request);
  194.             }
  195.         }
  196.         $csrfProtection->regenerateCsrfToken();
  197.         return $this->render('@PimcoreAdmin/Admin/Login/lostpassword.html.twig'$params);
  198.     }
  199.     /**
  200.      * @Route("/login/deeplink", name="pimcore_admin_login_deeplink")
  201.      */
  202.     public function deeplinkAction(Request $requestEventDispatcherInterface $eventDispatcher)
  203.     {
  204.         // check for deeplink
  205.         $queryString $_SERVER['QUERY_STRING'];
  206.         if (preg_match('/(document|asset|object)_([0-9]+)_([a-z]+)/'$queryString$deeplink)) {
  207.             $deeplink $deeplink[0];
  208.             $perspective strip_tags($request->get('perspective'));
  209.             if (strpos($queryString'token')) {
  210.                 $event = new LoginRedirectEvent('pimcore_admin_login', [
  211.                     'deeplink' => $deeplink,
  212.                     'perspective' => $perspective,
  213.                 ]);
  214.                 $eventDispatcher->dispatch($eventAdminEvents::LOGIN_REDIRECT);
  215.                 $url $this->generateUrl($event->getRouteName(), $event->getRouteParams());
  216.                 $url .= '&' $queryString;
  217.                 return $this->redirect($url);
  218.             } elseif ($queryString) {
  219.                 $event = new LoginRedirectEvent('pimcore_admin_login', [
  220.                     'deeplink' => 'true',
  221.                     'perspective' => $perspective,
  222.                 ]);
  223.                 $eventDispatcher->dispatch($eventAdminEvents::LOGIN_REDIRECT);
  224.                 return $this->render('@PimcoreAdmin/Admin/Login/deeplink.html.twig', [
  225.                     'tab' => $deeplink,
  226.                     'redirect' => $this->generateUrl($event->getRouteName(), $event->getRouteParams()),
  227.                 ]);
  228.             }
  229.         }
  230.     }
  231.     protected function buildLoginPageViewParams(Config $config): array
  232.     {
  233.         return [
  234.             'config' => $config,
  235.             'pluginCssPaths' => $this->getBundleManager()->getCssPaths(),
  236.         ];
  237.     }
  238.     /**
  239.      * @Route("/login/2fa", name="pimcore_admin_2fa")
  240.      */
  241.     public function twoFactorAuthenticationAction(Request $request, ?BruteforceProtectionHandler $bruteforceProtectionHandlerConfig $config)
  242.     {
  243.         $params $this->buildLoginPageViewParams($config);
  244.         if ($request->hasSession()) {
  245.             // we have to call the check here manually, because BruteforceProtectionListener uses the 'username' from the request
  246.             $bruteforceProtectionHandler?->checkProtection($this->getAdminUser()->getName(), $request);
  247.             $session $request->getSession();
  248.             $authException $session->get(Security::AUTHENTICATION_ERROR);
  249.             if ($authException instanceof AuthenticationException) {
  250.                 $session->remove(Security::AUTHENTICATION_ERROR);
  251.                 $params['error'] = $authException->getMessage();
  252.                 $bruteforceProtectionHandler?->addEntry($this->getAdminUser()->getName(), $request);
  253.             }
  254.         } else {
  255.             $params['error'] = 'No session available, it either timed out or cookies are not enabled.';
  256.         }
  257.         return $this->render('@PimcoreAdmin/Admin/Login/twoFactorAuthentication.html.twig'$params);
  258.     }
  259.     /**
  260.      * @Route("/login/2fa-verify", name="pimcore_admin_2fa-verify")
  261.      *
  262.      * @param Request $request
  263.      */
  264.     public function twoFactorAuthenticationVerifyAction(Request $request)
  265.     {
  266.     }
  267.     /**
  268.      * @return bool
  269.      */
  270.     public function detectBrowser()
  271.     {
  272.         $supported false;
  273.         $browser = new \Browser();
  274.         $browserVersion = (int)$browser->getVersion();
  275.         if ($browser->getBrowser() == \Browser::BROWSER_FIREFOX && $browserVersion >= 72) {
  276.             $supported true;
  277.         }
  278.         if ($browser->getBrowser() == \Browser::BROWSER_CHROME && $browserVersion >= 84) {
  279.             $supported true;
  280.         }
  281.         if ($browser->getBrowser() == \Browser::BROWSER_SAFARI && $browserVersion >= 13.1) {
  282.             $supported true;
  283.         }
  284.         if ($browser->getBrowser() == \Browser::BROWSER_EDGE && $browserVersion >= 90) {
  285.             $supported true;
  286.         }
  287.         return $supported;
  288.     }
  289. }