Passed
Push — master ( 8cec79...9b5c52 )
by
unknown
15:20
created

AbstractUserAuthentication::getHttpHeaders()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 0
dl 0
loc 7
rs 10
c 0
b 0
f 0
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\Log\LoggerAwareInterface;
19
use Psr\Log\LoggerAwareTrait;
20
use Symfony\Component\HttpFoundation\Cookie;
21
use TYPO3\CMS\Core\Core\Environment;
22
use TYPO3\CMS\Core\Crypto\Random;
23
use TYPO3\CMS\Core\Database\Connection;
24
use TYPO3\CMS\Core\Database\ConnectionPool;
25
use TYPO3\CMS\Core\Database\Query\Restriction\DefaultRestrictionContainer;
26
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
27
use TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction;
28
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
29
use TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionContainerInterface;
30
use TYPO3\CMS\Core\Database\Query\Restriction\RootLevelRestriction;
31
use TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction;
32
use TYPO3\CMS\Core\Exception;
33
use TYPO3\CMS\Core\Http\CookieHeaderTrait;
34
use TYPO3\CMS\Core\Session\Backend\Exception\SessionNotFoundException;
35
use TYPO3\CMS\Core\Session\Backend\HashableSessionBackendInterface;
36
use TYPO3\CMS\Core\Session\Backend\SessionBackendInterface;
37
use TYPO3\CMS\Core\Session\SessionManager;
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
     * Session timeout (on the server)
138
     *
139
     * If >0: session-timeout in seconds.
140
     * If <=0: Instant logout after login.
141
     *
142
     * @var int
143
     */
144
    public $sessionTimeout = 0;
145
146
    /**
147
     * Lifetime for the session-cookie (on the client)
148
     *
149
     * If >0: permanent cookie with given lifetime
150
     * If 0: session-cookie
151
     * Session-cookie means the browser will remove it when the browser is closed.
152
     *
153
     * @var int
154
     */
155
    public $lifetime = 0;
156
157
    /**
158
     * GarbageCollection
159
     * Purge all server session data older than $gc_time seconds.
160
     * if $this->sessionTimeout > 0, then the session timeout is used instead.
161
     * @var int
162
     */
163
    public $gc_time = 86400;
164
165
    /**
166
     * Probability for garbage collection to be run (in percent)
167
     * @var int
168
     */
169
    public $gc_probability = 1;
170
171
    /**
172
     * Decides if the writelog() function is called at login and logout
173
     * @var bool
174
     */
175
    public $writeStdLog = false;
176
177
    /**
178
     * Log failed login attempts
179
     * @var bool
180
     */
181
    public $writeAttemptLog = false;
182
183
    /**
184
     * Send no-cache headers
185
     * @var bool
186
     */
187
    public $sendNoCacheHeaders = true;
188
189
    /**
190
     * The ident-hash is normally 32 characters and should be!
191
     * But if you are making sites for WAP-devices or other low-bandwidth stuff,
192
     * you may shorten the length.
193
     * Never let this value drop below 6!
194
     * A length of 6 would give you more than 16 mio possibilities.
195
     * @var int
196
     */
197
    public $hash_length = 32;
198
199
    /**
200
     * If set, the user-record must be stored at the page defined by $checkPid_value
201
     * @var bool
202
     */
203
    public $checkPid = true;
204
205
    /**
206
     * The page id the user record must be stored at
207
     * @var int
208
     */
209
    public $checkPid_value = 0;
210
211
    /**
212
     * session_id (MD5-hash)
213
     * @var string
214
     * @internal
215
     */
216
    public $id;
217
218
    /**
219
     * Will be set to TRUE if the login session is actually written during auth-check.
220
     * @var bool
221
     */
222
    public $loginSessionStarted = false;
223
224
    /**
225
     * @var array|null contains user- AND session-data from database (joined tables)
226
     * @internal
227
     */
228
    public $user;
229
230
    /**
231
     * Will be set to TRUE if a new session ID was created
232
     * @var bool
233
     */
234
    public $newSessionID = false;
235
236
    /**
237
     * Will force the session cookie to be set every time (lifetime must be 0)
238
     * @var bool
239
     */
240
    public $forceSetCookie = false;
241
242
    /**
243
     * Will prevent the setting of the session cookie (takes precedence over forceSetCookie)
244
     * @var bool
245
     */
246
    public $dontSetCookie = false;
247
248
    /**
249
     * @var bool
250
     */
251
    protected $cookieWasSetOnCurrentRequest = false;
252
253
    /**
254
     * Login type, used for services.
255
     * @var string
256
     */
257
    public $loginType = '';
258
259
    /**
260
     * @var array
261
     */
262
    public $uc;
263
264
    /**
265
     * @var IpLocker
266
     */
267
    protected $ipLocker;
268
269
    /**
270
     * @var SessionBackendInterface
271
     */
272
    protected $sessionBackend;
273
274
    /**
275
     * Holds deserialized data from session records.
276
     * 'Reserved' keys are:
277
     *   - 'sys': Reserved for TypoScript standard code.
278
     * @var array
279
     */
280
    protected $sessionData = [];
281
282
    /**
283
     * Initialize some important variables
284
     *
285
     * @throws Exception
286
     */
287
    public function __construct()
288
    {
289
        // If there is a custom session timeout, use this instead of the 1d default gc time.
290
        if ($this->sessionTimeout > 0) {
291
            $this->gc_time = $this->sessionTimeout;
292
        }
293
        // Backend or frontend login - used for auth services
294
        if (empty($this->loginType)) {
295
            throw new Exception('No loginType defined, must be set explicitly by subclass', 1476045345);
296
        }
297
298
        $this->ipLocker = GeneralUtility::makeInstance(
299
            IpLocker::class,
300
            $GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['lockIP'],
301
            $GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['lockIPv6']
302
        );
303
    }
304
305
    /**
306
     * Starts a user session
307
     * Typical configurations will:
308
     * a) check if session cookie was set and if not, set one,
309
     * b) check if a password/username was sent and if so, try to authenticate the user
310
     * c) Lookup a session attached to a user and check timeout etc.
311
     * d) Garbage collection, setting of no-cache headers.
312
     * If a user is authenticated the database record of the user (array) will be set in the ->user internal variable.
313
     */
