Passed
Push — master ( f16b47...733353 )
by
unknown
13:48
created

AbstractUserAuthentication::getNewSessionRecord()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 7
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 10
rs 10
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
16
namespace TYPO3\CMS\Core\Authentication;
17
18
use Psr\Http\Message\ResponseInterface;
19
use Psr\Log\LoggerAwareInterface;
20
use Psr\Log\LoggerAwareTrait;
21
use Symfony\Component\HttpFoundation\Cookie;
22
use TYPO3\CMS\Core\Core\Environment;
23
use TYPO3\CMS\Core\Crypto\Random;
24
use TYPO3\CMS\Core\Database\Connection;
25
use TYPO3\CMS\Core\Database\ConnectionPool;
26
use TYPO3\CMS\Core\Database\Query\Restriction\DefaultRestrictionContainer;
27
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
28
use TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction;
29
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
30
use TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionContainerInterface;
31
use TYPO3\CMS\Core\Database\Query\Restriction\RootLevelRestriction;
32
use TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction;
33
use TYPO3\CMS\Core\Exception;
34
use TYPO3\CMS\Core\Http\CookieHeaderTrait;
35
use TYPO3\CMS\Core\Session\Backend\Exception\SessionNotFoundException;
36
use TYPO3\CMS\Core\Session\UserSession;
37
use TYPO3\CMS\Core\Session\UserSessionManager;
38
use TYPO3\CMS\Core\SysLog\Action\Login as SystemLogLoginAction;
39
use TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
40
use TYPO3\CMS\Core\SysLog\Type as SystemLogType;
41
use TYPO3\CMS\Core\Utility\GeneralUtility;
42
43
/**
44
 * Authentication of users in TYPO3
45
 *
46
 * This class is used to authenticate a login user.
47
 * The class is used by both the frontend and backend.
48
 * In both cases this class is a parent class to BackendUserAuthentication and FrontendUserAuthentication
49
 *
50
 * See Inside TYPO3 for more information about the API of the class and internal variables.
51
 */
52
abstract class AbstractUserAuthentication implements LoggerAwareInterface
53
{
54
    use LoggerAwareTrait;
55
    use CookieHeaderTrait;
56
57
    /**
58
     * Session/Cookie name
59
     * @var string
60
     */
61
    public $name = '';
62
63
    /**
64
     * Table in database with user data
65
     * @var string
66
     */
67
    public $user_table = '';
68
69
    /**
70
     * Table in database with user groups
71
     * @var string
72
     */
73
    public $usergroup_table = '';
74
75
    /**
76
     * Column for login-name
77
     * @var string
78
     */
79
    public $username_column = '';
80
81
    /**
82
     * Column for password
83
     * @var string
84
     */
85
    public $userident_column = '';
86
87
    /**
88
     * Column for user-id
89
     * @var string
90
     */
91
    public $userid_column = '';
92
93
    /**
94
     * Column for user group information
95
     * @var string
96
     */
97
    public $usergroup_column = '';
98
99
    /**
100
     * Column name for last login timestamp
101
     * @var string
102
     */
103
    public $lastLogin_column = '';
104
105
    /**
106
     * Enable field columns of user table
107
     * @var array
108
     */
109
    public $enablecolumns = [
110
        'rootLevel' => '',
111
        // Boolean: If TRUE, 'AND pid=0' will be a part of the query...
112
        'disabled' => '',
113
        'starttime' => '',
114
        'endtime' => '',
115
        'deleted' => '',
116
    ];
117
118
    /**
119
     * Form field with login-name
120
     * @var string
121
     */
122
    public $formfield_uname = '';
123
124
    /**
125
     * Form field with password
126
     * @var string
127
     */
128
    public $formfield_uident = '';
129
130
    /**
131
     * Form field with status: *'login', 'logout'. If empty login is not verified.
132
     * @var string
133
     */
134
    public $formfield_status = '';
135
136
    /**
137
     * Lifetime for the session-cookie (on the client)
138
     *
139
     * If >0: permanent cookie with given lifetime
140
     * If 0: session-cookie
141
     * Session-cookie means the browser will remove it when the browser is closed.
142
     *
143
     * @var int
144
     */
145
    protected $lifetime = 0;
146
147
    /**
148
     * Decides if the writelog() function is called at login and logout
149
     * @var bool
150
     */
151
    public $writeStdLog = false;
152
153
    /**
154
     * Log failed login attempts
155
     * @var bool
156
     */
157
    public $writeAttemptLog = false;
158
159
    /**
160
     * Send no-cache headers
161
     * @var bool
162
     */
163
    public $sendNoCacheHeaders = true;
164
165
    /**
166
     * If set, the user-record must be stored at the page defined by $checkPid_value
167
     * @var bool
168
     */
169
    public $checkPid = true;
170
171
    /**
172
     * The page id the user record must be stored at
173
     * @var int
174
     */
175
    public $checkPid_value = 0;
176
177
    /**
178
     * Will be set to TRUE if the login session is actually written during auth-check.
179
     * @var bool
180
     */
181
    public $loginSessionStarted = false;
182
183
    /**
184
     * @var array|null contains user- AND session-data from database (joined tables)
185
     * @internal
186
     */
187
    public $user;
188
189
    /**
190
     * Will force the session cookie to be set every time (lifetime must be 0)
191
     * @var bool
192
     */
193
    public $forceSetCookie = false;
194
195
    /**
196
     * Will prevent the setting of the session cookie (takes precedence over forceSetCookie)
197
     * @var bool
198
     */
199
    public $dontSetCookie = false;
200
201
    /**
202
     * Login type, used for services.
203
     * @var string
204
     */
205
    public $loginType = '';
206
207
    /**
208
     * @var array
209
     */
210
    public $uc;
211
212
    protected ?UserSession $userSession;
213
214
    protected UserSessionManager $userSessionManager;
215
216
    /**
217
     * If set, this cookie will be set to the response.
218
     *
219
     * @var Cookie|null
220
     */
221
    protected ?Cookie $setCookie;
222
223
    /**
224
     * Initialize some important variables
225
     *
226
     * @throws Exception
227
     */
228
    public function __construct()
229
    {
230
        // Backend or frontend login - used for auth services
231
        if (empty($this->loginType)) {
232
            throw new Exception('No loginType defined, must be set explicitly by subclass', 1476045345);
233
        }
234
        $this->lifetime = (int)($GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['lifetime'] ?? 0);
235
    }
236
237
    /**
238
     * Currently needed for various unit tests, until start() and checkAuthentication() methods
239
     * are smaller and extracted from this class.
240
     *
241
     * @param UserSessionManager|null $userSessionManager
242
     * @internal
243
     */
244
    public function initializeUserSessionManager(?UserSessionManager $userSessionManager = null): void
245
    {
246
        $this->userSessionManager = $userSessionManager ?? UserSessionManager::create($this->loginType);
247
        $this->userSession = $this->userSessionManager->createAnonymousSession();
248
    }
249
250
    /**
251
     * Starts a user session
252
     * Typical configurations will:
253
     * a) check if session cookie was set and if not, set one,
254
     * b) check if a password/username was sent and if so, try to authenticate the user
255
     * c) Lookup a session attached to a user and check timeout etc.
256
     * d) Garbage collection, setting of no-cache headers.
257
     * If a user is authenticated the database record of the user (array) will be set in the ->user internal variable.
258
     */
259
    public function start()
260
    {
261
        $this->logger->debug('## Beginning of auth logging.');
262
        // Make certain that NO user is set initially
263
        $this->user = null;
264
265
        if (!isset($this->userSessionManager)) {
266
            $this->initializeUserSessionManager();
267
        }
268
        $this->userSession = $this->userSessionManager->createFromGlobalCookieOrAnonymous($this->name);
269
270
        // Load user session, check to see if anyone has submitted login-information and if so authenticate
271
        // the user with the session. $this->user[uid] may be used to write log...
272
        $this->checkAuthentication();
273
        // Set cookie if generally enabled or if the current session is a non-session cookie (FE permalogin)
274
        if (!$this->dontSetCookie || $this->isRefreshTimeBasedCookie()) {
275
            $this->setSessionCookie();
276
        }
277
        // Hook for alternative ways of filling the $this->user array (is used by the "timtaw" extension)
278
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postUserLookUp'] ?? [] as $funcName) {
279
            $_params = [
280
                'pObj' => $this,
281
            ];
282
            GeneralUtility::callUserFunction($funcName, $_params, $this);
283
        }
284
    }
