‪TYPO3CMS  ‪main
MfaSetupController.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Psr\Http\Message\ResponseInterface;
21 use Psr\Http\Message\ServerRequestInterface;
22 use Psr\Log\LoggerInterface;
41 
48 #[AsController]
50 {
52 
53  protected const ‪ACTION_METHOD_MAP = [
54  'setup' => 'GET',
55  'activate' => 'POST',
56  'cancel' => 'GET',
57  ];
58 
59  public function ‪__construct(
60  protected readonly ‪UriBuilder $uriBuilder,
61  protected readonly ‪AuthenticationStyleInformation $authenticationStyleInformation,
62  protected readonly ‪PageRenderer $pageRenderer,
63  protected readonly ‪ExtensionConfiguration $extensionConfiguration,
64  protected readonly LoggerInterface $logger,
65  protected readonly ‪BackendViewFactory $backendViewFactory,
66  ) {}
67 
68  public function ‪handleRequest(ServerRequestInterface $request): ResponseInterface
69  {
71  $action = (string)($request->getQueryParams()['action'] ?? 'setup');
72 
73  $backendUser = $this->‪getBackendUser();
74  if (($backendUser->getSessionData('mfa') ?? false)
75  || $backendUser->getOriginalUserIdWhenInSwitchUserMode() !== null
76  || !$backendUser->isMfaSetupRequired()
77  || $this->mfaProviderRegistry->hasActiveProviders($backendUser)
78  ) {
79  // Since the current user either did already pass MFA, is in "switch-user" mode,
80  // is not required to set up MFA or has already activated a provider, throw an
81  // exception to prevent the endpoint from being called unintentionally by custom code.
82  throw new \InvalidArgumentException('MFA setup is not necessary. Do not call this endpoint on your own.', 1632154036);
83  }
84 
85  $actionMethod = self::ACTION_METHOD_MAP[$action] ?? null;
86  if ($actionMethod !== null && $request->getMethod() === $actionMethod) {
87  return $this->{$action . 'Action'}($request);
88  }
89  return new ‪HtmlResponse('', 404);
90  }
91 
96  protected function ‪setupAction(ServerRequestInterface $request): ResponseInterface
97  {
98  ‪$identifier = (string)($request->getQueryParams()['identifier'] ?? '');
99  if (‪$identifier === '' || !$this->‪isValidIdentifier(‪$identifier)) {
100  return $this->‪renderSelectionView($request);
101  }
102  $mfaProvider = $this->mfaProviderRegistry->getProvider(‪$identifier);
103  $this->‪log('Required MFA setup initiated', $mfaProvider);
104  return $this->‪renderSetupView($request, $mfaProvider);
105  }
106 
111  protected function ‪activateAction(ServerRequestInterface $request): ResponseInterface
112  {
113  ‪$identifier = (string)($request->getParsedBody()['identifier'] ?? '');
114  if (‪$identifier === '' || !$this->‪isValidIdentifier(‪$identifier)) {
115  // Return to selection view in case no valid identifier is given
116  return new RedirectResponse($this->uriBuilder->buildUriWithRedirect('setup_mfa', [], ‪RouteRedirect::createFromRequest($request)));
117  }
118  $mfaProvider = $this->mfaProviderRegistry->getProvider(‪$identifier);
119  $backendUser = $this->‪getBackendUser();
120  $propertyManager = ‪MfaProviderPropertyManager::create($mfaProvider, $backendUser);
121  // Check whether activation operation was successful and the provider is now active.
122  if (!$mfaProvider->activate($request, $propertyManager) || !$mfaProvider->isActive($propertyManager)) {
123  $this->‪log('Required MFA setup failed', $mfaProvider);
124  return new RedirectResponse(
125  $this->uriBuilder->buildUriWithRedirect(
126  'setup_mfa',
127  [
128  'identifier' => $mfaProvider->getIdentifier(),
129  'hasErrors' => true,
130  ],
132  )
133  );
134  }
135  $this->‪log('Required MFA setup successful', $mfaProvider);
136  // Set the activated provider as the default provider, store the "mfa" key in the session data,
137  // add a flash message to the session and finally initiate a redirect to the login, on which
138  // possible redirect parameters are evaluated again.
139  $backendUser->uc['mfa']['defaultProvider'] = $mfaProvider->getIdentifier();
140  $backendUser->writeUC();
141  $backendUser->setAndSaveSessionData('mfa', true);
142  $this->‪addSuccessMessage($mfaProvider->getTitle());
143  return new RedirectResponse($this->uriBuilder->buildUriWithRedirect('login', [], ‪RouteRedirect::createFromRequest($request)));
144  }
145 
151  protected function ‪cancelAction(ServerRequestInterface $request): ResponseInterface
152  {
153  $this->‪log('Required MFA setup canceled');
154  $this->‪getBackendUser()->logoff();
155  return new RedirectResponse($this->uriBuilder->buildUriWithRedirect('login', [], ‪RouteRedirect::createFromRequest($request)));
156  }
157 
161  protected function ‪renderSelectionView(ServerRequestInterface $request): ResponseInterface
162  {
163  $this->‪setUpBasicPageRendererForBackend($this->pageRenderer, $this->extensionConfiguration, $request, $this->‪getLanguageService());
164  $this->pageRenderer->setTitle('TYPO3 CMS Login: ' . (‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ?? ''));
165  $this->pageRenderer->loadJavaScriptModule('bootstrap');
166 
167  $recommendedProvider = $this->‪getRecommendedProvider();
168  $providers = array_filter($this->allowedProviders, static function (MfaProviderManifestInterface $provider) use ($recommendedProvider): bool {
169  // Remove the recommended provider and providers, which can not be used as default, e.g. recovery codes
170  return $provider->isDefaultProviderAllowed()
171  && ($recommendedProvider === null || $provider->getIdentifier() !== $recommendedProvider->getIdentifier());
172  });
173  $view = $this->‪initializeView($request);
174  $view->assignMultiple([
175  'recommendedProvider' => $recommendedProvider,
176  'providers' => $providers,
177  ]);
178  $this->pageRenderer->setBodyContent('<body>' . $view->render('Mfa/Standalone/Selection'));
179  return $this->pageRenderer->renderResponse();
180  }
181 
185  protected function ‪renderSetupView(
186  ServerRequestInterface $request,
187  MfaProviderManifestInterface $mfaProvider
188  ): ResponseInterface {
189  $this->‪setUpBasicPageRendererForBackend($this->pageRenderer, $this->extensionConfiguration, $request, $this->‪getLanguageService());
190  $this->pageRenderer->setTitle('TYPO3 CMS Login: ' . (‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ?? ''));
191  $this->pageRenderer->loadJavaScriptModule('bootstrap');
192 
193  $propertyManager = ‪MfaProviderPropertyManager::create($mfaProvider, $this->‪getBackendUser());
194  $providerResponse = $mfaProvider->handleRequest($request, $propertyManager, MfaViewType::SETUP);
195  $view = $this->‪initializeView($request);
196  $view->assignMultiple([
197  'provider' => $mfaProvider,
198  'providerContent' => $providerResponse->getBody(),
199  'hasErrors' => (bool)($request->getQueryParams()['hasErrors'] ?? false),
200  ]);
201  $this->pageRenderer->setBodyContent('<body>' . $view->render('Mfa/Standalone/Setup'));
202  return $this->pageRenderer->renderResponse();
203  }
204 
208  protected function ‪initializeView(ServerRequestInterface $request): ViewInterface
209  {
210  $view = $this->backendViewFactory->create($request);
211  $view->assignMultiple([
212  'redirect' => $request->getQueryParams()['redirect'] ?? '',
213  'redirectParams' => $request->getQueryParams()['redirectParams'] ?? '',
214  'siteName' => ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'],
215  'footerNote' => $this->authenticationStyleInformation->getFooterNote(),
216  ]);
218  return $view;
219  }
220 
221  protected function ‪addCustomAuthenticationFormStyles(): void
222  {
223  if (($backgroundImageStyles = $this->authenticationStyleInformation->getBackgroundImageStyles()) !== '') {
224  $this->pageRenderer->addCssInlineBlock('loginBackgroundImage', $backgroundImageStyles, useNonce: true);
225  }
226  if (($highlightColorStyles = $this->authenticationStyleInformation->getHighlightColorStyles()) !== '') {
227  $this->pageRenderer->addCssInlineBlock('loginHighlightColor', $highlightColorStyles, useNonce: true);
228  }
229  }
230 
235  protected function ‪isValidIdentifier(string ‪$identifier): bool
236  {
237  return parent::isValidIdentifier(‪$identifier)
238  && $this->mfaProviderRegistry->getProvider(‪$identifier)->isDefaultProviderAllowed();
239  }
240 
245  protected function ‪addSuccessMessage(string $mfaProviderTitle): void
246  {
247  $lang = $this->‪getLanguageService();
248  GeneralUtility::makeInstance(FlashMessageService::class)->getMessageQueueByIdentifier()->enqueue(
249  GeneralUtility::makeInstance(
250  FlashMessage::class,
251  sprintf($lang->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:standalone.setup.success.message'), $lang->sL($mfaProviderTitle)),
252  $lang->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:standalone.setup.success.title'),
253  ContextualFeedbackSeverity::OK,
254  true
255  )
256  );
257  }
258 
262  protected function ‪log(string $message, ?MfaProviderManifestInterface $mfaProvider = null): void
263  {
264  $user = $this->‪getBackendUser();
265  $context = [
266  'user' => [
267  'uid' => $user->user[$user->userid_column],
268  'username' => $user->user[$user->username_column],
269  ],
270  ];
271  if ($mfaProvider !== null) {
272  $context['provider'] = $mfaProvider->getIdentifier();
273  }
274  $this->logger->debug($message, $context);
275  }
276 }
‪TYPO3\CMS\Backend\Controller\MfaSetupController\handleRequest
‪handleRequest(ServerRequestInterface $request)
Definition: MfaSetupController.php:67
‪TYPO3\CMS\Backend\View\AuthenticationStyleInformation
Definition: AuthenticationStyleInformation.php:32
‪TYPO3\CMS\Core\View\ViewInterface\assignMultiple
‪assignMultiple(array $values)
‪TYPO3\CMS\Backend\Controller\MfaSetupController\renderSelectionView
‪renderSelectionView(ServerRequestInterface $request)
Definition: MfaSetupController.php:160
‪TYPO3\CMS\Core\View\ViewInterface
Definition: ViewInterface.php:24
‪TYPO3\CMS\Backend\Controller\AbstractMfaController\getBackendUser
‪getBackendUser()
Definition: AbstractMfaController.php:106
‪TYPO3\CMS\Backend\View\BackendViewFactory
Definition: BackendViewFactory.php:35
‪TYPO3\CMS\Core\Configuration\ExtensionConfiguration
Definition: ExtensionConfiguration.php:47
‪TYPO3\CMS\Core\Authentication\Mfa\MfaProviderInterface\handleRequest
‪handleRequest(ServerRequestInterface $request, MfaProviderPropertyManager $propertyManager, MfaViewType $type)
‪TYPO3\CMS\Backend\Controller\MfaSetupController\renderSetupView
‪renderSetupView(ServerRequestInterface $request, MfaProviderManifestInterface $mfaProvider)
Definition: MfaSetupController.php:184
‪TYPO3\CMS\Backend\Controller\AbstractMfaController\getLanguageService
‪getLanguageService()
Definition: AbstractMfaController.php:111
‪TYPO3\CMS\Backend\Template\PageRendererBackendSetupTrait
Definition: PageRendererBackendSetupTrait.php:45
‪TYPO3\CMS\Core\Authentication\Mfa\MfaProviderManifestInterface
Definition: MfaProviderManifestInterface.php:26
‪TYPO3\CMS\Backend\Controller\MfaSetupController
Definition: MfaSetupController.php:50
‪TYPO3\CMS\Backend\Controller\MfaSetupController\__construct
‪__construct(protected readonly UriBuilder $uriBuilder, protected readonly AuthenticationStyleInformation $authenticationStyleInformation, protected readonly PageRenderer $pageRenderer, protected readonly ExtensionConfiguration $extensionConfiguration, protected readonly LoggerInterface $logger, protected readonly BackendViewFactory $backendViewFactory,)
Definition: MfaSetupController.php:58
‪TYPO3\CMS\Backend\Controller\MfaSetupController\setupAction
‪setupAction(ServerRequestInterface $request)
Definition: MfaSetupController.php:95
‪TYPO3\CMS\Core\Type\ContextualFeedbackSeverity
‪ContextualFeedbackSeverity
Definition: ContextualFeedbackSeverity.php:25
‪TYPO3\CMS\Backend\Controller\MfaSetupController\cancelAction
‪cancelAction(ServerRequestInterface $request)
Definition: MfaSetupController.php:150
‪TYPO3\CMS\Core\Page\PageRenderer
Definition: PageRenderer.php:44
‪TYPO3\CMS\Backend\Controller\AbstractMfaController\getRecommendedProvider
‪getRecommendedProvider()
Definition: AbstractMfaController.php:87
‪TYPO3\CMS\Core\Authentication\Mfa\MfaProviderPropertyManager\create
‪static create(MfaProviderManifestInterface $provider, AbstractUserAuthentication $user)
Definition: MfaProviderPropertyManager.php:193
‪TYPO3\CMS\Backend\Routing\RouteRedirect
Definition: RouteRedirect.php:30
‪TYPO3\CMS\Backend\Routing\UriBuilder
Definition: UriBuilder.php:44
‪TYPO3\CMS\Core\Authentication\Mfa\MfaProviderManifestInterface\isDefaultProviderAllowed
‪isDefaultProviderAllowed()
‪TYPO3\CMS\Backend\Controller\MfaSetupController\addCustomAuthenticationFormStyles
‪addCustomAuthenticationFormStyles()
Definition: MfaSetupController.php:220
‪TYPO3\CMS\Core\Authentication\Mfa\MfaProviderPropertyManager
Definition: MfaProviderPropertyManager.php:33
‪TYPO3\CMS\Backend\Controller\AbstractMfaController\initializeMfaConfiguration
‪initializeMfaConfiguration()
Definition: AbstractMfaController.php:71
‪TYPO3\CMS\Backend\Controller\MfaSetupController\addSuccessMessage
‪addSuccessMessage(string $mfaProviderTitle)
Definition: MfaSetupController.php:244
‪TYPO3\CMS\Core\Http\RedirectResponse
Definition: RedirectResponse.php:30
‪TYPO3\CMS\Backend\Controller\MfaSetupController\initializeView
‪initializeView(ServerRequestInterface $request)
Definition: MfaSetupController.php:207
‪TYPO3\CMS\Backend\Controller\MfaSetupController\activateAction
‪activateAction(ServerRequestInterface $request)
Definition: MfaSetupController.php:110
‪TYPO3\CMS\Core\Messaging\FlashMessage
Definition: FlashMessage.php:27
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Authentication\Mfa\MfaProviderManifestInterface\getIdentifier
‪getIdentifier()
‪TYPO3\CMS\Backend\Template\PageRendererBackendSetupTrait\setUpBasicPageRendererForBackend
‪setUpBasicPageRendererForBackend(PageRenderer $pageRenderer, ExtensionConfiguration $extensionConfiguration, ServerRequestInterface $request, LanguageService $languageService,)
Definition: PageRendererBackendSetupTrait.php:49
‪TYPO3\CMS\Backend\Attribute\AsController
Definition: AsController.php:25
‪TYPO3\CMS\Backend\Controller\MfaSetupController\log
‪log(string $message, ?MfaProviderManifestInterface $mfaProvider=null)
Definition: MfaSetupController.php:261
‪TYPO3\CMS\Backend\Routing\RouteRedirect\createFromRequest
‪static createFromRequest(ServerRequestInterface $request)
Definition: RouteRedirect.php:58
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Authentication\Mfa\MfaViewType
‪MfaViewType
Definition: MfaViewType.php:24
‪TYPO3\CMS\Backend\Controller
Definition: AboutController.php:18
‪TYPO3\CMS\Backend\Controller\MfaSetupController\ACTION_METHOD_MAP
‪const ACTION_METHOD_MAP
Definition: MfaSetupController.php:52
‪TYPO3\CMS\Core\Messaging\FlashMessageService
Definition: FlashMessageService.php:27
‪TYPO3\CMS\Backend\Controller\MfaSetupController\isValidIdentifier
‪isValidIdentifier(string $identifier)
Definition: MfaSetupController.php:234
‪TYPO3\CMS\Webhooks\Message\$identifier
‪identifier readonly string $identifier
Definition: FileAddedMessage.php:37
‪TYPO3\CMS\Core\Http\HtmlResponse
Definition: HtmlResponse.php:28
‪TYPO3\CMS\Backend\Controller\AbstractMfaController
Definition: AbstractMfaController.php:34