314
    public function start()
315
    {
316
        $this->logger->debug('## Beginning of auth logging.');
317
        $this->newSessionID = false;
318
        // Make certain that NO user is set initially
319
        $this->user = null;
320
        // sessionID is set to ses_id if cookie is present. Otherwise a new session will start
321
        $this->id = $this->getCookie($this->name);
322
323
        // If new session or client tries to fix session...
324
        if (!$this->isExistingSessionRecord($this->id)) {
325
            $this->id = $this->createSessionId();
326
            $this->newSessionID = true;
327
        }
328
329
        // Load user session, check to see if anyone has submitted login-information and if so authenticate
330
        // the user with the session. $this->user[uid] may be used to write log...
331
        $this->checkAuthentication();
332
        // Set cookie if generally enabled or if the current session is a non-session cookie (FE permalogin)
333
        if (!$this->dontSetCookie || $this->isRefreshTimeBasedCookie()) {
334
            $this->setSessionCookie();
335
        }
336
        // Hook for alternative ways of filling the $this->user array (is used by the "timtaw" extension)
337
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postUserLookUp'] ?? [] as $funcName) {
338
            $_params = [
339
                'pObj' => $this,
340
            ];
341
            GeneralUtility::callUserFunction($funcName, $_params, $this);
342
        }
343
        // If we're lucky we'll get to clean up old sessions
344
        if (random_int(0, mt_getrandmax()) % 100 <= $this->gc_probability) {
345
            $this->gc();
0 ignored issues
show
Bug introduced by
The method gc() 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

345
            $this->/** @scrutinizer ignore-call */ 
346
                   gc();

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...
346
        }
347
    }
348
349
    /**
350
     * Sets the session cookie for the current disposal.
351
     *
352
     * @throws Exception
353
     */
354
    protected function setSessionCookie()
