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

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\BruteforceProtectionHandler;
  18. use Pimcore\Bundle\AdminBundle\Security\CsrfProtectionHandler;
  19. use Pimcore\Config;
  20. use Pimcore\Controller\KernelControllerEventInterface;
  21. use Pimcore\Controller\KernelResponseEventInterface;
  22. use Pimcore\Event\Admin\Login\LoginRedirectEvent;
  23. use Pimcore\Event\Admin\Login\LostPasswordEvent;
  24. use Pimcore\Event\AdminEvents;
  25. use Pimcore\Extension\Bundle\PimcoreBundleManager;
  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. /**
  43. * @internal
  44. */
  45. class LoginController extends AdminController implements BruteforceProtectedControllerInterface, KernelControllerEventInterface, KernelResponseEventInterface
  46. {
  47. /**
  48. * @var ResponseHelper
  49. */
  50. protected $reponseHelper;
  51. public function __construct(ResponseHelper $responseHelper)
  52. {
  53. $this->reponseHelper = $responseHelper;
  54. }
  55. /**
  56. * @param ControllerEvent $event
  57. */
  58. public function onKernelControllerEvent(ControllerEvent $event)
  59. {
  60. // use browser language for login page if possible
  61. $locale = 'en';
  62. $availableLocales = Tool\Admin::getLanguages();
  63. foreach ($event->getRequest()->getLanguages() as $userLocale) {
  64. if (in_array($userLocale, $availableLocales)) {
  65. $locale = $userLocale;
  66. break;
  67. }
  68. }
  69. $this->get('translator')->setLocale($locale);
  70. }
  71. /**
  72. * {@inheritdoc}
  73. */
  74. public function onKernelResponseEvent(ResponseEvent $event)
  75. {
  76. $response = $event->getResponse();
  77. $response->headers->set('X-Frame-Options', 'deny', true);
  78. $this->reponseHelper->disableCache($response, true);
  79. }
  80. /**
  81. * @Route("/login", name="pimcore_admin_login")
  82. * @Route("/login/", name="pimcore_admin_login_fallback")
  83. */
  84. public function loginAction(Request $request, CsrfProtectionHandler $csrfProtection, Config $config)
  85. {
  86. if ($request->get('_route') === 'pimcore_admin_login_fallback') {
  87. return $this->redirectToRoute('pimcore_admin_login', $request->query->all(), Response::HTTP_MOVED_PERMANENTLY);
  88. }
  89. $csrfProtection->regenerateCsrfToken();
  90. $user = $this->getAdminUser();
  91. if ($user instanceof UserInterface) {
  92. return $this->redirectToRoute('pimcore_admin_index');
  93. }
  94. $params = $this->buildLoginPageViewParams($config);
  95. $session_gc_maxlifetime = ini_get('session.gc_maxlifetime');
  96. if (empty($session_gc_maxlifetime)) {
  97. $session_gc_maxlifetime = 120;
  98. }
  99. $params['csrfTokenRefreshInterval'] = ((int)$session_gc_maxlifetime - 60) * 1000;
  100. if ($request->get('auth_failed')) {
  101. $params['error'] = 'error_auth_failed';
  102. }
  103. if ($request->get('session_expired')) {
  104. $params['error'] = 'error_session_expired';
  105. }
  106. if ($request->get('deeplink')) {
  107. $params['deeplink'] = true;
  108. }
  109. $params['browserSupported'] = $this->detectBrowser();
  110. $params['debug'] = \Pimcore::inDebugMode();
  111. return $this->render('@PimcoreAdmin/Admin/Login/login.html.twig', $params);
  112. }
  113. /**
  114. * @Route("/login/csrf-token", name="pimcore_admin_login_csrf_token")
  115. */
  116. public function csrfTokenAction(Request $request, CsrfProtectionHandler $csrfProtection)
  117. {
  118. if (!$this->getAdminUser()) {
  119. $csrfProtection->regenerateCsrfToken();
  120. }
  121. return $this->json([
  122. 'csrfToken' => $csrfProtection->getCsrfToken(),
  123. ]);
  124. }
  125. /**
  126. * @Route("/logout", name="pimcore_admin_logout")
  127. */
  128. public function logoutAction()
  129. {
  130. // this route will never be matched, but will be handled by the logout handler
  131. }
  132. /**
  133. * Dummy route used to check authentication
  134. *
  135. * @Route("/login/login", name="pimcore_admin_login_check")
  136. *
  137. * @see AdminAuthenticator for the security implementation
  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 $bruteforceProtectionHandler, CsrfProtectionHandler $csrfProtection, Config $config, EventDispatcherInterface $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($event, AdminEvents::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 $request, EventDispatcherInterface $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($event, AdminEvents::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($event, AdminEvents::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. $bundleManager = $this->get(PimcoreBundleManager::class);
  234. return [
  235. 'config' => $config,
  236. 'pluginCssPaths' => $bundleManager->getCssPaths(),
  237. ];
  238. }
  239. /**
  240. * @Route("/login/2fa", name="pimcore_admin_2fa")
  241. */
  242. public function twoFactorAuthenticationAction(Request $request, BruteforceProtectionHandler $bruteforceProtectionHandler, Config $config)
  243. {
  244. $params = $this->buildLoginPageViewParams($config);
  245. if ($request->hasSession()) {
  246. // we have to call the check here manually, because BruteforceProtectionListener uses the 'username' from the request
  247. $bruteforceProtectionHandler->checkProtection($this->getAdminUser()->getName(), $request);
  248. $session = $request->getSession();
  249. $authException = $session->get(Security::AUTHENTICATION_ERROR);
  250. if ($authException instanceof AuthenticationException) {
  251. $session->remove(Security::AUTHENTICATION_ERROR);
  252. $params['error'] = $authException->getMessage();
  253. $bruteforceProtectionHandler->addEntry($this->getAdminUser()->getName(), $request);
  254. }
  255. } else {
  256. $params['error'] = 'No session available, it either timed out or cookies are not enabled.';
  257. }
  258. return $this->render('@PimcoreAdmin/Admin/Login/twoFactorAuthentication.html.twig', $params);
  259. }
  260. /**
  261. * @Route("/login/2fa-verify", name="pimcore_admin_2fa-verify")
  262. *
  263. * @param Request $request
  264. */
  265. public function twoFactorAuthenticationVerifyAction(Request $request)
  266. {
  267. }
  268. /**
  269. * @return bool
  270. */
  271. public function detectBrowser()
  272. {
  273. $supported = false;
  274. $browser = new \Browser();
  275. $browserVersion = (int)$browser->getVersion();
  276. if ($browser->getBrowser() == \Browser::BROWSER_FIREFOX && $browserVersion >= 72) {
  277. $supported = true;
  278. }
  279. if ($browser->getBrowser() == \Browser::BROWSER_CHROME && $browserVersion >= 84) {
  280. $supported = true;
  281. }
  282. if ($browser->getBrowser() == \Browser::BROWSER_SAFARI && $browserVersion >= 13.1) {
  283. $supported = true;
  284. }
  285. if ($browser->getBrowser() == \Browser::BROWSER_EDGE && $browserVersion >= 90) {
  286. $supported = true;
  287. }
  288. return $supported;
  289. }
  290. }