Passed
Push — master ( a560b9...236448 )
by
unknown
16:47
created

MfaController::verifyAction()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 28
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 14
c 1
b 0
f 0
dl 0
loc 28
rs 9.7998
cc 4
nc 3
nop 2
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
18
namespace TYPO3\CMS\Backend\Controller;
19
20
use Psr\Http\Message\ResponseInterface;
21
use Psr\Http\Message\ServerRequestInterface;
22
use Psr\Log\LoggerAwareInterface;
23
use Psr\Log\LoggerAwareTrait;
24
use TYPO3\CMS\Backend\ContextMenu\ItemProviders\ProviderInterface;
25
use TYPO3\CMS\Backend\Routing\UriBuilder;
26
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
27
use TYPO3\CMS\Backend\View\AuthenticationStyleInformation;
28
use TYPO3\CMS\Core\Authentication\Mfa\MfaProviderManifestInterface;
29
use TYPO3\CMS\Core\Authentication\Mfa\MfaProviderPropertyManager;
30
use TYPO3\CMS\Core\Authentication\Mfa\MfaProviderRegistry;
31
use TYPO3\CMS\Core\Authentication\Mfa\MfaViewType;
32
use TYPO3\CMS\Core\Http\HtmlResponse;
33
use TYPO3\CMS\Core\Http\RedirectResponse;
34
use TYPO3\CMS\Core\Page\PageRenderer;
35
36
/**
37
 * Controller to provide a multi-factor authentication endpoint
38
 *
39
 * @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
40
 */
41
class MfaController extends AbstractMfaController implements LoggerAwareInterface
42
{
43
    use LoggerAwareTrait;
44
45
    protected AuthenticationStyleInformation $authenticationStyleInformation;
46
    protected PageRenderer $pageRenderer;
47
48
    public function __construct(
49
        UriBuilder $uriBuilder,
50
        MfaProviderRegistry $mfaProviderRegistry,
51
        ModuleTemplateFactory $moduleTemplateFactory,
52
        AuthenticationStyleInformation $authenticationStyleInformation,
53
        PageRenderer $pageRenderer
54
    ) {
55
        parent::__construct($uriBuilder, $mfaProviderRegistry, $moduleTemplateFactory);
56
        $this->authenticationStyleInformation = $authenticationStyleInformation;
57
        $this->pageRenderer = $pageRenderer;
58
    }
59
60
    /**
61
     * Main entry point, checking prerequisite, initializing and setting
62
     * up the view and finally dispatching to the requested action.
63
     */
64
    public function handleRequest(ServerRequestInterface $request): ResponseInterface
65
    {
66
        $this->moduleTemplate = $this->moduleTemplateFactory->create($request);
67
        $action = (string)($request->getQueryParams()['action'] ?? $request->getParsedBody()['action'] ?? 'auth');
68
69
        switch ($action) {
70
            case 'auth':
71
            case 'verify':
72
                $mfaProvider = $this->getMfaProviderFromRequest($request);
73
                // All actions except "cancel" require a provider to deal with.
74
                // If non is found at this point, throw an exception since this should never happen.
75
                if ($mfaProvider === null) {
76
                    throw new \InvalidArgumentException('No active MFA provider was found!', 1611879242);
77
                }
78
                return $this->{$action . 'Action'}($request, $mfaProvider);
79
            case 'cancel':
80
                return $this->cancelAction($request);
81
            default:
82
                throw new \InvalidArgumentException('Action not allowed', 1611879244);
83
        }
84
    }
85
86
    /**
87
     * Setup the authentication view for the provider by using provider specific content
88
     */
89
    public function authAction(ServerRequestInterface $request, MfaProviderManifestInterface $mfaProvider): ResponseInterface
90
    {
91
        $view = $this->moduleTemplate->getView();
0 ignored issues
show
Bug introduced by
The method getView() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

91
        /** @scrutinizer ignore-call */ 
92
        $view = $this->moduleTemplate->getView();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
92
        $view->setTemplateRootPaths(['EXT:backend/Resources/Private/Templates/Mfa']);
93
        $view->setTemplate('Auth');
94
        $view->assign('formUrl', $this->uriBuilder->buildUriWithRedirectFromRequest(
95
            'auth_mfa',
96
            [
97
                'action' => 'verify'
98
            ],
99
            $request
100
        ));
101
        $view->assign('redirectRoute', $request->getQueryParams()['redirect'] ?? '');
102
        $view->assign('redirectParams', $request->getQueryParams()['redirectParams'] ?? '');
103
        $view->assign('hasAuthError', (bool)($request->getQueryParams()['failure'] ?? false));
104
        $propertyManager = MfaProviderPropertyManager::create($mfaProvider, $this->getBackendUser());
105
        $providerResponse = $mfaProvider->handleRequest($request, $propertyManager, MfaViewType::AUTH);
106
        $view->assignMultiple([
107
            'provider' => $mfaProvider,
108
            'alternativeProviders' => $this->getAlternativeProviders($mfaProvider),
109
            'isLocked' => $mfaProvider->isLocked($propertyManager),
110
            'providerContent' => $providerResponse->getBody(),
111
            'footerNote' => $this->authenticationStyleInformation->getFooterNote()
112
        ]);
113
        $this->moduleTemplate->setTitle('TYPO3 CMS Login: ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename']);
114
        $this->addCustomAuthenticationFormStyles();
115
        return new HtmlResponse($this->moduleTemplate->renderContent());
116
    }