355
    {
356
        $isSetSessionCookie = $this->isSetSessionCookie();
357
        $isRefreshTimeBasedCookie = $this->isRefreshTimeBasedCookie();
358
        if ($isSetSessionCookie || $isRefreshTimeBasedCookie) {
359
            // Get the domain to be used for the cookie (if any):
360
            $cookieDomain = $this->getCookieDomain();
361
            // If no cookie domain is set, use the base path:
362
            $cookiePath = $cookieDomain ? '/' : GeneralUtility::getIndpEnv('TYPO3_SITE_PATH');
363
            // If the cookie lifetime is set, use it:
364
            $cookieExpire = $isRefreshTimeBasedCookie ? $GLOBALS['EXEC_TIME'] + $this->lifetime : 0;
365
            // Valid options are "strict", "lax" or "none", whereas "none" only works in HTTPS requests (default & fallback is "strict")
366
            $cookieSameSite = $this->sanitizeSameSiteCookieValue(
367
                strtolower($GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieSameSite'] ?? Cookie::SAMESITE_STRICT)
368
            );
369
            // Use the secure option when the current request is served by a secure connection:
370
            // SameSite "none" needs the secure option (only allowed on HTTPS)
371
            $isSecure = $cookieSameSite === Cookie::SAMESITE_NONE || GeneralUtility::getIndpEnv('TYPO3_SSL');
372
            $cookie = new Cookie(
373
                $this->name,
374
                $this->id,
375
                $cookieExpire,
376
                $cookiePath,
377
                $cookieDomain,
378
                $isSecure,
379
                true,
380
                false,
381
                $cookieSameSite
382
            );
383
            header('Set-Cookie: ' . $cookie->__toString(), false);
384
            $this->cookieWasSetOnCurrentRequest = true;
385
            $this->logger->debug(
386
                ($isRefreshTimeBasedCookie ? 'Updated Cookie: ' : 'Set Cookie: ')
387
                . $this->id . ($cookieDomain ? ', ' . $cookieDomain : '')
388
            );
389
        }
390
    }
391
392
    /**
393
     * Gets the domain to be used on setting cookies.
394
     * The information is taken from the value in $GLOBALS['TYPO3_CONF_VARS']['SYS']['cookieDomain'].
395
     *
396
     * @return string The domain to be used on setting cookies
397
     */
398
    protected function getCookieDomain()
399
    {
400
        $result = '';
401
        $cookieDomain = $GLOBALS['TYPO3_CONF_VARS']['SYS']['cookieDomain'];
402
        // If a specific cookie domain is defined for a given application type, use that domain
403
        if (!empty($GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieDomain'])) {
404
            $cookieDomain = $GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieDomain'];
405
        }
406
        if ($cookieDomain) {
407
            if ($cookieDomain[0] === '/') {
408
                $match = [];
409
                $matchCnt = @preg_match($cookieDomain, GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY'), $match);
410
                if ($matchCnt === false) {
411
                    $this->logger->critical('The regular expression for the cookie domain (' . $cookieDomain . ') contains errors. The session is not shared across sub-domains.');
412
                } elseif ($matchCnt) {
413
                    $result = $match[0];
414
                }
415
            } else {
416
                $result = $cookieDomain;
417
            }
418
        }
419
        return $result;
420
    }
421
422
    /**
423
     * Get the value of a specified cookie.
424
     *
425
     * @param string $cookieName The cookie ID
426
     * @return string The value stored in the cookie
427
     */
428
    protected function getCookie($cookieName)
429
    {
430
        return isset($_COOKIE[$cookieName]) ? stripslashes($_COOKIE[$cookieName]) : '';
431
    }
432
433
    /**
434
     * Determine whether a session cookie needs to be set (lifetime=0)
435
     *
436
     * @return bool
437
     * @internal
438
     */
439
    public function isSetSessionCookie()
440
    {
441
        return ($this->newSessionID || $this->forceSetCookie) && $this->lifetime == 0;
442
    }
443
444
    /**
445
     * Determine whether a non-session cookie needs to be set (lifetime>0)
446
     *
447
     * @return bool
448
     * @internal
449
     */
450
    public function isRefreshTimeBasedCookie()
451
    {
452
        return $this->lifetime > 0;
453
    }
454
455
    /**
456
     * "auth" services configuration array from $GLOBALS['TYPO3_CONF_VARS']['SVCONF']['auth']
457
     * @return array
458
     */
459
    protected function getAuthServiceConfiguration(): array
460
    {
461
        if (is_array($GLOBALS['TYPO3_CONF_VARS']['SVCONF']['auth']['setup'] ?? null)) {
462
            return $GLOBALS['TYPO3_CONF_VARS']['SVCONF']['auth']['setup'];
463
        }
464
        return [];
465
    }
466
467
    /**
468
     * Checks if a submission of username and password is present or use other authentication by auth services
469
     *
470
     * @throws \RuntimeException
471
     * @internal
472
     */
473
    public function checkAuthentication()
474
    {
475
        $authConfiguration = $this->getAuthServiceConfiguration();
476
        if (!empty($authConfiguration)) {
477
            $this->logger->debug('Authentication Service Configuration found.', $authConfiguration);
478
        }
479
        // No user for now - will be searched by service below
480
        $tempuserArr = [];
481
        $tempuser = false;
482
        // User is not authenticated by default
483
        $authenticated = false;
484
        // User want to login with passed login data (name/password)
485
        $activeLogin = false;
486
        $this->logger->debug('Login type: ' . $this->loginType);
487
        // The info array provide additional information for auth services
488
        $authInfo = $this->getAuthInfoArray();
489
        // Get Login/Logout data submitted by a form or params
490
        $loginData = $this->getLoginFormData();
491
        $this->logger->debug('Login data', $loginData);
492
        // Active logout (eg. with "logout" button)
493
        if ($loginData['status'] === LoginType::LOGOUT) {
494
            if ($this->writeStdLog) {
495
                // $type,$action,$error,$details_nr,$details,$data,$tablename,$recuid,$recpid
496
                $this->writelog(SystemLogType::LOGIN, SystemLogLoginAction::LOGOUT, SystemLogErrorClassification::MESSAGE, 2, 'User %s logged out', [$this->user['username']], '', 0, 0);
497
            }
498
            $this->logger->info('User logged out. Id: ' . $this->id);
499
            $this->logoff();
500
        }
501
        // Determine whether we need to skip session update.
502
        // This is used mainly for checking session timeout in advance without refreshing the current session's timeout.
503
        $skipSessionUpdate = (bool)GeneralUtility::_GP('skipSessionUpdate');
504
        $haveSession = false;
505
        $anonymousSession = false;
506
        if (!$this->newSessionID) {
507
            // Read user session
508
            $authInfo['userSession'] = $this->fetchUserSession($skipSessionUpdate);
509
            $haveSession = is_array($authInfo['userSession']);
510
            if ($haveSession && !empty($authInfo['userSession']['ses_anonymous'])) {
511
                $anonymousSession = true;
512
            }
513
        }
514
515
        // Active login (eg. with login form).
516
        if (!$haveSession && $loginData['status'] === LoginType::LOGIN) {
517
            $activeLogin = true;
518
            $this->logger->debug('Active login (eg. with login form)');
519
            // check referrer for submitted login values
520
            if ($this->formfield_status && $loginData['uident'] && $loginData['uname']) {
521
                // Delete old user session if any
522
                $this->logoff();
523
            }
524
            // Refuse login for _CLI users, if not processing a CLI request type
525
            // (although we shouldn't be here in case of a CLI request type)
526
            if (stripos($loginData['uname'], '_CLI_') === 0 && !Environment::isCli()) {
527
                throw new \RuntimeException('TYPO3 Fatal Error: You have tried to login using a CLI user. Access prohibited!', 1270853931);
528
            }
529
        }
530
531
        // Cause elevation of privilege, make sure regenerateSessionId is called later on
532
        if ($anonymousSession && $loginData['status'] === LoginType::LOGIN) {
533
            $activeLogin = true;
534
        }
535
536
        if ($haveSession) {
537
            $this->logger->debug('User session found', [
538
                $this->userid_column => $authInfo['userSession'][$this->userid_column] ?? null,
539
                $this->username_column => $authInfo['userSession'][$this->username_column] ?? null,
540
            ]);
541
        } else {
542
            $this->logger->debug('No user session found');
543
        }
544
545
        // Fetch user if ...
546
        if (
547
            $activeLogin || !empty($authConfiguration[$this->loginType . '_alwaysFetchUser'])
548
            || !$haveSession && !empty($authConfiguration[$this->loginType . '_fetchUserIfNoSession'])
549
        ) {
550
            // Use 'auth' service to find the user
551
            // First found user will be used
552
            $subType = 'getUser' . $this->loginType;
553
            /** @var AuthenticationService $serviceObj */
554
            foreach ($this->getAuthServices($subType, $loginData, $authInfo) as $serviceObj) {
555
                if ($row = $serviceObj->getUser()) {
556
                    $tempuserArr[] = $row;
557
                    $this->logger->debug('User found', [
558
                        $this->userid_column => $row[$this->userid_column],
559
                        $this->username_column => $row[$this->username_column],
560
                    ]);
561
                    // User found, just stop to search for more if not configured to go on
562
                    if (empty($authConfiguration[$this->loginType . '_fetchAllUsers'])) {
563
                        break;
564
                    }
565
                }
566
            }
567
568
            if (!empty($authConfiguration[$this->loginType . '_alwaysFetchUser'])) {
569
                $this->logger->debug($this->loginType . '_alwaysFetchUser option is enabled');
570
            }
571
            if (empty($tempuserArr)) {
572
                $this->logger->debug('No user found by services');
573
            } else {
574
                $this->logger->debug(count($tempuserArr) . ' user records found by services');
575
            }
576
        }
577
578
        // If no new user was set we use the already found user session
579
        if (empty($tempuserArr) && $haveSession && !$anonymousSession) {
580
            $tempuserArr[] = $authInfo['userSession'];
581
            $tempuser = $authInfo['userSession'];
582
            // User is authenticated because we found a user session
583
            $authenticated = true;
584
            $this->logger->debug('User session used', [
585
                $this->userid_column => $authInfo['userSession'][$this->userid_column],
586
                $this->username_column => $authInfo['userSession'][$this->username_column],
587
            ]);
588
        }
589
        // Re-auth user when 'auth'-service option is set
590
        if (!empty($authConfiguration[$this->loginType . '_alwaysAuthUser'])) {
591
            $authenticated = false;
592
            $this->logger->debug('alwaysAuthUser option is enabled');
593
        }
594
        // Authenticate the user if needed
595
        if (!empty($tempuserArr) && !$authenticated) {
596
            foreach ($tempuserArr as $tempuser) {
597
                // Use 'auth' service to authenticate the user
598
                // If one service returns FALSE then authentication failed
599
                // a service might return 100 which means there's no reason to stop but the user can't be authenticated by that service
600
                $this->logger->debug('Auth user', $tempuser);
601
                $subType = 'authUser' . $this->loginType;
602
603
                /** @var AuthenticationService $serviceObj */
604
                foreach ($this->getAuthServices($subType, $loginData, $authInfo) as $serviceObj) {
605
                    if (($ret = $serviceObj->authUser($tempuser)) > 0) {
606
                        // If the service returns >=200 then no more checking is needed - useful for IP checking without password
607
                        if ((int)$ret >= 200) {
608
                            $authenticated = true;
609
                            break;
610
                        }
611
                        if ((int)$ret >= 100) {
612
                        } else {
613
                            $authenticated = true;
614
                        }
615
                    } else {
616
                        $authenticated = false;
617
                        break;
618
                    }
619
                }
620
621
                if ($authenticated) {
622
                    // Leave foreach() because a user is authenticated
623
                    break;
624
                }
625
            }
626
        }
627
628
        // If user is authenticated a valid user is in $tempuser
629
        if ($authenticated) {
630
            // Insert session record if needed:
631
            if (!$haveSession || $anonymousSession || $tempuser['ses_id'] != $this->id && $tempuser['uid'] != $authInfo['userSession']['ses_userid']) {
632
                $sessionData = $this->createUserSession($tempuser);
633
634
                // Preserve session data on login
635
                if ($anonymousSession) {
636
                    $sessionData = $this->getSessionBackend()->update(
637
                        $this->id,
638
                        ['ses_data' => $authInfo['userSession']['ses_data']]
639
                    );
640
                }
641
642
                $this->user = array_merge(
643
                    $tempuser,
644
                    $sessionData
645
                );
646
                // The login session is started.
647
                $this->loginSessionStarted = true;
648
                if (is_array($this->user)) {
0 ignored issues
show
introduced by
The condition is_array($this->user) is always true.
Loading history...
649
                    $this->logger->debug('User session finally read', [
650
                        $this->userid_column => $this->user[$this->userid_column],
651
                        $this->username_column => $this->user[$this->username_column],
652
                    ]);
653
                }
654
            } elseif ($haveSession) {
0 ignored issues
show
introduced by
The condition $haveSession is always true.
Loading history...
655
                // Validate the session ID and promote it
656
                // This check can be removed in TYPO3 v11
657
                if ($this->getSessionBackend() instanceof HashableSessionBackendInterface && !empty($authInfo['userSession']['ses_id'] ?? '')) {
658
                    // The session is stored in plaintext, promote it
659
                    if ($authInfo['userSession']['ses_id'] === $this->id) {
660
                        $authInfo['userSession'] = $this->getSessionBackend()->update(
661
                            $this->id,
662
                            ['ses_data' => $authInfo['userSession']['ses_data']]
663
                        );
664
                    }
665
                }
666
                // if we come here the current session is for sure not anonymous as this is a pre-condition for $authenticated = true
667
                $this->user = $authInfo['userSession'];
668
            }
669
670
            if ($activeLogin && !$this->newSessionID) {
671
                $this->regenerateSessionId();
672
            }
673
674
            if ($activeLogin) {
675
                // User logged in - write that to the log!
676
                if ($this->writeStdLog) {
677
                    $this->writelog(SystemLogType::LOGIN, SystemLogLoginAction::LOGIN, SystemLogErrorClassification::MESSAGE, 1, 'User %s logged in from ###IP###', [$tempuser[$this->username_column]], '', '', '');
678
                }
679
                $this->logger->info('User ' . $tempuser[$this->username_column] . ' logged in from ' . GeneralUtility::getIndpEnv('REMOTE_ADDR'));
680
            } else {
681
                $this->logger->debug('User ' . $tempuser[$this->username_column] . ' authenticated from ' . GeneralUtility::getIndpEnv('REMOTE_ADDR'));
682
            }
683
        } else {
684
            // User was not authenticated, so we should reuse the existing anonymous session
685
            if ($anonymousSession) {
686
                $this->user = $authInfo['userSession'];
687
            }
688
689
            // Mark the current login attempt as failed
690
            if (empty($tempuserArr) && $activeLogin) {
691
                $this->logger->debug('Login failed', [
692
                    'loginData' => $loginData
693
                ]);
694
            } elseif (!empty($tempuserArr)) {
695
                $this->logger->debug('Login failed', [
696
                    $this->userid_column => $tempuser[$this->userid_column],
697
                    $this->username_column => $tempuser[$this->username_column],
698
                ]);
699
            }
700
701
            // If there were a login failure, check to see if a warning email should be sent
702
            if ($activeLogin) {
703
                $this->handleLoginFailure();
704
            }
705
        }
706
    }
707
708
    /**
709
     * Implement functionality when there was a failed login
710
     */
711
    protected function handleLoginFailure(): void
712
    {
713
        $_params = [];
714
        $sleep = true;
715
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postLoginFailureProcessing'] ?? [] as $hookIdentifier => $_funcRef) {
716
            GeneralUtility::callUserFunction($_funcRef, $_params, $this);
717
            // This hack will be removed once this is migrated into PSR-14 Events
718
            if ($hookIdentifier !== 'sendEmailOnFailedLoginAttempt') {
719
                $sleep = false;
720
            }
721
        }
722
        if ($sleep) {
723
            // No hooks were triggered - default login failure behavior is to sleep 5 seconds
724
            sleep(5);
725
        }
726
    }
727
728
    /**
729
     * Creates a new session ID.
730
     *
731
     * @return string The new session ID
732
     */
733
    public function createSessionId()
734
    {
735
        return GeneralUtility::makeInstance(Random::class)->generateRandomHexString($this->hash_length);
736
    }
737
738
    /**
739
     * Initializes authentication services to be used in a foreach loop
740
     *
741
     * @param string $subType e.g. getUserFE
742
     * @param array $loginData
743
     * @param array $authInfo
744
     * @return \Traversable A generator of service objects
745
     */
746
    protected function getAuthServices(string $subType, array $loginData, array $authInfo): \Traversable
747
    {
748
        $serviceChain = [];
749
        while (is_object($serviceObj = GeneralUtility::makeInstanceService('auth', $subType, $serviceChain))) {
750
            $serviceChain[] = $serviceObj->getServiceKey();
751
            $serviceObj->initAuth($subType, $loginData, $authInfo, $this);
752
            yield $serviceObj;
753
        }
754
        if (!empty($serviceChain)) {
755
            $this->logger->debug($subType . ' auth services called: ' . implode(', ', $serviceChain));
756
        }
757
    }
758
759
    /**
760
     * Regenerate the session ID and transfer the session to new ID
761
     * Call this method whenever a user proceeds to a higher authorization level
762
     * e.g. when an anonymous session is now authenticated.
763
     *
764
     * @param array $existingSessionRecord If given, this session record will be used instead of fetching again
765
     * @param bool $anonymous If true session will be regenerated as anonymous session
766
     */
767
    protected function regenerateSessionId(array $existingSessionRecord = [], bool $anonymous = false)
768
    {
769
        if (empty($existingSessionRecord)) {
770
            $existingSessionRecord = $this->getSessionBackend()->get($this->id);
771
        }
772
773
        // Update session record with new ID
774
        $oldSessionId = $this->id;
775
        $this->id = $this->createSessionId();
776
        $updatedSession = $this->getSessionBackend()->set($this->id, $existingSessionRecord);
777
        $this->sessionData = unserialize($updatedSession['ses_data']);
778
        // Merge new session data into user/session array
779
        $this->user = array_merge($this->user ?? [], $updatedSession);
780
        $this->getSessionBackend()->remove($oldSessionId);
781
        $this->newSessionID = true;
782
    }
783
784
    /*************************
785
     *
786
     * User Sessions
787
     *
788
     *************************/
789
790
    /**
791
     * Creates a user session record and returns its values.
792
     *
793
     * @param array $tempuser User data array
794
     *
795
     * @return array The session data for the newly created session.
796
     */
797
    public function createUserSession($tempuser)
798
    {
799
        $this->logger->debug('Create session ses_id = ' . $this->id);
800
        // Delete any session entry first
801
        $this->getSessionBackend()->remove($this->id);
802
        // Re-create session entry
803
        $sessionRecord = $this->getNewSessionRecord($tempuser);
804
        $sessionRecord = $this->getSessionBackend()->set($this->id, $sessionRecord);
805
        // Updating lastLogin_column carrying information about last login.
806
        $this->updateLoginTimestamp($tempuser[$this->userid_column]);
807
        return $sessionRecord;
808
    }
809
810
    /**
811
     * Updates the last login column in the user with the given id
812
     *
813
     * @param int $userId
814
     */
815
    protected function updateLoginTimestamp(int $userId)
816
    {
817
        if ($this->lastLogin_column) {
818
            $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->user_table);
819
            $connection->update(
820
                $this->user_table,
821
                [$this->lastLogin_column => $GLOBALS['EXEC_TIME']],
822
                [$this->userid_column => $userId]
823
            );
824
        }
825
    }
826
827
    /**
828
     * Returns a new session record for the current user for insertion into the DB.
829
     * This function is mainly there as a wrapper for inheriting classes to override it.
830
     *
831
     * @param array $tempuser
832
     * @return array User session record
833
     */
834
    public function getNewSessionRecord($tempuser)
835
    {
836
        $sessionIpLock = $this->ipLocker->getSessionIpLock((string)GeneralUtility::getIndpEnv('REMOTE_ADDR'));
837
838
        return [
839
            'ses_id' => $this->id,
840
            'ses_iplock' => $sessionIpLock,
841
            'ses_userid' => $tempuser[$this->userid_column] ?? 0,
842
            'ses_tstamp' => $GLOBALS['EXEC_TIME'],
843
            'ses_data' => '',
844
        ];
845
    }
846
847
    /**
848
     * Read the user session from db.
849
     *
850
     * @param bool $skipSessionUpdate
851
     * @return array|bool User session data, false if $this->id does not represent valid session
852
     */
853
    public function fetchUserSession($skipSessionUpdate = false)
854
    {
855
        $this->logger->debug('Fetch session ses_id = ' . $this->id);
856
        try {
857
            $sessionRecord = $this->getSessionBackend()->get($this->id);
858
        } catch (SessionNotFoundException $e) {
859
            return false;
860
        }
861
862
        $this->sessionData = unserialize($sessionRecord['ses_data']);
863
        // Session is anonymous so no need to fetch user
864
        if (!empty($sessionRecord['ses_anonymous'])) {
865
            return $sessionRecord;
866
        }
867
868
        // Fetch the user from the DB
869
        $userRecord = $this->getRawUserByUid((int)$sessionRecord['ses_userid']);
870
        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...
871
            $userRecord = array_merge($sessionRecord, $userRecord);
872
            // A user was found
873
            $userRecord['ses_tstamp'] = (int)$userRecord['ses_tstamp'];
874
            $userRecord['is_online'] = (int)$userRecord['ses_tstamp'];
875
876
            // If sessionTimeout > 0 (TRUE) and current time has not exceeded the latest sessions-time plus the timeout in seconds then accept user
877
            // Use a gracetime-value to avoid updating a session-record too often
878
            if ($this->sessionTimeout > 0 && $GLOBALS['EXEC_TIME'] < $userRecord['ses_tstamp'] + $this->sessionTimeout) {
879
                $sessionUpdateGracePeriod = 61;
880
                if (!$skipSessionUpdate && $GLOBALS['EXEC_TIME'] > ($userRecord['ses_tstamp'] + $sessionUpdateGracePeriod)) {
881
                    // Update the session timestamp by writing a dummy update. (Backend will update the timestamp)
882
                    $updatesSession = $this->getSessionBackend()->update($this->id, []);
883
                    $userRecord = array_merge($userRecord, $updatesSession);
884
                }
885
            } else {
886
                // Delete any user set...
887
                $this->logoff();
888
                $userRecord = false;
889
            }
890
        }
891
        return $userRecord;
892
    }
