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

AbstractUserAuthentication::writelog()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 0
nc 1
nop 9
dl 0
loc 2
rs 10
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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