285
286
    /**
287
     * Used to apply a cookie to a PSR-7 Response.
288
     *
289
     * @param ResponseInterface $response
290
     * @return ResponseInterface
291
     */
292
    public function appendCookieToResponse(ResponseInterface $response): ResponseInterface
293
    {
294
        if (isset($this->setCookie)) {
295
            $response = $response->withAddedHeader('Set-Cookie', $this->setCookie->__toString());
0 ignored issues
show
Bug introduced by
The method __toString() 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

295
            $response = $response->withAddedHeader('Set-Cookie', $this->setCookie->/** @scrutinizer ignore-call */ __toString());

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...
296
        }
297
        return $response;
298
    }
299
300
    /**
301
     * Sets the session cookie for the current disposal.
302
     */
303
    protected function setSessionCookie()
304
    {
305
        $isRefreshTimeBasedCookie = $this->isRefreshTimeBasedCookie();
306
        if ($this->isSetSessionCookie() || $isRefreshTimeBasedCookie) {
307
            // Get the domain to be used for the cookie (if any):
308
            $cookieDomain = $this->getCookieDomain();
309
            // If no cookie domain is set, use the base path:
310
            $cookiePath = $cookieDomain ? '/' : GeneralUtility::getIndpEnv('TYPO3_SITE_PATH');
311
            // If the cookie lifetime is set, use it:
312
            $cookieExpire = $isRefreshTimeBasedCookie ? $GLOBALS['EXEC_TIME'] + $this->lifetime : 0;
313
            // Valid options are "strict", "lax" or "none", whereas "none" only works in HTTPS requests (default & fallback is "strict")
314
            $cookieSameSite = $this->sanitizeSameSiteCookieValue(
315
                strtolower($GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieSameSite'] ?? Cookie::SAMESITE_STRICT)
316
            );
317
            // Use the secure option when the current request is served by a secure connection:
318
            // SameSite "none" needs the secure option (only allowed on HTTPS)
319
            $isSecure = $cookieSameSite === Cookie::SAMESITE_NONE || GeneralUtility::getIndpEnv('TYPO3_SSL');
320
            $sessionId = $this->userSession->getIdentifier();
0 ignored issues
show
Bug introduced by
The method getIdentifier() 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

320
            /** @scrutinizer ignore-call */ 
321
            $sessionId = $this->userSession->getIdentifier();

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...
321
            $this->setCookie = new Cookie(
322
                $this->name,
323
                $sessionId,
324
                $cookieExpire,
325
                $cookiePath,
326
                $cookieDomain,
327
                $isSecure,
328
                true,
329
                false,
330
                $cookieSameSite
331
            );
332
            $this->logger->debug(
333
                ($isRefreshTimeBasedCookie ? 'Updated Cookie: ' : 'Set Cookie: ')
334
                . $sessionId . ($cookieDomain ? ', ' . $cookieDomain : '')
335
            );
336
        }
337
    }
338
339
    /**
340
     * Gets the domain to be used on setting cookies.
341
     * The information is taken from the value in $GLOBALS['TYPO3_CONF_VARS']['SYS']['cookieDomain'].
342
     *
343
     * @return string The domain to be used on setting cookies
344
     */
345
    protected function getCookieDomain()
346
    {
347
        $result = '';
348
        $cookieDomain = $GLOBALS['TYPO3_CONF_VARS']['SYS']['cookieDomain'];
349
        // If a specific cookie domain is defined for a given application type, use that domain
350
        if (!empty($GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieDomain'])) {
351
            $cookieDomain = $GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieDomain'];
352
        }
353
        if ($cookieDomain) {
354
            if ($cookieDomain[0] === '/') {
355
                $match = [];
356
                $matchCnt = @preg_match($cookieDomain, GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY'), $match);
357
                if ($matchCnt === false) {
358
                    $this->logger->critical('The regular expression for the cookie domain (' . $cookieDomain . ') contains errors. The session is not shared across sub-domains.');
359
                } elseif ($matchCnt) {
360
                    $result = $match[0];
361
                }
362
            } else {
363
                $result = $cookieDomain;
364
            }
365
        }
366
        return $result;
367
    }
368
369
    /**
370
     * Get the value of a specified cookie.
371
     *
372
     * @param string $cookieName The cookie ID
373
     * @return string The value stored in the cookie
374
     */
375
    protected function getCookie($cookieName)
376
    {
377
        return isset($_COOKIE[$cookieName]) ? stripslashes($_COOKIE[$cookieName]) : '';
378
    }
379
380
    /**
381
     * Determine whether a session cookie needs to be set (lifetime=0)
382
     *
383
     * @return bool
384
     * @internal
385
     */
386
    public function isSetSessionCookie()
387
    {
388
        return ($this->userSession->isNew() || $this->forceSetCookie) && $this->lifetime === 0;
389
    }
390
391
    /**
392
     * Determine whether a non-session cookie needs to be set (lifetime>0)
393
     *
394
     * @return bool
395
     * @internal
396
     */
397
    public function isRefreshTimeBasedCookie()
398
    {
399
        return $this->lifetime > 0;
400
    }
401
402
    /**
403
     * "auth" services configuration array from $GLOBALS['TYPO3_CONF_VARS']['SVCONF']['auth']
404
     * @return array
405
     */
406
    protected function getAuthServiceConfiguration(): array
407
    {
408
        if (is_array($GLOBALS['TYPO3_CONF_VARS']['SVCONF']['auth']['setup'] ?? null)) {
409
            return $GLOBALS['TYPO3_CONF_VARS']['SVCONF']['auth']['setup'];
410
        }
411
        return [];
412
    }
413
414
    /**
415
     * Checks if a submission of username and password is present or use other authentication by auth services
416
     *
417
     * @throws \RuntimeException
418
     * @internal
419
     */
420
    public function checkAuthentication()
421
    {
422
        $authConfiguration = $this->getAuthServiceConfiguration();
423
        if (!empty($authConfiguration)) {
424
            $this->logger->debug('Authentication Service Configuration found.', $authConfiguration);
425
        }
426
        // No user for now - will be searched by service below
427
        $tempuserArr = [];
428
        $tempuser = false;
429
        // User is not authenticated by default
430
        $authenticated = false;
431
        // User want to login with passed login data (name/password)
432
        $activeLogin = false;
433
        $this->logger->debug('Login type: ' . $this->loginType);
434
        // The info array provide additional information for auth services
435
        $authInfo = $this->getAuthInfoArray();
436
        // Get Login/Logout data submitted by a form or params
437
        $loginData = $this->getLoginFormData();
438
        $this->logger->debug('Login data', $loginData);
439
        // Active logout (eg. with "logout" button)
440
        if ($loginData['status'] === LoginType::LOGOUT) {
441
            if ($this->writeStdLog) {
442
                // $type,$action,$error,$details_nr,$details,$data,$tablename,$recuid,$recpid
443
                $this->writelog(SystemLogType::LOGIN, SystemLogLoginAction::LOGOUT, SystemLogErrorClassification::MESSAGE, 2, 'User %s logged out', [$this->user['username']], '', 0, 0);
444
            }
445
            $this->logger->info('User logged out. Id: ' . $this->userSession->getIdentifier());
446
            $this->logoff();
447
        }
448
        // Determine whether we need to skip session update.
449
        // This is used mainly for checking session timeout in advance without refreshing the current session's timeout.
450
        $skipSessionUpdate = (bool)GeneralUtility::_GP('skipSessionUpdate');
451
        $haveSession = false;
452
        $anonymousSession = false;
453
        if (!$this->userSession->isNew()) {
454
            // Read user data if this is bound to a user
455
            // However, if the user data is not valid, or the session has timeed out we'll recreate a new anonymous session
456
            if ($this->userSession->getUserId() > 0) {
457
                $authInfo['user'] = $this->fetchValidUserFromSessionOrDestroySession($skipSessionUpdate);
458
                if (is_array($authInfo['user'])) {
459
                    $authInfo['userSession'] = $authInfo['user'];
460
                } else {
461
                    $authInfo['userSession'] = false;
462
                }
463
            }
464
            $authInfo['session'] = $this->userSession;
465
            $haveSession = !$this->userSession->isNew();
466
            $anonymousSession = $haveSession && $this->userSession->isAnonymous();
467
        }
468
469
        // Active login (eg. with login form).
470
        if (!$haveSession && $loginData['status'] === LoginType::LOGIN) {
471
            $activeLogin = true;
472
            $this->logger->debug('Active login (eg. with login form)');
473
            // check referrer for submitted login values
474
            if ($this->formfield_status && $loginData['uident'] && $loginData['uname']) {
475
                // Delete old user session if any
476
                $this->logoff();
477
            }
478
            // Refuse login for _CLI users, if not processing a CLI request type
479
            // (although we shouldn't be here in case of a CLI request type)
480
            if (stripos($loginData['uname'], '_CLI_') === 0 && !Environment::isCli()) {
481
                throw new \RuntimeException('TYPO3 Fatal Error: You have tried to login using a CLI user. Access prohibited!', 1270853931);
482
            }
483
        }
484
485
        // Cause elevation of privilege, make sure regenerateSessionId is called later on
486
        if ($anonymousSession && $loginData['status'] === LoginType::LOGIN) {
487
            $activeLogin = true;
488
        }
489
490
        if ($haveSession) {
491
            $this->logger->debug('User session found', [
492
                $this->userid_column => $authInfo['userSession'][$this->userid_column] ?? null,
493
                $this->username_column => $authInfo['userSession'][$this->username_column] ?? null,
494
            ]);
495
        } else {
496
            $this->logger->debug('No user session found');
497
        }
498
499
        // Fetch user if ...
500
        if (
501
            $activeLogin || !empty($authConfiguration[$this->loginType . '_alwaysFetchUser'])
502
            || !$haveSession && !empty($authConfiguration[$this->loginType . '_fetchUserIfNoSession'])
503
        ) {
504
            // Use 'auth' service to find the user
505
            // First found user will be used
506
            $subType = 'getUser' . $this->loginType;
507
            /** @var AuthenticationService $serviceObj */
508
            foreach ($this->getAuthServices($subType, $loginData, $authInfo) as $serviceObj) {
509
                if ($row = $serviceObj->getUser()) {
510
                    $tempuserArr[] = $row;
511
                    $this->logger->debug('User found', [
512
                        $this->userid_column => $row[$this->userid_column],
513
                        $this->username_column => $row[$this->username_column],
514
                    ]);
515
                    // User found, just stop to search for more if not configured to go on
516
                    if (empty($authConfiguration[$this->loginType . '_fetchAllUsers'])) {
517
                        break;
518
                    }
519
                }
520
            }
521
522
            if (!empty($authConfiguration[$this->loginType . '_alwaysFetchUser'])) {
523
                $this->logger->debug($this->loginType . '_alwaysFetchUser option is enabled');
524
            }
525
            if (empty($tempuserArr)) {
526
                $this->logger->debug('No user found by services');
527
            } else {
528
                $this->logger->debug(count($tempuserArr) . ' user records found by services');
529
            }
530
        }
531
532
        // If no new user was set we use the already found user session
533
        if (empty($tempuserArr) && $haveSession && !$anonymousSession) {
534
            $tempuserArr[] = $authInfo['userSession'];
535
            $tempuser = $authInfo['userSession'];
536
            // User is authenticated because we found a user session
537
            $authenticated = true;
538
            $this->logger->debug('User session used', [
539
                $this->userid_column => $authInfo['userSession'][$this->userid_column],
540
                $this->username_column => $authInfo['userSession'][$this->username_column],
541
            ]);
542
        }
543
        // Re-auth user when 'auth'-service option is set
544
        if (!empty($authConfiguration[$this->loginType . '_alwaysAuthUser'])) {
545
            $authenticated = false;
546
            $this->logger->debug('alwaysAuthUser option is enabled');
547
        }
548
        // Authenticate the user if needed
549
        if (!empty($tempuserArr) && !$authenticated) {
550
            foreach ($tempuserArr as $tempuser) {
551
                // Use 'auth' service to authenticate the user
552
                // If one service returns FALSE then authentication failed
553
                // a service might return 100 which means there's no reason to stop but the user can't be authenticated by that service
554
                $this->logger->debug('Auth user', $tempuser);
555
                $subType = 'authUser' . $this->loginType;
556
557
                /** @var AuthenticationService $serviceObj */
558
                foreach ($this->getAuthServices($subType, $loginData, $authInfo) as $serviceObj) {
559
                    if (($ret = $serviceObj->authUser($tempuser)) > 0) {
560
                        // If the service returns >=200 then no more checking is needed - useful for IP checking without password
561
                        if ((int)$ret >= 200) {
562
                            $authenticated = true;
563
                            break;
564
                        }
565
                        if ((int)$ret >= 100) {
566
                        } else {
567
                            $authenticated = true;
568
                        }
569
                    } else {
570
                        $authenticated = false;
571
                        break;
572
                    }
573
                }
574
575
                if ($authenticated) {
576
                    // Leave foreach() because a user is authenticated
577
                    break;
578
                }
579
            }
580
        }
581
582
        // If user is authenticated a valid user is in $tempuser
583
        if ($authenticated) {
584
            // Insert session record if needed:
585
            if (!$haveSession
586
                || $anonymousSession
587
                || $tempuser['uid'] !== $this->userSession->getUserId()
588
            ) {
589
                $sessionData = $this->userSession->getData();
590
                // Create a new session with a fixated user
591
                $this->userSession = $this->createUserSession($tempuser);
592
593
                // Preserve session data on login
594
                if ($anonymousSession || $haveSession) {
595
                    $this->userSession->overrideData($sessionData);
596
                }
597
598
                $this->user = array_merge($this->user ?? [], $tempuser);
599
600
                // The login session is started.
601
                $this->loginSessionStarted = true;
602
                if (is_array($this->user)) {
0 ignored issues
show
introduced by
The condition is_array($this->user) is always true.
Loading history...
603
                    $this->logger->debug('User session finally read', [
604
                        $this->userid_column => $this->user[$this->userid_column],
605
                        $this->username_column => $this->user[$this->username_column],
606
                    ]);
607
                }
608
            } elseif ($haveSession) {
0 ignored issues
show
introduced by
The condition $haveSession is always true.
Loading history...
609
                // if we come here the current session is for sure not anonymous as this is a pre-condition for $authenticated = true
610
                $this->user = $authInfo['userSession'];
611
            }
612
613
            if ($activeLogin && !$this->userSession->isNew()) {
614
                $this->regenerateSessionId();
615
            }
616
617
            if ($activeLogin) {
618
                // User logged in - write that to the log!
619
                if ($this->writeStdLog) {
620
                    $this->writelog(SystemLogType::LOGIN, SystemLogLoginAction::LOGIN, SystemLogErrorClassification::MESSAGE, 1, 'User %s logged in from ###IP###', [$tempuser[$this->username_column]], '', '', '');
621
                }
622
                $this->logger->info('User ' . $tempuser[$this->username_column] . ' logged in from ' . GeneralUtility::getIndpEnv('REMOTE_ADDR'));
623
            } else {
624
                $this->logger->debug('User ' . $tempuser[$this->username_column] . ' authenticated from ' . GeneralUtility::getIndpEnv('REMOTE_ADDR'));
625
            }
626
        } else {
627
            // Mark the current login attempt as failed
628
            if (empty($tempuserArr) && $activeLogin) {
629
                $this->logger->debug('Login failed', [
630
                    'loginData' => $loginData
631
                ]);
632
            } elseif (!empty($tempuserArr)) {
633
                $this->logger->debug('Login failed', [
634
                    $this->userid_column => $tempuser[$this->userid_column],
635
                    $this->username_column => $tempuser[$this->username_column],
636
                ]);
637
            }
638
639
            // If there were a login failure, check to see if a warning email should be sent
640
            if ($activeLogin) {
641
                $this->handleLoginFailure();
642
            }
643
        }
644
    }
645
646
    /**
647
     * Implement functionality when there was a failed login
648
     */
649
    protected function handleLoginFailure(): void
650
    {
651
        $_params = [];
652
        $sleep = true;
653
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postLoginFailureProcessing'] ?? [] as $hookIdentifier => $_funcRef) {
654
            GeneralUtility::callUserFunction($_funcRef, $_params, $this);
655
            // This hack will be removed once this is migrated into PSR-14 Events
656
            if ($hookIdentifier !== 'sendEmailOnFailedLoginAttempt') {
657
                $sleep = false;
658
            }
659
        }
660
        if ($sleep) {
661
            // No hooks were triggered - default login failure behavior is to sleep 5 seconds
662
            sleep(5);
663
        }
664
    }
665
666
    /**
667
     * Creates a new session ID.
668
     *
669
     * @return string The new session ID
670
     * @deprecated since TYPO3 v11.0, will be removed in TYPO3 v12, is kept because it is used in Testing Framework
671
     */
672
    public function createSessionId()
673
    {
674
        return GeneralUtility::makeInstance(Random::class)->generateRandomHexString(32);
675
    }
676
677
    /**
678
     * Initializes authentication services to be used in a foreach loop
679
     *
680
     * @param string $subType e.g. getUserFE
681
     * @param array $loginData
682
     * @param array $authInfo
683
     * @return \Traversable A generator of service objects
684
     */
685
    protected function getAuthServices(string $subType, array $loginData, array $authInfo): \Traversable
686
    {
687
        $serviceChain = [];
688
        while (is_object($serviceObj = GeneralUtility::makeInstanceService('auth', $subType, $serviceChain))) {
689
            $serviceChain[] = $serviceObj->getServiceKey();
690
            $serviceObj->initAuth($subType, $loginData, $authInfo, $this);
691
            yield $serviceObj;
692
        }
693
        if (!empty($serviceChain)) {
694
            $this->logger->debug($subType . ' auth services called: ' . implode(', ', $serviceChain));
695
        }
696
    }
697
698
    /**
699
     * Regenerate the session ID and transfer the session to new ID
700
     * Call this method whenever a user proceeds to a higher authorization level
701
     * e.g. when an anonymous session is now authenticated.
702
     */
703
    protected function regenerateSessionId()
704
    {
705
        $this->userSession = $this->userSessionManager->regenerateSession($this->userSession->getIdentifier());
706
    }
707
708
    /*************************
709
     *
710
     * User Sessions
711
     *
712
     *************************/
713
714
    /**
715
     * Creates a user session record and returns its values.
716
     *
717
     * @param array $tempuser User data array
718
     * @return UserSession The session data for the newly created session.
719
     */
720
    public function createUserSession(array $tempuser): UserSession
721
    {
722
        // Needed for testing framework
723
        if (!isset($this->userSessionManager)) {
724
            $this->initializeUserSessionManager();
725
        }
726
        $tempUserId = (int)($tempuser[$this->userid_column] ?? 0);
727
        $session = $this->userSessionManager->elevateToFixatedUserSession($this->userSession, $tempUserId);
0 ignored issues
show
Bug introduced by
It seems like $this->userSession can also be of type null; however, parameter $session of TYPO3\CMS\Core\Session\U...eToFixatedUserSession() does only seem to accept TYPO3\CMS\Core\Session\UserSession, 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

727
        $session = $this->userSessionManager->elevateToFixatedUserSession(/** @scrutinizer ignore-type */ $this->userSession, $tempUserId);
Loading history...
728
        // Updating lastLogin_column carrying information about last login.
729
        $this->updateLoginTimestamp($tempUserId);
730
        return $session;
731
    }
732
733
    /**
734
     * Updates the last login column in the user with the given id
735
     *
736
     * @param int $userId
737
     */
738
    protected function updateLoginTimestamp(int $userId)
739
    {
740
        if ($this->lastLogin_column) {
741
            $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->user_table);
742
            $connection->update(
743
                $this->user_table,
744
                [$this->lastLogin_column => $GLOBALS['EXEC_TIME']],
745
                [$this->userid_column => $userId]
746
            );
747
            $this->user[$this->lastLogin_column] = $GLOBALS['EXEC_TIME'];
748
        }
749
    }
750
751
    /**
752
     * Read the user session from db.
753
     *
754
     * @param bool $skipSessionUpdate
755
     * @return array|bool User session data, false if $userSession->getIdentifier() does not represent valid session
756
     * @deprecated since TYPO3 v11, will be removed in TYPO3 v12.
757
     */
758
    public function fetchUserSession($skipSessionUpdate = false)
759
    {
760
        try {
761
            $session = $this->userSessionManager->createSessionFromStorage($this->userSession->getIdentifier());
762
        } catch (SessionNotFoundException $e) {
763
            return false;
764
        }
765
        $this->userSession = $session;
766
        // Session is anonymous so no need to fetch user
767
        if ($session->isAnonymous()) {
768
            return $session->toArray();
769
        }
770
771
        // Fetch the user from the DB
772
        $userRecord = $this->fetchValidUserFromSessionOrDestroySession($skipSessionUpdate);
773
        return is_array($userRecord) ? $userRecord : false;
774
    }
775
776
    /**
777
     * If the session is bound to a user, this method fetches the user record, and returns it.
778
     * If the session has a timeout, the session date is extended if needed. Also the ìs_online
779
     * flag is updated for the user.
780
     *
781
     * However, if the session has expired the session is removed and the request is treated as an anonymous session.
782
     *
783
     * @param bool $skipSessionUpdate
784
     * @return array|null
785
     */
786
    protected function fetchValidUserFromSessionOrDestroySession(bool $skipSessionUpdate = false): ?array
787
    {
788
        if ($this->userSession->isAnonymous()) {
789
            return null;
790
        }
791
        // Fetch the user from the DB
792
        $userRecord = $this->getRawUserByUid($this->userSession->getUserId());
793
        if ($userRecord) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $userRecord of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
794
            // A user was found
795
            $userRecord['is_online'] = $this->userSession->getLastUpdated();
796
            if (!$this->userSessionManager->hasExpired($this->userSession)) {
0 ignored issues
show
Bug introduced by
It seems like $this->userSession can also be of type null; however, parameter $session of TYPO3\CMS\Core\Session\U...onManager::hasExpired() does only seem to accept TYPO3\CMS\Core\Session\UserSession, 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

796
            if (!$this->userSessionManager->hasExpired(/** @scrutinizer ignore-type */ $this->userSession)) {
Loading history...
797
                if (!$skipSessionUpdate) {
798
                    $this->userSessionManager->updateSessionTimestamp($this->userSession);
0 ignored issues
show
Bug introduced by
It seems like $this->userSession can also be of type null; however, parameter $session of TYPO3\CMS\Core\Session\U...pdateSessionTimestamp() does only seem to accept TYPO3\CMS\Core\Session\UserSession, 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

798
                    $this->userSessionManager->updateSessionTimestamp(/** @scrutinizer ignore-type */ $this->userSession);
Loading history...
799
                }
800
            } else {
801
                // Delete any user set...
802
                $this->logoff();
803
                $userRecord = false;
804
                $this->userSession = $this->userSessionManager->createAnonymousSession();
805
            }
806
        }
807
        return is_array($userRecord) ? $userRecord : null;
808
    }
809
810
    /**
811
     * Regenerates the session ID and sets the cookie again.
812
     *
813
     * @internal
814
     */
815
    public function enforceNewSessionId()
816
    {
817
        $this->regenerateSessionId();
818
        $this->setSessionCookie();
819
    }
820
821
    /**
822
     * Log out current user!
823
     * Removes the current session record, sets the internal ->user array to a blank string;
824
     * Thereby the current user (if any) is effectively logged out!
825
     */
826
    public function logoff()
827
    {
828
        $this->logger->debug('logoff: ses_id = ' . $this->userSession->getIdentifier());
829
830
        $_params = [];
831
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'] ?? [] as $_funcRef) {
832
            if ($_funcRef) {
833
                GeneralUtility::callUserFunction($_funcRef, $_params, $this);
834
            }
835
        }
836
        $this->performLogoff();
0 ignored issues
show
Bug introduced by
The method performLogoff() 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

836
        $this->/** @scrutinizer ignore-call */ 
837
               performLogoff();

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...
837
838
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_post_processing'] ?? [] as $_funcRef) {
839
            if ($_funcRef) {
840
                GeneralUtility::callUserFunction($_funcRef, $_params, $this);
841
            }
842
        }
