Skip to content

Commit

Permalink
[TASK] Avoid misusing ModuleTemplate in login
Browse files Browse the repository at this point in the history
There are a couple of login related controllers:
* LoginController - handles user+password login
* ResetPasswordController - handles p/w reset concerns
* MfaSetupController - handles mfa setup during login
  if mfa is enforced and not set up for a user, yet
* MfaController - handles mfa auth during login

All these controllers do not deal with backend
module doc headers and thus shouldn't abuse
ModuleTemplate for their views.

The patch transitions these controller views
away from ModuleTemplate, feeding PageRenderer
directly.

Change-Id: I893f30aaf42d9a807c2f7db4b17062107ff38169
Resolves: #96632
Related: #96623
Releases: main
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/73127
Tested-by: Oliver Bartsch <bo@cedev.de>
Tested-by: Stefan Bürk <stefan@buerk.tech>
Tested-by: core-ci <typo3@b13.com>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Oliver Bartsch <bo@cedev.de>
Reviewed-by: Stefan Bürk <stefan@buerk.tech>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
  • Loading branch information
lolli42 committed Jan 25, 2022
1 parent 73fbce5 commit 22f6458
Show file tree
Hide file tree
Showing 27 changed files with 512 additions and 383 deletions.
17 changes: 2 additions & 15 deletions typo3/sysext/backend/Classes/Controller/AbstractMfaController.php
Expand Up @@ -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;
Expand All @@ -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();
}

/**
Expand Down Expand Up @@ -115,7 +103,6 @@ protected function getRecommendedProvider(): ?MfaProviderManifestInterface
return null;
}
}

return $this->mfaProviderRegistry->getProvider($recommendedProviderIdentifier);
}

Expand Down
144 changes: 70 additions & 74 deletions typo3/sysext/backend/Classes/Controller/LoginController.php
Expand Up @@ -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;
Expand All @@ -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.
*
Expand Down Expand Up @@ -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;
}

/**
Expand All @@ -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);
Expand All @@ -145,15 +130,33 @@ 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));
return $this->appendLoginProviderCookie($request->getAttribute('normalizedParams'), $response);
}

/**
* 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
{
Expand All @@ -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');

Expand All @@ -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;
Expand All @@ -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());
Expand All @@ -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);
Expand All @@ -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([
Expand All @@ -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',
Expand All @@ -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,
Expand All @@ -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('<body>' . $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']);
Expand All @@ -324,7 +326,6 @@ protected function renderHtmlViaLoginProvider(ServerRequestInterface $request):
)
);
$loginProvider->render($this->view, $this->pageRenderer, $this);
$this->currentRequest = null;
}

/**
Expand Down Expand Up @@ -396,19 +397,20 @@ 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']);
if (count($parts) > 1) {
// 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',
],
Expand All @@ -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
{
Expand Down Expand Up @@ -482,9 +483,4 @@ protected function getBackendUserAuthentication(): BackendUserAuthentication
{
return $GLOBALS['BE_USER'];
}

public function getCurrentRequest(): ?ServerRequestInterface
{
return $this->currentRequest;
}
}

0 comments on commit 22f6458

Please sign in to comment.