893
894
    /**
895
     * Regenerates the session ID and sets the cookie again.
896
     *
897
     * @internal
898
     */
899
    public function enforceNewSessionId()
900
    {
901
        $this->regenerateSessionId();
902
        $this->setSessionCookie();
903
    }
904
905
    /**
906
     * Log out current user!
907
     * Removes the current session record, sets the internal ->user array to a blank string;
908
     * Thereby the current user (if any) is effectively logged out!
909
     */
910
    public function logoff()
911
    {
912
        $this->logger->debug('logoff: ses_id = ' . $this->id);
913
914
        $_params = [];
915
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'] ?? [] as $_funcRef) {
916
            if ($_funcRef) {
917
                GeneralUtility::callUserFunction($_funcRef, $_params, $this);
918
            }
919
        }
920
        $this->performLogoff();
921
922
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_post_processing'] ?? [] as $_funcRef) {
923
            if ($_funcRef) {
924
                GeneralUtility::callUserFunction($_funcRef, $_params, $this);
925
            }
926
        }
927
    }
928
929
    /**
930
     * Perform the logoff action. Called from logoff() as a way to allow subclasses to override
931
     * what happens when a user logs off, without needing to reproduce the hook calls and logging
932
     * that happens in the public logoff() API method.
933
     */
934
    protected function performLogoff()