843
    }
844
845
    /**
846
     * Perform the logoff action. Called from logoff() as a way to allow subclasses to override
847
     * what happens when a user logs off, without needing to reproduce the hook calls and logging
848
     * that happens in the public logoff() API method.
849
     */
850
    protected function performLogoff()
851
    {
852
        if ($this->userSession) {
853
            $this->userSessionManager->removeSession($this->userSession);
854
        }
855
        $this->userSession = $this->userSessionManager->createAnonymousSession();
856
        $this->user = null;
857
        if ($this->isCookieSet()) {
858
            $this->removeCookie($this->name);
859
        }
860
    }
861
862
    /**
863
     * Empty / unset the cookie
864
     *
865
     * @param string|null $cookieName usually, this is $this->name
866
     */
867
    public function removeCookie($cookieName = null)
868
    {
869
        $cookieName = $cookieName ?? $this->name;
870
        $cookieDomain = $this->getCookieDomain();
871
        // If no cookie domain is set, use the base path
872
        $cookiePath = $cookieDomain ? '/' : GeneralUtility::getIndpEnv('TYPO3_SITE_PATH');
873
        $this->setCookie = new Cookie(
874
            $cookieName,
875
            '',
876
            -1,
877
            $cookiePath,
878
            $cookieDomain
879
        );
880
    }
