initializeMfaConfiguration(); $action = (string)($request->getQueryParams()['action'] ?? $request->getParsedBody()['action'] ?? 'auth'); switch ($action) { case 'auth': case 'verify': $mfaProvider = $this->getMfaProviderFromRequest($request); // All actions except "cancel" require a provider to deal with. // If non is found at this point, throw an exception since this should never happen. if ($mfaProvider === null) { throw new \InvalidArgumentException('No active MFA provider was found!', 1611879242); } return $this->{$action . 'Action'}($request, $mfaProvider); case 'cancel': return $this->cancelAction($request); default: throw new \InvalidArgumentException('Action not allowed', 1611879244); } } /** * Set up the authentication view for the provider by using provider specific content. */ protected function authAction(ServerRequestInterface $request, MfaProviderManifestInterface $mfaProvider): ResponseInterface { $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 = $this->backendViewFactory->create($request); $propertyManager = MfaProviderPropertyManager::create($mfaProvider, $this->getBackendUser()); $providerResponse = $mfaProvider->handleRequest($request, $propertyManager, MfaViewType::AUTH); $view->assignMultiple([ 'provider' => $mfaProvider, 'alternativeProviders' => $this->getAlternativeProviders($mfaProvider), '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->addCustomAuthenticationFormStyles($request); $this->pageRenderer->setBodyContent('' . $view->render('Mfa/Auth')); return $this->pageRenderer->renderResponse($request); } /** * Handle verification request, receiving from the auth view * by forwarding the request to the appropriate provider. */ protected function verifyAction(ServerRequestInterface $request, MfaProviderManifestInterface $mfaProvider): ResponseInterface { $backendUser = $this->getBackendUser(); $propertyManager = MfaProviderPropertyManager::create($mfaProvider, $backendUser); // Check if the provider can process the request and is not temporarily blocked if (!$mfaProvider->canProcess($request) || $mfaProvider->isLocked($propertyManager)) { // If this fails, cancel the authentication return $this->cancelAction($request); } // Call the provider to verify the request if (!$mfaProvider->verify($request, $propertyManager)) { $this->log( 'Multi-factor authentication failed for user \'###USERNAME###\' with provider \'' . $mfaProvider->getIdentifier() . '\'!', [], null, Login::ATTEMPT, SystemLogErrorClassification::SECURITY_NOTICE ); $this->eventDispatcher->dispatch( new MfaVerificationFailedEvent($request, $propertyManager, $mfaProvider) ); // If failed, initiate a redirect back to the auth view return new RedirectResponse($this->uriBuilder->buildUriWithRedirect( 'auth_mfa', [ 'identifier' => $mfaProvider->getIdentifier(), 'failure' => true, ], RouteRedirect::createFromRequest($request) )); } $this->log('Multi-factor authentication successful for user ###USERNAME###'); // If verified, store this information in the session // and initiate a redirect back to the login view. $backendUser->setAndSaveSessionData('mfa', true); $backendUser->handleUserLoggedIn($request); return new RedirectResponse( $this->uriBuilder->buildUriWithRedirect('login', [], RouteRedirect::createFromRequest($request)) ); } /** * Allow the user to cancel the multi-factor authentication 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. */ protected function cancelAction(ServerRequestInterface $request): ResponseInterface { $this->log('Multi-factor authentication canceled for user ###USERNAME###'); $this->getBackendUser()->logoff(); return new RedirectResponse($this->uriBuilder->buildUriWithRedirect('login', [], RouteRedirect::createFromRequest($request))); } /** * Fetch alternative (activated and allowed) providers for the user to chose from * * @return ProviderInterface[] */ protected function getAlternativeProviders(MfaProviderManifestInterface $mfaProvider): array { return array_filter($this->allowedProviders, function (MfaProviderManifestInterface $provider) use ($mfaProvider): bool { return $provider !== $mfaProvider && $provider->isActive(MfaProviderPropertyManager::create($provider, $this->getBackendUser())); }); } /** * Log debug information for MFA events */ protected function log( string $message, array $additionalData = [], ?MfaProviderManifestInterface $mfaProvider = null, int $action = Login::LOGIN, int $error = SystemLogErrorClassification::MESSAGE ): void { $user = $this->getBackendUser(); $username = $user->getUserName(); $context = [ 'user' => [ 'uid' => $user->getUserId(), 'username' => $username, ], ]; if ($mfaProvider !== null) { $context['provider'] = $mfaProvider->getIdentifier(); $context['isProviderLocked'] = $mfaProvider->isLocked( MfaProviderPropertyManager::create($mfaProvider, $user) ); } $message = str_replace('###USERNAME###', $username, $message); $data = array_replace_recursive($context, $additionalData); $this->logger->debug($message, $data); if ($user->writeStdLog) { // Write to sys_log if enabled $user->writelog(SystemLogType::LOGIN, $action, $error, null, $message, $data); } } protected function getMfaProviderFromRequest(ServerRequestInterface $request): ?MfaProviderManifestInterface { $identifier = (string)($request->getQueryParams()['identifier'] ?? $request->getParsedBody()['identifier'] ?? ''); // Check if given identifier is valid if ($this->isValidIdentifier($identifier)) { $provider = $this->mfaProviderRegistry->getProvider($identifier); // Only add provider if it was activated by the current user if ($provider->isActive(MfaProviderPropertyManager::create($provider, $this->getBackendUser()))) { return $provider; } } return null; } protected function addCustomAuthenticationFormStyles(ServerRequestInterface $request): void { if (($backgroundImageStyles = $this->authenticationStyleInformation->getBackgroundImageStyles($request)) !== '') { $this->pageRenderer->addCssInlineBlock('loginBackgroundImage', $backgroundImageStyles, null, false, true); } if (($highlightColorStyles = $this->authenticationStyleInformation->getHighlightColorStyles()) !== '') { $this->pageRenderer->addCssInlineBlock('loginHighlightColor', $highlightColorStyles, null, false, true); } } }