935
    {
936
        if ($this->id) {
937
            $this->getSessionBackend()->remove($this->id);
938
        }
939
        $this->sessionData = [];
940
        $this->user = null;
941
        if ($this->isCookieSet()) {
942
            $this->removeCookie($this->name);
943
        }
944
    }
945
946
    /**
947
     * Empty / unset the cookie
948
     *
949
     * @param string $cookieName usually, this is $this->name
950
     */
951
    public function removeCookie($cookieName)
952
    {
953
        $cookieDomain = $this->getCookieDomain();
954
        // If no cookie domain is set, use the base path
955
        $cookiePath = $cookieDomain ? '/' : GeneralUtility::getIndpEnv('TYPO3_SITE_PATH');
956
        setcookie($cookieName, '', -1, $cookiePath, $cookieDomain);
957
    }
958
959
    /**
960
     * Determine whether there's an according session record to a given session_id.
961
     * Don't care if session record is still valid or not.
962
     *
963
     * @param string $id Claimed Session ID
964
     * @return bool Returns TRUE if a corresponding session was found in the database
965
     */
966
    public function isExistingSessionRecord($id)
967
    {
968
        if (empty($id)) {
969
            return false;
970
        }
971
        try {
972
            $sessionRecord = $this->getSessionBackend()->get($id);
973
            if (empty($sessionRecord)) {
974
                return false;
975
            }
976
            // If the session does not match the current IP lock, it should be treated as invalid
977
            // and a new session should be created.
978
            return $this->ipLocker->validateRemoteAddressAgainstSessionIpLock((string)GeneralUtility::getIndpEnv('REMOTE_ADDR'), $sessionRecord['ses_iplock']);
979
        } catch (SessionNotFoundException $e) {
980
            return false;
981
        }
982
    }