881
882
    /**
883
     * Returns whether this request is going to set a cookie
884
     * or a cookie was already found in the system
885
     *
886
     * @return bool Returns TRUE if a cookie is set
887
     */
888
    public function isCookieSet()
889
    {
890
        return isset($this->setCookie) || $this->getCookie($this->name);
891
    }
892
893
    /*************************
894
     *
895
     * SQL Functions
896
     *
897
     *************************/
898
    /**
899
     * This returns the restrictions needed to select the user respecting
900
     * enable columns and flags like deleted, hidden, starttime, endtime
901
     * and rootLevel
902
     *
903
     * @return QueryRestrictionContainerInterface
904
     * @internal
905
     */
906
    protected function userConstraints(): QueryRestrictionContainerInterface
907
    {
908
        $restrictionContainer = GeneralUtility::makeInstance(DefaultRestrictionContainer::class);
909
910
        if (empty($this->enablecolumns['disabled'])) {
911
            $restrictionContainer->removeByType(HiddenRestriction::class);
912
        }
913
914
        if (empty($this->enablecolumns['deleted'])) {
915
            $restrictionContainer->removeByType(DeletedRestriction::class);
916
        }
917
918
        if (empty($this->enablecolumns['starttime'])) {
919
            $restrictionContainer->removeByType(StartTimeRestriction::class);
920
        }
921
922
        if (empty($this->enablecolumns['endtime'])) {
923
            $restrictionContainer->removeByType(EndTimeRestriction::class);
924
        }
925
926
        if (!empty($this->enablecolumns['rootLevel'])) {
927
            $restrictionContainer->add(GeneralUtility::makeInstance(RootLevelRestriction::class, [$this->user_table]));
928
        }
929
930
        return $restrictionContainer;
931
    }
