diff --git a/typo3/sysext/backend/Classes/Controller/AbstractMfaController.php b/typo3/sysext/backend/Classes/Controller/AbstractMfaController.php index df7ec6722589..6f9bdfe551cb 100644 --- a/typo3/sysext/backend/Classes/Controller/AbstractMfaController.php +++ b/typo3/sysext/backend/Classes/Controller/AbstractMfaController.php @@ -19,9 +19,6 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use TYPO3\CMS\Backend\Routing\UriBuilder; -use TYPO3\CMS\Backend\Template\ModuleTemplate; -use TYPO3\CMS\Backend\Template\ModuleTemplateFactory; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; use TYPO3\CMS\Core\Authentication\Mfa\MfaProviderManifestInterface; use TYPO3\CMS\Core\Authentication\Mfa\MfaProviderRegistry; @@ -35,24 +32,15 @@ */ abstract class AbstractMfaController { - protected UriBuilder $uriBuilder; protected MfaProviderRegistry $mfaProviderRegistry; - protected ModuleTemplateFactory $moduleTemplateFactory; - protected ?ModuleTemplate $moduleTemplate; protected array $mfaTsConfig; protected bool $mfaRequired; protected array $allowedProviders; protected array $allowedActions = []; - public function __construct( - UriBuilder $uriBuilder, - MfaProviderRegistry $mfaProviderRegistry, - ModuleTemplateFactory $moduleTemplateFactory - ) { - $this->uriBuilder = $uriBuilder; + public function injectMfaProviderRegistry(MfaProviderRegistry $mfaProviderRegistry): void + { $this->mfaProviderRegistry = $mfaProviderRegistry; - $this->moduleTemplateFactory = $moduleTemplateFactory; - $this->initializeMfaConfiguration(); } /** @@ -115,7 +103,6 @@ protected function getRecommendedProvider(): ?MfaProviderManifestInterface return null; } } - return $this->mfaProviderRegistry->getProvider($recommendedProviderIdentifier); } diff --git a/typo3/sysext/backend/Classes/Controller/LoginController.php b/typo3/sysext/backend/Classes/Controller/LoginController.php index d74adfc3f879..392a1e1b76aa 100644 --- a/typo3/sysext/backend/Classes/Controller/LoginController.php +++ b/typo3/sysext/backend/Classes/Controller/LoginController.php @@ -27,10 +27,10 @@ use TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException; use TYPO3\CMS\Backend\Routing\RouteRedirect; use TYPO3\CMS\Backend\Routing\UriBuilder; -use TYPO3\CMS\Backend\Template\ModuleTemplate; -use TYPO3\CMS\Backend\Template\ModuleTemplateFactory; +use TYPO3\CMS\Backend\Template\PageRendererBackendSetupTrait; use TYPO3\CMS\Backend\View\AuthenticationStyleInformation; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; +use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; use TYPO3\CMS\Core\Configuration\Features; use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\Database\ConnectionPool; @@ -48,11 +48,14 @@ use TYPO3\CMS\Fluid\View\StandaloneView; /** - * Controller responsible for rendering the TYPO3 Backend login form + * Controller responsible for rendering the TYPO3 Backend login form. + * * @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API. */ class LoginController { + use PageRendererBackendSetupTrait; + /** * The URL to redirect to after login. * @@ -94,39 +97,20 @@ class LoginController protected $view; /** - * @var ModuleTemplate + * @todo: Only set for getCurrentRequest(). Should vanish. */ - protected $moduleTemplate; - - protected EventDispatcherInterface $eventDispatcher; - protected Typo3Information $typo3Information; - protected PageRenderer $pageRenderer; - protected UriBuilder $uriBuilder; - protected Features $features; - protected Context $context; - protected ModuleTemplateFactory $moduleTemplateFactory; - protected LoginProviderResolver $loginProviderResolver; - - protected ?ServerRequestInterface $currentRequest = null; + protected ServerRequestInterface $request; public function __construct( - Typo3Information $typo3Information, - EventDispatcherInterface $eventDispatcher, - PageRenderer $pageRenderer, - UriBuilder $uriBuilder, - Features $features, - Context $context, - ModuleTemplateFactory $moduleTemplateFactory, - LoginProviderResolver $loginProviderResolver + protected readonly Typo3Information $typo3Information, + protected readonly EventDispatcherInterface $eventDispatcher, + protected readonly PageRenderer $pageRenderer, + protected readonly UriBuilder $uriBuilder, + protected readonly Features $features, + protected readonly Context $context, + protected readonly LoginProviderResolver $loginProviderResolver, + protected readonly ExtensionConfiguration $extensionConfiguration, ) { - $this->typo3Information = $typo3Information; - $this->eventDispatcher = $eventDispatcher; - $this->uriBuilder = $uriBuilder; - $this->pageRenderer = $pageRenderer; - $this->features = $features; - $this->context = $context; - $this->moduleTemplateFactory = $moduleTemplateFactory; - $this->loginProviderResolver = $loginProviderResolver; } /** @@ -135,6 +119,7 @@ public function __construct( */ public function formAction(ServerRequestInterface $request): ResponseInterface { + $this->request = $request; $this->init($request); $response = new HtmlResponse($this->createLoginLogoutForm($request)); return $this->appendLoginProviderCookie($request->getAttribute('normalizedParams'), $response); @@ -145,6 +130,7 @@ public function formAction(ServerRequestInterface $request): ResponseInterface */ public function refreshAction(ServerRequestInterface $request): ResponseInterface { + $this->request = $request; $this->init($request); $this->loginRefresh = true; $response = new HtmlResponse($this->createLoginLogoutForm($request)); @@ -152,8 +138,25 @@ public function refreshAction(ServerRequestInterface $request): ResponseInterfac } /** - * If a login provider was chosen in the previous request, which is not the default provider, it is stored in a - * Cookie and appended to the HTTP Response. + * @todo: Ugly. This can be used by login providers, they receive an instance of $this. + * Unused in core, though. It should vanish when login providers receive love. + */ + public function getLoginProviderIdentifier(): string + { + return $this->loginProviderIdentifier; + } + + /** + * @todo: Ugly. This can be used by login providers, they receive an instance of $this. + */ + public function getCurrentRequest(): ServerRequestInterface + { + return $this->request; + } + + /** + * If a login provider was chosen in the previous request, which is not the default provider, + * it is stored in a Cookie and appended to the HTTP Response. */ protected function appendLoginProviderCookie(NormalizedParams $normalizedParams, ResponseInterface $response): ResponseInterface { @@ -175,24 +178,19 @@ protected function appendLoginProviderCookie(NormalizedParams $normalizedParams, return $response->withAddedHeader('Set-Cookie', $cookie->__toString()); } - /** - * This can be called by single login providers, they receive an instance of $this. - */ - public function getLoginProviderIdentifier(): string - { - return $this->loginProviderIdentifier; - } - /** * Initialize the login box. Will also react on a &L=OUT flag and exit. */ protected function init(ServerRequestInterface $request): void { - $this->moduleTemplate = $this->moduleTemplateFactory->create($request); - $this->moduleTemplate->setTitle('TYPO3 CMS Login: ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename']); + $languageService = $this->getLanguageService(); + $backendUser = $this->getBackendUserAuthentication(); $parsedBody = $request->getParsedBody(); $queryParams = $request->getQueryParams(); + $this->setUpBasicPageRendererForBackend($this->pageRenderer, $this->extensionConfiguration, $request, $languageService); + $this->pageRenderer->setTitle('TYPO3 CMS Login: ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ?? ''); + $this->redirectUrl = GeneralUtility::sanitizeLocalUrl($parsedBody['redirect_url'] ?? $queryParams['redirect_url'] ?? null); $this->loginProviderIdentifier = $this->loginProviderResolver->resolveLoginProviderIdentifierFromRequest($request, 'be_lastLoginProvider'); @@ -203,15 +201,13 @@ protected function init(ServerRequestInterface $request): void $httpAcceptLanguage = $request->getServerParams()['HTTP_ACCEPT_LANGUAGE'] ?? ''; $preferredBrowserLanguage = GeneralUtility::makeInstance(Locales::class)->getPreferredClientLanguage($httpAcceptLanguage); - // If we found a $preferredBrowserLanguage and it is not the default language and no be_user is logged in + // If we found a $preferredBrowserLanguage, and it is not the default language and no be_user is logged in, // initialize $this->getLanguageService() again with $preferredBrowserLanguage - if ($preferredBrowserLanguage !== 'default' && empty($this->getBackendUserAuthentication()->user['uid'])) { - $this->getLanguageService()->init($preferredBrowserLanguage); + if ($preferredBrowserLanguage !== 'default' && empty($backendUser->user['uid'])) { + $languageService->init($preferredBrowserLanguage); $this->pageRenderer->setLanguage($preferredBrowserLanguage); } - $this->getLanguageService()->includeLLFile('EXT:backend/Resources/Private/Language/locallang_login.xlf'); - // Setting the redirect URL to "index.php?M=main" if no alternative input is given if ($this->redirectUrl) { $this->redirectToURL = $this->redirectUrl; @@ -221,14 +217,18 @@ protected function init(ServerRequestInterface $request): void } // If "L" is "OUT", then any logged in is logged out. If redirect_url is given, we redirect to it - if (($parsedBody['L'] ?? $queryParams['L'] ?? null) === 'OUT' && is_object($this->getBackendUserAuthentication())) { - $this->getBackendUserAuthentication()->logoff(); + if (($parsedBody['L'] ?? $queryParams['L'] ?? null) === 'OUT' && is_object($backendUser)) { + $backendUser->logoff(); $this->redirectToUrl(); } - $this->pageRenderer->loadJavaScriptModule('TYPO3/CMS/Backend/Login.js'); - $this->view = $this->moduleTemplate->getView(); - $this->view->getRequest()->setControllerExtensionName('Backend'); + // @todo: This should be ViewInterface. But this breaks LoginProviderInterface AND ModifyPageLayoutOnLoginProviderSelectionEvent + $this->view = GeneralUtility::makeInstance(StandaloneView::class); + // StandaloneView should NOT receive a request at all, override the default StandaloneView constructor here. + $this->view->setRequest(); + $this->view->setTemplateRootPaths(['EXT:backend/Resources/Private/Templates']); + $this->view->setLayoutRootPaths(['EXT:backend/Resources/Private/Layouts']); + $this->view->setPartialRootPaths(['EXT:backend/Resources/Private/Partials']); $this->provideCustomLoginStyling(); $this->view->assign('referrerCheckEnabled', $this->features->isFeatureEnabled('security.backend.enforceReferrer')); $this->view->assign('loginUrl', (string)$request->getUri()); @@ -237,6 +237,7 @@ protected function init(ServerRequestInterface $request): void protected function provideCustomLoginStyling(): void { + $languageService = $this->getLanguageService(); $authenticationStyleInformation = GeneralUtility::makeInstance(AuthenticationStyleInformation::class); if (($backgroundImageStyles = $authenticationStyleInformation->getBackgroundImageStyles()) !== '') { $this->pageRenderer->addCssInlineBlock('loginBackgroundImage', $backgroundImageStyles); @@ -248,10 +249,10 @@ protected function provideCustomLoginStyling(): void $this->pageRenderer->addCssInlineBlock('loginHighlightColor', $highlightColorStyles); } if (($logo = $authenticationStyleInformation->getLogo()) !== '') { - $logoAlt = $authenticationStyleInformation->getLogoAlt() ?: $this->getLanguageService()->getLL('typo3.altText'); + $logoAlt = $authenticationStyleInformation->getLogoAlt() ?: $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_login.xlf:typo3.altText'); } else { $logo = $authenticationStyleInformation->getDefaultLogo(); - $logoAlt = $this->getLanguageService()->getLL('typo3.altText'); + $logoAlt = $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_login.xlf:typo3.altText'); $this->pageRenderer->addCssInlineBlock('loginLogo', $authenticationStyleInformation->getDefaultLogoStyles()); } $this->view->assignMultiple([ @@ -267,12 +268,14 @@ protected function provideCustomLoginStyling(): void */ protected function createLoginLogoutForm(ServerRequestInterface $request): string { + $backendUser = $this->getBackendUserAuthentication(); + // Checking, if we should make a redirect. // Might set JavaScript in the header to close window. $this->checkRedirect($request); // Show login form - if (empty($this->getBackendUserAuthentication()->user['uid'])) { + if (empty($backendUser->user['uid'])) { $action = 'login'; $formActionUrl = $this->uriBuilder->buildUriWithRedirect( 'login', @@ -287,7 +290,7 @@ protected function createLoginLogoutForm(ServerRequestInterface $request): strin $formActionUrl = $this->uriBuilder->buildUriFromRoute('logout'); } $this->view->assignMultiple([ - 'backendUser' => $this->getBackendUserAuthentication()->user, + 'backendUser' => $backendUser->user, 'hasLoginError' => $this->isLoginInProgress($request), 'action' => $action, 'formActionUrl' => $formActionUrl, @@ -304,15 +307,14 @@ protected function createLoginLogoutForm(ServerRequestInterface $request): strin // Initialize interface selectors: $this->makeInterfaceSelector($request); - $this->renderHtmlViaLoginProvider($request); + $this->renderHtmlViaLoginProvider(); - $this->moduleTemplate->setContent($this->view->render()); - return $this->moduleTemplate->renderContent(); + $this->pageRenderer->setBodyContent('' . $this->view->render()); + return $this->pageRenderer->render(); } - protected function renderHtmlViaLoginProvider(ServerRequestInterface $request): void + protected function renderHtmlViaLoginProvider(): void { - $this->currentRequest = $request; $loginProviderConfiguration = $this->loginProviderResolver->getLoginProviderConfigurationByIdentifier($this->loginProviderIdentifier); /** @var LoginProviderInterface $loginProvider */ $loginProvider = GeneralUtility::makeInstance($loginProviderConfiguration['provider']); @@ -324,7 +326,6 @@ protected function renderHtmlViaLoginProvider(ServerRequestInterface $request): ) ); $loginProvider->render($this->view, $this->pageRenderer, $this); - $this->currentRequest = null; } /** @@ -396,6 +397,7 @@ protected function checkRedirect(ServerRequestInterface $request): void */ protected function makeInterfaceSelector(ServerRequestInterface $request): void { + $languageService = $this->getLanguageService(); // If interfaces are defined AND no input redirect URL in GET vars: if ($GLOBALS['TYPO3_CONF_VARS']['BE']['interfaces'] && ($this->isLoginInProgress($request) || !$this->redirectUrl)) { $parts = GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['BE']['interfaces']); @@ -403,12 +405,12 @@ protected function makeInterfaceSelector(ServerRequestInterface $request): void // Only if more than one interface is defined we will show the selector $interfaces = [ 'backend' => [ - 'label' => $this->getLanguageService()->getLL('interface.backend'), + 'label' => $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_login.xlf:interface.backend'), 'jumpScript' => (string)$this->uriBuilder->buildUriFromRoute('main'), 'interface' => 'backend', ], 'frontend' => [ - 'label' => $this->getLanguageService()->getLL('interface.frontend'), + 'label' => $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_login.xlf:interface.frontend'), 'jumpScript' => '../', 'interface' => 'frontend', ], @@ -425,9 +427,8 @@ protected function makeInterfaceSelector(ServerRequestInterface $request): void } /** - * Gets news from sys_news and converts them into a format suitable for showing them at the login screen. - * - * @return array An array of login news. + * Gets news as array from sys_news and converts them into a + * format suitable for showing them at the login screen. */ protected function getSystemNews(): array { @@ -482,9 +483,4 @@ protected function getBackendUserAuthentication(): BackendUserAuthentication { return $GLOBALS['BE_USER']; } - - public function getCurrentRequest(): ?ServerRequestInterface - { - return $this->currentRequest; - } } diff --git a/typo3/sysext/backend/Classes/Controller/MfaConfigurationController.php b/typo3/sysext/backend/Classes/Controller/MfaConfigurationController.php index 5a6adbf860c8..ca3d21513ae5 100644 --- a/typo3/sysext/backend/Classes/Controller/MfaConfigurationController.php +++ b/typo3/sysext/backend/Classes/Controller/MfaConfigurationController.php @@ -22,15 +22,16 @@ use Psr\Http\Message\UriInterface; use TYPO3\CMS\Backend\Routing\UriBuilder; use TYPO3\CMS\Backend\Template\Components\ButtonBar; +use TYPO3\CMS\Backend\Template\ModuleTemplate; use TYPO3\CMS\Backend\Template\ModuleTemplateFactory; use TYPO3\CMS\Core\Authentication\Mfa\MfaProviderManifestInterface; use TYPO3\CMS\Core\Authentication\Mfa\MfaProviderPropertyManager; -use TYPO3\CMS\Core\Authentication\Mfa\MfaProviderRegistry; use TYPO3\CMS\Core\Authentication\Mfa\MfaViewType; use TYPO3\CMS\Core\Http\HtmlResponse; use TYPO3\CMS\Core\Http\RedirectResponse; use TYPO3\CMS\Core\Imaging\Icon; use TYPO3\CMS\Core\Imaging\IconFactory; +use TYPO3\CMS\Core\Messaging\AbstractMessage; use TYPO3\CMS\Core\Messaging\FlashMessage; use TYPO3\CMS\Core\Messaging\FlashMessageService; use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; @@ -47,17 +48,13 @@ class MfaConfigurationController extends AbstractMfaController protected array $allowedActions = ['overview', 'setup', 'activate', 'deactivate', 'unlock', 'edit', 'save']; private array $providerActionsWhenInactive = ['setup', 'activate']; private array $providerActionsWhenActive = ['deactivate', 'unlock', 'edit', 'save']; - - protected IconFactory $iconFactory; + protected ModuleTemplate $moduleTemplate; public function __construct( - IconFactory $iconFactory, - UriBuilder $uriBuilder, - MfaProviderRegistry $mfaProviderRegistry, - ModuleTemplateFactory $moduleTemplateFactory + protected readonly IconFactory $iconFactory, + protected readonly UriBuilder $uriBuilder, + protected readonly ModuleTemplateFactory $moduleTemplateFactory, ) { - $this->iconFactory = $iconFactory; - parent::__construct($uriBuilder, $mfaProviderRegistry, $moduleTemplateFactory); } /** @@ -66,6 +63,7 @@ public function __construct( */ public function handleRequest(ServerRequestInterface $request): ResponseInterface { + $this->initializeMfaConfiguration(); $this->moduleTemplate = $this->moduleTemplateFactory->create($request); $action = (string)($request->getQueryParams()['action'] ?? $request->getParsedBody()['action'] ?? 'overview'); @@ -82,7 +80,7 @@ public function handleRequest(ServerRequestInterface $request): ResponseInterfac // All actions expect "overview" require a provider to deal with. // If non is found at this point, initiate a redirect to the overview. if ($mfaProvider === null && $action !== 'overview') { - $this->addFlashMessage($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:providerNotFound'), '', FlashMessage::ERROR); + $this->addFlashMessage($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:providerNotFound'), '', AbstractMessage::ERROR); return new RedirectResponse($this->getActionUri('overview')); } // If a valid provider is given, check if the requested action can be performed on this provider @@ -92,12 +90,12 @@ public function handleRequest(ServerRequestInterface $request): ResponseInterfac ); // Some actions require the provider to be inactive if ($isProviderActive && in_array($action, $this->providerActionsWhenInactive, true)) { - $this->addFlashMessage($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:providerActive'), '', FlashMessage::ERROR); + $this->addFlashMessage($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:providerActive'), '', AbstractMessage::ERROR); return new RedirectResponse($this->getActionUri('overview')); } // Some actions require the provider to be active if (!$isProviderActive && in_array($action, $this->providerActionsWhenActive, true)) { - $this->addFlashMessage($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:providerNotActive'), '', FlashMessage::ERROR); + $this->addFlashMessage($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:providerNotActive'), '', AbstractMessage::ERROR); return new RedirectResponse($this->getActionUri('overview')); } } @@ -120,7 +118,7 @@ public function handleRequest(ServerRequestInterface $request): ResponseInterfac /** * Setup the overview with all available MFA providers */ - public function overviewAction(ServerRequestInterface $request): ResponseInterface + protected function overviewAction(ServerRequestInterface $request): ResponseInterface { $this->addOverviewButtons($request); $view = $this->initializeView(); @@ -137,7 +135,7 @@ public function overviewAction(ServerRequestInterface $request): ResponseInterfa /** * Render form to setup a provider by using provider specific content */ - public function setupAction(ServerRequestInterface $request, MfaProviderManifestInterface $mfaProvider): ResponseInterface + protected function setupAction(ServerRequestInterface $request, MfaProviderManifestInterface $mfaProvider): ResponseInterface { $this->addFormButtons(); $propertyManager = MfaProviderPropertyManager::create($mfaProvider, $this->getBackendUser()); @@ -160,7 +158,7 @@ public function setupAction(ServerRequestInterface $request, MfaProviderManifest * to be a default provider and there are no other providers which * would suite as default provider. */ - public function activateAction(ServerRequestInterface $request, MfaProviderManifestInterface $mfaProvider): ResponseInterface + protected function activateAction(ServerRequestInterface $request, MfaProviderManifestInterface $mfaProvider): ResponseInterface { $backendUser = $this->getBackendUser(); $isRecommendedProvider = $this->getRecommendedProviderIdentifier() === $mfaProvider->getIdentifier(); @@ -168,7 +166,7 @@ public function activateAction(ServerRequestInterface $request, MfaProviderManif $languageService = $this->getLanguageService(); // Check whether activation operation was successful and the provider is now active. if (!$mfaProvider->activate($request, $propertyManager) || !$mfaProvider->isActive($propertyManager)) { - $this->addFlashMessage(sprintf($languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:activate.failure'), $languageService->sL($mfaProvider->getTitle())), '', FlashMessage::ERROR); + $this->addFlashMessage(sprintf($languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:activate.failure'), $languageService->sL($mfaProvider->getTitle())), '', AbstractMessage::ERROR); return new RedirectResponse($this->getActionUri('setup', ['identifier' => $mfaProvider->getIdentifier()])); } if ($isRecommendedProvider @@ -186,7 +184,7 @@ public function activateAction(ServerRequestInterface $request, MfaProviderManif if (!(bool)($backendUser->getSessionData('mfa') ?? false)) { $backendUser->setSessionData('mfa', true); } - $this->addFlashMessage(sprintf($languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:activate.success'), $languageService->sL($mfaProvider->getTitle())), '', FlashMessage::OK); + $this->addFlashMessage(sprintf($languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:activate.success'), $languageService->sL($mfaProvider->getTitle())), '', AbstractMessage::OK); return new RedirectResponse($this->getActionUri('overview')); } @@ -195,17 +193,17 @@ public function activateAction(ServerRequestInterface $request, MfaProviderManif * appropriate provider. Also remove the provider as default * provider from user UC, if set. */ - public function deactivateAction(ServerRequestInterface $request, MfaProviderManifestInterface $mfaProvider): ResponseInterface + protected function deactivateAction(ServerRequestInterface $request, MfaProviderManifestInterface $mfaProvider): ResponseInterface { $propertyManager = MfaProviderPropertyManager::create($mfaProvider, $this->getBackendUser()); $languageService = $this->getLanguageService(); if (!$mfaProvider->deactivate($request, $propertyManager)) { - $this->addFlashMessage(sprintf($languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:deactivate.failure'), $languageService->sL($mfaProvider->getTitle())), '', FlashMessage::ERROR); + $this->addFlashMessage(sprintf($languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:deactivate.failure'), $languageService->sL($mfaProvider->getTitle())), '', AbstractMessage::ERROR); } else { if ($this->isDefaultProvider($mfaProvider)) { $this->removeDefaultProvider(); } - $this->addFlashMessage(sprintf($languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:deactivate.success'), $languageService->sL($mfaProvider->getTitle())), '', FlashMessage::OK); + $this->addFlashMessage(sprintf($languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:deactivate.success'), $languageService->sL($mfaProvider->getTitle())), '', AbstractMessage::OK); } return new RedirectResponse($this->getActionUri('overview')); } @@ -213,14 +211,14 @@ public function deactivateAction(ServerRequestInterface $request, MfaProviderMan /** * Handle unlock request by forwarding the request to the appropriate provider */ - public function unlockAction(ServerRequestInterface $request, MfaProviderManifestInterface $mfaProvider): ResponseInterface + protected function unlockAction(ServerRequestInterface $request, MfaProviderManifestInterface $mfaProvider): ResponseInterface { $propertyManager = MfaProviderPropertyManager::create($mfaProvider, $this->getBackendUser()); $languageService = $this->getLanguageService(); if (!$mfaProvider->unlock($request, $propertyManager)) { - $this->addFlashMessage(sprintf($languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:unlock.failure'), $languageService->sL($mfaProvider->getTitle())), '', FlashMessage::ERROR); + $this->addFlashMessage(sprintf($languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:unlock.failure'), $languageService->sL($mfaProvider->getTitle())), '', AbstractMessage::ERROR); } else { - $this->addFlashMessage(sprintf($languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:unlock.success'), $languageService->sL($mfaProvider->getTitle())), '', FlashMessage::OK); + $this->addFlashMessage(sprintf($languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:unlock.success'), $languageService->sL($mfaProvider->getTitle())), '', AbstractMessage::OK); } return new RedirectResponse($this->getActionUri('overview')); } @@ -228,12 +226,12 @@ public function unlockAction(ServerRequestInterface $request, MfaProviderManifes /** * Render form to edit a provider by using provider specific content */ - public function editAction(ServerRequestInterface $request, MfaProviderManifestInterface $mfaProvider): ResponseInterface + protected function editAction(ServerRequestInterface $request, MfaProviderManifestInterface $mfaProvider): ResponseInterface { $propertyManager = MfaProviderPropertyManager::create($mfaProvider, $this->getBackendUser()); if ($mfaProvider->isLocked($propertyManager)) { // Do not show edit view for locked providers - $this->addFlashMessage($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:providerIsLocked'), '', FlashMessage::ERROR); + $this->addFlashMessage($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:providerIsLocked'), '', AbstractMessage::ERROR); return new RedirectResponse($this->getActionUri('overview')); } $this->addFormButtons(); @@ -252,19 +250,19 @@ public function editAction(ServerRequestInterface $request, MfaProviderManifestI * Handle save request, receiving from the edit view by * forwarding the request to the appropriate provider. */ - public function saveAction(ServerRequestInterface $request, MfaProviderManifestInterface $mfaProvider): ResponseInterface + protected function saveAction(ServerRequestInterface $request, MfaProviderManifestInterface $mfaProvider): ResponseInterface { $propertyManager = MfaProviderPropertyManager::create($mfaProvider, $this->getBackendUser()); $languageService = $this->getLanguageService(); if (!$mfaProvider->update($request, $propertyManager)) { - $this->addFlashMessage(sprintf($languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:save.failure'), $languageService->sL($mfaProvider->getTitle())), '', FlashMessage::ERROR); + $this->addFlashMessage(sprintf($languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:save.failure'), $languageService->sL($mfaProvider->getTitle())), '', AbstractMessage::ERROR); } else { - if ((bool)($request->getParsedBody()['defaultProvider'] ?? false)) { + if ($request->getParsedBody()['defaultProvider'] ?? false) { $this->setDefaultProvider($mfaProvider); } elseif ($this->isDefaultProvider($mfaProvider)) { $this->removeDefaultProvider(); } - $this->addFlashMessage(sprintf($languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:save.success'), $languageService->sL($mfaProvider->getTitle())), '', FlashMessage::OK); + $this->addFlashMessage(sprintf($languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:save.success'), $languageService->sL($mfaProvider->getTitle())), '', AbstractMessage::OK); } if (!$mfaProvider->isActive($propertyManager)) { return new RedirectResponse($this->getActionUri('overview')); @@ -365,7 +363,7 @@ protected function removeDefaultProvider(): void $this->getBackendUser()->writeUC(); } - protected function addFlashMessage(string $message, string $title = '', int $severity = FlashMessage::INFO): void + protected function addFlashMessage(string $message, string $title = '', int $severity = AbstractMessage::INFO): void { $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, $title, $severity, true); $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class); diff --git a/typo3/sysext/backend/Classes/Controller/MfaController.php b/typo3/sysext/backend/Classes/Controller/MfaController.php index 2574ac452548..1668f3e68f63 100644 --- a/typo3/sysext/backend/Classes/Controller/MfaController.php +++ b/typo3/sysext/backend/Classes/Controller/MfaController.php @@ -19,43 +19,39 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Psr\Log\LoggerAwareInterface; -use Psr\Log\LoggerAwareTrait; +use Psr\Log\LoggerInterface; use TYPO3\CMS\Backend\ContextMenu\ItemProviders\ProviderInterface; use TYPO3\CMS\Backend\Routing\RouteRedirect; use TYPO3\CMS\Backend\Routing\UriBuilder; -use TYPO3\CMS\Backend\Template\ModuleTemplateFactory; +use TYPO3\CMS\Backend\Template\PageRendererBackendSetupTrait; use TYPO3\CMS\Backend\View\AuthenticationStyleInformation; use TYPO3\CMS\Core\Authentication\Mfa\MfaProviderManifestInterface; use TYPO3\CMS\Core\Authentication\Mfa\MfaProviderPropertyManager; -use TYPO3\CMS\Core\Authentication\Mfa\MfaProviderRegistry; use TYPO3\CMS\Core\Authentication\Mfa\MfaViewType; +use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; use TYPO3\CMS\Core\Http\HtmlResponse; use TYPO3\CMS\Core\Http\RedirectResponse; use TYPO3\CMS\Core\Page\PageRenderer; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Fluid\View\BackendTemplateView; /** - * Controller to provide a multi-factor authentication endpoint + * Controller to provide a multi-factor authentication endpoint. + * This is the backend login related view to authenticate against chosen MFA provider. * * @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API. */ -class MfaController extends AbstractMfaController implements LoggerAwareInterface +class MfaController extends AbstractMfaController { - use LoggerAwareTrait; - - protected AuthenticationStyleInformation $authenticationStyleInformation; - protected PageRenderer $pageRenderer; + use PageRendererBackendSetupTrait; public function __construct( - UriBuilder $uriBuilder, - MfaProviderRegistry $mfaProviderRegistry, - ModuleTemplateFactory $moduleTemplateFactory, - AuthenticationStyleInformation $authenticationStyleInformation, - PageRenderer $pageRenderer + protected readonly UriBuilder $uriBuilder, + protected readonly AuthenticationStyleInformation $authenticationStyleInformation, + protected readonly PageRenderer $pageRenderer, + protected readonly ExtensionConfiguration $extensionConfiguration, + protected readonly LoggerInterface $logger, ) { - parent::__construct($uriBuilder, $mfaProviderRegistry, $moduleTemplateFactory); - $this->authenticationStyleInformation = $authenticationStyleInformation; - $this->pageRenderer = $pageRenderer; } /** @@ -64,7 +60,7 @@ public function __construct( */ public function handleRequest(ServerRequestInterface $request): ResponseInterface { - $this->moduleTemplate = $this->moduleTemplateFactory->create($request); + $this->initializeMfaConfiguration(); $action = (string)($request->getQueryParams()['action'] ?? $request->getParsedBody()['action'] ?? 'auth'); switch ($action) { @@ -85,23 +81,15 @@ public function handleRequest(ServerRequestInterface $request): ResponseInterfac } /** - * Setup the authentication view for the provider by using provider specific content + * Set up the authentication view for the provider by using provider specific content. */ - public function authAction(ServerRequestInterface $request, MfaProviderManifestInterface $mfaProvider): ResponseInterface + protected function authAction(ServerRequestInterface $request, MfaProviderManifestInterface $mfaProvider): ResponseInterface { - $view = $this->moduleTemplate->getView(); - $view->setTemplateRootPaths(['EXT:backend/Resources/Private/Templates/Mfa']); - $view->setTemplate('Auth'); - $view->assign('formUrl', $this->uriBuilder->buildUriWithRedirect( - 'auth_mfa', - [ - 'action' => 'verify', - ], - RouteRedirect::createFromRequest($request) - )); - $view->assign('redirectRoute', $request->getQueryParams()['redirect'] ?? ''); - $view->assign('redirectParams', $request->getQueryParams()['redirectParams'] ?? ''); - $view->assign('hasAuthError', (bool)($request->getQueryParams()['failure'] ?? false)); + $this->setUpBasicPageRendererForBackend($this->pageRenderer, $this->extensionConfiguration, $request, $this->getLanguageService()); + $this->pageRenderer->setTitle('TYPO3 CMS Login: ' . ($GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ?? '')); + $this->pageRenderer->loadJavaScriptModule('bootstrap'); + $view = GeneralUtility::makeInstance(BackendTemplateView::class); + $view->setTemplateRootPaths(['EXT:backend/Resources/Private/Templates']); $propertyManager = MfaProviderPropertyManager::create($mfaProvider, $this->getBackendUser()); $providerResponse = $mfaProvider->handleRequest($request, $propertyManager, MfaViewType::AUTH); $view->assignMultiple([ @@ -110,17 +98,21 @@ public function authAction(ServerRequestInterface $request, MfaProviderManifestI 'isLocked' => $mfaProvider->isLocked($propertyManager), 'providerContent' => $providerResponse->getBody(), 'footerNote' => $this->authenticationStyleInformation->getFooterNote(), + 'formUrl' => $this->uriBuilder->buildUriWithRedirect('auth_mfa', ['action' => 'verify'], RouteRedirect::createFromRequest($request)), + 'redirectRoute' => $request->getQueryParams()['redirect'] ?? '', + 'redirectParams' => $request->getQueryParams()['redirectParams'] ?? '', + 'hasAuthError' => (bool)($request->getQueryParams()['failure'] ?? false), ]); - $this->moduleTemplate->setTitle('TYPO3 CMS Login: ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename']); $this->addCustomAuthenticationFormStyles(); - return new HtmlResponse($this->moduleTemplate->renderContent()); + $this->pageRenderer->setBodyContent('' . $view->render('Mfa/Auth')); + return new HtmlResponse($this->pageRenderer->render()); } /** * Handle verification request, receiving from the auth view * by forwarding the request to the appropriate provider. */ - public function verifyAction(ServerRequestInterface $request, MfaProviderManifestInterface $mfaProvider): ResponseInterface + protected function verifyAction(ServerRequestInterface $request, MfaProviderManifestInterface $mfaProvider): ResponseInterface { $propertyManager = MfaProviderPropertyManager::create($mfaProvider, $this->getBackendUser()); @@ -157,7 +149,7 @@ public function verifyAction(ServerRequestInterface $request, MfaProviderManifes * other already gathered information and finally initiate a * redirect back to the login. */ - public function cancelAction(ServerRequestInterface $request): ResponseInterface + protected function cancelAction(ServerRequestInterface $request): ResponseInterface { $this->log('Multi-factor authentication canceled'); $this->getBackendUser()->logoff(); diff --git a/typo3/sysext/backend/Classes/Controller/MfaSetupController.php b/typo3/sysext/backend/Classes/Controller/MfaSetupController.php index 1c5351da5f82..c99fdf7a55c1 100644 --- a/typo3/sysext/backend/Classes/Controller/MfaSetupController.php +++ b/typo3/sysext/backend/Classes/Controller/MfaSetupController.php @@ -22,12 +22,12 @@ use Psr\Log\LoggerInterface; use TYPO3\CMS\Backend\Routing\RouteRedirect; use TYPO3\CMS\Backend\Routing\UriBuilder; -use TYPO3\CMS\Backend\Template\ModuleTemplateFactory; +use TYPO3\CMS\Backend\Template\PageRendererBackendSetupTrait; use TYPO3\CMS\Backend\View\AuthenticationStyleInformation; use TYPO3\CMS\Core\Authentication\Mfa\MfaProviderManifestInterface; use TYPO3\CMS\Core\Authentication\Mfa\MfaProviderPropertyManager; -use TYPO3\CMS\Core\Authentication\Mfa\MfaProviderRegistry; use TYPO3\CMS\Core\Authentication\Mfa\MfaViewType; +use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; use TYPO3\CMS\Core\Http\HtmlResponse; use TYPO3\CMS\Core\Http\RedirectResponse; use TYPO3\CMS\Core\Messaging\AbstractMessage; @@ -35,42 +35,36 @@ use TYPO3\CMS\Core\Messaging\FlashMessageService; use TYPO3\CMS\Core\Page\PageRenderer; use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Fluid\View\StandaloneView; +use TYPO3\CMS\Fluid\View\BackendTemplateView; /** - * Controller to provide the standalone setup endpoint for multi-factor authentication + * Controller to provide the standalone setup endpoint for multi-factor authentication. + * This is used when MFA is enforced and a backend user logs in the first time to then set up MFA. * * @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API. */ class MfaSetupController extends AbstractMfaController { + use PageRendererBackendSetupTrait; + protected const ACTION_METHOD_MAP = [ 'setup' => 'GET', 'activate' => 'POST', 'cancel' => 'GET', ]; - protected AuthenticationStyleInformation $authenticationStyleInformation; - protected PageRenderer $pageRenderer; - protected LoggerInterface $logger; - public function __construct( - UriBuilder $uriBuilder, - MfaProviderRegistry $mfaProviderRegistry, - ModuleTemplateFactory $moduleTemplateFactory, - AuthenticationStyleInformation $authenticationStyleInformation, - PageRenderer $pageRenderer, - LoggerInterface $logger + protected readonly UriBuilder $uriBuilder, + protected readonly AuthenticationStyleInformation $authenticationStyleInformation, + protected readonly PageRenderer $pageRenderer, + protected readonly ExtensionConfiguration $extensionConfiguration, + protected readonly LoggerInterface $logger, ) { - parent::__construct($uriBuilder, $mfaProviderRegistry, $moduleTemplateFactory); - $this->authenticationStyleInformation = $authenticationStyleInformation; - $this->pageRenderer = $pageRenderer; - $this->logger = $logger; } public function handleRequest(ServerRequestInterface $request): ResponseInterface { - $this->moduleTemplate = $this->moduleTemplateFactory->create($request); + $this->initializeMfaConfiguration(); $action = (string)($request->getQueryParams()['action'] ?? 'setup'); $backendUser = $this->getBackendUser(); @@ -96,10 +90,8 @@ public function handleRequest(ServerRequestInterface $request): ResponseInterfac * Render form to setup a provider by using provider specific content. Fall * back to provider selection view, in case no valid provider was yet selected. */ - public function setupAction(ServerRequestInterface $request): ResponseInterface + protected function setupAction(ServerRequestInterface $request): ResponseInterface { - $this->moduleTemplate->setTitle('TYPO3 CMS Login: ' . ($GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ?? '')); - $identifier = (string)($request->getQueryParams()['identifier'] ?? ''); if ($identifier === '' || !$this->isValidIdentifier($identifier)) { return new HtmlResponse($this->renderSelectionView($request)); @@ -113,7 +105,7 @@ public function setupAction(ServerRequestInterface $request): ResponseInterface * Handle activate request, receiving from the setup view * by forwarding the request to the appropriate provider. */ - public function activateAction(ServerRequestInterface $request): ResponseInterface + protected function activateAction(ServerRequestInterface $request): ResponseInterface { $identifier = (string)($request->getParsedBody()['identifier'] ?? ''); if ($identifier === '' || !$this->isValidIdentifier($identifier)) { @@ -153,7 +145,7 @@ public function activateAction(ServerRequestInterface $request): ResponseInterfa * by calling logoff on the user object, to destroy the session and other * already gathered information and finally initiate a redirect back to the login. */ - public function cancelAction(ServerRequestInterface $request): ResponseInterface + protected function cancelAction(ServerRequestInterface $request): ResponseInterface { $this->log('Required MFA setup canceled'); $this->getBackendUser()->logoff(); @@ -165,19 +157,23 @@ public function cancelAction(ServerRequestInterface $request): ResponseInterface */ protected function renderSelectionView(ServerRequestInterface $request): string { + $this->setUpBasicPageRendererForBackend($this->pageRenderer, $this->extensionConfiguration, $request, $this->getLanguageService()); + $this->pageRenderer->setTitle('TYPO3 CMS Login: ' . ($GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ?? '')); + $this->pageRenderer->loadJavaScriptModule('bootstrap'); + $recommendedProvider = $this->getRecommendedProvider(); $providers = array_filter($this->allowedProviders, static function ($provider) use ($recommendedProvider) { // Remove the recommended provider and providers, which can not be used as default, e.g. recovery codes return $provider->isDefaultProviderAllowed() && ($recommendedProvider === null || $provider->getIdentifier() !== $recommendedProvider->getIdentifier()); }); - $view = $this->initializeView($request, 'Selection'); + $view = $this->initializeView($request); $view->assignMultiple([ 'recommendedProvider' => $recommendedProvider, 'providers' => $providers, ]); - $this->moduleTemplate->setContent($view->render()); - return $this->moduleTemplate->renderContent(); + $this->pageRenderer->setBodyContent('' . $view->render('Mfa/Standalone/Selection')); + return $this->pageRenderer->render(); } /** @@ -187,28 +183,31 @@ protected function renderSetupView( ServerRequestInterface $request, MfaProviderManifestInterface $mfaProvider ): string { + $this->setUpBasicPageRendererForBackend($this->pageRenderer, $this->extensionConfiguration, $request, $this->getLanguageService()); + $this->pageRenderer->setTitle('TYPO3 CMS Login: ' . ($GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ?? '')); + $this->pageRenderer->loadJavaScriptModule('bootstrap'); + $propertyManager = MfaProviderPropertyManager::create($mfaProvider, $this->getBackendUser()); $providerResponse = $mfaProvider->handleRequest($request, $propertyManager, MfaViewType::SETUP); - $view = $this->initializeView($request, 'Setup'); + $view = $this->initializeView($request); $view->assignMultiple([ 'provider' => $mfaProvider, 'providerContent' => $providerResponse->getBody(), 'hasErrors' => (bool)($request->getQueryParams()['hasErrors'] ?? false), ]); - $this->moduleTemplate->setContent($view->render()); - return $this->moduleTemplate->renderContent(); + $this->pageRenderer->setBodyContent('' . $view->render('Mfa/Standalone/Setup')); + return $this->pageRenderer->render(); } /** * Initialize the standalone view by setting the paths and assigning view variables */ - protected function initializeView(ServerRequestInterface $request, string $templateName): StandaloneView + protected function initializeView(ServerRequestInterface $request): BackendTemplateView { - $view = $this->moduleTemplate->getView(); - $view->setTemplateRootPaths(['EXT:backend/Resources/Private/Templates/Mfa/Standalone']); + $view = GeneralUtility::makeInstance(BackendTemplateView::class); + $view->setTemplateRootPaths(['EXT:backend/Resources/Private/Templates/']); $view->setPartialRootPaths(['EXT:backend/Resources/Private/Partials']); $view->setLayoutRootPaths(['EXT:backend/Resources/Private/Layouts']); - $view->setTemplate($templateName); $view->assignMultiple([ 'redirect' => $request->getQueryParams()['redirect'] ?? '', 'redirectParams' => $request->getQueryParams()['redirectParams'] ?? '', diff --git a/typo3/sysext/backend/Classes/Controller/ResetPasswordController.php b/typo3/sysext/backend/Classes/Controller/ResetPasswordController.php index 7e3897665ac4..8e1ca155f9d6 100644 --- a/typo3/sysext/backend/Classes/Controller/ResetPasswordController.php +++ b/typo3/sysext/backend/Classes/Controller/ResetPasswordController.php @@ -22,9 +22,9 @@ use TYPO3\CMS\Backend\Authentication\PasswordReset; use TYPO3\CMS\Backend\Routing\RouteRedirect; use TYPO3\CMS\Backend\Routing\UriBuilder; -use TYPO3\CMS\Backend\Template\ModuleTemplate; -use TYPO3\CMS\Backend\Template\ModuleTemplateFactory; +use TYPO3\CMS\Backend\Template\PageRendererBackendSetupTrait; use TYPO3\CMS\Backend\View\AuthenticationStyleInformation; +use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; use TYPO3\CMS\Core\Configuration\Features; use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\Http\HtmlResponse; @@ -35,7 +35,7 @@ use TYPO3\CMS\Core\Localization\Locales; use TYPO3\CMS\Core\Page\PageRenderer; use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Fluid\View\StandaloneView; +use TYPO3\CMS\Fluid\View\BackendTemplateView; /** * Controller responsible for rendering and processing backend user password reset requests. @@ -44,40 +44,22 @@ */ class ResetPasswordController { - protected string $loginProvider = ''; - protected ?StandaloneView $view = null; - protected ?ModuleTemplate $moduleTemplate = null; + use PageRendererBackendSetupTrait; - protected Context $context; - protected Locales $locales; - protected Features $features; - protected UriBuilder $uriBuilder; - protected PageRenderer $pageRenderer; - protected PasswordReset $passwordReset; - protected Typo3Information $typo3Information; - protected ModuleTemplateFactory $moduleTemplateFactory; - protected AuthenticationStyleInformation $authenticationStyleInformation; + protected string $loginProvider = ''; + protected BackendTemplateView $view; public function __construct( - Context $context, - Locales $locales, - Features $features, - UriBuilder $uriBuilder, - PageRenderer $pageRenderer, - PasswordReset $passwordReset, - Typo3Information $typo3Information, - ModuleTemplateFactory $moduleTemplateFactory, - AuthenticationStyleInformation $authenticationStyleInformation + protected readonly Context $context, + protected readonly Locales $locales, + protected readonly Features $features, + protected readonly UriBuilder $uriBuilder, + protected readonly PageRenderer $pageRenderer, + protected readonly PasswordReset $passwordReset, + protected readonly Typo3Information $typo3Information, + protected readonly AuthenticationStyleInformation $authenticationStyleInformation, + protected readonly ExtensionConfiguration $extensionConfiguration, ) { - $this->context = $context; - $this->locales = $locales; - $this->features = $features; - $this->uriBuilder = $uriBuilder; - $this->pageRenderer = $pageRenderer; - $this->passwordReset = $passwordReset; - $this->typo3Information = $typo3Information; - $this->moduleTemplateFactory = $moduleTemplateFactory; - $this->authenticationStyleInformation = $authenticationStyleInformation; } /** @@ -88,9 +70,10 @@ public function __construct( */ public function forgetPasswordFormAction(ServerRequestInterface $request): ResponseInterface { + $this->initialize($request); $this->initializeForgetPasswordView($request); - $this->moduleTemplate->setContent($this->view->render()); - return new HtmlResponse($this->moduleTemplate->renderContent()); + $this->pageRenderer->setBodyContent('' . $this->view->render('Login/ForgetPasswordForm')); + return new HtmlResponse($this->pageRenderer->render()); } /** @@ -103,6 +86,7 @@ public function forgetPasswordFormAction(ServerRequestInterface $request): Respo */ public function initiatePasswordResetAction(ServerRequestInterface $request): ResponseInterface { + $this->initialize($request); $this->initializeForgetPasswordView($request); $emailAddress = $request->getParsedBody()['email'] ?? ''; $this->view->assign('email', $emailAddress); @@ -112,13 +96,13 @@ public function initiatePasswordResetAction(ServerRequestInterface $request): Re $this->passwordReset->initiateReset($request, $this->context, $emailAddress); $this->view->assign('resetInitiated', true); } - $this->moduleTemplate->setContent($this->view->render()); + $this->pageRenderer->setBodyContent('' . $this->view->render('Login/ForgetPasswordForm')); // Prevent time based information disclosure by waiting a random time // before sending a response. This prevents that the response time // can be an indicator if the used email exists or not. Wait a random // time between 200 milliseconds and 3 seconds. usleep(random_int(200000, 3000000)); - return new HtmlResponse($this->moduleTemplate->renderContent()); + return new HtmlResponse($this->pageRenderer->render()); } /** @@ -129,12 +113,13 @@ public function initiatePasswordResetAction(ServerRequestInterface $request): Re */ public function passwordResetAction(ServerRequestInterface $request): ResponseInterface { + $this->initialize($request); $this->initializeResetPasswordView($request); if (!$this->passwordReset->isValidResetTokenFromRequest($request)) { $this->view->assign('invalidToken', true); } - $this->moduleTemplate->setContent($this->view->render()); - return new HtmlResponse($this->moduleTemplate->renderContent()); + $this->pageRenderer->setBodyContent('' . $this->view->render('Login/ResetPasswordForm')); + return new HtmlResponse($this->pageRenderer->render()); } /** @@ -151,20 +136,19 @@ public function passwordResetFinishAction(ServerRequestInterface $request): Resp if (!$this->passwordReset->isValidResetTokenFromRequest($request)) { return $this->passwordResetAction($request); } + $this->initialize($request); $this->initializeResetPasswordView($request); if ($this->passwordReset->resetPassword($request, $this->context)) { $this->view->assign('resetExecuted', true); } else { $this->view->assign('error', true); } - $this->moduleTemplate->setContent($this->view->render()); - return new HtmlResponse($this->moduleTemplate->renderContent()); + $this->pageRenderer->setBodyContent('' . $this->view->render('Login/ResetPasswordForm')); + return new HtmlResponse($this->pageRenderer->render()); } protected function initializeForgetPasswordView(ServerRequestInterface $request): void { - $this->initialize($request); - $this->view->setTemplate('Login/ForgetPasswordForm'); $parameters = array_filter(['loginProvider' => $this->loginProvider]); $this->view->assignMultiple([ 'formUrl' => $this->uriBuilder->buildUriWithRedirect('password_forget_initiate_reset', $parameters, RouteRedirect::createFromRequest($request)), @@ -174,7 +158,6 @@ protected function initializeForgetPasswordView(ServerRequestInterface $request) protected function initializeResetPasswordView(ServerRequestInterface $request): void { - $this->initialize($request); $token = $request->getQueryParams()['t'] ?? ''; $identity = $request->getQueryParams()['i'] ?? ''; $expirationDate = $request->getQueryParams()['e'] ?? ''; @@ -188,7 +171,6 @@ protected function initializeResetPasswordView(ServerRequestInterface $request): ])), RouteRedirect::createFromRequest($request) ); - $this->view->setTemplate('Login/ResetPasswordForm'); $this->view->assignMultiple([ 'token' => $token, 'identity' => $identity, @@ -200,6 +182,8 @@ protected function initializeResetPasswordView(ServerRequestInterface $request): protected function initialize(ServerRequestInterface $request): void { + $languageService = $this->getLanguageService(); + // Only allow to execute this if not logged in as a user right now if ($this->context->getAspect('backend.user')->isLoggedIn()) { throw new PropagateResponseException( @@ -218,29 +202,32 @@ protected function initialize(ServerRequestInterface $request): void // If we found a $preferredBrowserLanguage and it is not the default language // initialize $this->getLanguageService() again with $preferredBrowserLanguage if ($preferredBrowserLanguage !== 'default') { - $this->getLanguageService()->init($preferredBrowserLanguage); + $languageService->init($preferredBrowserLanguage); $this->pageRenderer->setLanguage($preferredBrowserLanguage); } - $this->getLanguageService()->includeLLFile('EXT:backend/Resources/Private/Language/locallang_login.xlf'); + $this->setUpBasicPageRendererForBackend($this->pageRenderer, $this->extensionConfiguration, $request, $languageService); + $this->pageRenderer->setTitle('TYPO3 CMS Login: ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ?? ''); + $this->pageRenderer->loadJavaScriptModule('bootstrap'); + $this->pageRenderer->loadJavaScriptModule('TYPO3/CMS/Backend/Login.js'); - $this->moduleTemplate = $this->moduleTemplateFactory->create($request); - $this->moduleTemplate->setTitle('TYPO3 CMS Login: ' . ($GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ?? '')); + $this->view = GeneralUtility::makeInstance(BackendTemplateView::class); + $this->view->setTemplateRootPaths(['EXT:backend/Resources/Private/Templates']); + $this->view->setLayoutRootPaths(['EXT:backend/Resources/Private/Layouts']); + $this->view->setPartialRootPaths(['EXT:backend/Resources/Private/Partials']); - $this->view = $this->moduleTemplate->getView(); - $this->view->getRequest()->setControllerExtensionName('Backend'); $this->view->assignMultiple([ 'enablePasswordReset' => $this->passwordReset->isEnabled(), 'referrerCheckEnabled' => $this->features->isFeatureEnabled('security.backend.enforceReferrer'), 'loginUrl' => (string)$request->getUri(), ]); - $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Login'); $this->provideCustomLoginStyling(); } protected function provideCustomLoginStyling(): void { + $languageService = $this->getLanguageService(); if (($backgroundImageStyles = $this->authenticationStyleInformation->getBackgroundImageStyles()) !== '') { $this->pageRenderer->addCssInlineBlock('loginBackgroundImage', $backgroundImageStyles); } @@ -251,10 +238,11 @@ protected function provideCustomLoginStyling(): void $this->pageRenderer->addCssInlineBlock('loginHighlightColor', $highlightColorStyles); } if (($logo = $this->authenticationStyleInformation->getLogo()) !== '') { - $logoAlt = $this->authenticationStyleInformation->getLogoAlt() ?: $this->getLanguageService()->getLL('typo3.altText'); + $logoAlt = $this->authenticationStyleInformation->getLogoAlt() + ?: $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_login.xlf:typo3.altText'); } else { $logo = $this->authenticationStyleInformation->getDefaultLogo(); - $logoAlt = $this->getLanguageService()->getLL('typo3.altText'); + $logoAlt = $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_login.xlf:typo3.altText'); $this->pageRenderer->addCssInlineBlock('loginLogo', $this->authenticationStyleInformation->getDefaultLogoStyles()); } $this->view->assignMultiple([ diff --git a/typo3/sysext/backend/Classes/LoginProvider/UsernamePasswordLoginProvider.php b/typo3/sysext/backend/Classes/LoginProvider/UsernamePasswordLoginProvider.php index 9c83a2d4aec4..637d3148f4be 100644 --- a/typo3/sysext/backend/Classes/LoginProvider/UsernamePasswordLoginProvider.php +++ b/typo3/sysext/backend/Classes/LoginProvider/UsernamePasswordLoginProvider.php @@ -22,24 +22,17 @@ use TYPO3\CMS\Fluid\View\StandaloneView; /** - * Class UsernamePasswordLoginProvider + * The default username + password based backend login form. + * * @internal This class is a specific Backend implementation and is not considered part of the Public TYPO3 API. */ class UsernamePasswordLoginProvider implements LoginProviderInterface { - /** - * @param StandaloneView $view - * @param PageRenderer $pageRenderer - * @param LoginController $loginController - * @throws \UnexpectedValueException - */ - public function render(StandaloneView $view, PageRenderer $pageRenderer, LoginController $loginController) + public function render(StandaloneView $view, PageRenderer $pageRenderer, LoginController $loginController): void { - $pageRenderer->loadJavaScriptModule('TYPO3/CMS/Backend/UserPassLogin.js'); - - $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Templates/UserPassLoginForm.html')); + $view->setTemplate('Login/UserPassLoginForm'); $request = $loginController->getCurrentRequest(); - if ($request !== null && $request->getAttribute('normalizedParams')->isHttps()) { + if ($request->getAttribute('normalizedParams')->isHttps()) { $username = $request->getParsedBody()['u'] ?? $request->getQueryParams()['u'] ?? null; $password = $request->getParsedBody()['p'] ?? $request->getQueryParams()['p'] ?? null; $view->assignMultiple([ @@ -47,7 +40,6 @@ public function render(StandaloneView $view, PageRenderer $pageRenderer, LoginCo 'presetPassword' => $password, ]); } - $view->assign('enablePasswordReset', GeneralUtility::makeInstance(PasswordReset::class)->isEnabled()); } } diff --git a/typo3/sysext/backend/Resources/Private/Language/locallang_login.xlf b/typo3/sysext/backend/Resources/Private/Language/locallang_login.xlf index f7eabb9e5a0b..82fbc1e08d66 100644 --- a/typo3/sysext/backend/Resources/Private/Language/locallang_login.xlf +++ b/typo3/sysext/backend/Resources/Private/Language/locallang_login.xlf @@ -18,9 +18,6 @@ Frontend - - Traditional Backend - Login to the TYPO3 CMS Backend on ###SITENAME### diff --git a/typo3/sysext/backend/Resources/Private/Layouts/Login.html b/typo3/sysext/backend/Resources/Private/Layouts/Login.html index 84dba838ecdc..b52eb13a3344 100644 --- a/typo3/sysext/backend/Resources/Private/Layouts/Login.html +++ b/typo3/sysext/backend/Resources/Private/Layouts/Login.html @@ -1,11 +1,22 @@ - + + + +
@@ -32,10 +40,26 @@

- +
@@ -43,10 +67,18 @@

- +
@@ -61,4 +93,5 @@

+ + + + + + + + + + + +
+
+ +
+
+
+ + diff --git a/typo3/sysext/backend/Resources/Private/Templates/Mfa/Auth.html b/typo3/sysext/backend/Resources/Private/Templates/Mfa/Auth.html index fac2c6ad0ed3..5b4b46cffcdd 100644 --- a/typo3/sysext/backend/Resources/Private/Templates/Mfa/Auth.html +++ b/typo3/sysext/backend/Resources/Private/Templates/Mfa/Auth.html @@ -1,4 +1,7 @@ - +