983
984
    /**
985
     * Returns whether this request is going to set a cookie
986
     * or a cookie was already found in the system
987
     *
988
     * @return bool Returns TRUE if a cookie is set
989
     */
990
    public function isCookieSet()
991
    {
992
        return $this->cookieWasSetOnCurrentRequest || $this->getCookie($this->name);
993
    }
994
995
    /*************************
996
     *
997
     * SQL Functions
998
     *
999
     *************************/
1000
    /**
1001
     * This returns the restrictions needed to select the user respecting
1002
     * enable columns and flags like deleted, hidden, starttime, endtime
1003
     * and rootLevel
1004
     *
1005
     * @return QueryRestrictionContainerInterface
1006
     * @internal
1007
     */
1008
    protected function userConstraints(): QueryRestrictionContainerInterface
1009
    {
1010
        $restrictionContainer = GeneralUtility::makeInstance(DefaultRestrictionContainer::class);
1011
1012
        if (empty($this->enablecolumns['disabled'])) {
1013
            $restrictionContainer->removeByType(HiddenRestriction::class);
1014
        }
1015
1016
        if (empty($this->enablecolumns['deleted'])) {
1017
            $restrictionContainer->removeByType(DeletedRestriction::class);
1018
        }
1019
1020
        if (empty($this->enablecolumns['starttime'])) {
1021
            $restrictionContainer->removeByType(StartTimeRestriction::class);
1022
        }
1023
1024
        if (empty($this->enablecolumns['endtime'])) {
1025
            $restrictionContainer->removeByType(EndTimeRestriction::class);
1026
        }
1027
1028
        if (!empty($this->enablecolumns['rootLevel'])) {
1029
            $restrictionContainer->add(GeneralUtility::makeInstance(RootLevelRestriction::class, [$this->user_table]));
1030
        }
1031
1032
        return $restrictionContainer;
1033
    }
1034
1035
    /*************************
1036
     *
1037
     * Session and Configuration Handling
1038
     *
1039
     *************************/
1040
    /**
1041
     * This writes $variable to the user-record. This is a way of providing session-data.
1042
     * You can fetch the data again through $this->uc in this class!
1043
     * If $variable is not an array, $this->uc is saved!
1044
     *
1045
     * @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
1046
     */
1047
    public function writeUC($variable = '')
1048
    {
1049
        if (is_array($this->user) && $this->user[$this->userid_column]) {
1050
            if (!is_array($variable)) {
1051
                $variable = $this->uc;
1052
            }
1053
            $this->logger->debug('writeUC: ' . $this->userid_column . '=' . (int)$this->user[$this->userid_column]);
1054
            GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->user_table)->update(
1055
                $this->user_table,
1056
                ['uc' => serialize($variable)],
1057
                [$this->userid_column => (int)$this->user[$this->userid_column]],
1058
                ['uc' => Connection::PARAM_LOB]
1059
            );
1060
        }
1061
    }
