1<?php 2 3/* 4 * This file is part of the TYPO3 CMS project. 5 * 6 * It is free software; you can redistribute it and/or modify it under 7 * the terms of the GNU General Public License, either version 2 8 * of the License, or any later version. 9 * 10 * For the full copyright and license information, please read the 11 * LICENSE.txt file that was distributed with this source code. 12 * 13 * The TYPO3 project - inspiring people to share! 14 */ 15 16namespace TYPO3\CMS\Beuser\Controller; 17 18use TYPO3\CMS\Backend\Authentication\Event\SwitchUserEvent; 19use TYPO3\CMS\Backend\Authentication\PasswordReset; 20use TYPO3\CMS\Backend\Utility\BackendUtility; 21use TYPO3\CMS\Beuser\Domain\Model\BackendUser; 22use TYPO3\CMS\Beuser\Domain\Model\Demand; 23use TYPO3\CMS\Beuser\Domain\Repository\BackendUserGroupRepository; 24use TYPO3\CMS\Beuser\Domain\Repository\BackendUserRepository; 25use TYPO3\CMS\Beuser\Domain\Repository\BackendUserSessionRepository; 26use TYPO3\CMS\Beuser\Service\ModuleDataStorageService; 27use TYPO3\CMS\Beuser\Service\UserInformationService; 28use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; 29use TYPO3\CMS\Core\Context\Context; 30use TYPO3\CMS\Core\Messaging\FlashMessage; 31use TYPO3\CMS\Core\Session\Backend\HashableSessionBackendInterface; 32use TYPO3\CMS\Core\Session\Backend\SessionBackendInterface; 33use TYPO3\CMS\Core\Session\SessionManager; 34use TYPO3\CMS\Core\Utility\GeneralUtility; 35use TYPO3\CMS\Core\Utility\HttpUtility; 36use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; 37use TYPO3\CMS\Extbase\Mvc\Exception\StopActionException; 38use TYPO3\CMS\Extbase\Mvc\RequestInterface; 39use TYPO3\CMS\Extbase\Mvc\ResponseInterface; 40use TYPO3\CMS\Extbase\Mvc\View\ViewInterface; 41use TYPO3\CMS\Extbase\Utility\LocalizationUtility; 42 43/** 44 * Backend module user administration controller 45 * @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API. 46 */ 47class BackendUserController extends ActionController 48{ 49 /** 50 * @var int 51 */ 52 const RECENT_USERS_LIMIT = 3; 53 54 /** 55 * @var \TYPO3\CMS\Beuser\Domain\Model\ModuleData 56 */ 57 protected $moduleData; 58 59 /** 60 * @var ModuleDataStorageService 61 */ 62 protected $moduleDataStorageService; 63 64 /** 65 * @var BackendUserRepository 66 */ 67 protected $backendUserRepository; 68 69 /** 70 * @var BackendUserGroupRepository 71 */ 72 protected $backendUserGroupRepository; 73 74 /** 75 * @var BackendUserSessionRepository 76 */ 77 protected $backendUserSessionRepository; 78 79 /** 80 * @var UserInformationService 81 */ 82 protected $userInformationService; 83 84 public function __construct( 85 ModuleDataStorageService $moduleDataStorageService, 86 BackendUserRepository $backendUserRepository, 87 BackendUserGroupRepository $backendUserGroupRepository, 88 BackendUserSessionRepository $backendUserSessionRepository, 89 UserInformationService $userInformationService 90 ) { 91 $this->moduleDataStorageService = $moduleDataStorageService; 92 $this->backendUserRepository = $backendUserRepository; 93 $this->backendUserGroupRepository = $backendUserGroupRepository; 94 $this->backendUserSessionRepository = $backendUserSessionRepository; 95 $this->userInformationService = $userInformationService; 96 } 97 98 /** 99 * Load and persist module data 100 * 101 * @param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request 102 * @param \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response 103 * @throws \TYPO3\CMS\Extbase\Mvc\Exception\StopActionException 104 */ 105 public function processRequest(RequestInterface $request, ResponseInterface $response) 106 { 107 $this->moduleData = $this->moduleDataStorageService->loadModuleData(); 108 // We "finally" persist the module data. 109 try { 110 parent::processRequest($request, $response); 111 $this->moduleDataStorageService->persistModuleData($this->moduleData); 112 } catch (StopActionException $e) { 113 $this->moduleDataStorageService->persistModuleData($this->moduleData); 114 throw $e; 115 } 116 } 117 118 /** 119 * Assign default variables to view 120 * @param ViewInterface $view 121 */ 122 protected function initializeView(ViewInterface $view) 123 { 124 $view->assignMultiple([ 125 'shortcutLabel' => 'backendUsers', 126 'dateFormat' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], 127 'timeFormat' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'], 128 ]); 129 } 130 131 /** 132 * Displays all BackendUsers 133 * - Switch session to different user 134 * 135 * @param \TYPO3\CMS\Beuser\Domain\Model\Demand $demand 136 */ 137 public function indexAction(Demand $demand = null) 138 { 139 if ($demand === null) { 140 $demand = $this->moduleData->getDemand(); 141 } else { 142 $this->moduleData->setDemand($demand); 143 } 144 // Switch user until logout 145 $switchUser = (int)GeneralUtility::_GP('SwitchUser'); 146 if ($switchUser > 0) { 147 $this->switchUser($switchUser); 148 } 149 $compareUserList = $this->moduleData->getCompareUserList(); 150 151 $this->view->assignMultiple([ 152 'onlineBackendUsers' => $this->getOnlineBackendUsers(), 153 'demand' => $demand, 154 'backendUsers' => $this->backendUserRepository->findDemanded($demand), 155 'backendUserGroups' => array_merge([''], $this->backendUserGroupRepository->findAll()->toArray()), 156 'compareUserUidList' => array_combine($compareUserList, $compareUserList), 157 'currentUserUid' => $this->getBackendUserAuthentication()->user['uid'], 158 'compareUserList' => !empty($compareUserList) ? $this->backendUserRepository->findByUidList($compareUserList) : '', 159 ]); 160 } 161 162 /** 163 * Views all currently logged in BackendUsers and their sessions 164 */ 165 public function onlineAction() 166 { 167 $onlineUsersAndSessions = []; 168 $onlineUsers = $this->backendUserRepository->findOnline(); 169 foreach ($onlineUsers as $onlineUser) { 170 $onlineUsersAndSessions[] = [ 171 'backendUser' => $onlineUser, 172 'sessions' => $this->backendUserSessionRepository->findByBackendUser($onlineUser) 173 ]; 174 } 175 176 $currentSessionId = $this->getBackendUserAuthentication()->getSessionId(); 177 $sessionBackend = $this->getSessionBackend(); 178 if ($sessionBackend instanceof HashableSessionBackendInterface) { 179 $currentSessionId = $sessionBackend->hash($currentSessionId); 180 } 181 $this->view->assignMultiple([ 182 'shortcutLabel' => 'onlineUsers', 183 'onlineUsersAndSessions' => $onlineUsersAndSessions, 184 'currentSessionId' => $currentSessionId, 185 ]); 186 } 187 188 /** 189 * @param int $uid 190 */ 191 public function showAction(int $uid = 0): void 192 { 193 $data = $this->userInformationService->getUserInformation($uid); 194 $this->view->assignMultiple([ 195 'shortcutLabel' => 'showUser', 196 'data' => $data 197 ]); 198 } 199 200 /** 201 * Compare backend users from demand 202 */ 203 public function compareAction() 204 { 205 $compareUserList = $this->moduleData->getCompareUserList(); 206 if (empty($compareUserList)) { 207 $this->redirect('index'); 208 } 209 210 $compareData = []; 211 foreach ($compareUserList as $uid) { 212 if ($compareInformation = $this->userInformationService->getUserInformation($uid)) { 213 $compareData[] = $compareInformation; 214 } 215 } 216 217 $this->view->assignMultiple([ 218 'shortcutLabel' => 'compareUsers', 219 'compareUserList' => $compareData, 220 'onlineBackendUsers' => $this->getOnlineBackendUsers() 221 ]); 222 } 223 224 /** 225 * Starts the password reset process for a selected user. 226 * 227 * @param int $user 228 */ 229 public function initiatePasswordResetAction(int $user): void 230 { 231 $context = GeneralUtility::makeInstance(Context::class); 232 /** @var BackendUser $user */ 233 $user = $this->backendUserRepository->findByUid($user); 234 if (!$user || !$user->isPasswordResetEnabled() || !$context->getAspect('backend.user')->isAdmin()) { 235 // Add an error message 236 $this->addFlashMessage( 237 LocalizationUtility::translate('LLL:EXT:beuser/Resources/Private/Language/locallang.xlf:flashMessage.resetPassword.error.text', 'beuser') ?? '', 238 LocalizationUtility::translate('LLL:EXT:beuser/Resources/Private/Language/locallang.xlf:flashMessage.resetPassword.error.title', 'beuser') ?? '', 239 FlashMessage::ERROR 240 ); 241 } else { 242 GeneralUtility::makeInstance(PasswordReset::class)->initiateReset( 243 $GLOBALS['TYPO3_REQUEST'], 244 $context, 245 $user->getEmail() 246 ); 247 $this->addFlashMessage( 248 LocalizationUtility::translate('LLL:EXT:beuser/Resources/Private/Language/locallang.xlf:flashMessage.resetPassword.success.text', 'beuser', [$user->getEmail()]) ?? '', 249 LocalizationUtility::translate('LLL:EXT:beuser/Resources/Private/Language/locallang.xlf:flashMessage.resetPassword.success.title', 'beuser') ?? '', 250 FlashMessage::OK 251 ); 252 } 253 $this->forward('index'); 254 } 255 256 /** 257 * Attaches one backend user to the compare list 258 * 259 * @param int $uid 260 */ 261 public function addToCompareListAction($uid) 262 { 263 $this->moduleData->attachUidCompareUser($uid); 264 $this->moduleDataStorageService->persistModuleData($this->moduleData); 265 $this->forward('index'); 266 } 267 268 /** 269 * Removes given backend user to the compare list 270 * 271 * @param int $uid 272 * @param int $redirectToCompare 273 */ 274 public function removeFromCompareListAction($uid, int $redirectToCompare = 0) 275 { 276 $this->moduleData->detachUidCompareUser($uid); 277 $this->moduleDataStorageService->persistModuleData($this->moduleData); 278 if ($redirectToCompare) { 279 $this->redirect('compare'); 280 } else { 281 $this->redirect('index'); 282 } 283 } 284 285 /** 286 * Removes all backend users from the compare list 287 */ 288 public function removeAllFromCompareListAction(): void 289 { 290 foreach ($this->moduleData->getCompareUserList() as $user) { 291 $this->moduleData->detachUidCompareUser($user); 292 } 293 $this->moduleDataStorageService->persistModuleData($this->moduleData); 294 $this->redirect('index'); 295 } 296 297 /** 298 * Terminate BackendUser session and logout corresponding client 299 * Redirects to onlineAction with message 300 * 301 * @param \TYPO3\CMS\Beuser\Domain\Model\BackendUser $backendUser 302 * @param string $sessionId 303 */ 304 protected function terminateBackendUserSessionAction(BackendUser $backendUser, $sessionId) 305 { 306 // terminating value of persisted session ID (probably hashed value) 307 $sessionBackend = $this->getSessionBackend(); 308 $success = $sessionBackend->remove($sessionId); 309 310 if ($success) { 311 $this->addFlashMessage(LocalizationUtility::translate('LLL:EXT:beuser/Resources/Private/Language/locallang.xlf:terminateSessionSuccess', 'beuser') ?? ''); 312 } 313 $this->forward('online'); 314 } 315 316 /** 317 * Switches to a given user (SU-mode) and then redirects to the start page of the backend to refresh the navigation etc. 318 * 319 * @param int $switchUser BE-user record that will be switched to 320 */ 321 protected function switchUser($switchUser) 322 { 323 $targetUser = BackendUtility::getRecord('be_users', $switchUser); 324 if (is_array($targetUser) && $this->getBackendUserAuthentication()->isAdmin()) { 325 // Set backend user listing module as starting module for switchback 326 $this->getBackendUserAuthentication()->uc['startModuleOnFirstLogin'] = 'system_BeuserTxBeuser'; 327 $this->getBackendUserAuthentication()->uc['recentSwitchedToUsers'] = $this->generateListOfMostRecentSwitchedUsers($targetUser['uid']); 328 $this->getBackendUserAuthentication()->writeUC(); 329 330 // User switch written to log 331 $this->getBackendUserAuthentication()->writelog( 332 255, 333 2, 334 0, 335 1, 336 'User %s switched to user %s (be_users:%s)', 337 [ 338 $this->getBackendUserAuthentication()->user['username'], 339 $targetUser['username'], 340 $targetUser['uid'], 341 ] 342 ); 343 344 $this->getSessionBackend()->update( 345 $this->getBackendUserAuthentication()->getSessionId(), 346 [ 347 'ses_userid' => (int)$targetUser['uid'], 348 'ses_backuserid' => (int)$this->getBackendUserAuthentication()->user['uid'] 349 ] 350 ); 351 352 $event = new SwitchUserEvent( 353 $this->getBackendUserAuthentication()->getSessionId(), 354 $targetUser, 355 (array)$this->getBackendUserAuthentication()->user 356 ); 357 $this->eventDispatcher->dispatch($event); 358 359 $redirectUrl = 'index.php' . ($GLOBALS['TYPO3_CONF_VARS']['BE']['interfaces'] ? '' : '?commandLI=1'); 360 HttpUtility::redirect($redirectUrl); 361 } 362 } 363 364 /** 365 * Generates a list of users to whom where switched in the past. This is limited by RECENT_USERS_LIMIT. 366 * 367 * @param int $targetUserUid 368 * @return int[] 369 */ 370 protected function generateListOfMostRecentSwitchedUsers(int $targetUserUid): array 371 { 372 $latestUserUids = []; 373 $backendUser = $this->getBackendUserAuthentication(); 374 375 if (isset($backendUser->uc['recentSwitchedToUsers']) && is_array($backendUser->uc['recentSwitchedToUsers'])) { 376 $latestUserUids = $backendUser->uc['recentSwitchedToUsers']; 377 } 378 379 // Remove potentially existing user in that list 380 $index = array_search($targetUserUid, $latestUserUids, true); 381 if ($index !== false) { 382 unset($latestUserUids[$index]); 383 } 384 385 array_unshift($latestUserUids, $targetUserUid); 386 $latestUserUids = array_slice($latestUserUids, 0, static::RECENT_USERS_LIMIT); 387 388 return $latestUserUids; 389 } 390 391 /** 392 * @return BackendUserAuthentication 393 */ 394 protected function getBackendUserAuthentication(): BackendUserAuthentication 395 { 396 return $GLOBALS['BE_USER']; 397 } 398 399 /** 400 * @return SessionBackendInterface 401 */ 402 protected function getSessionBackend() 403 { 404 $loginType = $this->getBackendUserAuthentication()->getLoginType(); 405 return GeneralUtility::makeInstance(SessionManager::class)->getSessionBackend($loginType); 406 } 407 408 /** 409 * Create an array with the uids of online users as the keys 410 * [ 411 * 1 => true, 412 * 5 => true 413 * ] 414 * @return array 415 */ 416 protected function getOnlineBackendUsers(): array 417 { 418 $onlineUsers = $this->backendUserSessionRepository->findAllActive(); 419 $onlineBackendUsers = []; 420 if (is_array($onlineUsers)) { 421 foreach ($onlineUsers as $onlineUser) { 422 $onlineBackendUsers[$onlineUser['ses_userid']] = true; 423 } 424 } 425 return $onlineBackendUsers; 426 } 427} 428