Passed
Push — master ( 7e03c5...0126ea )
by
unknown
17:01
created

AbstractUserAuthentication::logoff()   A

Complexity

Conditions 5
Paths 9

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 9
nc 9
nop 0
dl 0
loc 15
rs 9.6111
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
        // Set all possible headers that could ensure that the script is not cached on the client-side
330
        $this->sendHttpHeaders();
331
        // Load user session, check to see if anyone has submitted login-information and if so authenticate
332
        // the user with the session. $this->user[uid] may be used to write log...
333
        $this->checkAuthentication();
334
        // Set cookie if generally enabled or if the current session is a non-session cookie (FE permalogin)
335
        if (!$this->dontSetCookie || $this->isRefreshTimeBasedCookie()) {
336
            $this->setSessionCookie();
337
        }
338
        // Hook for alternative ways of filling the $this->user array (is used by the "timtaw" extension)
339
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postUserLookUp'] ?? [] as $funcName) {
340
            $_params = [
341
                'pObj' => $this,
342
            ];
343
            GeneralUtility::callUserFunction($funcName, $_params, $this);
344
        }
345
        // If we're lucky we'll get to clean up old sessions
346
        if (random_int(0, mt_getrandmax()) % 100 <= $this->gc_probability) {
347
            $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

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