<?php
/**
* Pimcore
*
* This source file is available under following license:
* - Pimcore Commercial License (PCL)
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license PCL
*/
namespace Pimcore\Bundle\PortalEngineBundle\Service\Security\Authenticator;
use Pimcore\Bundle\PortalEngineBundle\Enum\Permission;
use Pimcore\Bundle\PortalEngineBundle\Event\Auth\LoginCheckPasswordEvent;
use Pimcore\Bundle\PortalEngineBundle\Event\Auth\LoginGetUserEvent;
use Pimcore\Bundle\PortalEngineBundle\Form\LoginForm;
use Pimcore\Bundle\PortalEngineBundle\Model\DataObject\PortalUserInterface;
use Pimcore\Bundle\PortalEngineBundle\Service\PortalConfig\PortalConfigService;
use Pimcore\Bundle\PortalEngineBundle\Service\Security\Authentication\UserProvider;
use Pimcore\Bundle\PortalEngineBundle\Service\Security\PermissionService;
use Pimcore\Bundle\PortalEngineBundle\Service\StatisticsTracker\Elasticsearch\PortalUserLoginTracker;
use Pimcore\Model\DataObject\ClassDefinition\Data\Password;
use Pimcore\Model\DataObject\Concrete;
use Pimcore\Model\Site;
use Pimcore\Model\User;
use Pimcore\Tool\Authentication;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CustomCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
class LoginAuthenticator extends AbstractAuthenticator implements AuthenticationEntryPointInterface, InteractiveAuthenticatorInterface
{
use TargetPathTrait;
/** @var FormFactoryInterface */
protected FormFactoryInterface $formFactory;
/** @var PortalUserLoginTracker */
protected PortalUserLoginTracker $portalUserLoginTracker;
/** @var EventDispatcherInterface */
protected EventDispatcherInterface $eventDispatcher;
/** @var UserProvider */
protected UserProvider $userProvider;
public function __construct(
FormFactoryInterface $formFactory,
PortalUserLoginTracker $portalUserLoginTracker,
EventDispatcherInterface $eventDispatcher,
UserProvider $userProvider,
UrlGeneratorInterface $urlGenerator,
PortalConfigService $portalConfigService,
PermissionService $permissionService
) {
parent::__construct($urlGenerator, $portalConfigService, $permissionService);
$this->formFactory = $formFactory;
$this->portalUserLoginTracker = $portalUserLoginTracker;
$this->eventDispatcher = $eventDispatcher;
$this->userProvider = $userProvider;
}
/**
* Does the authenticator support the given Request?
*
* If this returns false, the authenticator will be skipped.
*
* @param Request $request
*
* @return bool|null
*/
public function supports(Request $request): ?bool
{
if (!$this->portalConfigService->isPortalEngineSite()) {
return false;
}
if ('pimcore_portalengine_auth_login' !== $request->attributes->get('_route')) {
return false;
}
$loginForm = $this->formFactory->create(LoginForm::class);
$loginForm->handleRequest($request);
return $loginForm->isSubmitted() && $loginForm->isValid();
}
/**
* Get the authentication credentials from the request as any an associate array.
* Check credentials in the passport through CustomCredentials
*
* @param Request $request
*
* @return Passport
*/
public function authenticate(Request $request): Passport
{
$loginForm = $this->formFactory->create(LoginForm::class);
$loginForm->handleRequest($request);
$credentials = $loginForm->getData();
$event = new LoginGetUserEvent($credentials['username'], $credentials['password']);
$this->eventDispatcher->dispatch($event);
$passport = new Passport(
new UserBadge($credentials['username']),
new CustomCredentials(function ($credentials) {
/** @var LoginGetUserEvent $event */
$event = $credentials['event'];
if ($event->getPortalUserResolved()) {
$user = $event->getPortalUser();
} else {
$user = null;
try {
$user = $this->userProvider->loadUserByIdentifier($credentials['username']);
} catch (\Exception $e) {
// nothing to do
}
}
if (empty($user) || !$user instanceof PortalUserInterface) {
throw new AuthenticationException(sprintf('User with email %s not found.', $credentials['username']));
}
$event = new LoginCheckPasswordEvent($user, $credentials['password']);
$this->eventDispatcher->dispatch($event);
if (is_bool($event->getLoginValid())) {
$success = $event->getLoginValid();
} elseif ($user->getUsePimcoreUserPassword() && $user->getPimcoreUser()) {
$pimcoreUser = User::getById(intval($user->getPimcoreUser()));
$success = Authentication::isValidUser($pimcoreUser) && Authentication::verifyPassword($pimcoreUser, $credentials['password']);
} else {
$fieldDefinition = $user->getClass()->getFieldDefinition('portalPassword');
$success = $fieldDefinition instanceof Password && $user instanceof Concrete
&& $fieldDefinition->verifyPassword($credentials['password'], $user);
}
if ($success) {
$portalId = Site::getCurrentSite()->getRootId();
$success = $this->permissionService->isAllowed($user, Permission::PORTAL_ACCESS . Permission::PERMISSION_DELIMITER . $portalId);
}
if (!$success) {
throw new AuthenticationException('Password wrong');
}
return true;
}, ['username' => $credentials['username'], 'password' => $credentials['password'], 'event' => $event]),
);
if ($credentials['_remember_me'] ?? false) {
$passport->addBadge(new RememberMeBadge());
}
return $passport;
}
/**
* Called when authentication executed and was successful!
*
* This should return the Response sent back to the user, like a
* RedirectResponse to the last page they visited.
*
* If you return null, the current request will continue, and the user
* will be authenticated. This makes sense, for example, with an API.
*
* @param Request $request
* @param TokenInterface $token
* @param string $firewallName
*
* @return RedirectResponse|null
*
* @throws \Exception
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?RedirectResponse
{
$this->portalUserLoginTracker->trackEvent(['user' => $token->getUser()]);
if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) {
if (!$this->isRestApiPath($targetPath)) {
return new RedirectResponse($targetPath);
}
}
return new RedirectResponse('/');
}
public function isInteractive(): bool
{
return true;
}
}