932
933
    /*************************
934
     *
935
     * Session and Configuration Handling
936
     *
937
     *************************/
938
    /**
939
     * This writes $variable to the user-record. This is a way of providing session-data.
940
     * You can fetch the data again through $this->uc in this class!
941
     * If $variable is not an array, $this->uc is saved!
942
     *
943
     * @param array|string $variable An array you want to store for the user as session data. If $variable is not supplied (is null), the internal variable, ->uc, is stored by default
944
     */
945
    public function writeUC($variable = '')
946
    {
947
        if (is_array($this->user) && $this->user[$this->userid_column]) {
948
            if (!is_array($variable)) {
949
                $variable = $this->uc;
950
            }
951
            $this->logger->debug('writeUC: ' . $this->userid_column . '=' . (int)$this->user[$this->userid_column]);
952
            GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->user_table)->update(
953
                $this->user_table,
954
                ['uc' => serialize($variable)],
955
                [$this->userid_column => (int)$this->user[$this->userid_column]],
956
                ['uc' => Connection::PARAM_LOB]
957
            );
958
        }
959
    }
960
961
    /**
962
     * Sets $theUC as the internal variable ->uc IF $theUC is an array.
963
     * If $theUC is FALSE, the 'uc' content from the ->user array will be unserialized and restored in ->uc
964
     *
965
     * @param mixed $theUC If an array, then set as ->uc, otherwise load from user record
966
     */
