Passed
Push — master ( 25a926...39145a )
by
unknown
20:30
created

MfaController   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 164
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 18
eloc 58
c 1
b 0
f 0
dl 0
loc 164
rs 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
A log() 0 16 2
A getAlternativeProviders() 0 5 2
A initializeAction() 0 9 3
A verifyAction() 0 26 4
A handleRequest() 0 26 5
A cancelAction() 0 5 1
A authAction() 0 9 1
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\Exception\RouteNotFoundException;
26
use TYPO3\CMS\Core\Authentication\Mfa\MfaProviderPropertyManager;
27
use TYPO3\CMS\Core\Authentication\Mfa\MfaViewType;
28
use TYPO3\CMS\Core\Http\HtmlResponse;
29
use TYPO3\CMS\Core\Http\RedirectResponse;
30
31
/**
32
 * Controller to provide a multi-factor authentication endpoint
33
 *
34
 * @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
35
 */
36
class MfaController extends AbstractMfaController implements LoggerAwareInterface
37
{
38
    use LoggerAwareTrait;
39
40
    protected array $allowedActions = ['auth', 'verify', 'cancel'];
41
42
    /**
43
     * Main entry point, checking prerequisite, initializing and setting
44
     * up the view and finally dispatching to the requested action.
45
     *
46
     * @param ServerRequestInterface $request
47
     * @return ResponseInterface
48
     */
49
    public function handleRequest(ServerRequestInterface $request): ResponseInterface
50
    {
51
        $action = (string)($request->getQueryParams()['action'] ?? $request->getParsedBody()['action'] ?? 'auth');
52
53
        if (!$this->isActionAllowed($action)) {
54
            throw new \InvalidArgumentException('Action not allowed', 1611879243);
55
        }
56
57
        $this->initializeAction($request);
58
        // All actions expect "cancel" require a provider to deal with.
59
        // If non is found at this point, throw an exception since this should never happen.
60
        if ($this->mfaProvider === null && $action !== 'cancel') {
61
            throw new \InvalidArgumentException('No active MFA provider was found!', 1611879242);
62
        }
63
64
        $this->view = $this->moduleTemplate->getView();
65
        $this->view->setTemplateRootPaths(['EXT:backend/Resources/Private/Templates/Mfa']);
0 ignored issues
show
Bug introduced by
The method setTemplateRootPaths() does not exist on TYPO3\CMS\Extbase\Mvc\View\ViewInterface. It seems like you code against a sub-type of said class. However, the method does not exist in TYPO3\CMS\Extbase\Mvc\View\AbstractView or TYPO3\CMS\Extbase\Mvc\View\JsonView. Are you sure you never get one of those? ( Ignorable by Annotation )

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

65
        $this->view->/** @scrutinizer ignore-call */ 
66
                     setTemplateRootPaths(['EXT:backend/Resources/Private/Templates/Mfa']);
Loading history...
Bug introduced by
The method setTemplateRootPaths() 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

65
        $this->view->/** @scrutinizer ignore-call */ 
66
                     setTemplateRootPaths(['EXT:backend/Resources/Private/Templates/Mfa']);

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...
66
        $this->view->setTemplate('Auth');
0 ignored issues
show
Bug introduced by
The method setTemplate() does not exist on TYPO3\CMS\Extbase\Mvc\View\ViewInterface. It seems like you code against a sub-type of TYPO3\CMS\Extbase\Mvc\View\ViewInterface such as TYPO3\CMS\Extbase\Mvc\View\EmptyView or TYPO3\CMS\Fluid\View\AbstractTemplateView or TYPO3\CMS\Extbase\Mvc\View\NotFoundView. ( Ignorable by Annotation )

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

66
        $this->view->/** @scrutinizer ignore-call */ 
67
                     setTemplate('Auth');
Loading history...
67
        $this->view->assign('hasAuthError', (bool)($request->getQueryParams()['failure'] ?? false));
68
69
        $result = $this->{$action . 'Action'}($request);
70
        if ($result instanceof ResponseInterface) {
71
            return $result;
72
        }
73
        $this->moduleTemplate->setTitle('TYPO3 CMS Login: ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename']);
74
        return new HtmlResponse($this->moduleTemplate->renderContent());
75
    }
76
77
    /**
78
     * Setup the authentication view for the provider by using provider specific content
79
     *
80
     * @param ServerRequestInterface $request
81
     */
82
    public function authAction(ServerRequestInterface $request): void
83
    {
84
        $propertyManager = MfaProviderPropertyManager::create($this->mfaProvider, $this->getBackendUser());
0 ignored issues
show
Bug introduced by
It seems like $this->mfaProvider can also be of type null; however, parameter $provider of TYPO3\CMS\Core\Authentic...opertyManager::create() does only seem to accept TYPO3\CMS\Core\Authentic...oviderManifestInterface, maybe add an additional type check? ( Ignorable by Annotation )

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

84
        $propertyManager = MfaProviderPropertyManager::create(/** @scrutinizer ignore-type */ $this->mfaProvider, $this->getBackendUser());
Loading history...
85
        $providerResponse = $this->mfaProvider->handleRequest($request, $propertyManager, MfaViewType::AUTH);
0 ignored issues
show
Bug introduced by
The method handleRequest() 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

85
        /** @scrutinizer ignore-call */ 
86
        $providerResponse = $this->mfaProvider->handleRequest($request, $propertyManager, MfaViewType::AUTH);

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...
86
        $this->view->assignMultiple([
87
            'provider' => $this->mfaProvider,
88
            'alternativeProviders' => $this->getAlternativeProviders(),
89
            'isLocked' => $this->mfaProvider->isLocked($propertyManager),
90
            'providerContent' => $providerResponse->getBody()
91
        ]);
92
    }
