<?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\Controller;
use GuzzleHttp\ClientInterface;
use Pimcore\Bundle\OpenIdConnectBundle\Client\Provider\OpenIdConnectProvider;
use Pimcore\Bundle\OpenIdConnectBundle\Session\Configurator;
use Pimcore\Bundle\PortalEngineBundle\Exception\OutputErrorException;
use Pimcore\Bundle\PortalEngineBundle\Form\LoginForm;
use Pimcore\Bundle\PortalEngineBundle\Form\RecoverPasswordForm;
use Pimcore\Bundle\PortalEngineBundle\Model\View\Notification;
use Pimcore\Bundle\PortalEngineBundle\Service\Content\HeadTitleService;
use Pimcore\Bundle\PortalEngineBundle\Service\Frontend\FrontendNotificationService;
use Pimcore\Bundle\PortalEngineBundle\Service\PortalConfig\PortalConfigService;
use Pimcore\Bundle\PortalEngineBundle\Service\Security\Authentication\OpenIdConnectAuthenticator;
use Pimcore\Bundle\PortalEngineBundle\Service\Security\Authentication\User\PasswordChangeableService;
use Pimcore\Bundle\PortalEngineBundle\Service\Security\Authentication\User\RecoverPasswordService;
use Pimcore\Tool;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Attribute\NamespacedAttributeBag;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\Translation\LocaleAwareInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @Route("/auth", condition="request.attributes.get('isPortalEngineSite')")
*/
class AuthController extends AbstractSiteController
{
protected ClientInterface $httpClient;
/**
* @param ClientInterface $httpClient
*/
public function __construct(ClientInterface $httpClient)
{
$this->httpClient = $httpClient;
}
/**
* @Route("/login",
* name="pimcore_portalengine_auth_login"
* )
*/
public function loginAction(Request $request, FormFactoryInterface $formFactory, HeadTitleService $headTitleService, TranslatorInterface $translator, PortalConfigService $portalConfigService, PasswordChangeableService $passwordChangeableService)
{
/** @var string $locale */
$locale = $request->getPreferredLanguage(Tool::getValidLanguages());
if ($translator instanceof LocaleAwareInterface) {
$translator->setLocale($locale);
}
$request->setLocale($locale);
$portalName = $portalConfigService->getPortalName();
$oidcProviders = $portalConfigService->getCurrentPortalConfig()->getOidcConfigs();
$headTitleService->setTitle($translator->trans('portal-engine.content.title.auth-login', ['%name%' => $portalName]));
$loginForm = $formFactory->create(LoginForm::class);
return $this->renderTemplate('@PimcorePortalEngine/auth/login.html.twig', [
'form' => $loginForm->createView(),
'loginFailed' => (bool)$request->query->get('loginFailed'),
'portalName' => $portalName,
'oidcProviderNames' => array_keys($oidcProviders),
'showRecoverPassword' => $passwordChangeableService->isPasswordChangeable()
]);
}
/**
* @Route("/logout",
* name="pimcore_portalengine_auth_logout"
* )
*/
public function logoutAction(Request $request)
{
return $this->redirectToRoute('pimcore_portalengine_auth_login');
}
/**
* @Route("/recover-password",
* name="pimcore_portalengine_auth_recover_password"
* )
*/
public function recoverPasswordAction(Request $request, TranslatorInterface $translator, FormFactoryInterface $formFactory, FrontendNotificationService $frontendNotificationService, RecoverPasswordService $recoverPasswordService, PasswordChangeableService $passwordChangeableService, HeadTitleService $headTitleService)
{
if (!$passwordChangeableService->isPasswordChangeable()) {
throw new NotFoundHttpException('password not changeable');
}
/** @var string $locale */
$locale = $request->getPreferredLanguage(Tool::getValidLanguages());
if ($translator instanceof LocaleAwareInterface) {
$translator->setLocale($locale);
}
$request->setLocale($locale);
$headTitleService->setTitle($translator->trans('portal-engine.content.title.auth-recover-password'));
/** @var FormInterface $recoverPasswordForm */
$recoverPasswordForm = $formFactory
->create(RecoverPasswordForm::class)
->handleRequest($request);
if ($request->isMethod(Request::METHOD_POST) && $recoverPasswordForm->isSubmitted() && $recoverPasswordForm->isValid()) {
try {
/** @var string $userIdentifier */
$userIdentifier = (string)$recoverPasswordForm->get('userIdentifier')->getData();
if (empty($userIdentifier)) {
throw new OutputErrorException($translator->trans('portal-engine.auth.user-not-found'));
}
$recoverPasswordService->recoverPassword($userIdentifier);
$frontendNotificationService
->addNotification($translator->trans('portal-engine.auth.password-recover-email'), Notification::HTML_CLASS_SUCCESS);
} catch (\Exception $e) {
if ($e instanceof OutputErrorException) {
$frontendNotificationService->addNotification($e->getMessage(), Notification::HTML_CLASS_DANGER);
}
}
}
return $this->renderTemplate('@PimcorePortalEngine/auth/recover_password.html.twig', [
'recoverPasswordForm' => $recoverPasswordForm->createView(),
'notification' => $frontendNotificationService->getNotification()
]);
}
/**
* @Route("/oidc/endpoint",
* name="pimcore_portalengine_auth_oidc"
* )
*/
public function oidcAction(Request $request, PortalConfigService $portalConfigService)
{
if (!class_exists('Pimcore\\Bundle\\OpenIdConnectBundle\\PimcoreOpenIdConnectBundle')) {
throw new \Exception('OpenID Connect Bundle not available');
}
if ($request->get('error')) {
throw new \Exception(strip_tags($request->get('error')) . ': ' . strip_tags($request->get('error_description')));
}
$portalConfig = $portalConfigService->getCurrentPortalConfig();
$session = $request->getSession();
/** @var NamespacedAttributeBag $sessionBag */
$sessionBag = $session->getBag(Configurator::SESSION_BAG_NAME); // @phpstan-ignore-line
// if coming back from provider, use provider from session
if ($request->get('code')) {
$providerName = $sessionBag->get(OpenIdConnectAuthenticator::SESSION_KEY_PROVIDER);
} else {
$providerName = $request->get('provider');
$sessionBag->set(OpenIdConnectAuthenticator::SESSION_KEY_PROVIDER, $providerName);
}
$oidcConfig = $portalConfig->getOidcConfig($providerName);
if (empty($oidcConfig)) {
throw new \InvalidArgumentException(sprintf('Provider `%s` unknown.', strip_tags($request->get('provider'))));
}
$provider = new OpenIdConnectProvider([ // @phpstan-ignore-line
'clientId' => $oidcConfig->getClientId(),
'clientSecret' => $oidcConfig->getClientSecret(),
'urlDiscovery' => $oidcConfig->getDiscoveryUrl(),
'redirectUri' => $request->getSchemeAndHttpHost() . $request->getPathInfo(),
'scopes' => $oidcConfig->getScopes()
]);
$provider->setHttpClient($this->httpClient); // @phpstan-ignore-line
if (!$request->get('code')) {
// If we don't have an authorization code then get one
$authUrl = $provider->getAuthorizationUrl(); // @phpstan-ignore-line
$sessionBag->set(OpenIdConnectAuthenticator::SESSION_KEY_STATE, $provider->getState()); // @phpstan-ignore-line
return $this->redirect($authUrl);
} elseif (!$request->get('state') || ($request->get('state') !== $sessionBag->get('oauth2state'))) {
// Check given state against previously stored one to mitigate CSRF attack
$sessionBag->remove(OpenIdConnectAuthenticator::SESSION_KEY_STATE);
throw new \Exception('Invalid state');
}
return $this->redirectToRoute('pimcore_portalengine_auth_login');
}
}