117
118
    /**
119
     * Handle verification request, receiving from the auth view
120
     * by forwarding the request to the appropriate provider.
121
     */
122
    public function verifyAction(ServerRequestInterface $request, MfaProviderManifestInterface $mfaProvider): ResponseInterface
123
    {
124
        $propertyManager = MfaProviderPropertyManager::create($mfaProvider, $this->getBackendUser());
125
126
        // Check if the provider can process the request and is not temporarily blocked
127
        if (!$mfaProvider->canProcess($request) || $mfaProvider->isLocked($propertyManager)) {
128
            // If this fails, cancel the authentication
129
            return $this->cancelAction($request);
130
        }
131
        // Call the provider to verify the request
132
        if (!$mfaProvider->verify($request, $propertyManager)) {
133
            $this->log('Multi-factor authentication failed');
134
            // If failed, initiate a redirect back to the auth view
135
            return new RedirectResponse($this->uriBuilder->buildUriWithRedirectFromRequest(
136
                'auth_mfa',
137
                [
138
                    'identifier' => $mfaProvider->getIdentifier(),
139
                    'failure' => true
140
                ],
141
                $request
142
            ));
143
        }
144
        $this->log('Multi-factor authentication successful');
145
        // If verified, store this information in the session
146
        // and initiate a redirect back to the login view.
147
        $this->getBackendUser()->setAndSaveSessionData('mfa', true);
148
        return new RedirectResponse(
149
            $this->uriBuilder->buildUriWithRedirectFromRequest('login', [], $request)
150
        );
151
    }
152
153
    /**
154
     * Allow the user to cancel the multi-factor authentication by
155
     * calling logoff on the user object, to destroy the session and
156
     * other already gathered information and finally initiate a
157
     * redirect back to the login.
158
     */
159
    public function cancelAction(ServerRequestInterface $request): ResponseInterface
160
    {
161
        $this->log('Multi-factor authentication canceled');
162
        $this->getBackendUser()->logoff();
163
        return new RedirectResponse($this->uriBuilder->buildUriWithRedirectFromRequest('login', [], $request));
164
    }
165
166
    /**
167
     * Fetch alternative (activated and allowed) providers for the user to chose from
168
     *
169
     * @return ProviderInterface[]
170
     */
171
    protected function getAlternativeProviders(MfaProviderManifestInterface $mfaProvider): array
172
    {
173
        return array_filter($this->allowedProviders, function ($provider) use ($mfaProvider) {
174
            return $provider !== $mfaProvider
175
                && $provider->isActive(MfaProviderPropertyManager::create($provider, $this->getBackendUser()));
176
        });
177
    }
178
179
    /**
180
     * Log debug information for MFA events
181
     */
182
    protected function log(string $message, array $additionalData = [], ?MfaProviderManifestInterface $mfaProvider = null): void
183
    {
184
        $user = $this->getBackendUser();
185
        $context = [
186
            'user' => [
187
                'uid' => $user->user[$user->userid_column],
188
                'username' => $user->user[$user->username_column]
189
            ]
190
        ];
191
        if ($mfaProvider !== null) {
192
            $context['provider'] = $mfaProvider->getIdentifier();
193
            $context['isProviderLocked'] = $mfaProvider->isLocked(
194
                MfaProviderPropertyManager::create($mfaProvider, $user)
195
            );
196
        }
197
        $this->logger->debug($message, array_replace_recursive($context, $additionalData));
198
    }
199
200
    protected function getMfaProviderFromRequest(ServerRequestInterface $request): ?MfaProviderManifestInterface
201
    {
202
        $identifier = (string)($request->getQueryParams()['identifier'] ?? $request->getParsedBody()['identifier'] ?? '');
203
        // Check if given identifier is valid
204
        if ($this->isValidIdentifier($identifier)) {
205
            $provider = $this->mfaProviderRegistry->getProvider($identifier);
206
            // Only add provider if it was activated by the current user
207
            if ($provider->isActive(MfaProviderPropertyManager::create($provider, $this->getBackendUser()))) {
208
                return $provider;
209
            }
210
        }
211
        return null;
212
    }
213
214
    protected function addCustomAuthenticationFormStyles(): void
215
    {
216
        if (($backgroundImageStyles = $this->authenticationStyleInformation->getBackgroundImageStyles()) !== '') {
217
            $this->pageRenderer->addCssInlineBlock('loginBackgroundImage', $backgroundImageStyles);
218
        }
219
        if (($highlightColorStyles = $this->authenticationStyleInformation->getHighlightColorStyles()) !== '') {
220
            $this->pageRenderer->addCssInlineBlock('loginHighlightColor', $highlightColorStyles);
221
        }
222
    }
223
}
224