967
    public function unpack_uc($theUC = '')
968
    {
969
        if (!$theUC && isset($this->user['uc'])) {
970
            $theUC = unserialize($this->user['uc'], ['allowed_classes' => false]);
971
        }
972
        if (is_array($theUC)) {
973
            $this->uc = $theUC;
974
        }
975
    }
976
977
    /**
978
     * Stores data for a module.
979
     * The data is stored with the session id so you can even check upon retrieval
980
     * if the module data is from a previous session or from the current session.
981
     *
982
     * @param string $module Is the name of the module ($MCONF['name'])
983
     * @param mixed $data Is the data you want to store for that module (array, string, ...)
984
     * @param bool|int $noSave If $noSave is set, then the ->uc array (which carries all kinds of user data) is NOT written immediately, but must be written by some subsequent call.
985
     */
986
    public function pushModuleData($module, $data, $noSave = 0)
987
    {
988
        $this->uc['moduleData'][$module] = $data;
989
        $this->uc['moduleSessionID'][$module] = $this->userSession->getIdentifier();
990
        if (!$noSave) {
991
            $this->writeUC();
992
        }
993
    }
994
995
    /**
996
     * Gets module data for a module (from a loaded ->uc array)
997
     *
998
     * @param string $module Is the name of the module ($MCONF['name'])
999
     * @param string $type If $type = 'ses' then module data is returned only if it was stored in the current session, otherwise data from a previous session will be returned (if available).
1000
     * @return mixed The module data if available: $this->uc['moduleData'][$module];
1001
     */
1002
    public function getModuleData($module, $type = '')