1062
1063
    /**
1064
     * Sets $theUC as the internal variable ->uc IF $theUC is an array.
1065
     * If $theUC is FALSE, the 'uc' content from the ->user array will be unserialized and restored in ->uc
1066
     *
1067
     * @param mixed $theUC If an array, then set as ->uc, otherwise load from user record
1068
     */
1069
    public function unpack_uc($theUC = '')
1070
    {
1071
        if (!$theUC && isset($this->user['uc'])) {
1072
            $theUC = unserialize($this->user['uc'], ['allowed_classes' => false]);
1073
        }
1074
        if (is_array($theUC)) {
1075
            $this->uc = $theUC;
1076
        }
1077
    }
1078
1079
    /**
1080
     * Stores data for a module.
1081
     * The data is stored with the session id so you can even check upon retrieval
1082
     * if the module data is from a previous session or from the current session.
1083
     *
1084
     * @param string $module Is the name of the module ($MCONF['name'])
1085
     * @param mixed $data Is the data you want to store for that module (array, string, ...)
1086
     * @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.
1087
     */
1088
    public function pushModuleData($module, $data, $noSave = 0)
1089
    {
1090
        $this->uc['moduleData'][$module] = $data;
1091
        $this->uc['moduleSessionID'][$module] = $this->id;
1092
        if (!$noSave) {
1093
            $this->writeUC();
1094
        }
1095
    }
1096
1097
    /**
1098
     * Gets module data for a module (from a loaded ->uc array)
1099
     *
1100
     * @param string $module Is the name of the module ($MCONF['name'])
1101
     * @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).
1102
     * @return mixed The module data if available: $this->uc['moduleData'][$module];
1103
     */
1104
    public function getModuleData($module, $type = '')
1105
    {
1106
        if ($type !== 'ses' || (isset($this->uc['moduleSessionID'][$module]) && $this->uc['moduleSessionID'][$module] == $this->id)) {
1107
            return $this->uc['moduleData'][$module];
1108
        }
1109
        return null;
1110
    }
1111
1112
    /**
1113
     * Returns the session data stored for $key.
1114
     * The data will last only for this login session since it is stored in the user session.
1115
     *
1116
     * @param string $key The key associated with the session data
1117
     * @return mixed
1118
     */
1119
    public function getSessionData($key)
1120
    {
1121
        return $this->sessionData[$key] ?? null;
1122
    }
1123
1124
    /**
1125
     * Set session data by key.
1126
     * The data will last only for this login session since it is stored in the user session.
1127
     *
1128
     * @param string $key A non empty string to store the data under
1129
     * @param mixed $data Data store store in session
1130
     */
1131
    public function setSessionData($key, $data)
1132
    {
1133
        if (empty($key)) {
1134
            throw new \InvalidArgumentException('Argument key must not be empty', 1484311516);
1135
        }
1136
        $this->sessionData[$key] = $data;
1137
    }
1138
1139
    /**
1140
     * Sets the session data ($data) for $key and writes all session data (from ->user['ses_data']) to the database.
1141
     * The data will last only for this login session since it is stored in the session table.
1142
     *
1143
     * @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.
1144
     * @param mixed $data The data to store in index $key
1145
     */
1146
    public function setAndSaveSessionData($key, $data)
1147
    {
1148
        $this->sessionData[$key] = $data;
1149
        $this->user['ses_data'] = serialize($this->sessionData);
1150
        $this->logger->debug('setAndSaveSessionData: ses_id = ' . $this->id);
1151
        $updatedSession = $this->getSessionBackend()->update(
1152
            $this->id,
1153
            ['ses_data' => $this->user['ses_data']]
1154
        );
1155
        $this->user = array_merge($this->user ?? [], $updatedSession);
1156
    }
1157
1158
    /*************************
1159
     *
1160
     * Misc
1161
     *
1162
     *************************/
1163
    /**
1164
     * Returns an info array with Login/Logout data submitted by a form or params
1165
     *
1166
     * @return array
1167
     * @internal
1168
     */
1169
    public function getLoginFormData()
1170
    {
1171
        $loginData = [
1172
            'status' => GeneralUtility::_GP($this->formfield_status),
1173
            'uname'  => GeneralUtility::_POST($this->formfield_uname),
1174
            'uident' => GeneralUtility::_POST($this->formfield_uident)
1175
        ];
1176
        // Only process the login data if a login is requested
1177
        if ($loginData['status'] === LoginType::LOGIN) {
1178
            $loginData = $this->processLoginData($loginData);
1179
        }
1180
        return $loginData;
1181
    }
1182
1183
    /**
1184
     * Processes Login data submitted by a form or params depending on the
1185
     * passwordTransmissionStrategy
1186
     *
1187
     * @param array $loginData Login data array
1188
     * @param string $passwordTransmissionStrategy Alternative passwordTransmissionStrategy. Used when authentication services wants to override the default.
1189
     * @return array
1190
     * @internal
1191
     */
1192
    public function processLoginData($loginData, $passwordTransmissionStrategy = '')
1193
    {
1194
        $loginSecurityLevel = trim($GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['loginSecurityLevel']) ?: 'normal';
1195
        $passwordTransmissionStrategy = $passwordTransmissionStrategy ?: $loginSecurityLevel;
1196
        $this->logger->debug('Login data before processing', $loginData);
1197
        $subType = 'processLoginData' . $this->loginType;
1198
        $authInfo = $this->getAuthInfoArray();
1199
        $isLoginDataProcessed = false;
1200
        $processedLoginData = $loginData;
1201
        /** @var AuthenticationService $serviceObject */
1202
        foreach ($this->getAuthServices($subType, $loginData, $authInfo) as $serviceObject) {
1203
            $serviceResult = $serviceObject->processLoginData($processedLoginData, $passwordTransmissionStrategy);
1204
            if (!empty($serviceResult)) {
1205
                $isLoginDataProcessed = true;
1206
                // If the service returns >=200 then no more processing is needed
1207
                if ((int)$serviceResult >= 200) {
1208
                    break;
1209
                }
1210
            }
1211
        }
1212
        if ($isLoginDataProcessed) {
1213
            $loginData = $processedLoginData;
1214
            $this->logger->debug('Processed login data', $processedLoginData);
1215
        }
1216
        return $loginData;
1217
    }
