‪TYPO3CMS  ‪main
MfaController.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\EventDispatcher\EventDispatcherInterface;
21 use Psr\Http\Message\ResponseInterface;
22 use Psr\Http\Message\ServerRequestInterface;
23 use Psr\Log\LoggerInterface;
39 use ‪TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
40 use ‪TYPO3\CMS\Core\SysLog\Type as SystemLogType;
41 
48 #[AsController]
50 {
52 
53  public function ‪__construct(
54  protected readonly ‪UriBuilder $uriBuilder,
55  protected readonly ‪AuthenticationStyleInformation $authenticationStyleInformation,
56  protected readonly ‪PageRenderer $pageRenderer,
57  protected readonly ‪ExtensionConfiguration $extensionConfiguration,
58  protected readonly LoggerInterface $logger,
59  protected readonly ‪BackendViewFactory $backendViewFactory,
60  protected readonly EventDispatcherInterface $eventDispatcher,
61  ) {}
62 
67  public function ‪handleRequest(ServerRequestInterface $request): ResponseInterface
68  {
70  $action = (string)($request->getQueryParams()['action'] ?? $request->getParsedBody()['action'] ?? 'auth');
71 
72  switch ($action) {
73  case 'auth':
74  case 'verify':
75  $mfaProvider = $this->‪getMfaProviderFromRequest($request);
76  // All actions except "cancel" require a provider to deal with.
77  // If non is found at this point, throw an exception since this should never happen.
78  if ($mfaProvider === null) {
79  throw new \InvalidArgumentException('No active MFA provider was found!', 1611879242);
80  }
81  return $this->{$action . 'Action'}($request, $mfaProvider);
82  case 'cancel':
83  return $this->‪cancelAction($request);
84  default:
85  throw new \InvalidArgumentException('Action not allowed', 1611879244);
86  }
87  }
88 
92  protected function ‪authAction(ServerRequestInterface $request, MfaProviderManifestInterface $mfaProvider): ResponseInterface
93  {
94  $this->‪setUpBasicPageRendererForBackend($this->pageRenderer, $this->extensionConfiguration, $request, $this->‪getLanguageService());
95  $this->pageRenderer->setTitle('TYPO3 CMS Login: ' . (‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ?? ''));
96  $this->pageRenderer->loadJavaScriptModule('bootstrap');
97  $view = $this->backendViewFactory->create($request);
98  $propertyManager = ‪MfaProviderPropertyManager::create($mfaProvider, $this->‪getBackendUser());
99  $providerResponse = $mfaProvider->handleRequest($request, $propertyManager, ‪MfaViewType::AUTH);
100  $view->assignMultiple([
101  'provider' => $mfaProvider,
102  'alternativeProviders' => $this->‪getAlternativeProviders($mfaProvider),
103  'isLocked' => $mfaProvider->isLocked($propertyManager),
104  'providerContent' => $providerResponse->getBody(),
105  'footerNote' => $this->authenticationStyleInformation->getFooterNote(),
106  'formUrl' => $this->uriBuilder->buildUriWithRedirect('auth_mfa', ['action' => 'verify'], ‪RouteRedirect::createFromRequest($request)),
107  'redirectRoute' => $request->getQueryParams()['redirect'] ?? '',
108  'redirectParams' => $request->getQueryParams()['redirectParams'] ?? '',
109  'hasAuthError' => (bool)($request->getQueryParams()['failure'] ?? false),
110  ]);
112  $this->pageRenderer->setBodyContent('<body>' . $view->render('Mfa/Auth'));
113  return $this->pageRenderer->renderResponse();
114  }
115 
120  protected function ‪verifyAction(ServerRequestInterface $request, MfaProviderManifestInterface $mfaProvider): ResponseInterface
121  {
122  $backendUser = $this->‪getBackendUser();
123  $propertyManager = ‪MfaProviderPropertyManager::create($mfaProvider, $backendUser);
124 
125  // Check if the provider can process the request and is not temporarily blocked
126  if (!$mfaProvider->canProcess($request) || $mfaProvider->isLocked($propertyManager)) {
127  // If this fails, cancel the authentication
128  return $this->‪cancelAction($request);
129  }
130  // Call the provider to verify the request
131  if (!$mfaProvider->verify($request, $propertyManager)) {
132  $this->‪log(
133  message: 'Multi-factor authentication failed for user \'###USERNAME###\' with provider \'' . $mfaProvider->getIdentifier() . '\'!',
134  action: Login::ATTEMPT,
135  error: SystemLogErrorClassification::SECURITY_NOTICE
136  );
137  $this->eventDispatcher->dispatch(
138  new MfaVerificationFailedEvent($request, $propertyManager, $mfaProvider)
139  );
140  // If failed, initiate a redirect back to the auth view
141  return new RedirectResponse($this->uriBuilder->buildUriWithRedirect(
142  'auth_mfa',
143  [
144  'identifier' => $mfaProvider->getIdentifier(),
145  'failure' => true,
146  ],
147  RouteRedirect::createFromRequest($request)
148  ));
149  }
150  $this->log('Multi-factor authentication successful for user ###USERNAME###');
151  // If verified, store this information in the session
152  // and initiate a redirect back to the login view.
153  $backendUser->setAndSaveSessionData('mfa', true);
154  $backendUser->handleUserLoggedIn($request);
155  return new RedirectResponse(
156  $this->uriBuilder->buildUriWithRedirect('login', [], RouteRedirect::createFromRequest($request))
157  );
158  }
159 
166  protected function cancelAction(ServerRequestInterface $request): ResponseInterface
167  {
168  $this->log('Multi-factor authentication canceled for user ###USERNAME###');
169  $this->getBackendUser()->logoff();
170  return new RedirectResponse($this->uriBuilder->buildUriWithRedirect('login', [], RouteRedirect::createFromRequest($request)));
171  }
172 
178  protected function getAlternativeProviders(MfaProviderManifestInterface $mfaProvider): array
179  {
180  return array_filter($this->allowedProviders, function (MfaProviderManifestInterface $provider) use ($mfaProvider): bool {
181  return $provider !== $mfaProvider
182  && $provider->isActive(MfaProviderPropertyManager::create($provider, $this->getBackendUser()));
183  });
184  }
185 
189  protected function log(
190  string $message,
191  array $additionalData = [],
192  ?MfaProviderManifestInterface $mfaProvider = null,
193  int $action = Login::LOGIN,
194  int $error = SystemLogErrorClassification::MESSAGE
195  ): void {
196  $user = $this->getBackendUser();
197  $username = $user->getUserName();
198  $context = [
199  'user' => [
200  'uid' => $user->getUserId(),
201  'username' => $username,
202  ],
203  ];
204  if ($mfaProvider !== null) {
205  $context['provider'] = $mfaProvider->getIdentifier();
206  $context['isProviderLocked'] = $mfaProvider->isLocked(
207  MfaProviderPropertyManager::create($mfaProvider, $user)
208  );
209  }
210  $message = str_replace('###USERNAME###', $username, $message);
211  $data = array_replace_recursive($context, $additionalData);
212  $this->logger->debug($message, $data);
213  if ($user->writeStdLog) {
214  // Write to sys_log if enabled
215  $user->writelog(SystemLogType::LOGIN, $action, $error, 1, $message, $data);
216  }
217  }
218 
219  protected function getMfaProviderFromRequest(ServerRequestInterface $request): ?MfaProviderManifestInterface
220  {
221  $identifier = (string)($request->getQueryParams()['identifier'] ?? $request->getParsedBody()['identifier'] ?? '');
222  // Check if given identifier is valid
223  if ($this->isValidIdentifier($identifier)) {
224  $provider = $this->mfaProviderRegistry->getProvider($identifier);
225  // Only add provider if it was activated by the current user
226  if ($provider->isActive(MfaProviderPropertyManager::create($provider, $this->getBackendUser()))) {
227  return $provider;
228  }
229  }
230  return null;
231  }
232 
233  protected function addCustomAuthenticationFormStyles(): void
234  {
235  if (($backgroundImageStyles = $this->authenticationStyleInformation->getBackgroundImageStyles()) !== '') {
236  $this->pageRenderer->addCssInlineBlock('loginBackgroundImage', $backgroundImageStyles, useNonce: true);
237  }
238  if (($highlightColorStyles = $this->authenticationStyleInformation->getHighlightColorStyles()) !== '') {
239  $this->pageRenderer->addCssInlineBlock('loginHighlightColor', $highlightColorStyles, useNonce: true);
240  }
241  }
242 }
‪TYPO3\CMS\Backend\View\AuthenticationStyleInformation
Definition: AuthenticationStyleInformation.php:32
‪TYPO3\CMS\Backend\Controller\MfaController\cancelAction
‪cancelAction(ServerRequestInterface $request)
Definition: MfaController.php:165
‪TYPO3\CMS\Backend\Controller\AbstractMfaController\getBackendUser
‪getBackendUser()
Definition: AbstractMfaController.php:106
‪TYPO3\CMS\Backend\Controller\MfaController\authAction
‪authAction(ServerRequestInterface $request, MfaProviderManifestInterface $mfaProvider)
Definition: MfaController.php:91
‪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\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\Core\Authentication\Event\MfaVerificationFailedEvent
Definition: MfaVerificationFailedEvent.php:29
‪TYPO3\CMS\Core\SysLog\Action\Login
Definition: Login.php:24
‪TYPO3\CMS\Core\Authentication\Mfa\AUTH
‪@ AUTH
Definition: MfaViewType.php:27
‪TYPO3\CMS\Backend\Controller\MfaController\handleRequest
‪handleRequest(ServerRequestInterface $request)
Definition: MfaController.php:66
‪TYPO3\CMS\Core\Page\PageRenderer
Definition: PageRenderer.php:44
‪TYPO3\CMS\Core\Authentication\Mfa\MfaProviderPropertyManager\create
‪static create(MfaProviderManifestInterface $provider, AbstractUserAuthentication $user)
Definition: MfaProviderPropertyManager.php:193
‪TYPO3\CMS\Backend\Controller\MfaController\addCustomAuthenticationFormStyles
‪addCustomAuthenticationFormStyles()
Definition: MfaController.php:232
‪TYPO3\CMS\Backend\Routing\RouteRedirect
Definition: RouteRedirect.php:30
‪TYPO3\CMS\Backend\Routing\UriBuilder
Definition: UriBuilder.php:44
‪TYPO3\CMS\Backend\ContextMenu\ItemProviders\ProviderInterface
Definition: ProviderInterface.php:24
‪TYPO3\CMS\Core\SysLog\Error
Definition: Error.php:24
‪TYPO3\CMS\Core\Authentication\Mfa\MfaProviderInterface\canProcess
‪canProcess(ServerRequestInterface $request)
‪TYPO3\CMS\Backend\Controller\MfaController\verifyAction
‪verifyAction(ServerRequestInterface $request, MfaProviderManifestInterface $mfaProvider)
Definition: MfaController.php:119
‪TYPO3\CMS\Backend\Controller\MfaController\__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, protected readonly EventDispatcherInterface $eventDispatcher,)
Definition: MfaController.php:52
‪TYPO3\CMS\Core\Authentication\Mfa\MfaProviderInterface\verify
‪verify(ServerRequestInterface $request, MfaProviderPropertyManager $propertyManager)
‪TYPO3\CMS\Core\Authentication\Mfa\MfaProviderPropertyManager
Definition: MfaProviderPropertyManager.php:33
‪TYPO3\CMS\Core\Authentication\Mfa\MfaProviderInterface\isLocked
‪isLocked(MfaProviderPropertyManager $propertyManager)
‪TYPO3\CMS\Backend\Controller\AbstractMfaController\initializeMfaConfiguration
‪initializeMfaConfiguration()
Definition: AbstractMfaController.php:71
‪TYPO3\CMS\Core\Http\RedirectResponse
Definition: RedirectResponse.php:30
‪TYPO3\CMS\Backend\Controller\MfaController\getMfaProviderFromRequest
‪getMfaProviderFromRequest(ServerRequestInterface $request)
Definition: MfaController.php:218
‪TYPO3\CMS\Backend\Controller\MfaController
Definition: MfaController.php:50
‪$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\Controller\MfaController\getAlternativeProviders
‪ProviderInterface[] getAlternativeProviders(MfaProviderManifestInterface $mfaProvider)
Definition: MfaController.php:177
‪TYPO3\CMS\Backend\Attribute\AsController
Definition: AsController.php:25
‪TYPO3\CMS\Backend\Routing\RouteRedirect\createFromRequest
‪static createFromRequest(ServerRequestInterface $request)
Definition: RouteRedirect.php:58
‪TYPO3\CMS\Core\Authentication\Mfa\MfaViewType
‪MfaViewType
Definition: MfaViewType.php:24
‪TYPO3\CMS\Backend\Controller
Definition: AboutController.php:18
‪TYPO3\CMS\Core\SysLog\Type
Definition: Type.php:28
‪TYPO3\CMS\Backend\Controller\AbstractMfaController
Definition: AbstractMfaController.php:34
‪TYPO3\CMS\Backend\Controller\MfaController\log
‪log(string $message, array $additionalData=[], ?MfaProviderManifestInterface $mfaProvider=null, int $action=Login::LOGIN, int $error=SystemLogErrorClassification::MESSAGE)
Definition: MfaController.php:188