vendor/pimcore/portal-engine/src/Controller/AuthController.php line 41
<?php/*** This source file is available under the terms of the* Pimcore Open Core License (POCL)* Full copyright and license information is available in* LICENSE.md which is distributed with this source code.** @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)* @license Pimcore Open Core License (POCL)*/namespace Pimcore\Bundle\PortalEngineBundle\Controller;use Exception;use GuzzleHttp\ClientInterface;use InvalidArgumentException;use Pimcore\Bundle\OpenIdConnectBundle\Client\Provider\OpenIdConnectProvider;use Pimcore\Bundle\OpenIdConnectBundle\EventListener\OpenIdSessionBagListener;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\Frontend\FrontendVariantService;use Pimcore\Bundle\PortalEngineBundle\Service\PortalConfig\PortalConfigService;use Pimcore\Bundle\PortalEngineBundle\Service\Security\Authentication\User\PasswordChangeableService;use Pimcore\Bundle\PortalEngineBundle\Service\Security\Authentication\User\RecoverPasswordService;use Pimcore\Bundle\PortalEngineBundle\Service\Security\Authenticator\OpenIdConnectAuthenticator;use Pimcore\Tool;use Pimcore\Tool\Session;use Symfony\Component\Form\FormFactoryInterface;use Symfony\Component\Form\FormInterface;use Symfony\Component\HttpFoundation\RedirectResponse;use Symfony\Component\HttpFoundation\Request;use Symfony\Component\HttpFoundation\Response;use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface;use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;use Symfony\Component\Routing\Attribute\Route;use Symfony\Contracts\Translation\LocaleAwareInterface;use Symfony\Contracts\Translation\TranslatorInterface;/*** @internal*/#[Route(path: '/auth', condition: "request.attributes.get('isPortalEngineSite')")]class AuthController extends AbstractSiteController{public function __construct(protected ClientInterface $httpClient){}/*** @return Response*/#[Route(path: '/login', name: 'pimcore_portalengine_auth_login')]public function loginAction(Request $request, FormFactoryInterface $formFactory, HeadTitleService $headTitleService, TranslatorInterface $translator, PortalConfigService $portalConfigService, PasswordChangeableService $passwordChangeableService, FrontendVariantService $frontendVariantService){/** @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();$guestUserActivated = !empty($portalConfigService->getCurrentPortalConfig()->getGuestUser());$headTitleService->setTitle($translator->trans('portal-engine.content.title.auth-login', ['%name%' => $portalName], 'portal_engine_frontend'));$loginForm = $formFactory->create(LoginForm::class);// The new studio login is served only to portals on the new frontend; legacy portals keep// their original Bootstrap login (auth/login.html.twig).$template = $frontendVariantService->isNew()? '@PimcorePortalEngine/auth/login-studio.html.twig': '@PimcorePortalEngine/auth/login.html.twig';return $this->renderTemplate($template, ['form' => $loginForm->createView(),'loginFailed' => (bool)$request->query->get('loginFailed'),'portalName' => $portalName,'oidcProviderNames' => array_keys($oidcProviders),'guestUserActivated' => $guestUserActivated,'showRecoverPassword' => $passwordChangeableService->isPasswordChangeable()]);}/*** @return RedirectResponse*/#[Route(path: '/logout', name: 'pimcore_portalengine_auth_logout')]public function logoutAction(Request $request){return $this->redirectToRoute('pimcore_portalengine_auth_login');}/*** @return Response*/#[Route(path: '/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, FrontendVariantService $frontendVariantService): Response{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', [], 'portal_engine_frontend'));/** @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', [], 'portal_engine_frontend'));}$recoverPasswordService->recoverPassword($userIdentifier);$frontendNotificationService->addNotification($translator->trans('portal-engine.auth.password-recover-email', [], 'portal_engine_frontend'), Notification::HTML_CLASS_SUCCESS);} catch (Exception $e) {if ($e instanceof OutputErrorException) {$frontendNotificationService->addNotification($e->getMessage(), Notification::HTML_CLASS_DANGER);}}}// Same coexistence rule as loginAction(): the studio recover page is served only to// portals on the new frontend; legacy portals keep the original Bootstrap page. Without// this the "forgot password" link dropped out of the new design and back into it at the// /user/data step, which is already migrated.$template = $frontendVariantService->isNew()? '@PimcorePortalEngine/auth/recover_password-studio.html.twig': '@PimcorePortalEngine/auth/recover_password.html.twig';return $this->renderTemplate($template, ['recoverPasswordForm' => $recoverPasswordForm->createView(),'notification' => $frontendNotificationService->getNotification()]);}/**** @return RedirectResponse|Response** @throws Exception*/#[Route(path: '/oidc/endpoint', name: 'pimcore_portalengine_auth_oidc')]public function oidcAction(Request $request, PortalConfigService $portalConfigService){if (($request->query->get('code') || $request->query->get('error')) && !$request->query->get('redirect')) {$url = $request->getSchemeAndHttpHost() . $request->getRequestUri() . '&redirect=1';$content = <<<RESPONSE<html><head><meta http-equiv="refresh" content="0;URL=' {$url}'"/></head><body><p>Redirecting to <a href="{$url}">{$url}</a>.</p></body></html>RESPONSE;return new Response($content);}if (!class_exists('Pimcore\\Bundle\\OpenIdConnectBundle\\PimcoreOpenIdConnectBundle')) {throw new Exception('OpenID Connect Bundle not available');}if ($request->query->get('error')) {throw new Exception(strip_tags($request->query->getString('error')) . ': ' . strip_tags($request->query->getString('error_description')));}$portalConfig = $portalConfigService->getCurrentPortalConfig();$session = $request->getSession();/** @var AttributeBag $sessionBag */$sessionBag = $session->getBag(OpenIdSessionBagListener::SESSION_BAG_NAME);// if coming back from provider, use provider from sessionif ($request->query->get('code')) {$providerName = $sessionBag->get(OpenIdConnectAuthenticator::SESSION_KEY_PROVIDER);} else {$providerName = $request->query->get('provider');Session::useBag($request->getSession(), function (AttributeBagInterface $sessionBag) use ($providerName) {$sessionBag->set(OpenIdConnectAuthenticator::SESSION_KEY_PROVIDER, $providerName);}, OpenIdSessionBagListener::SESSION_BAG_NAME);}$oidcConfig = $portalConfig?->getOidcConfig($providerName);if ($oidcConfig === null) {throw new InvalidArgumentException(sprintf('Provider `%s` unknown.', strip_tags($request->query->getString('provider'))));}$provider = new OpenIdConnectProvider(['clientId' => $oidcConfig->getClientId(),'clientSecret' => $oidcConfig->getClientSecret(),'urlDiscovery' => $oidcConfig->getDiscoveryUrl(),'redirectUri' => $request->getSchemeAndHttpHost() . $request->getPathInfo(),'scopes' => $oidcConfig->getScopes()]);$provider->setHttpClient($this->httpClient);if (!$request->query->get('code')) {// If we don't have an authorization code then get one$authUrl = $provider->getAuthorizationUrl();Session::useBag($request->getSession(), function (AttributeBagInterface $sessionBag) use ($provider) {$sessionBag->set(OpenIdConnectAuthenticator::SESSION_KEY_STATE, $provider->getState());}, OpenIdSessionBagListener::SESSION_BAG_NAME);return $this->redirect($authUrl);} elseif (!$request->query->get('state') || ($request->query->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');}}