1218
1219
    /**
1220
     * Returns an info array which provides additional information for auth services
1221
     *
1222
     * @return array
1223
     * @internal
1224
     */
1225
    public function getAuthInfoArray()
1226
    {
1227
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
1228
        $expressionBuilder = $queryBuilder->expr();
1229
        $authInfo = [];
1230
        $authInfo['loginType'] = $this->loginType;
1231
        $authInfo['refInfo'] = parse_url(GeneralUtility::getIndpEnv('HTTP_REFERER'));
1232
        $authInfo['HTTP_HOST'] = GeneralUtility::getIndpEnv('HTTP_HOST');
1233
        $authInfo['REMOTE_ADDR'] = GeneralUtility::getIndpEnv('REMOTE_ADDR');
1234
        $authInfo['REMOTE_HOST'] = GeneralUtility::getIndpEnv('REMOTE_HOST');
1235
        // Can be overridden in localconf by SVCONF:
1236
        $authInfo['db_user']['table'] = $this->user_table;
1237
        $authInfo['db_user']['userid_column'] = $this->userid_column;
1238
        $authInfo['db_user']['username_column'] = $this->username_column;
1239
        $authInfo['db_user']['userident_column'] = $this->userident_column;
1240
        $authInfo['db_user']['usergroup_column'] = $this->usergroup_column;
1241
        $authInfo['db_user']['enable_clause'] = $this->userConstraints()->buildExpression(
1242
            [$this->user_table => $this->user_table],
1243
            $expressionBuilder
1244
        );
1245
        if ($this->checkPid && $this->checkPid_value !== null) {
1246
            $authInfo['db_user']['checkPidList'] = $this->checkPid_value;
1247
            $authInfo['db_user']['check_pid_clause'] = $expressionBuilder->in(
1248
                'pid',
1249
                GeneralUtility::intExplode(',', (string)$this->checkPid_value)
1250
            );
1251
        } else {
1252
            $authInfo['db_user']['checkPidList'] = '';
1253
            $authInfo['db_user']['check_pid_clause'] = '';
1254
        }
1255
        $authInfo['db_groups']['table'] = $this->usergroup_table;
1256
        return $authInfo;
1257
    }
1258
1259
    /**
1260
     * Garbage collector, removing old expired sessions.
1261
     *
1262
     * @internal
1263
     */
1264
    public function gc()
1265
    {
1266
        $this->getSessionBackend()->collectGarbage($this->gc_time);
1267
    }
1268
1269
    /**
1270
     * DUMMY: Writes to log database table (in some extension classes)
1271
     *
1272
     * @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
1273
     * @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 !!)
1274
     * @param int $error flag. 0 = message, 1 = error (user problem), 2 = System Error (which should not happen), 3 = security notice (admin)
1275
     * @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
1276
     * @param string $details Default text that follows the message
1277
     * @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...
1278
     * @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.)
1279
     * @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.)
1280
     * @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.)
1281
     */
1282
    public function writelog($type, $action, $error, $details_nr, $details, $data, $tablename, $recuid, $recpid)
1283
    {
1284
    }
1285
1286
    /**
1287
     * Raw initialization of the be_user with uid=$uid
1288
     * This will circumvent all login procedures and select a be_users record from the
1289
     * database and set the content of ->user to the record selected.
1290
     * Thus the BE_USER object will appear like if a user was authenticated - however without
1291
     * a session id and the fields from the session table of course.
1292
     * Will check the users for disabled, start/endtime, etc. ($this->user_where_clause())
1293
     *
1294
     * @param int $uid The UID of the backend user to set in ->user
1295
     * @internal
1296
     */
1297
    public function setBeUserByUid($uid)
1298
    {
1299
        $this->user = $this->getRawUserByUid($uid);
1300
    }
1301
1302
    /**
1303
     * Raw initialization of the be_user with username=$name
1304
     *
1305
     * @param string $name The username to look up.
1306
     * @see \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::setBeUserByUid()
1307
     * @internal
1308
     */
1309
    public function setBeUserByName($name)
1310
    {
1311
        $this->user = $this->getRawUserByName($name);
1312
    }
1313
1314
    /**
1315
     * Fetching raw user record with uid=$uid
1316
     *
1317
     * @param int $uid The UID of the backend user to set in ->user
1318
     * @return array user record or FALSE
1319
     * @internal
1320
     */
1321
    public function getRawUserByUid($uid)
1322
    {
1323
        $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
1324
        $query->setRestrictions($this->userConstraints());
1325
        $query->select('*')
1326
            ->from($this->user_table)
1327
            ->where($query->expr()->eq('uid', $query->createNamedParameter($uid, \PDO::PARAM_INT)));
1328
1329
        return $query->execute()->fetch();
1330
    }
1331
1332
    /**
1333
     * Fetching raw user record with username=$name
1334
     *
1335
     * @param string $name The username to look up.
1336
     * @return array user record or FALSE
1337
     * @see \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::getUserByUid()
1338
     * @internal
1339
     */
1340
    public function getRawUserByName($name)
1341
    {
1342
        $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
1343
        $query->setRestrictions($this->userConstraints());
1344
        $query->select('*')
1345
            ->from($this->user_table)
1346
            ->where($query->expr()->eq('username', $query->createNamedParameter($name, \PDO::PARAM_STR)));
1347
1348
        return $query->execute()->fetch();
1349
    }
1350
1351
    /**
1352
     * @internal
1353
     * @return string
1354
     */
1355
    public function getSessionId(): string
1356
    {
1357
        return $this->id;
1358
    }
1359
1360
    /**
1361
     * @internal
1362
     * @return string
1363
     */
1364
    public function getLoginType(): string
1365
    {
1366
        return $this->loginType;
1367
    }
1368
1369
    /**
1370
     * Returns initialized session backend. Returns same session backend if called multiple times
1371
     *
1372
     * @return SessionBackendInterface
1373
     */
1374
    protected function getSessionBackend()
1375
    {
1376
        if (!isset($this->sessionBackend)) {
1377
            $this->sessionBackend = GeneralUtility::makeInstance(SessionManager::class)->getSessionBackend($this->loginType);
1378
        }
1379
        return $this->sessionBackend;
1380
    }
1381
}
1382