1003
    {
1004
        if ($type !== 'ses' || (isset($this->uc['moduleSessionID'][$module]) && $this->uc['moduleSessionID'][$module] == $this->userSession->getIdentifier())) {
1005
            return $this->uc['moduleData'][$module];
1006
        }
1007
        return null;
1008
    }
1009
1010
    /**
1011
     * Returns the session data stored for $key.
1012
     * The data will last only for this login session since it is stored in the user session.
1013
     *
1014
     * @param string $key The key associated with the session data
1015
     * @return mixed
1016
     */
1017
    public function getSessionData($key)
1018
    {
1019
        return $this->userSession->get($key);
1020
    }
1021
1022
    /**
1023
     * Set session data by key.
1024
     * The data will last only for this login session since it is stored in the user session.
1025
     *
1026
     * @param string $key A non empty string to store the data under
1027
     * @param mixed $data Data store store in session
1028
     */
1029
    public function setSessionData($key, $data)
1030
    {
1031
        $this->userSession->set($key, $data);
1032
    }
1033
1034
    /**
1035
     * Sets the session data ($data) for $key and writes all session data (from ->user['ses_data']) to the database.
1036
     * The data will last only for this login session since it is stored in the session table.
1037
     *
1038
     * @param string $key Pointer to an associative key in the session data array which is stored serialized in the field "ses_data" of the session table.
1039
     * @param mixed $data The data to store in index $key
1040
     */
1041
    public function setAndSaveSessionData($key, $data)
1042
    {
1043
        $this->userSession->set($key, $data);
1044
        $this->logger->debug('setAndSaveSessionData: ses_id = ' . $this->userSession->getIdentifier());
1045
        $this->userSessionManager->updateSession($this->userSession);
0 ignored issues
show
Bug introduced by
It seems like $this->userSession can also be of type null; however, parameter $session of TYPO3\CMS\Core\Session\U...anager::updateSession() does only seem to accept TYPO3\CMS\Core\Session\UserSession, 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

1045
        $this->userSessionManager->updateSession(/** @scrutinizer ignore-type */ $this->userSession);
Loading history...
1046
    }
1047
1048
    /*************************
1049
     *
1050
     * Misc
1051
     *
1052
     *************************/
1053
    /**
1054
     * Returns an info array with Login/Logout data submitted by a form or params
1055
     *
1056
     * @return array
1057
     * @internal
1058
     */
1059
    public function getLoginFormData()
1060
    {
1061
        $loginData = [
1062
            'status' => GeneralUtility::_GP($this->formfield_status),
1063
            'uname'  => GeneralUtility::_POST($this->formfield_uname),
1064
            'uident' => GeneralUtility::_POST($this->formfield_uident)
1065
        ];
1066
        // Only process the login data if a login is requested
1067
        if ($loginData['status'] === LoginType::LOGIN) {
1068
            $loginData = $this->processLoginData($loginData);
1069
        }
1070
        return $loginData;
1071
    }
1072
1073
    /**
1074
     * Processes Login data submitted by a form or params depending on the
1075
     * passwordTransmissionStrategy
1076
     *
1077
     * @param array $loginData Login data array
1078
     * @param string $passwordTransmissionStrategy Alternative passwordTransmissionStrategy. Used when authentication services wants to override the default.
1079
     * @return array
1080
     * @internal
1081
     */
1082
    public function processLoginData($loginData, $passwordTransmissionStrategy = '')
1083
    {
1084
        $loginSecurityLevel = trim($GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['loginSecurityLevel']) ?: 'normal';
1085
        $passwordTransmissionStrategy = $passwordTransmissionStrategy ?: $loginSecurityLevel;
1086
        $this->logger->debug('Login data before processing', $loginData);
1087
        $subType = 'processLoginData' . $this->loginType;
1088
        $authInfo = $this->getAuthInfoArray();
1089
        $isLoginDataProcessed = false;
1090
        $processedLoginData = $loginData;
1091
        /** @var AuthenticationService $serviceObject */
1092
        foreach ($this->getAuthServices($subType, $loginData, $authInfo) as $serviceObject) {
1093
            $serviceResult = $serviceObject->processLoginData($processedLoginData, $passwordTransmissionStrategy);
1094
            if (!empty($serviceResult)) {
1095
                $isLoginDataProcessed = true;
1096
                // If the service returns >=200 then no more processing is needed
1097
                if ((int)$serviceResult >= 200) {
1098
                    break;
1099
                }
1100
            }
1101
        }
1102
        if ($isLoginDataProcessed) {
1103
            $loginData = $processedLoginData;
1104
            $this->logger->debug('Processed login data', $processedLoginData);
1105
        }
1106
        return $loginData;
1107
    }
1108
1109
    /**
1110
     * Returns an info array which provides additional information for auth services
1111
     *
1112
     * @return array
1113
     * @internal
1114
     */
1115
    public function getAuthInfoArray()
1116
    {
1117
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
1118
        $expressionBuilder = $queryBuilder->expr();
1119
        $authInfo = [];
1120
        $authInfo['loginType'] = $this->loginType;
1121
        $authInfo['refInfo'] = parse_url(GeneralUtility::getIndpEnv('HTTP_REFERER'));
1122
        $authInfo['HTTP_HOST'] = GeneralUtility::getIndpEnv('HTTP_HOST');
1123
        $authInfo['REMOTE_ADDR'] = GeneralUtility::getIndpEnv('REMOTE_ADDR');
1124
        $authInfo['REMOTE_HOST'] = GeneralUtility::getIndpEnv('REMOTE_HOST');
1125
        // Can be overridden in localconf by SVCONF:
1126
        $authInfo['db_user']['table'] = $this->user_table;
1127
        $authInfo['db_user']['userid_column'] = $this->userid_column;
1128
        $authInfo['db_user']['username_column'] = $this->username_column;
1129
        $authInfo['db_user']['userident_column'] = $this->userident_column;
1130
        $authInfo['db_user']['usergroup_column'] = $this->usergroup_column;
1131
        $authInfo['db_user']['enable_clause'] = $this->userConstraints()->buildExpression(
1132
            [$this->user_table => $this->user_table],
1133
            $expressionBuilder
1134
        );
1135
        if ($this->checkPid && $this->checkPid_value !== null) {
1136
            $authInfo['db_user']['checkPidList'] = $this->checkPid_value;
1137
            $authInfo['db_user']['check_pid_clause'] = $expressionBuilder->in(
1138
                'pid',
1139
                GeneralUtility::intExplode(',', (string)$this->checkPid_value)
1140
            );
1141
        } else {
1142
            $authInfo['db_user']['checkPidList'] = '';
1143
            $authInfo['db_user']['check_pid_clause'] = '';
1144
        }
1145
        $authInfo['db_groups']['table'] = $this->usergroup_table;
1146
        return $authInfo;
1147
    }
