Passed
Push — master ( 67a56c...50d7c6 )
by
unknown
12:39
created

appendCookieToResponse()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
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\Backend\HashableSessionBackendInterface;
37
use TYPO3\CMS\Core\Session\Backend\SessionBackendInterface;
38
use TYPO3\CMS\Core\Session\SessionManager;
39
use TYPO3\CMS\Core\SysLog\Action\Login as SystemLogLoginAction;
40
use TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
41
use TYPO3\CMS\Core\SysLog\Type as SystemLogType;
42
use TYPO3\CMS\Core\Utility\GeneralUtility;
43
44
/**
45
 * Authentication of users in TYPO3
46
 *
47
 * This class is used to authenticate a login user.
48
 * The class is used by both the frontend and backend.
49
 * In both cases this class is a parent class to BackendUserAuthentication and FrontendUserAuthentication
50
 *
51
 * See Inside TYPO3 for more information about the API of the class and internal variables.
52
 */
53
abstract class AbstractUserAuthentication implements LoggerAwareInterface
54
{
55
    use LoggerAwareTrait;
56
    use CookieHeaderTrait;
57
58
    /**
59
     * Session/Cookie name
60
     * @var string
61
     */
62
    public $name = '';
63
64
    /**
65
     * Table in database with user data
66
     * @var string
67
     */
68
    public $user_table = '';
69
70
    /**
71
     * Table in database with user groups
72
     * @var string
73
     */
74
    public $usergroup_table = '';
75
76
    /**
77
     * Column for login-name
78
     * @var string
79
     */
80
    public $username_column = '';
81
82
    /**
83
     * Column for password
84
     * @var string
85
     */
86
    public $userident_column = '';
87
88
    /**
89
     * Column for user-id
90
     * @var string
91
     */
92
    public $userid_column = '';
93
94
    /**
95
     * Column for user group information
96
     * @var string
97
     */
98
    public $usergroup_column = '';
99
100
    /**
101
     * Column name for last login timestamp
102
     * @var string
103
     */
104
    public $lastLogin_column = '';
105
106
    /**
107
     * Enable field columns of user table
108
     * @var array
109
     */
110
    public $enablecolumns = [
111
        'rootLevel' => '',
112
        // Boolean: If TRUE, 'AND pid=0' will be a part of the query...
113
        'disabled' => '',
114
        'starttime' => '',
115
        'endtime' => '',
116
        'deleted' => '',
117
    ];
118
119
    /**
120
     * Form field with login-name
121
     * @var string
122
     */
123
    public $formfield_uname = '';
124
125
    /**
126
     * Form field with password
127
     * @var string
128
     */
129
    public $formfield_uident = '';
130
131
    /**
132
     * Form field with status: *'login', 'logout'. If empty login is not verified.
133
     * @var string
134
     */
135
    public $formfield_status = '';
136
137
    /**
138
     * Session timeout (on the server)
139
     *
140
     * If >0: session-timeout in seconds.
141
     * If <=0: Instant logout after login.
142
     *
143
     * @var int
144
     */
145
    public $sessionTimeout = 0;
146
147
    /**
148
     * Lifetime for the session-cookie (on the client)
149
     *
150
     * If >0: permanent cookie with given lifetime
151
     * If 0: session-cookie
152
     * Session-cookie means the browser will remove it when the browser is closed.
153
     *
154
     * @var int
155
     */
156
    public $lifetime = 0;
157
158
    /**
159
     * GarbageCollection
160
     * Purge all server session data older than $gc_time seconds.
161
     * if $this->sessionTimeout > 0, then the session timeout is used instead.
162
     * @var int
163
     */
164
    public $gc_time = 86400;
165
166
    /**
167
     * Probability for garbage collection to be run (in percent)
168
     * @var int
169
     */
170
    public $gc_probability = 1;
171
172
    /**
173
     * Decides if the writelog() function is called at login and logout
174
     * @var bool
175
     */
176
    public $writeStdLog = false;
177
178
    /**
179
     * Log failed login attempts
180
     * @var bool
181
     */
182
    public $writeAttemptLog = false;
183
184
    /**
185
     * Send no-cache headers
186
     * @var bool
187
     */
188
    public $sendNoCacheHeaders = true;
189
190
    /**
191
     * The ident-hash is normally 32 characters and should be!
192
     * But if you are making sites for WAP-devices or other low-bandwidth stuff,
193
     * you may shorten the length.
194
     * Never let this value drop below 6!
195
     * A length of 6 would give you more than 16 mio possibilities.
196
     * @var int
197
     */
198
    public $hash_length = 32;
199
200
    /**
201
     * If set, the user-record must be stored at the page defined by $checkPid_value
202
     * @var bool
203
     */
204
    public $checkPid = true;
205
206
    /**
207
     * The page id the user record must be stored at
208
     * @var int
209
     */
210
    public $checkPid_value = 0;
211
212
    /**
213
     * session_id (MD5-hash)
214
     * @var string
215
     * @internal
216
     */
217
    public $id;
218
219
    /**
220
     * Will be set to TRUE if the login session is actually written during auth-check.
221
     * @var bool
222
     */
223
    public $loginSessionStarted = false;
224
225
    /**
226
     * @var array|null contains user- AND session-data from database (joined tables)
227
     * @internal
228
     */
229
    public $user;
230
231
    /**
232
     * Will be set to TRUE if a new session ID was created
233
     * @var bool
234
     */
235
    public $newSessionID = false;
236
237
    /**
238
     * Will force the session cookie to be set every time (lifetime must be 0)
239
     * @var bool
240
     */
241
    public $forceSetCookie = false;
242
243
    /**
244
     * Will prevent the setting of the session cookie (takes precedence over forceSetCookie)
245
     * @var bool
246
     */
247
    public $dontSetCookie = false;
248
249
    /**
250
     * Login type, used for services.
251
     * @var string
252
     */
253
    public $loginType = '';
254
255
    /**
256
     * @var array
257
     */
258
    public $uc;
259
260
    /**
261
     * @var IpLocker
262
     */
263
    protected $ipLocker;
264
265
    /**
266
     * @var SessionBackendInterface
267
     */
268
    protected $sessionBackend;
269
270
    /**
271
     * Holds deserialized data from session records.
272
     * 'Reserved' keys are:
273
     *   - 'sys': Reserved for TypoScript standard code.
274
     * @var array
275
     */
276
    protected $sessionData = [];
277
278
    /**
279
     * If set, this cookie will be set to the response.
280
     *
281
     * @var Cookie|null
282
     */
283
    protected ?Cookie $setCookie;
284
285
    /**
286
     * Initialize some important variables
287
     *
288
     * @throws Exception
289
     */
290
    public function __construct()
291
    {
292
        // If there is a custom session timeout, use this instead of the 1d default gc time.
293
        if ($this->sessionTimeout > 0) {
294
            $this->gc_time = $this->sessionTimeout;
295
        }
296
        // Backend or frontend login - used for auth services
297
        if (empty($this->loginType)) {
298
            throw new Exception('No loginType defined, must be set explicitly by subclass', 1476045345);
299
        }
300
301
        $this->ipLocker = GeneralUtility::makeInstance(
302
            IpLocker::class,
303
            $GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['lockIP'],
304
            $GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['lockIPv6']
305
        );
306
    }
307
308
    /**
309
     * Starts a user session
310
     * Typical configurations will:
311
     * a) check if session cookie was set and if not, set one,
312
     * b) check if a password/username was sent and if so, try to authenticate the user
313
     * c) Lookup a session attached to a user and check timeout etc.
314
     * d) Garbage collection, setting of no-cache headers.
315
     * If a user is authenticated the database record of the user (array) will be set in the ->user internal variable.
316
     */
317
    public function start()
318
    {
319
        $this->logger->debug('## Beginning of auth logging.');
320
        $this->newSessionID = false;
321
        // Make certain that NO user is set initially
322
        $this->user = null;
323
        // sessionID is set to ses_id if cookie is present. Otherwise a new session will start
324
        $this->id = $this->getCookie($this->name);
325
326
        // If new session or client tries to fix session...
327
        if (!$this->isExistingSessionRecord($this->id)) {
328
            $this->id = $this->createSessionId();
329
            $this->newSessionID = true;
330
        }
331
332
        // Load user session, check to see if anyone has submitted login-information and if so authenticate
333
        // the user with the session. $this->user[uid] may be used to write log...
334
        $this->checkAuthentication();
335
        // Set cookie if generally enabled or if the current session is a non-session cookie (FE permalogin)
336
        if (!$this->dontSetCookie || $this->isRefreshTimeBasedCookie()) {
337
            $this->setSessionCookie();
338
        }
339
        // Hook for alternative ways of filling the $this->user array (is used by the "timtaw" extension)
340
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postUserLookUp'] ?? [] as $funcName) {
341
            $_params = [
342
                'pObj' => $this,
343
            ];
344
            GeneralUtility::callUserFunction($funcName, $_params, $this);
345
        }
346
        // If we're lucky we'll get to clean up old sessions
347
        if (random_int(0, mt_getrandmax()) % 100 <= $this->gc_probability) {
348
            $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

348
            $this->/** @scrutinizer ignore-call */ 
349
                   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...
349
        }
350
    }
351
352
    /**
353
     * Used to apply a cookie to a PSR-7 Response.
354
     *
355
     * @param ResponseInterface $response
356
     * @return ResponseInterface
357
     */
358
    public function appendCookieToResponse(ResponseInterface $response): ResponseInterface
359
    {
360
        if (isset($this->setCookie)) {
361
            $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

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