Passed
Push — master ( da9c6d...70f855 )
by
unknown
17:46
created

SwitchUserController::switchUserAction()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 50
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 32
dl 0
loc 50
rs 8.4746
c 1
b 0
f 0
cc 7
nc 4
nop 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\ResponseFactoryInterface;
21
use Psr\Http\Message\ResponseInterface;
22
use Psr\Http\Message\ServerRequestInterface;
23
use TYPO3\CMS\Backend\Authentication\Event\SwitchUserEvent;
24
use TYPO3\CMS\Backend\Routing\UriBuilder;
25
use TYPO3\CMS\Backend\Utility\BackendUtility;
26
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
27
use TYPO3\CMS\Core\EventDispatcher\EventDispatcher;
28
use TYPO3\CMS\Core\Session\Backend\SessionBackendInterface;
29
use TYPO3\CMS\Core\Session\SessionManager;
30
use TYPO3\CMS\Core\SysLog\Type;
31
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
32
use TYPO3\CMS\Core\Utility\GeneralUtility;
33
34
/**
35
 * @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
36
 */
37
class SwitchUserController
38
{
39
    protected const RECENT_USERS_LIMIT = 3;
40
41
    protected EventDispatcher $eventDispatcher;
42
    protected UriBuilder $uriBuilder;
43
    protected ResponseFactoryInterface $responseFactory;
44
    protected SessionBackendInterface $sessionBackend;
45
46
    public function __construct(
47
        EventDispatcher $eventDispatcher,
48
        UriBuilder $uriBuilder,
49
        ResponseFactoryInterface $responseFactory
50
    ) {
51
        $this->eventDispatcher = $eventDispatcher;
52
        $this->uriBuilder = $uriBuilder;
53
        $this->responseFactory = $responseFactory;
54
        $this->sessionBackend = GeneralUtility::makeInstance(SessionManager::class)->getSessionBackend('BE');
55
    }
56
57
    /**
58
     * Handle switching current user to the requested target user
59
     */
60
    public function switchUserAction(ServerRequestInterface $request): ResponseInterface
61
    {
62
        $currentUser = $this->getBackendUserAuthentication();
63
        $targetUserId = (int)($request->getParsedBody()['targetUser'] ?? 0);
64
65
        if (!$targetUserId
66
            || $targetUserId === (int)($currentUser->user[$currentUser->userid_column] ?? 0)
67
            || !$currentUser->isAdmin()
68
            || $currentUser->getOriginalUserIdWhenInSwitchUserMode() !== null
69
        ) {
70
            return $this->jsonResponse(['success' => false]);
71
        }
72
73
        $targetUser = BackendUtility::getRecord('be_users', $targetUserId, '*', BackendUtility::BEenableFields('be_users'));
74
        if ($targetUser === null) {
75
            return $this->jsonResponse(['success' => false]);
76
        }
77
78
        if (ExtensionManagementUtility::isLoaded('beuser')) {
79
            // Set backend user listing module as starting module if installed
80
            $currentUser->uc['startModuleOnFirstLogin'] = 'system_BeuserTxBeuser';
81
        }
82
        $currentUser->uc['recentSwitchedToUsers'] = $this->generateListOfMostRecentSwitchedUsers($targetUserId);
83
        $currentUser->writeUC();
84
85
        // Write user switch to log
86
        $currentUser->writelog(Type::LOGIN, 2, 0, 1, 'User %s switched to user %s (be_users:%s)', [
87
            $currentUser->user[$currentUser->username_column] ?? '',
88
            $targetUser['username'] ?? '',
89
            $targetUserId,
90
        ]);
91
92
        $sessionObject = $currentUser->getSession();
93
        $sessionObject->set('backuserid', (int)($currentUser->user[$currentUser->userid_column] ?? 0));
94
        $sessionRecord = $sessionObject->toArray();
95
        $sessionRecord['ses_userid'] = $targetUserId;
96
        $this->sessionBackend->update($sessionObject->getIdentifier(), $sessionRecord);
97
        // We must regenerate the internal session so the new ses_userid is present in the userObject
98
        $currentUser->enforceNewSessionId();
99
100
        $event = new SwitchUserEvent(
101
            $currentUser->getSession()->getIdentifier(),
102
            $targetUser,
103
            (array)$currentUser->user
104
        );
105
        $this->eventDispatcher->dispatch($event);
106
107
        return $this->jsonResponse([
108
            'success' => true,
109
            'url' => $this->uriBuilder->buildUriFromRoute('main')
110
        ]);
111
    }
112
113
    /**
114
     * Handle exiting the switch user mode
115
     */
116
    public function exitSwitchUserAction(ServerRequestInterface $request): ResponseInterface
117
    {
118
        $currentUser = $this->getBackendUserAuthentication();
119
120
        if ($currentUser->getOriginalUserIdWhenInSwitchUserMode() === null) {
121
            return $this->jsonResponse(['success' => false]);
122
        }
123
124
        $sessionObject = $currentUser->getSession();
125
        $originalUser = (int)$sessionObject->get('backuserid');
126
        $sessionObject->set('backuserid', null);
127
        $sessionRecord = $sessionObject->toArray();
128
        $sessionRecord['ses_userid'] = $originalUser;
129
        $this->sessionBackend->update($sessionObject->getIdentifier(), $sessionRecord);
130
        // We must regenerate the internal session so the new ses_userid is present in the userObject
131
        $currentUser->enforceNewSessionId();
132
133
        return $this->jsonResponse([
134
            'success' => true,
135
            'url' => $this->uriBuilder->buildUriFromRoute('main')
136
        ]);
137
    }
138
139
    /**
140
     * Generates a list of users to whom where switched in the past. This is limited by RECENT_USERS_LIMIT.
141
     *
142
     * @param int $targetUserUid
143
     * @return int[]
144
     */
145
    protected function generateListOfMostRecentSwitchedUsers(int $targetUserUid): array
146
    {
147
        $latestUserUids = [];
148
        $backendUser = $this->getBackendUserAuthentication();
149
150
        if (isset($backendUser->uc['recentSwitchedToUsers']) && is_array($backendUser->uc['recentSwitchedToUsers'])) {
151
            $latestUserUids = $backendUser->uc['recentSwitchedToUsers'];
152
        }
153
154
        // Remove potentially existing user in that list
155
        $index = array_search($targetUserUid, $latestUserUids, true);
156
        if ($index !== false) {
157
            unset($latestUserUids[$index]);
158
        }
159
        array_unshift($latestUserUids, $targetUserUid);
160
161
        return array_slice($latestUserUids, 0, static::RECENT_USERS_LIMIT);
162
    }
163
164
    protected function jsonResponse(array $data): ResponseInterface
165
    {
166
        $response = $this->responseFactory
167
            ->createResponse()
168
            ->withAddedHeader('Content-Type', 'application/json; charset=utf-8');
169
170
        $response->getBody()->write(json_encode($data));
171
        return $response;
172
    }
173
174
    protected function getBackendUserAuthentication(): BackendUserAuthentication
175
    {
176
        return $GLOBALS['BE_USER'];
177
    }
178
}
179