vendor/pimcore/pimcore/bundles/AdminBundle/Security/Guard/AdminAuthenticator.php line 127

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\Security\Guard;
  15. use Pimcore\Bundle\AdminBundle\Security\Authentication\Token\TwoFactorRequiredToken;
  16. use Pimcore\Bundle\AdminBundle\Security\BruteforceProtectionHandler;
  17. use Pimcore\Bundle\AdminBundle\Security\User\User;
  18. use Pimcore\Cache\Runtime;
  19. use Pimcore\Event\Admin\Login\LoginCredentialsEvent;
  20. use Pimcore\Event\Admin\Login\LoginFailedEvent;
  21. use Pimcore\Event\Admin\Login\LoginRedirectEvent;
  22. use Pimcore\Event\AdminEvents;
  23. use Pimcore\Model\User as UserModel;
  24. use Pimcore\Tool\Admin;
  25. use Pimcore\Tool\Authentication;
  26. use Pimcore\Tool\Session;
  27. use Psr\Log\LoggerAwareInterface;
  28. use Psr\Log\LoggerAwareTrait;
  29. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  30. use Symfony\Component\HttpFoundation\Cookie;
  31. use Symfony\Component\HttpFoundation\RedirectResponse;
  32. use Symfony\Component\HttpFoundation\Request;
  33. use Symfony\Component\HttpFoundation\Response;
  34. use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface;
  35. use Symfony\Component\Routing\RouterInterface;
  36. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  37. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  38. use Symfony\Component\Security\Core\Exception\AuthenticationException;
  39. use Symfony\Component\Security\Core\User\UserInterface;
  40. use Symfony\Component\Security\Core\User\UserProviderInterface;
  41. use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
  42. use Symfony\Component\Security\Http\HttpUtils;
  43. use Symfony\Contracts\Translation\TranslatorInterface;
  44. /**
  45. * @internal
  46. */
  47. class AdminAuthenticator extends AbstractGuardAuthenticator implements LoggerAwareInterface
  48. {
  49. use LoggerAwareTrait;
  50. /**
  51. * @var TokenStorageInterface
  52. */
  53. protected $tokenStorage;
  54. /**
  55. * @var RouterInterface
  56. */
  57. protected $router;
  58. /**
  59. * @var EventDispatcherInterface
  60. */
  61. protected $dispatcher;
  62. /**
  63. * @var TranslatorInterface
  64. */
  65. protected $translator;
  66. /**
  67. * @var HttpUtils
  68. */
  69. protected $httpUtils;
  70. /**
  71. * @var BruteforceProtectionHandler
  72. */
  73. protected $bruteforceProtectionHandler;
  74. /**
  75. * @var bool
  76. */
  77. protected $twoFactorRequired = false;
  78. /**
  79. * @param TokenStorageInterface $tokenStorage
  80. * @param RouterInterface $router
  81. * @param EventDispatcherInterface $dispatcher
  82. * @param TranslatorInterface $translator
  83. * @param HttpUtils $httpUtils
  84. * @param BruteforceProtectionHandler $bruteforceProtectionHandler
  85. */
  86. public function __construct(
  87. TokenStorageInterface $tokenStorage,
  88. RouterInterface $router,
  89. EventDispatcherInterface $dispatcher,
  90. TranslatorInterface $translator,
  91. HttpUtils $httpUtils,
  92. BruteforceProtectionHandler $bruteforceProtectionHandler
  93. ) {
  94. $this->tokenStorage = $tokenStorage;
  95. $this->router = $router;
  96. $this->dispatcher = $dispatcher;
  97. $this->translator = $translator;
  98. $this->httpUtils = $httpUtils;
  99. $this->bruteforceProtectionHandler = $bruteforceProtectionHandler;
  100. }
  101. /**
  102. * {@inheritdoc}
  103. */
  104. public function supports(Request $request)
  105. {
  106. return $request->attributes->get('_route') === 'pimcore_admin_login_check'
  107. || Authentication::authenticateSession($request);
  108. }
  109. /**
  110. * {@inheritdoc}
  111. */
  112. public function start(Request $request, AuthenticationException $authException = null)
  113. {
  114. if ($request->isXmlHttpRequest()) {
  115. // TODO use a JSON formatted error response?
  116. $response = new Response('Session expired or unauthorized request. Please reload and try again!');
  117. $response->setStatusCode(Response::HTTP_FORBIDDEN);
  118. return $response;
  119. }
  120. $event = new LoginRedirectEvent('pimcore_admin_login', ['perspective' => strip_tags($request->get('perspective'))]);
  121. $this->dispatcher->dispatch($event, AdminEvents::LOGIN_REDIRECT);
  122. $url = $this->router->generate($event->getRouteName(), $event->getRouteParams());
  123. return new RedirectResponse($url);
  124. }
  125. /**
  126. * {@inheritdoc}
  127. */
  128. public function getCredentials(Request $request)
  129. {
  130. $credentials = [];
  131. if ($request->attributes->get('_route') === 'pimcore_admin_login_check') {
  132. $username = $request->get('username');
  133. if ($request->getMethod() === 'POST' && $request->get('password') && $username) {
  134. $this->bruteforceProtectionHandler->checkProtection($username);
  135. $credentials = [
  136. 'username' => $username,
  137. 'password' => $request->get('password'),
  138. ];
  139. } elseif ($token = $request->get('token')) {
  140. $this->bruteforceProtectionHandler->checkProtection();
  141. $credentials = [
  142. 'token' => $token,
  143. 'reset' => (bool) $request->get('reset', false),
  144. ];
  145. } else {
  146. $this->bruteforceProtectionHandler->checkProtection();
  147. throw new AuthenticationException('Missing username or token');
  148. }
  149. $event = new LoginCredentialsEvent($request, $credentials);
  150. $this->dispatcher->dispatch($event, AdminEvents::LOGIN_CREDENTIALS);
  151. return $event->getCredentials();
  152. } else {
  153. if ($pimcoreUser = Authentication::authenticateSession($request)) {
  154. return [
  155. 'user' => $pimcoreUser,
  156. ];
  157. }
  158. }
  159. return $credentials;
  160. }
  161. /**
  162. * {@inheritdoc}
  163. */
  164. public function getUser($credentials, UserProviderInterface $userProvider)
  165. {
  166. /** @var User|null $user */
  167. $user = null;
  168. if (!is_array($credentials)) {
  169. throw new AuthenticationException('Invalid credentials');
  170. }
  171. if (isset($credentials['user']) && $credentials['user'] instanceof UserModel) {
  172. $user = new User($credentials['user']);
  173. $session = Session::getReadOnly();
  174. if ($session->has('2fa_required') && $session->get('2fa_required') === true) {
  175. $this->twoFactorRequired = true;
  176. }
  177. } else {
  178. if (!isset($credentials['username']) && !isset($credentials['token'])) {
  179. throw new AuthenticationException('Missing username/token');
  180. }
  181. if (isset($credentials['password'])) {
  182. $pimcoreUser = Authentication::authenticatePlaintext($credentials['username'], $credentials['password']);
  183. if ($pimcoreUser) {
  184. $user = new User($pimcoreUser);
  185. } else {
  186. // trigger LOGIN_FAILED event if user could not be authenticated via username/password
  187. $event = new LoginFailedEvent($credentials);
  188. $this->dispatcher->dispatch($event, AdminEvents::LOGIN_FAILED);
  189. if ($event->hasUser()) {
  190. $user = new User($event->getUser());
  191. } else {
  192. throw new AuthenticationException('Failed to authenticate with username and password');
  193. }
  194. }
  195. } elseif (isset($credentials['token'])) {
  196. $pimcoreUser = Authentication::authenticateToken($credentials['token']);
  197. if ($pimcoreUser) {
  198. //disable two factor authentication for token based credentials e.g. reset password, admin access links
  199. $pimcoreUser->setTwoFactorAuthentication('required', false);
  200. $user = new User($pimcoreUser);
  201. } else {
  202. throw new AuthenticationException('Failed to authenticate with username and token');
  203. }
  204. if ($credentials['reset']) {
  205. // save the information to session when the user want's to reset the password
  206. // this is because otherwise the old password is required => see also PIMCORE-1468
  207. Session::useSession(function (AttributeBagInterface $adminSession) {
  208. $adminSession->set('password_reset', true);
  209. });
  210. }
  211. } else {
  212. throw new AuthenticationException('Invalid authentication method, must be either password or token');
  213. }
  214. if ($user && Authentication::isValidUser($user->getUser())) {
  215. $pimcoreUser = $user->getUser();
  216. Session::useSession(function (AttributeBagInterface $adminSession) use ($pimcoreUser) {
  217. Session::regenerateId();
  218. $adminSession->set('user', $pimcoreUser);
  219. // this flag gets removed after successful authentication in \Pimcore\Bundle\AdminBundle\EventListener\TwoFactorListener
  220. if ($pimcoreUser->getTwoFactorAuthentication('required') && $pimcoreUser->getTwoFactorAuthentication('enabled')) {
  221. $adminSession->set('2fa_required', true);
  222. }
  223. });
  224. }
  225. }
  226. return $user;
  227. }
  228. /**
  229. * {@inheritdoc}
  230. */
  231. public function checkCredentials($credentials, UserInterface $user)
  232. {
  233. // we rely on getUser returning a valid user
  234. if ($user instanceof User) {
  235. return true;
  236. }
  237. return false;
  238. }
  239. /**
  240. * {@inheritdoc}
  241. */
  242. public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
  243. {
  244. $this->bruteforceProtectionHandler->addEntry($request->get('username'), $request);
  245. $url = $this->router->generate('pimcore_admin_login', [
  246. 'auth_failed' => 'true',
  247. ]);
  248. return new RedirectResponse($url);
  249. }
  250. /**
  251. * {@inheritdoc}
  252. */
  253. public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
  254. {
  255. /** @var UserModel $user */
  256. $user = $token->getUser()->getUser();
  257. // set user language
  258. $request->setLocale($user->getLanguage());
  259. $this->translator->setLocale($user->getLanguage());
  260. // set user on runtime cache for legacy compatibility
  261. Runtime::set('pimcore_admin_user', $user);
  262. if ($user->isAdmin()) {
  263. if (Admin::isMaintenanceModeScheduledForLogin()) {
  264. Admin::activateMaintenanceMode(Session::getSessionId());
  265. Admin::unscheduleMaintenanceModeOnLogin();
  266. }
  267. }
  268. // as we authenticate statelessly (short lived sessions) the authentication is called for
  269. // every request. therefore we only redirect if we're on the login page
  270. if (!in_array($request->attributes->get('_route'), [
  271. 'pimcore_admin_login',
  272. 'pimcore_admin_login_check',
  273. ])) {
  274. return null;
  275. }
  276. $url = null;
  277. if ($request->get('deeplink') && $request->get('deeplink') !== 'true') {
  278. $url = $this->router->generate('pimcore_admin_login_deeplink');
  279. $url .= '?' . $request->get('deeplink');
  280. } else {
  281. $url = $this->router->generate('pimcore_admin_index', [
  282. '_dc' => time(),
  283. 'perspective' => strip_tags($request->get('perspective')),
  284. ]);
  285. }
  286. if ($url) {
  287. $response = new RedirectResponse($url);
  288. $response->headers->setCookie(new Cookie('pimcore_admin_sid', true, 0, '/', null, false, true));
  289. return $response;
  290. }
  291. return null;
  292. }
  293. /**
  294. * {@inheritdoc}
  295. */
  296. public function supportsRememberMe()
  297. {
  298. return false;
  299. }
  300. public function createAuthenticatedToken(UserInterface $user, $providerKey)
  301. {
  302. if ($this->twoFactorRequired) {
  303. return new TwoFactorRequiredToken(
  304. $user,
  305. $providerKey,
  306. $user->getRoles()
  307. );
  308. } else {
  309. return parent::createAuthenticatedToken($user, $providerKey);
  310. }
  311. }
  312. }