93
94
    /**
95
     * Handle verification request, receiving from the auth view
96
     * by forwarding the request to the appropriate provider.
97
     *
98
     * @param ServerRequestInterface $request
99
     * @return ResponseInterface
100
     * @throws RouteNotFoundException
101
     */
102
    public function verifyAction(ServerRequestInterface $request): ResponseInterface
103
    {
104
        $propertyManager = MfaProviderPropertyManager::create($this->mfaProvider, $this->getBackendUser());
0 ignored issues
show
Bug introduced by
It seems like $this->mfaProvider can also be of type null; however, parameter $provider of TYPO3\CMS\Core\Authentic...opertyManager::create() does only seem to accept TYPO3\CMS\Core\Authentic...oviderManifestInterface, maybe add an additional type check? ( Ignorable by Annotation )

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

104
        $propertyManager = MfaProviderPropertyManager::create(/** @scrutinizer ignore-type */ $this->mfaProvider, $this->getBackendUser());
Loading history...
105
106
        // Check if the provider can process the request and is not temporarily blocked
107
        if (!$this->mfaProvider->canProcess($request) || $this->mfaProvider->isLocked($propertyManager)) {
108
            // If this fails, cancel the authentication
109
            return $this->cancelAction($request);
110
        }
111
        // Call the provider to verify the request
112
        if (!$this->mfaProvider->verify($request, $propertyManager)) {
113
            $this->log('Multi-factor authentication failed');
114
            // If failed, initiate a redirect back to the auth view
115
            return new RedirectResponse($this->uriBuilder->buildUriFromRoute(
116
                'auth_mfa',
117
                [
118
                    'identifier' => $this->mfaProvider->getIdentifier(),
119
                    'failure' => true
120
                ]
121
            ));
122
        }
123
        $this->log('Multi-factor authentication successfull');
124
        // If verified, store this information in the session
125
        // and initiate a redirect back to the login view.
126
        $this->getBackendUser()->setAndSaveSessionData('mfa', true);
127
        return new RedirectResponse($this->uriBuilder->buildUriFromRoute('login'));
128
    }
129
130
    /**
131
     * Allow the user to cancel the multi-factor authentication by
132
     * calling logoff on the user object, to destroy the session and
133
     * other already gathered information and finally initiate a
134
     * redirect back to the login.
135
     *
136
     * @param ServerRequestInterface $request
137
     * @return ResponseInterface
138
     * @throws RouteNotFoundException
139
     */
140
    public function cancelAction(ServerRequestInterface $request): ResponseInterface
141
    {
142
        $this->log('Multi-factor authentication canceled');
143
        $this->getBackendUser()->logoff();
144
        return new RedirectResponse($this->uriBuilder->buildUriFromRoute('login'));
145
    }
146
147
    /**
148
     * Initialize the action by fetching the requested provider by its identifier
149
     *
150
     * @param ServerRequestInterface $request
151
     */
152
    protected function initializeAction(ServerRequestInterface $request): void
153
    {
154
        $identifier = (string)($request->getQueryParams()['identifier'] ?? $request->getParsedBody()['identifier'] ?? '');
155
        // Check if given identifier is valid
156
        if ($this->isValidIdentifier($identifier)) {
157
            $provider = $this->mfaProviderRegistry->getProvider($identifier);
158
            // Only add provider if it was activated by the current user
159
            if ($provider->isActive(MfaProviderPropertyManager::create($provider, $this->getBackendUser()))) {
160
                $this->mfaProvider = $provider;
161
            }
162
        }
163
    }
164
165
    /**
166
     * Fetch alternative (activated and allowed) providers for the user to chose from
167
     *
168
     * @return ProviderInterface[]
169
     */
170
    protected function getAlternativeProviders(): array
171
    {
172
        return array_filter($this->allowedProviders, function ($provider) {
173
            return $provider !== $this->mfaProvider
174
                && $provider->isActive(MfaProviderPropertyManager::create($provider, $this->getBackendUser()));
175
        });
176
    }
177
178
    /**
179
     * Log debug information for MFA events
180
     *
181
     * @param string $message
182
     * @param array $additionalData
183
     */
184
    protected function log(string $message, array $additionalData = []): void
185
    {
186
        $user = $this->getBackendUser();
187
        $context = [
188
            'user' => [
189
                'uid' => $user->user[$user->userid_column],
190
                'username' => $user->user[$user->username_column]
191
            ]
192
        ];
193
        if ($this->mfaProvider !== null) {
194
            $context['provider'] = $this->mfaProvider->getIdentifier();
195
            $context['isProviderLocked'] = $this->mfaProvider->isLocked(
196
                MfaProviderPropertyManager::create($this->mfaProvider, $user)
197
            );
198
        }
199
        $this->logger->debug($message, array_replace_recursive($context, $additionalData));
200
    }
201
}
202