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

MfaAjaxController::getLanguageService()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 0
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 TYPO3\CMS\Core\Authentication\AbstractUserAuthentication;
23
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
24
use TYPO3\CMS\Core\Authentication\Mfa\MfaProviderPropertyManager;
25
use TYPO3\CMS\Core\Authentication\Mfa\MfaProviderRegistry;
26
use TYPO3\CMS\Core\Http\JsonResponse;
27
use TYPO3\CMS\Core\Localization\LanguageService;
28
use TYPO3\CMS\Core\Messaging\FlashMessage;
29
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
30
use TYPO3\CMS\Core\Utility\GeneralUtility;
31
use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
32
33
/**
34
 * Controller to manipulate MFA providers via AJAX in the backend
35
 *
36
 * @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
37
 */
38
class MfaAjaxController
39
{
40
    private const ALLOWED_ACTIONS = ['deactivate'];
41
42
    protected MfaProviderRegistry $mfaProviderRegistry;
43
    protected ?AbstractUserAuthentication $user = null;
44
45
    public function __construct(MfaProviderRegistry $mfaProviderRegistry)
46
    {
47
        $this->mfaProviderRegistry = $mfaProviderRegistry;
48
    }
49
50
    /**
51
     * Main entry point, checking prerequisite and dispatching to the requested action
52
     *
53
     * @param ServerRequestInterface $request
54
     * @return ResponseInterface
55
     */
56
    public function handleRequest(ServerRequestInterface $request): ResponseInterface
57
    {
58
        $action = (string)($request->getQueryParams()['action'] ?? $request->getParsedBody()['action'] ?? '');
59
60
        if (!in_array($action, self::ALLOWED_ACTIONS, true)) {
61
            return new JsonResponse($this->getResponseData(false, $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:ajax.invalidRequest')));
62
        }
63
64
        $userId = (int)($request->getParsedBody()['userId'] ?? 0);
65
        $tableName = (string)($request->getParsedBody()['tableName'] ?? '');
66
67
        if (!$userId || !in_array($tableName, ['be_users', 'fe_users'], true)) {
68
            return new JsonResponse($this->getResponseData(false, $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:ajax.invalidRequest')));
69
        }
70
71
        $this->user = $this->initializeUser($userId, $tableName);
72
73
        if (!$this->isAllowedToPerformAction($action)) {
74
            return new JsonResponse($this->getResponseData(false, $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:ajax.insufficientPermissions')));
75
        }
76
77
        return new JsonResponse($this->{$action . 'Action'}($request));
78
    }
79
80
    /**
81
     * Deactivate MFA providers
82
     * If the request contains a provider, it will be deactivated.
83
     * Otherwise all active providers are deactivated.
84
     *
85
     * @param ServerRequestInterface $request
86
     * @return array
87
     */
88
    protected function deactivateAction(ServerRequestInterface $request): array
89
    {
90
        $lang = $this->getLanguageService();
91
        $userName = (string)($this->user->user[$this->user->username_column] ?? '');
92
        $providerToDeactivate = (string)($request->getParsedBody()['provider'] ?? '');
93
94
        if ($providerToDeactivate === '') {
95
            // In case no provider is given, try to deactivate all active providers
96
            $providersToDeactivate = $this->mfaProviderRegistry->getActiveProviders($this->user);
0 ignored issues
show
Bug introduced by
It seems like $this->user can also be of type null; however, parameter $user of TYPO3\CMS\Core\Authentic...y::getActiveProviders() does only seem to accept TYPO3\CMS\Core\Authentic...tractUserAuthentication, 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

96
            $providersToDeactivate = $this->mfaProviderRegistry->getActiveProviders(/** @scrutinizer ignore-type */ $this->user);
Loading history...
97
            if ($providersToDeactivate === []) {
98
                return $this->getResponseData(false, $lang->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:ajax.deactivate.providersNotDeactivated'));
99
            }
100
            foreach ($providersToDeactivate as $identifier => $provider) {
101
                $propertyManager = MfaProviderPropertyManager::create($provider, $this->user);
0 ignored issues
show
Bug introduced by
It seems like $this->user can also be of type null; however, parameter $user of TYPO3\CMS\Core\Authentic...opertyManager::create() does only seem to accept TYPO3\CMS\Core\Authentic...tractUserAuthentication, 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

101
                $propertyManager = MfaProviderPropertyManager::create($provider, /** @scrutinizer ignore-type */ $this->user);
Loading history...
102
                if (!$provider->deactivate($request, $propertyManager)) {
103
                    return $this->getResponseData(false, sprintf($lang->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:ajax.deactivate.providerNotDeactivated'), $lang->sL($provider->getTitle())));
104
                }
105
            }
106
            return $this->getResponseData(true, sprintf($lang->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:ajax.deactivate.providersDeactivated'), $userName));
107
        }
108
109
        if (!$this->mfaProviderRegistry->hasProvider($providerToDeactivate)) {
110
            return $this->getResponseData(false, sprintf($lang->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:ajax.deactivate.providerNotFound'), $providerToDeactivate));
111
        }
112
113
        $provider = $this->mfaProviderRegistry->getProvider($providerToDeactivate);
114
        $propertyManager = MfaProviderPropertyManager::create($provider, $this->user);
115
116
        if (!$provider->deactivate($request, $propertyManager)) {
117
            return $this->getResponseData(false, sprintf($lang->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:ajax.deactivate.providerNotDeactivated'), $lang->sL($provider->getTitle())));
118
        }
119
120
        return $this->getResponseData(true, sprintf($lang->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:ajax.deactivate.providerDeactivated'), $lang->sL($provider->getTitle()), $userName));
121
    }
122
123
    /**
124
     * Initialize a user based on the table name
125
     *
126
     * @param int $userId
127
     * @param string $tableName
128
     * @return AbstractUserAuthentication
129
     */
130
    protected function initializeUser(int $userId, string $tableName): AbstractUserAuthentication
131
    {
132
        $user = $tableName === 'be_users'
133
            ? GeneralUtility::makeInstance(BackendUserAuthentication::class)
134
            : GeneralUtility::makeInstance(FrontendUserAuthentication::class);
135
136
        $user->enablecolumns = ['deleted' => true];
137
        $user->setBeUserByUid($userId);
138
139
        return $user;
140
    }
141
142
    /**
143
     * Prepare response data for a JSON response
144
     *
145
     * @param bool $success
146
     * @param string $message
147
     * @return array
148
     */
149
    protected function getResponseData(bool $success, string $message): array
150
    {
151
        return [
152
            'success' => $success,
153
            'status' => (new FlashMessageQueue('backend'))->enqueue(
154
                new FlashMessage(
155
                    $message,
156
                    $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_mfa.xlf:ajax.' . ($success ? 'success' : 'error'))
157
                )
158
            ),
159
            'remaining' => count($this->mfaProviderRegistry->getActiveProviders($this->user))
0 ignored issues
show
Bug introduced by
It seems like $this->user can also be of type null; however, parameter $user of TYPO3\CMS\Core\Authentic...y::getActiveProviders() does only seem to accept TYPO3\CMS\Core\Authentic...tractUserAuthentication, 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

159
            'remaining' => count($this->mfaProviderRegistry->getActiveProviders(/** @scrutinizer ignore-type */ $this->user))
Loading history...
160
        ];
161
    }
162
163
    /**
164
     * Check if the current logged in user is allowed to perform
165
     * the requested action on the selected user.
166
     *
167
     * @param string $action
168
     * @return bool
169
     */
170
    protected function isAllowedToPerformAction(string $action): bool
171
    {
172
        if ($action === 'deactivate') {
173
            $currentBackendUser = $this->getBackendUser();
174
            // Only admins are allowed to deactivate providers
175
            if (!$currentBackendUser->isAdmin()) {
176
                return false;
177
            }
178
            // System maintainer checks are only required for backend users
179
            if ($this->user instanceof BackendUserAuthentication) {
180
                $systemMaintainer = array_map('intval', $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemMaintainers'] ?? []);
181
                $isCurrentBackendUserSystemMaintainer = in_array((int)$currentBackendUser->user[$currentBackendUser->userid_column], $systemMaintainer, true);
182
                $isTargetUserSystemMaintainer = in_array((int)$this->user->user[$this->user->userid_column], $systemMaintainer, true);
183
                // Providers from system maintainers can only be deactivated by system maintainers
184
                if ($isTargetUserSystemMaintainer && !$isCurrentBackendUserSystemMaintainer) {
185
                    return false;
186
                }
187
            }
188
            return true;
189
        }
190
191
        return false;
192
    }
193
194
    protected function getBackendUser(): BackendUserAuthentication
195
    {
196
        return $GLOBALS['BE_USER'];
197
    }
198
199
    protected function getLanguageService(): LanguageService
200
    {
201
        return $GLOBALS['LANG'];
202
    }
203
}
204