1148
1149
    /**
1150
     * DUMMY: Writes to log database table (in some extension classes)
1151
     *
1152
     * @param int $type denotes which module that has submitted the entry. This is the current list:  1=tce_db; 2=tce_file; 3=system (eg. sys_history save); 4=modules; 254=Personal settings changed; 255=login / out action: 1=login, 2=logout, 3=failed login (+ errorcode 3), 4=failure_warning_email sent
1153
     * @param int $action denotes which specific operation that wrote the entry (eg. 'delete', 'upload', 'update' and so on...). Specific for each $type. Also used to trigger update of the interface. (see the log-module for the meaning of each number !!)
1154
     * @param int $error flag. 0 = message, 1 = error (user problem), 2 = System Error (which should not happen), 3 = security notice (admin)
1155
     * @param int $details_nr The message number. Specific for each $type and $action. in the future this will make it possible to translate error messages to other languages
1156
     * @param string $details Default text that follows the message
1157
     * @param array $data Data that follows the log. Might be used to carry special information. If an array the first 5 entries (0-4) will be sprintf'ed the details-text...
1158
     * @param string $tablename Special field used by tce_main.php. These ($tablename, $recuid, $recpid) holds the reference to the record which the log-entry is about. (Was used in attic status.php to update the interface.)
1159
     * @param int|string $recuid Special field used by tce_main.php. These ($tablename, $recuid, $recpid) holds the reference to the record which the log-entry is about. (Was used in attic status.php to update the interface.)
1160
     * @param int|string $recpid Special field used by tce_main.php. These ($tablename, $recuid, $recpid) holds the reference to the record which the log-entry is about. (Was used in attic status.php to update the interface.)
1161
     */
1162
    public function writelog($type, $action, $error, $details_nr, $details, $data, $tablename, $recuid, $recpid)
1163
    {
1164
    }
1165
1166
    /**
1167
     * Raw initialization of the be_user with uid=$uid
1168
     * This will circumvent all login procedures and select a be_users record from the
1169
     * database and set the content of ->user to the record selected.
1170
     * Thus the BE_USER object will appear like if a user was authenticated - however without
1171
     * a session id and the fields from the session table of course.
1172
     * Will check the users for disabled, start/endtime, etc. ($this->user_where_clause())
1173
     *
1174
     * @param int $uid The UID of the backend user to set in ->user
1175
     * @internal
1176
     */
1177
    public function setBeUserByUid($uid)
1178
    {
1179
        $this->user = $this->getRawUserByUid($uid);
1180
    }
1181
1182
    /**
1183
     * Raw initialization of the be_user with username=$name
1184
     *
1185
     * @param string $name The username to look up.
1186
     * @see \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::setBeUserByUid()
1187
     * @internal
1188
     */
1189
    public function setBeUserByName($name)
1190
    {
1191
        $this->user = $this->getRawUserByName($name);
1192
    }
1193
1194
    /**
1195
     * Fetching raw user record with uid=$uid
1196
     *
1197
     * @param int $uid The UID of the backend user to set in ->user
1198
     * @return array user record or FALSE
1199
     * @internal
1200
     */
1201
    public function getRawUserByUid($uid)
1202
    {
1203
        $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
1204
        $query->setRestrictions($this->userConstraints());
1205
        $query->select('*')
1206
            ->from($this->user_table)
1207
            ->where($query->expr()->eq('uid', $query->createNamedParameter($uid, \PDO::PARAM_INT)));
1208
1209
        return $query->execute()->fetch();
1210
    }
1211
1212
    /**
1213
     * Fetching raw user record with username=$name
1214
     *
1215
     * @param string $name The username to look up.
1216
     * @return array user record or FALSE
1217
     * @see \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::getUserByUid()
1218
     * @internal
1219
     */
1220
    public function getRawUserByName($name)
1221
    {
1222
        $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
1223
        $query->setRestrictions($this->userConstraints());
1224
        $query->select('*')
1225
            ->from($this->user_table)
1226
            ->where($query->expr()->eq('username', $query->createNamedParameter($name, \PDO::PARAM_STR)));
1227
1228
        return $query->execute()->fetch();
1229
    }
1230
1231
    /**
1232
     * @return UserSession
1233
     */
1234
    public function getSession(): UserSession
1235
    {
1236
        return $this->userSession;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->userSession could return the type null which is incompatible with the type-hinted return TYPO3\CMS\Core\Session\UserSession. Consider adding an additional type-check to rule them out.
Loading history...
1237
    }
1238
1239
    public function __isset(string $propertyName): bool
1240
    {
1241
        switch ($propertyName) {
1242
            case 'id':
1243
                trigger_error('Property id is removed in v11.', E_USER_DEPRECATED);
1244
                return isset($this->userSession);
1245
        }
1246
        return isset($this->propertyName);
0 ignored issues
show
Bug Best Practice introduced by
The property propertyName does not exist on TYPO3\CMS\Core\Authentic...tractUserAuthentication. Since you implemented __get, consider adding a @property annotation.
Loading history...
1247
    }
1248
1249
    public function __set(string $propertyName, $propertyValue)
1250
    {
1251
        switch ($propertyName) {
1252
            case 'id':
1253
                if (!isset($this->userSessionManager)) {
1254
                    $this->initializeUserSessionManager();
1255
                }
1256
                $this->userSession = UserSession::createNonFixated($propertyValue);
1257
                // No deprecation due to adaptions in testing framework to remove ->id = ...
1258
                break;
1259
        }
1260
        $this->$propertyName = $propertyValue;
1261
    }
1262
1263
    public function __get(string $propertyName)
1264
    {
1265
        switch ($propertyName) {
1266
            case 'id':
1267
                trigger_error('Property id is marked as protected now. Use ->getSession()->getIdentifier().', E_USER_DEPRECATED);
1268
                return $this->getSession()->getIdentifier();
1269
        }
1270
        return $this->$propertyName;
1271
    }
1272
1273
    public function __unset(string $propertyName): void
1274
    {
1275
        switch ($propertyName) {
1276
            case 'id':
1277
                trigger_error('Property id is marked as protected now. Use ->getSession()->getIdentifier().', E_USER_DEPRECATED);
1278
                return;
1279
        }
1280
        unset($this->$propertyName);
1281
    }
1282
}
1283