Passed
Push — master ( 7e03c5...0126ea )
by
unknown
17:01
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\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