Passed
Branch master (6c65a4)
by Christian
16:31
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
namespace TYPO3\CMS\Core\Authentication;
3
4
/*
5
 * This file is part of the TYPO3 CMS project.
6
 *
7
 * It is free software; you can redistribute it and/or modify it under
8
 * the terms of the GNU General Public License, either version 2
9
 * of the License, or any later version.
10
 *
11
 * For the full copyright and license information, please read the
12
 * LICENSE.txt file that was distributed with this source code.
13
 *
14
 * The TYPO3 project - inspiring people to share!
15
 */
16
17
use Psr\Log\LoggerAwareInterface;
18
use Psr\Log\LoggerAwareTrait;
19
use TYPO3\CMS\Backend\Utility\BackendUtility;
20
use TYPO3\CMS\Core\Crypto\Random;
21
use TYPO3\CMS\Core\Database\Connection;
22
use TYPO3\CMS\Core\Database\ConnectionPool;
23
use TYPO3\CMS\Core\Database\Query\QueryHelper;
24
use TYPO3\CMS\Core\Database\Query\Restriction\DefaultRestrictionContainer;
25
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
26
use TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction;
27
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
28
use TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionContainerInterface;
29
use TYPO3\CMS\Core\Database\Query\Restriction\RootLevelRestriction;
30
use TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction;
31
use TYPO3\CMS\Core\Exception;
32
use TYPO3\CMS\Core\Session\Backend\Exception\SessionNotFoundException;
33
use TYPO3\CMS\Core\Session\Backend\SessionBackendInterface;
34
use TYPO3\CMS\Core\Session\SessionManager;
35
use TYPO3\CMS\Core\Utility\GeneralUtility;
36
use TYPO3\CMS\Core\Utility\MathUtility;
37
38
/**
39
 * Authentication of users in TYPO3
40
 *
41
 * This class is used to authenticate a login user.
42
 * The class is used by both the frontend and backend.
43
 * In both cases this class is a parent class to BackendUserAuthentication and FrontenUserAuthentication
44
 *
45
 * See Inside TYPO3 for more information about the API of the class and internal variables.
46
 */
47
abstract class AbstractUserAuthentication implements LoggerAwareInterface
48
{
49
    use LoggerAwareTrait;
50
51
    /**
52
     * Session/Cookie name
53
     * @var string
54
     */
55
    public $name = '';
56
57
    /**
58
     * Session/GET-var name
59
     * @var string
60
     */
61
    public $get_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
     * @var bool
120
     */
121
    public $showHiddenRecords = false;
122
123
    /**
124
     * Form field with login-name
125
     * @var string
126
     */
127
    public $formfield_uname = '';
128
129
    /**
130
     * Form field with password
131
     * @var string
132
     */
133
    public $formfield_uident = '';
134
135
    /**
136
     * Form field with status: *'login', 'logout'. If empty login is not verified.
137
     * @var string
138
     */
139
    public $formfield_status = '';
140
141
    /**
142
     * Session timeout (on the server)
143
     *
144
     * If >0: session-timeout in seconds.
145
     * If <=0: Instant logout after login.
146
     *
147
     * @var int
148
     */
149
    public $sessionTimeout = 0;
150
151
    /**
152
     * Name for a field to fetch the server session timeout from.
153
     * If not empty this is a field name from the user table where the timeout can be found.
154
     * @var string
155
     */
156
    public $auth_timeout_field = '';
157
158
    /**
159
     * Lifetime for the session-cookie (on the client)
160
     *
161
     * If >0: permanent cookie with given lifetime
162
     * If 0: session-cookie
163
     * Session-cookie means the browser will remove it when the browser is closed.
164
     *
165
     * @var int
166
     */
167
    public $lifetime = 0;
168
169
    /**
170
     * GarbageCollection
171
     * Purge all server session data older than $gc_time seconds.
172
     * 0 = default to $this->sessionTimeout or use 86400 seconds (1 day) if $this->sessionTimeout == 0
173
     * @var int
174
     */
175
    public $gc_time = 0;
176
177
    /**
178
     * Probability for garbage collection to be run (in percent)
179
     * @var int
180
     */
181
    public $gc_probability = 1;
182
183
    /**
184
     * Decides if the writelog() function is called at login and logout
185
     * @var bool
186
     */
187
    public $writeStdLog = false;
188
189
    /**
190
     * Log failed login attempts
191
     * @var bool
192
     */
193
    public $writeAttemptLog = false;
194
195
    /**
196
     * Send no-cache headers
197
     * @var bool
198
     */
199
    public $sendNoCacheHeaders = true;
200
201
    /**
202
     * If this is set, authentication is also accepted by $_GET.
203
     * Notice that the identification is NOT 128bit MD5 hash but reduced.
204
     * This is done in order to minimize the size for mobile-devices, such as WAP-phones
205
     * @var bool
206
     */
207
    public $getFallBack = false;
208
209
    /**
210
     * The ident-hash is normally 32 characters and should be!
211
     * But if you are making sites for WAP-devices or other low-bandwidth stuff,
212
     * you may shorten the length.
213
     * Never let this value drop below 6!
214
     * A length of 6 would give you more than 16 mio possibilities.
215
     * @var int
216
     */
217
    public $hash_length = 32;
218
219
    /**
220
     * Setting this flag TRUE lets user-authentication happen from GET_VARS if
221
     * POST_VARS are not set. Thus you may supply username/password with the URL.
222
     * @var bool
223
     */
224
    public $getMethodEnabled = false;
225
226
    /**
227
     * If set to 4, the session will be locked to the user's IP address (all four numbers).
228
     * Reducing this to 1-3 means that only the given number of parts of the IP address is used.
229
     * @var int
230
     */
231
    public $lockIP = 4;
232
233
    /**
234
     * @var string
235
     */
236
    public $warningEmail = '';
237
238
    /**
239
     * Time span (in seconds) within the number of failed logins are collected
240
     * @var int
241
     */
242
    public $warningPeriod = 3600;
243
244
    /**
245
     * The maximum accepted number of warnings before an email to $warningEmail is sent
246
     * @var int
247
     */
248
    public $warningMax = 3;
249
250
    /**
251
     * If set, the user-record must be stored at the page defined by $checkPid_value
252
     * @var bool
253
     */
254
    public $checkPid = true;
255
256
    /**
257
     * The page id the user record must be stored at
258
     * @var int
259
     */
260
    public $checkPid_value = 0;
261
262
    /**
263
     * session_id (MD5-hash)
264
     * @var string
265
     * @internal
266
     */
267
    public $id;
268
269
    /**
270
     * Indicates if an authentication was started but failed
271
     * @var bool
272
     */
273
    public $loginFailure = false;
274
275
    /**
276
     * Will be set to TRUE if the login session is actually written during auth-check.
277
     * @var bool
278
     */
279
    public $loginSessionStarted = false;
280
281
    /**
282
     * @var array|null contains user- AND session-data from database (joined tables)
283
     * @internal
284
     */
285
    public $user = null;
286
287
    /**
288
     * Will be added to the url (eg. '&login=ab7ef8d...')
289
     * GET-auth-var if getFallBack is TRUE. Should be inserted in links!
290
     * @var string
291
     * @internal
292
     */
293
    public $get_URL_ID = '';
294
295
    /**
296
     * Will be set to TRUE if a new session ID was created
297
     * @var bool
298
     */
299
    public $newSessionID = false;
300
301
    /**
302
     * Will force the session cookie to be set every time (lifetime must be 0)
303
     * @var bool
304
     */
305
    public $forceSetCookie = false;
306
307
    /**
308
     * Will prevent the setting of the session cookie (takes precedence over forceSetCookie)
309
     * @var bool
310
     */
311
    public $dontSetCookie = false;
312
313
    /**
314
     * @var bool
315
     */
316
    protected $cookieWasSetOnCurrentRequest = false;
317
318
    /**
319
     * Login type, used for services.
320
     * @var string
321
     */
322
    public $loginType = '';
323
324
    /**
325
     * "auth" services configuration array from $GLOBALS['TYPO3_CONF_VARS']['SVCONF']['auth']
326
     * @var array
327
     */
328
    public $svConfig = [];
329
330
    /**
331
     * @var array
332
     */
333
    public $uc;
334
335
    /**
336
     * @var SessionBackendInterface
337
     */
338
    protected $sessionBackend;
339
340
    /**
341
     * Holds deserialized data from session records.
342
     * 'Reserved' keys are:
343
     *   - 'sys': Reserved for TypoScript standard code.
344
     * @var array
345
     */
346
    protected $sessionData = [];
347
348
    /**
349
     * Initialize some important variables
350
     */
351
    public function __construct()
352
    {
353
        // This function has to stay even if it's empty
354
        // Implementations of that abstract class might call parent::__construct();
355
    }
356
357
    /**
358
     * Starts a user session
359
     * Typical configurations will:
360
     * a) check if session cookie was set and if not, set one,
361
     * b) check if a password/username was sent and if so, try to authenticate the user
362
     * c) Lookup a session attached to a user and check timeout etc.
363
     * d) Garbage collection, setting of no-cache headers.
364
     * If a user is authenticated the database record of the user (array) will be set in the ->user internal variable.
365
     *
366
     * @throws Exception
367
     */
368
    public function start()
369
    {
370
        // Backend or frontend login - used for auth services
371
        if (empty($this->loginType)) {
372
            throw new Exception('No loginType defined, should be set explicitly by subclass', 1476045345);
373
        }
374
        $this->logger->debug('## Beginning of auth logging.');
375
        // Init vars.
376
        $mode = '';
377
        $this->newSessionID = false;
378
        // $id is set to ses_id if cookie is present. Else set to FALSE, which will start a new session
379
        $id = $this->getCookie($this->name);
380
        $this->svConfig = $GLOBALS['TYPO3_CONF_VARS']['SVCONF']['auth'] ?? [];
381
382
        // If fallback to get mode....
383
        if (!$id && $this->getFallBack && $this->get_name) {
384
            $id = isset($_GET[$this->get_name]) ? GeneralUtility::_GET($this->get_name) : '';
385
            if (strlen($id) != $this->hash_length) {
386
                $id = '';
387
            }
388
            $mode = 'get';
389
        }
390
391
        // If new session or client tries to fix session...
392
        if (!$id || !$this->isExistingSessionRecord($id)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $id of type null|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
393
            // New random session-$id is made
394
            $id = $this->createSessionId();
395
            // New session
396
            $this->newSessionID = true;
397
        }
398
        // Internal var 'id' is set
399
        $this->id = $id;
400
        // If fallback to get mode....
401
        if ($mode === 'get' && $this->getFallBack && $this->get_name) {
402
            $this->get_URL_ID = '&' . $this->get_name . '=' . $id;
403
        }
404
        // Make certain that NO user is set initially
405
        $this->user = null;
406
        // Set all possible headers that could ensure that the script is not cached on the client-side
407
        if ($this->sendNoCacheHeaders && !(TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_CLI)) {
408
            header('Expires: 0');
409
            header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
410
            $cacheControlHeader = 'no-cache, must-revalidate';
411
            $pragmaHeader = 'no-cache';
412
            // Prevent error message in IE when using a https connection
413
            // see http://forge.typo3.org/issues/24125
414
            $clientInfo = GeneralUtility::clientInfo();
415
            if ($clientInfo['BROWSER'] === 'msie' && GeneralUtility::getIndpEnv('TYPO3_SSL')) {
416
                // Some IEs can not handle no-cache
417
                // see http://support.microsoft.com/kb/323308/en-us
418
                $cacheControlHeader = 'must-revalidate';
419
                // IE needs "Pragma: private" if SSL connection
420
                $pragmaHeader = 'private';
421
            }
422
            header('Cache-Control: ' . $cacheControlHeader);
423
            header('Pragma: ' . $pragmaHeader);
424
        }
425
        // Load user session, check to see if anyone has submitted login-information and if so authenticate
426
        // the user with the session. $this->user[uid] may be used to write log...
427
        $this->checkAuthentication();
428
        // Setting cookies
429
        if (!$this->dontSetCookie) {
430
            $this->setSessionCookie();
431
        }
432
        // Hook for alternative ways of filling the $this->user array (is used by the "timtaw" extension)
433
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postUserLookUp'] ?? [] as $funcName) {
434
            $_params = [
435
                'pObj' => $this,
436
            ];
437
            GeneralUtility::callUserFunction($funcName, $_params, $this);
438
        }
439
        // Set $this->gc_time if not explicitly specified
440
        if ($this->gc_time === 0) {
441
            // Default to 86400 seconds (1 day) if $this->sessionTimeout is 0
442
            $this->gc_time = $this->sessionTimeout === 0 ? 86400 : $this->sessionTimeout;
443
        }
444
        // If we're lucky we'll get to clean up old sessions
445
        if (rand() % 100 <= $this->gc_probability) {
0 ignored issues
show
Bug introduced by
The call to rand() has too few arguments starting with min. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

445
        if (/** @scrutinizer ignore-call */ rand() % 100 <= $this->gc_probability) {

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
446
            $this->gc();
447
        }
448
    }
449
450
    /**
451
     * Sets the session cookie for the current disposal.
452
     *
453
     * @throws Exception
454
     */
455
    protected function setSessionCookie()
456
    {
457
        $isSetSessionCookie = $this->isSetSessionCookie();
458
        $isRefreshTimeBasedCookie = $this->isRefreshTimeBasedCookie();
459
        if ($isSetSessionCookie || $isRefreshTimeBasedCookie) {
460
            $settings = $GLOBALS['TYPO3_CONF_VARS']['SYS'];
461
            // Get the domain to be used for the cookie (if any):
462
            $cookieDomain = $this->getCookieDomain();
463
            // If no cookie domain is set, use the base path:
464
            $cookiePath = $cookieDomain ? '/' : GeneralUtility::getIndpEnv('TYPO3_SITE_PATH');
465
            // If the cookie lifetime is set, use it:
466
            $cookieExpire = $isRefreshTimeBasedCookie ? $GLOBALS['EXEC_TIME'] + $this->lifetime : 0;
467
            // Use the secure option when the current request is served by a secure connection:
468
            $cookieSecure = (bool)$settings['cookieSecure'] && GeneralUtility::getIndpEnv('TYPO3_SSL');
469
            // Do not set cookie if cookieSecure is set to "1" (force HTTPS) and no secure channel is used:
470
            if ((int)$settings['cookieSecure'] !== 1 || GeneralUtility::getIndpEnv('TYPO3_SSL')) {
471
                setcookie($this->name, $this->id, $cookieExpire, $cookiePath, $cookieDomain, $cookieSecure, true);
472
                $this->cookieWasSetOnCurrentRequest = true;
473
            } else {
474
                throw new Exception('Cookie was not set since HTTPS was forced in $TYPO3_CONF_VARS[SYS][cookieSecure].', 1254325546);
475
            }
476
            $this->logger->debug(
477
                ($isRefreshTimeBasedCookie ? 'Updated Cookie: ' : 'Set Cookie: ')
478
                . $this->id . ($cookieDomain ? ', ' . $cookieDomain : '')
479
            );
480
        }
481
    }
482
483
    /**
484
     * Gets the domain to be used on setting cookies.
485
     * The information is taken from the value in $GLOBALS['TYPO3_CONF_VARS']['SYS']['cookieDomain'].
486
     *
487
     * @return string The domain to be used on setting cookies
488
     */
489
    protected function getCookieDomain()
490
    {
491
        $result = '';
492
        $cookieDomain = $GLOBALS['TYPO3_CONF_VARS']['SYS']['cookieDomain'];
493
        // If a specific cookie domain is defined for a given TYPO3_MODE,
494
        // use that domain
495
        if (!empty($GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieDomain'])) {
496
            $cookieDomain = $GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieDomain'];
497
        }
498
        if ($cookieDomain) {
499
            if ($cookieDomain[0] === '/') {
500
                $match = [];
501
                $matchCnt = @preg_match($cookieDomain, GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY'), $match);
502
                if ($matchCnt === false) {
503
                    $this->logger->critical('The regular expression for the cookie domain (' . $cookieDomain . ') contains errors. The session is not shared across sub-domains.');
504
                } elseif ($matchCnt) {
505
                    $result = $match[0];
506
                }
507
            } else {
508
                $result = $cookieDomain;
509
            }
510
        }
511
        return $result;
512
    }
513
514
    /**
515
     * Get the value of a specified cookie.
516
     *
517
     * @param string $cookieName The cookie ID
518
     * @return string The value stored in the cookie
519
     */
520
    protected function getCookie($cookieName)
521
    {
522
        return isset($_COOKIE[$cookieName]) ? stripslashes($_COOKIE[$cookieName]) : '';
523
    }
524
525
    /**
526
     * Determine whether a session cookie needs to be set (lifetime=0)
527
     *
528
     * @return bool
529
     * @internal
530
     */
531
    public function isSetSessionCookie()
532
    {
533
        return ($this->newSessionID || $this->forceSetCookie) && $this->lifetime == 0;
534
    }
535
536
    /**
537
     * Determine whether a non-session cookie needs to be set (lifetime>0)
538
     *
539
     * @return bool
540
     * @internal
541
     */
542
    public function isRefreshTimeBasedCookie()
543
    {
544
        return $this->lifetime > 0;
545
    }
546
547
    /**
548
     * Checks if a submission of username and password is present or use other authentication by auth services
549
     *
550
     * @throws \RuntimeException
551
     * @internal
552
     */
553
    public function checkAuthentication()
554
    {
555
        // No user for now - will be searched by service below
556
        $tempuserArr = [];
557
        $tempuser = false;
558
        // User is not authenticated by default
559
        $authenticated = false;
560
        // User want to login with passed login data (name/password)
561
        $activeLogin = false;
562
        // Indicates if an active authentication failed (not auto login)
563
        $this->loginFailure = false;
564
        $this->logger->debug('Login type: ' . $this->loginType);
565
        // The info array provide additional information for auth services
566
        $authInfo = $this->getAuthInfoArray();
567
        // Get Login/Logout data submitted by a form or params
568
        $loginData = $this->getLoginFormData();
569
        $this->logger->debug('Login data', $loginData);
570
        // Active logout (eg. with "logout" button)
571
        if ($loginData['status'] === 'logout') {
572
            if ($this->writeStdLog) {
573
                // $type,$action,$error,$details_nr,$details,$data,$tablename,$recuid,$recpid
574
                $this->writelog(255, 2, 0, 2, 'User %s logged out', [$this->user['username']], '', 0, 0);
575
            }
576
            $this->logger->info('User logged out. Id: ' . $this->id);
577
            $this->logoff();
578
        }
579
        // Determine whether we need to skip session update.
580
        // This is used mainly for checking session timeout in advance without refreshing the current session's timeout.
581
        $skipSessionUpdate = (bool)GeneralUtility::_GP('skipSessionUpdate');
582
        $haveSession = false;
583
        $anonymousSession = false;
584
        if (!$this->newSessionID) {
585
            // Read user session
586
            $authInfo['userSession'] = $this->fetchUserSession($skipSessionUpdate);
587
            $haveSession = is_array($authInfo['userSession']);
588
            if ($haveSession && !empty($authInfo['userSession']['ses_anonymous'])) {
589
                $anonymousSession = true;
590
            }
591
        }
592
593
        // Active login (eg. with login form).
594
        if (!$haveSession && $loginData['status'] === 'login') {
595
            $activeLogin = true;
596
            $this->logger->debug('Active login (eg. with login form)');
597
            // check referrer for submitted login values
598
            if ($this->formfield_status && $loginData['uident'] && $loginData['uname']) {
599
                $httpHost = GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY');
600
                if (!$this->getMethodEnabled && ($httpHost != $authInfo['refInfo']['host'] && !$GLOBALS['TYPO3_CONF_VARS']['SYS']['doNotCheckReferer'])) {
601
                    throw new \RuntimeException('TYPO3 Fatal Error: Error: This host address ("' . $httpHost . '") and the referer host ("' . $authInfo['refInfo']['host'] . '") mismatches! ' .
602
                        'It is possible that the environment variable HTTP_REFERER is not passed to the script because of a proxy. ' .
603
                        'The site administrator can disable this check in the "All Configuration" section of the Install Tool (flag: TYPO3_CONF_VARS[SYS][doNotCheckReferer]).', 1270853930);
604
                }
605
                // Delete old user session if any
606
                $this->logoff();
607
            }
608
            // Refuse login for _CLI users, if not processing a CLI request type
609
            // (although we shouldn't be here in case of a CLI request type)
610
            if (strtoupper(substr($loginData['uname'], 0, 5)) === '_CLI_' && !(TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_CLI)) {
611
                throw new \RuntimeException('TYPO3 Fatal Error: You have tried to login using a CLI user. Access prohibited!', 1270853931);
612
            }
613
        }
614
615
        // Cause elevation of privilege, make sure regenerateSessionId is called later on
616
        if ($anonymousSession && $loginData['status'] === 'login') {
617
            $activeLogin = true;
618
        }
619
620
        if ($haveSession) {
621
            $this->logger->debug('User session found', [
622
                $this->userid_column => $authInfo['userSession'][$this->userid_column],
623
                $this->username_column => $authInfo['userSession'][$this->username_column],
624
            ]);
625
        } else {
626
            $this->logger->debug('No user session found');
627
        }
628
        if (is_array($this->svConfig['setup'] ?? false)) {
629
            $this->logger->debug('SV setup', $this->svConfig['setup']);
630
        }
631
632
        // Fetch user if ...
633
        if (
634
            $activeLogin || !empty($this->svConfig['setup'][$this->loginType . '_alwaysFetchUser'])
635
            || !$haveSession && !empty($this->svConfig['setup'][$this->loginType . '_fetchUserIfNoSession'])
636
        ) {
637
            // Use 'auth' service to find the user
638
            // First found user will be used
639
            $subType = 'getUser' . $this->loginType;
640
            foreach ($this->getAuthServices($subType, $loginData, $authInfo) as $serviceObj) {
641
                if ($row = $serviceObj->getUser()) {
642
                    $tempuserArr[] = $row;
643
                    $this->logger->debug('User found', [
644
                        $this->userid_column => $row[$this->userid_column],
645
                        $this->username_column => $row[$this->username_column],
646
                    ]);
647
                    // User found, just stop to search for more if not configured to go on
648
                    if (!$this->svConfig['setup'][$this->loginType . '_fetchAllUsers']) {
649
                        break;
650
                    }
651
                }
652
            }
653
654
            if ($this->svConfig['setup'][$this->loginType . '_alwaysFetchUser']) {
655
                $this->logger->debug($this->loginType . '_alwaysFetchUser option is enabled');
656
            }
657
            if (empty($tempuserArr)) {
658
                $this->logger->debug('No user found by services');
659
            } else {
660
                $this->logger->debug(count($tempuserArr) . ' user records found by services');
661
            }
662
        }
663
664
        // If no new user was set we use the already found user session
665
        if (empty($tempuserArr) && $haveSession && !$anonymousSession) {
666
            $tempuserArr[] = $authInfo['userSession'];
667
            $tempuser = $authInfo['userSession'];
668
            // User is authenticated because we found a user session
669
            $authenticated = true;
670
            $this->logger->debug('User session used', [
671
                $this->userid_column => $authInfo['userSession'][$this->userid_column],
672
                $this->username_column => $authInfo['userSession'][$this->username_column],
673
            ]);
674
        }
675
        // Re-auth user when 'auth'-service option is set
676
        if (!empty($this->svConfig['setup'][$this->loginType . '_alwaysAuthUser'])) {
677
            $authenticated = false;
678
            $this->logger->debug('alwaysAuthUser option is enabled');
679
        }
680
        // Authenticate the user if needed
681
        if (!empty($tempuserArr) && !$authenticated) {
682
            foreach ($tempuserArr as $tempuser) {
683
                // Use 'auth' service to authenticate the user
684
                // If one service returns FALSE then authentication failed
685
                // a service might return 100 which means there's no reason to stop but the user can't be authenticated by that service
686
                $this->logger->debug('Auth user', $tempuser);
687
                $subType = 'authUser' . $this->loginType;
688
689
                foreach ($this->getAuthServices($subType, $loginData, $authInfo) as $serviceObj) {
690
                    if (($ret = $serviceObj->authUser($tempuser)) > 0) {
691
                        // If the service returns >=200 then no more checking is needed - useful for IP checking without password
692
                        if ((int)$ret >= 200) {
693
                            $authenticated = true;
694
                            break;
695
                        }
696
                        if ((int)$ret >= 100) {
697
                        } else {
698
                            $authenticated = true;
699
                        }
700
                    } else {
701
                        $authenticated = false;
702
                        break;
703
                    }
704
                }
705
706
                if ($authenticated) {
707
                    // Leave foreach() because a user is authenticated
708
                    break;
709
                }
710
            }
711
        }
712
713
        // If user is authenticated a valid user is in $tempuser
714
        if ($authenticated) {
715
            // Reset failure flag
716
            $this->loginFailure = false;
717
            // Insert session record if needed:
718
            if (!$haveSession || $anonymousSession || $tempuser['ses_id'] != $this->id && $tempuser['uid'] != $authInfo['userSession']['ses_userid']) {
719
                $sessionData = $this->createUserSession($tempuser);
0 ignored issues
show
Bug introduced by
It seems like $tempuser can also be of type false; however, parameter $tempuser of TYPO3\CMS\Core\Authentic...on::createUserSession() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

719
                $sessionData = $this->createUserSession(/** @scrutinizer ignore-type */ $tempuser);
Loading history...
720
721
                // Preserve session data on login
722
                if ($anonymousSession) {
723
                    $sessionData = $this->getSessionBackend()->update(
724
                        $this->id,
725
                        ['ses_data' => $authInfo['userSession']['ses_data']]
726
                    );
727
                }
728
729
                $this->user = array_merge(
730
                    $tempuser,
0 ignored issues
show
Bug introduced by
It seems like $tempuser can also be of type false; however, parameter $array1 of array_merge() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

730
                    /** @scrutinizer ignore-type */ $tempuser,
Loading history...
731
                    $sessionData
732
                );
733
                // The login session is started.
734
                $this->loginSessionStarted = true;
735
                if (is_array($this->user)) {
736
                    $this->logger->debug('User session finally read', [
737
                        $this->userid_column => $this->user[$this->userid_column],
738
                        $this->username_column => $this->user[$this->username_column],
739
                    ]);
740
                }
741
            } elseif ($haveSession) {
742
                // if we come here the current session is for sure not anonymous as this is a pre-condition for $authenticated = true
743
                $this->user = $authInfo['userSession'];
744
            }
745
746
            if ($activeLogin && !$this->newSessionID) {
747
                $this->regenerateSessionId();
748
            }
749
750
            // User logged in - write that to the log!
751
            if ($this->writeStdLog && $activeLogin) {
752
                $this->writelog(255, 1, 0, 1, 'User %s logged in from %s (%s)', [$tempuser[$this->username_column], GeneralUtility::getIndpEnv('REMOTE_ADDR'), GeneralUtility::getIndpEnv('REMOTE_HOST')], '', '', '');
0 ignored issues
show
Bug introduced by
'' of type string is incompatible with the type integer expected by parameter $recpid of TYPO3\CMS\Core\Authentic...hentication::writelog(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

752
                $this->writelog(255, 1, 0, 1, 'User %s logged in from %s (%s)', [$tempuser[$this->username_column], GeneralUtility::getIndpEnv('REMOTE_ADDR'), GeneralUtility::getIndpEnv('REMOTE_HOST')], '', '', /** @scrutinizer ignore-type */ '');
Loading history...
Bug introduced by
'' of type string is incompatible with the type integer expected by parameter $recuid of TYPO3\CMS\Core\Authentic...hentication::writelog(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

752
                $this->writelog(255, 1, 0, 1, 'User %s logged in from %s (%s)', [$tempuser[$this->username_column], GeneralUtility::getIndpEnv('REMOTE_ADDR'), GeneralUtility::getIndpEnv('REMOTE_HOST')], '', /** @scrutinizer ignore-type */ '', '');
Loading history...
753
            }
754
            if ($activeLogin) {
755
                $this->logger->info('User ' . $tempuser[$this->username_column] . ' logged in from ' . GeneralUtility::getIndpEnv('REMOTE_ADDR') . ' (' . GeneralUtility::getIndpEnv('REMOTE_HOST') . ')');
756
            }
757
            if (!$activeLogin) {
758
                $this->logger->debug('User ' . $tempuser[$this->username_column] . ' authenticated from ' . GeneralUtility::getIndpEnv('REMOTE_ADDR') . ' (' . GeneralUtility::getIndpEnv('REMOTE_HOST') . ')');
759
            }
760
        } else {
761
            // User was not authenticated, so we should reuse the existing anonymous session
762
            if ($anonymousSession) {
763
                $this->user = $authInfo['userSession'];
764
            }
765
766
            // Mark the current login attempt as failed
767
            if ($activeLogin || !empty($tempuserArr)) {
768
                $this->loginFailure = true;
769
                if (empty($tempuserArr) && $activeLogin) {
770
                    $logData = [
771
                        'loginData' => $loginData
772
                    ];
773
                    $this->logger->warning('Login failed', $logData);
774
                }
775
                if (!empty($tempuserArr)) {
776
                    $logData = [
777
                        $this->userid_column => $tempuser[$this->userid_column],
778
                        $this->username_column => $tempuser[$this->username_column],
779
                    ];
780
                    $this->logger->warning('Login failed', $logData);
781
                }
782
            }
783
        }
784
785
        // If there were a login failure, check to see if a warning email should be sent:
786
        if ($this->loginFailure && $activeLogin) {
787
            $this->logger->debug(
788
                'Call checkLogFailures',
789
                [
790
                    'warningEmail' => $this->warningEmail,
791
                    'warningPeriod' => $this->warningPeriod,
792
                    'warningMax' => $this->warningMax
793
                ]
794
            );
795
796
            // Hook to implement login failure tracking methods
797
            $_params = [];
798
            $sleep = true;
799
            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postLoginFailureProcessing'] ?? [] as $_funcRef) {
800
                GeneralUtility::callUserFunction($_funcRef, $_params, $this);
801
                $sleep = false;
802
            }
803
804
            if ($sleep) {
805
                // No hooks were triggered - default login failure behavior is to sleep 5 seconds
806
                sleep(5);
807
            }
808
809
            $this->checkLogFailures($this->warningEmail, $this->warningPeriod, $this->warningMax);
810
        }
811
    }
812
813
    /**
814
     * Creates a new session ID.
815
     *
816
     * @return string The new session ID
817
     */
818
    public function createSessionId()
819
    {
820
        return GeneralUtility::makeInstance(Random::class)->generateRandomHexString($this->hash_length);
821
    }
822
823
    /**
824
     * Initializes authentication services to be used in a foreach loop
825
     *
826
     * @param string $subType e.g. getUserFE
827
     * @param array $loginData
828
     * @param array $authInfo
829
     * @return \Traversable A generator of service objects
830
     */
831
    protected function getAuthServices(string $subType, array $loginData, array $authInfo): \Traversable
832
    {
833
        $serviceChain = '';
834
        while (is_object($serviceObj = GeneralUtility::makeInstanceService('auth', $subType, $serviceChain))) {
835
            $serviceChain .= ',' . $serviceObj->getServiceKey();
836
            $serviceObj->initAuth($subType, $loginData, $authInfo, $this);
837
            yield $serviceObj;
838
        }
839
        if ($serviceChain) {
840
            $this->logger->debug($subType . ' auth services called: ' . $serviceChain);
841
        }
842
    }
843
844
    /**
845
     * Regenerate the session ID and transfer the session to new ID
846
     * Call this method whenever a user proceeds to a higher authorization level
847
     * e.g. when an anonymous session is now authenticated.
848
     *
849
     * @param array $existingSessionRecord If given, this session record will be used instead of fetching again
850
     * @param bool $anonymous If true session will be regenerated as anonymous session
851
     */
852
    protected function regenerateSessionId(array $existingSessionRecord = [], bool $anonymous = false)
0 ignored issues
show
Unused Code introduced by
The parameter $anonymous is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

852
    protected function regenerateSessionId(array $existingSessionRecord = [], /** @scrutinizer ignore-unused */ bool $anonymous = false)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
853
    {
854
        if (empty($existingSessionRecord)) {
855
            $existingSessionRecord = $this->getSessionBackend()->get($this->id);
856
        }
857
858
        // Update session record with new ID
859
        $oldSessionId = $this->id;
860
        $this->id = $this->createSessionId();
861
        $updatedSession = $this->getSessionBackend()->set($this->id, $existingSessionRecord);
862
        $this->sessionData = unserialize($updatedSession['ses_data']);
863
        // Merge new session data into user/session array
864
        $this->user = array_merge($this->user ?? [], $updatedSession);
865
        $this->getSessionBackend()->remove($oldSessionId);
866
        $this->newSessionID = true;
867
    }
868
869
    /*************************
870
     *
871
     * User Sessions
872
     *
873
     *************************/
874
875
    /**
876
     * Creates a user session record and returns its values.
877
     *
878
     * @param array $tempuser User data array
879
     *
880
     * @return array The session data for the newly created session.
881
     */
882
    public function createUserSession($tempuser)
883
    {
884
        $this->logger->debug('Create session ses_id = ' . $this->id);
885
        // Delete any session entry first
886
        $this->getSessionBackend()->remove($this->id);
887
        // Re-create session entry
888
        $sessionRecord = $this->getNewSessionRecord($tempuser);
889
        $sessionRecord = $this->getSessionBackend()->set($this->id, $sessionRecord);
890
        // Updating lastLogin_column carrying information about last login.
891
        $this->updateLoginTimestamp($tempuser[$this->userid_column]);
892
        return $sessionRecord;
893
    }
894
895
    /**
896
     * Updates the last login column in the user with the given id
897
     *
898
     * @param int $userId
899
     */
900
    protected function updateLoginTimestamp(int $userId)
901
    {
902
        if ($this->lastLogin_column) {
903
            $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->user_table);
904
            $connection->update(
905
                $this->user_table,
906
                [$this->lastLogin_column => $GLOBALS['EXEC_TIME']],
907
                [$this->userid_column => $userId]
908
            );
909
        }
910
    }
911
912
    /**
913
     * Returns a new session record for the current user for insertion into the DB.
914
     * This function is mainly there as a wrapper for inheriting classes to override it.
915
     *
916
     * @param array $tempuser
917
     * @return array User session record
918
     */
919
    public function getNewSessionRecord($tempuser)
920
    {
921
        $sessionIpLock = '[DISABLED]';
922
        if ($this->lockIP && empty($tempuser['disableIPlock'])) {
923
            $sessionIpLock = $this->ipLockClause_remoteIPNumber($this->lockIP);
924
        }
925
926
        return [
927
            'ses_id' => $this->id,
928
            'ses_iplock' => $sessionIpLock,
929
            'ses_userid' => $tempuser[$this->userid_column] ?? 0,
930
            'ses_tstamp' => $GLOBALS['EXEC_TIME'],
931
            'ses_data' => '',
932
        ];
933
    }
934
935
    /**
936
     * Read the user session from db.
937
     *
938
     * @param bool $skipSessionUpdate
939
     * @return array|bool User session data, false if $this->id does not represent valid session
940
     */
941
    public function fetchUserSession($skipSessionUpdate = false)
942
    {
943
        $this->logger->debug('Fetch session ses_id = ' . $this->id);
944
        try {
945
            $sessionRecord = $this->getSessionBackend()->get($this->id);
946
        } catch (SessionNotFoundException $e) {
947
            return false;
948
        }
949
950
        $this->sessionData = unserialize($sessionRecord['ses_data']);
951
        // Session is anonymous so no need to fetch user
952
        if (!empty($sessionRecord['ses_anonymous'])) {
953
            return $sessionRecord;
954
        }
955
956
        // Fetch the user from the DB
957
        $userRecord = $this->getRawUserByUid((int)$sessionRecord['ses_userid']);
958
        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...
959
            $userRecord = array_merge($sessionRecord, $userRecord);
960
            // A user was found
961
            $userRecord['ses_tstamp'] = (int)$userRecord['ses_tstamp'];
962
            $userRecord['is_online'] = (int)$userRecord['ses_tstamp'];
963
964
            if (!empty($this->auth_timeout_field)) {
965
                // Get timeout-time from usertable
966
                $timeout = (int)$userRecord[$this->auth_timeout_field];
967
            } else {
968
                $timeout = $this->sessionTimeout;
969
            }
970
            // If timeout > 0 (TRUE) and current time has not exceeded the latest sessions-time plus the timeout in seconds then accept user
971
            // Use a gracetime-value to avoid updating a session-record too often
972
            if ($timeout > 0 && $GLOBALS['EXEC_TIME'] < $userRecord['ses_tstamp'] + $timeout) {
973
                $sessionUpdateGracePeriod = 61;
974
                if (!$skipSessionUpdate && $GLOBALS['EXEC_TIME'] > ($userRecord['ses_tstamp'] + $sessionUpdateGracePeriod)) {
975
                    // Update the session timestamp by writing a dummy update. (Backend will update the timestamp)
976
                    $updatesSession = $this->getSessionBackend()->update($this->id, []);
977
                    $userRecord = array_merge($userRecord, $updatesSession);
978
                }
979
            } else {
980
                // Delete any user set...
981
                $this->logoff();
982
                $userRecord = false;
983
            }
984
        }
985
        return $userRecord;
986
    }
987
988
    /**
989
     * Log out current user!
990
     * Removes the current session record, sets the internal ->user array to a blank string;
991
     * Thereby the current user (if any) is effectively logged out!
992
     */
993
    public function logoff()
994
    {
995
        $this->logger->debug('logoff: ses_id = ' . $this->id);
996
        // Release the locked records
997
        BackendUtility::lockRecords();
998
999
        $_params = [];
1000
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'] ?? [] as $_funcRef) {
1001
            if ($_funcRef) {
1002
                GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1003
            }
1004
        }
1005
        $this->performLogoff();
1006
1007
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_post_processing'] ?? [] as $_funcRef) {
1008
            if ($_funcRef) {
1009
                GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1010
            }
1011
        }
1012
    }
1013
1014
    /**
1015
     * Perform the logoff action. Called from logoff() as a way to allow subclasses to override
1016
     * what happens when a user logs off, without needing to reproduce the hook calls and logging
1017
     * that happens in the public logoff() API method.
1018
     */
1019
    protected function performLogoff()
1020
    {
1021
        if ($this->id) {
1022
            $this->getSessionBackend()->remove($this->id);
1023
        }
1024
        $this->user = null;
1025
    }
1026
1027
    /**
1028
     * Empty / unset the cookie
1029
     *
1030
     * @param string $cookieName usually, this is $this->name
1031
     */
1032
    public function removeCookie($cookieName)
1033
    {
1034
        $cookieDomain = $this->getCookieDomain();
1035
        // If no cookie domain is set, use the base path
1036
        $cookiePath = $cookieDomain ? '/' : GeneralUtility::getIndpEnv('TYPO3_SITE_PATH');
1037
        setcookie($cookieName, null, -1, $cookiePath, $cookieDomain);
1038
    }
1039
1040
    /**
1041
     * Determine whether there's an according session record to a given session_id.
1042
     * Don't care if session record is still valid or not.
1043
     *
1044
     * @param string $id Claimed Session ID
1045
     * @return bool Returns TRUE if a corresponding session was found in the database
1046
     */
1047
    public function isExistingSessionRecord($id)
1048
    {
1049
        try {
1050
            $sessionRecord = $this->getSessionBackend()->get($id);
1051
            if (empty($sessionRecord)) {
1052
                return false;
1053
            }
1054
            // If the session does not match the current IP lock, it should be treated as invalid
1055
            // and a new session should be created.
1056
            if ($sessionRecord['ses_iplock'] !== $this->ipLockClause_remoteIPNumber($this->lockIP) && $sessionRecord['ses_iplock'] !== '[DISABLED]') {
1057
                return false;
1058
            }
1059
            return true;
1060
        } catch (SessionNotFoundException $e) {
1061
            return false;
1062
        }
1063
    }
1064
1065
    /**
1066
     * Returns whether this request is going to set a cookie
1067
     * or a cookie was already found in the system
1068
     *
1069
     * @return bool Returns TRUE if a cookie is set
1070
     */
1071
    public function isCookieSet()
1072
    {
1073
        return $this->cookieWasSetOnCurrentRequest || $this->getCookie($this->name);
1074
    }
1075
1076
    /*************************
1077
     *
1078
     * SQL Functions
1079
     *
1080
     *************************/
1081
    /**
1082
     * This returns the restrictions needed to select the user respecting
1083
     * enable columns and flags like deleted, hidden, starttime, endtime
1084
     * and rootLevel
1085
     *
1086
     * @return QueryRestrictionContainerInterface
1087
     * @internal
1088
     */
1089
    protected function userConstraints(): QueryRestrictionContainerInterface
1090
    {
1091
        $restrictionContainer = GeneralUtility::makeInstance(DefaultRestrictionContainer::class);
1092
1093
        if (empty($this->enablecolumns['disabled'])) {
1094
            $restrictionContainer->removeByType(HiddenRestriction::class);
1095
        }
1096
1097
        if (empty($this->enablecolumns['deleted'])) {
1098
            $restrictionContainer->removeByType(DeletedRestriction::class);
1099
        }
1100
1101
        if (empty($this->enablecolumns['starttime'])) {
1102
            $restrictionContainer->removeByType(StartTimeRestriction::class);
1103
        }
1104
1105
        if (empty($this->enablecolumns['endtime'])) {
1106
            $restrictionContainer->removeByType(EndTimeRestriction::class);
1107
        }
1108
1109
        if (!empty($this->enablecolumns['rootLevel'])) {
1110
            $restrictionContainer->add(GeneralUtility::makeInstance(RootLevelRestriction::class, [$this->user_table]));
1111
        }
1112
1113
        return $restrictionContainer;
1114
    }
1115
1116
    /**
1117
     * Returns the IP address to lock to.
1118
     * The IP address may be partial based on $parts.
1119
     *
1120
     * @param int $parts 1-4: Indicates how many parts of the IP address to return. 4 means all, 1 means only first number.
1121
     * @return string (Partial) IP address for REMOTE_ADDR
1122
     */
1123
    protected function ipLockClause_remoteIPNumber($parts)
1124
    {
1125
        $IP = GeneralUtility::getIndpEnv('REMOTE_ADDR');
1126
        if ($parts >= 4) {
1127
            return $IP;
1128
        }
1129
        $parts = MathUtility::forceIntegerInRange($parts, 1, 3);
1130
        $IPparts = explode('.', $IP);
1131
        for ($a = 4; $a > $parts; $a--) {
1132
            unset($IPparts[$a - 1]);
1133
        }
1134
        return implode('.', $IPparts);
1135
    }
1136
1137
    /*************************
1138
     *
1139
     * Session and Configuration Handling
1140
     *
1141
     *************************/
1142
    /**
1143
     * This writes $variable to the user-record. This is a way of providing session-data.
1144
     * You can fetch the data again through $this->uc in this class!
1145
     * If $variable is not an array, $this->uc is saved!
1146
     *
1147
     * @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
1148
     */
1149
    public function writeUC($variable = '')
1150
    {
1151
        if (is_array($this->user) && $this->user[$this->userid_column]) {
1152
            if (!is_array($variable)) {
1153
                $variable = $this->uc;
1154
            }
1155
            $this->logger->debug('writeUC: ' . $this->userid_column . '=' . (int)$this->user[$this->userid_column]);
1156
            GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->user_table)->update(
1157
                $this->user_table,
1158
                ['uc' => serialize($variable)],
1159
                [$this->userid_column => (int)$this->user[$this->userid_column]],
1160
                ['uc' => Connection::PARAM_LOB]
1161
            );
1162
        }
1163
    }
1164
1165
    /**
1166
     * Sets $theUC as the internal variable ->uc IF $theUC is an array.
1167
     * If $theUC is FALSE, the 'uc' content from the ->user array will be unserialized and restored in ->uc
1168
     *
1169
     * @param mixed $theUC If an array, then set as ->uc, otherwise load from user record
1170
     */
1171
    public function unpack_uc($theUC = '')
1172
    {
1173
        if (!$theUC && isset($this->user['uc'])) {
1174
            $theUC = unserialize($this->user['uc']);
1175
        }
1176
        if (is_array($theUC)) {
1177
            $this->uc = $theUC;
1178
        }
1179
    }
1180
1181
    /**
1182
     * Stores data for a module.
1183
     * The data is stored with the session id so you can even check upon retrieval
1184
     * if the module data is from a previous session or from the current session.
1185
     *
1186
     * @param string $module Is the name of the module ($MCONF['name'])
1187
     * @param mixed $data Is the data you want to store for that module (array, string, ...)
1188
     * @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.
1189
     */
1190
    public function pushModuleData($module, $data, $noSave = 0)
1191
    {
1192
        $this->uc['moduleData'][$module] = $data;
1193
        $this->uc['moduleSessionID'][$module] = $this->id;
1194
        if (!$noSave) {
1195
            $this->writeUC();
1196
        }
1197
    }
1198
1199
    /**
1200
     * Gets module data for a module (from a loaded ->uc array)
1201
     *
1202
     * @param string $module Is the name of the module ($MCONF['name'])
1203
     * @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).
1204
     * @return mixed The module data if available: $this->uc['moduleData'][$module];
1205
     */
1206
    public function getModuleData($module, $type = '')
1207
    {
1208
        if ($type !== 'ses' || (isset($this->uc['moduleSessionID'][$module]) && $this->uc['moduleSessionID'][$module] == $this->id)) {
1209
            return $this->uc['moduleData'][$module];
1210
        }
1211
        return null;
1212
    }
1213
1214
    /**
1215
     * Returns the session data stored for $key.
1216
     * The data will last only for this login session since it is stored in the user session.
1217
     *
1218
     * @param string $key The key associated with the session data
1219
     * @return mixed
1220
     */
1221
    public function getSessionData($key)
1222
    {
1223
        return $this->sessionData[$key] ?? null;
1224
    }
1225
1226
    /**
1227
     * Set session data by key.
1228
     * The data will last only for this login session since it is stored in the user session.
1229
     *
1230
     * @param string $key A non empty string to store the data under
1231
     * @param mixed $data Data store store in session
1232
     */
1233
    public function setSessionData($key, $data)
1234
    {
1235
        if (empty($key)) {
1236
            throw new \InvalidArgumentException('Argument key must not be empty', 1484311516);
1237
        }
1238
        $this->sessionData[$key] = $data;
1239
    }
1240
1241
    /**
1242
     * Sets the session data ($data) for $key and writes all session data (from ->user['ses_data']) to the database.
1243
     * The data will last only for this login session since it is stored in the session table.
1244
     *
1245
     * @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.
1246
     * @param mixed $data The data to store in index $key
1247
     */
1248
    public function setAndSaveSessionData($key, $data)
1249
    {
1250
        $this->sessionData[$key] = $data;
1251
        $this->user['ses_data'] = serialize($this->sessionData);
1252
        $this->logger->debug('setAndSaveSessionData: ses_id = ' . $this->id);
1253
        $updatedSession = $this->getSessionBackend()->update(
1254
            $this->id,
1255
            ['ses_data' => $this->user['ses_data']]
1256
        );
1257
        $this->user = array_merge($this->user ?? [], $updatedSession);
1258
    }
1259
1260
    /*************************
1261
     *
1262
     * Misc
1263
     *
1264
     *************************/
1265
    /**
1266
     * Returns an info array with Login/Logout data submitted by a form or params
1267
     *
1268
     * @return array
1269
     * @internal
1270
     */
1271
    public function getLoginFormData()
1272
    {
1273
        $loginData = [];
1274
        $loginData['status'] = GeneralUtility::_GP($this->formfield_status);
1275
        if ($this->getMethodEnabled) {
1276
            $loginData['uname'] = GeneralUtility::_GP($this->formfield_uname);
1277
            $loginData['uident'] = GeneralUtility::_GP($this->formfield_uident);
1278
        } else {
1279
            $loginData['uname'] = GeneralUtility::_POST($this->formfield_uname);
1280
            $loginData['uident'] = GeneralUtility::_POST($this->formfield_uident);
1281
        }
1282
        // Only process the login data if a login is requested
1283
        if ($loginData['status'] === 'login') {
1284
            $loginData = $this->processLoginData($loginData);
1285
        }
1286
        $loginData = array_map('trim', $loginData);
1287
        return $loginData;
1288
    }
1289
1290
    /**
1291
     * Processes Login data submitted by a form or params depending on the
1292
     * passwordTransmissionStrategy
1293
     *
1294
     * @param array $loginData Login data array
1295
     * @param string $passwordTransmissionStrategy Alternative passwordTransmissionStrategy. Used when authentication services wants to override the default.
1296
     * @return array
1297
     * @internal
1298
     */
1299
    public function processLoginData($loginData, $passwordTransmissionStrategy = '')
1300
    {
1301
        $loginSecurityLevel = trim($GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['loginSecurityLevel']) ?: 'normal';
1302
        $passwordTransmissionStrategy = $passwordTransmissionStrategy ?: $loginSecurityLevel;
1303
        $this->logger->debug('Login data before processing', $loginData);
1304
        $serviceChain = '';
1305
        $subType = 'processLoginData' . $this->loginType;
1306
        $authInfo = $this->getAuthInfoArray();
1307
        $isLoginDataProcessed = false;
1308
        $processedLoginData = $loginData;
1309
        while (is_object($serviceObject = GeneralUtility::makeInstanceService('auth', $subType, $serviceChain))) {
1310
            $serviceChain .= ',' . $serviceObject->getServiceKey();
1311
            $serviceObject->initAuth($subType, $loginData, $authInfo, $this);
1312
            $serviceResult = $serviceObject->processLoginData($processedLoginData, $passwordTransmissionStrategy);
1313
            if (!empty($serviceResult)) {
1314
                $isLoginDataProcessed = true;
1315
                // If the service returns >=200 then no more processing is needed
1316
                if ((int)$serviceResult >= 200) {
1317
                    unset($serviceObject);
1318
                    break;
1319
                }
1320
            }
1321
            unset($serviceObject);
1322
        }
1323
        if ($isLoginDataProcessed) {
1324
            $loginData = $processedLoginData;
1325
            $this->logger->debug('Processed login data', $processedLoginData);
1326
        }
1327
        return $loginData;
1328
    }
1329
1330
    /**
1331
     * Returns an info array which provides additional information for auth services
1332
     *
1333
     * @return array
1334
     * @internal
1335
     */
1336
    public function getAuthInfoArray()
1337
    {
1338
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
1339
        $expressionBuilder = $queryBuilder->expr();
1340
        $authInfo = [];
1341
        $authInfo['loginType'] = $this->loginType;
1342
        $authInfo['refInfo'] = parse_url(GeneralUtility::getIndpEnv('HTTP_REFERER'));
1343
        $authInfo['HTTP_HOST'] = GeneralUtility::getIndpEnv('HTTP_HOST');
1344
        $authInfo['REMOTE_ADDR'] = GeneralUtility::getIndpEnv('REMOTE_ADDR');
1345
        $authInfo['REMOTE_HOST'] = GeneralUtility::getIndpEnv('REMOTE_HOST');
1346
        $authInfo['showHiddenRecords'] = $this->showHiddenRecords;
1347
        // Can be overidden in localconf by SVCONF:
1348
        $authInfo['db_user']['table'] = $this->user_table;
1349
        $authInfo['db_user']['userid_column'] = $this->userid_column;
1350
        $authInfo['db_user']['username_column'] = $this->username_column;
1351
        $authInfo['db_user']['userident_column'] = $this->userident_column;
1352
        $authInfo['db_user']['usergroup_column'] = $this->usergroup_column;
1353
        $authInfo['db_user']['enable_clause'] = $this->userConstraints()->buildExpression(
1354
            [$this->user_table => $this->user_table],
1355
            $expressionBuilder
1356
        );
1357
        if ($this->checkPid && $this->checkPid_value !== null) {
1358
            $authInfo['db_user']['checkPidList'] = $this->checkPid_value;
1359
            $authInfo['db_user']['check_pid_clause'] = $expressionBuilder->in(
1360
                'pid',
1361
                GeneralUtility::intExplode(',', $this->checkPid_value)
1362
            );
1363
        } else {
1364
            $authInfo['db_user']['checkPidList'] = '';
1365
            $authInfo['db_user']['check_pid_clause'] = '';
1366
        }
1367
        $authInfo['db_groups']['table'] = $this->usergroup_table;
1368
        return $authInfo;
1369
    }
1370
1371
    /**
1372
     * Check the login data with the user record data for builtin login methods
1373
     *
1374
     * @param array $user User data array
1375
     * @param array $loginData Login data array
1376
     * @param string $passwordCompareStrategy Alternative passwordCompareStrategy. Used when authentication services wants to override the default.
1377
     * @return bool TRUE if login data matched
1378
     */
1379
    public function compareUident($user, $loginData, $passwordCompareStrategy = '')
0 ignored issues
show
Unused Code introduced by
The parameter $passwordCompareStrategy is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1379
    public function compareUident($user, $loginData, /** @scrutinizer ignore-unused */ $passwordCompareStrategy = '')

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1380
    {
1381
        return (string)$loginData['uident_text'] !== '' && (string)$loginData['uident_text'] === (string)$user[$this->userident_column];
1382
    }
1383
1384
    /**
1385
     * Garbage collector, removing old expired sessions.
1386
     *
1387
     * @internal
1388
     */
1389
    public function gc()
1390
    {
1391
        $this->getSessionBackend()->collectGarbage($this->gc_time);
1392
    }
1393
1394
    /**
1395
     * DUMMY: Writes to log database table (in some extension classes)
1396
     *
1397
     * @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
1398
     * @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 !!)
1399
     * @param int $error flag. 0 = message, 1 = error (user problem), 2 = System Error (which should not happen), 3 = security notice (admin)
1400
     * @param int $details_nr The message number. Specific for each $type and $action. in the future this will make it possible to translate errormessages to other languages
1401
     * @param string $details Default text that follows the message
1402
     * @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...
1403
     * @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.)
1404
     * @param int $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.)
1405
     * @param int $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.)
1406
     */
1407
    public function writelog($type, $action, $error, $details_nr, $details, $data, $tablename, $recuid, $recpid)
0 ignored issues
show
Unused Code introduced by
The parameter $details is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1407
    public function writelog($type, $action, $error, $details_nr, /** @scrutinizer ignore-unused */ $details, $data, $tablename, $recuid, $recpid)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $recuid is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1407
    public function writelog($type, $action, $error, $details_nr, $details, $data, $tablename, /** @scrutinizer ignore-unused */ $recuid, $recpid)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $error is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1407
    public function writelog($type, $action, /** @scrutinizer ignore-unused */ $error, $details_nr, $details, $data, $tablename, $recuid, $recpid)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $tablename is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1407
    public function writelog($type, $action, $error, $details_nr, $details, $data, /** @scrutinizer ignore-unused */ $tablename, $recuid, $recpid)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $details_nr is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1407
    public function writelog($type, $action, $error, /** @scrutinizer ignore-unused */ $details_nr, $details, $data, $tablename, $recuid, $recpid)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $recpid is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1407
    public function writelog($type, $action, $error, $details_nr, $details, $data, $tablename, $recuid, /** @scrutinizer ignore-unused */ $recpid)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $action is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1407
    public function writelog($type, /** @scrutinizer ignore-unused */ $action, $error, $details_nr, $details, $data, $tablename, $recuid, $recpid)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1408
    {
1409
    }
1410
1411
    /**
1412
     * DUMMY: Check login failures (in some extension classes)
1413
     *
1414
     * @param string $email Email address
1415
     * @param int $secondsBack Number of sections back in time to check. This is a kind of limit for how many failures an hour for instance
1416
     * @param int $maxFailures Max allowed failures before a warning mail is sent
1417
     * @ignore
1418
     */
1419
    public function checkLogFailures($email, $secondsBack, $maxFailures)
0 ignored issues
show
Unused Code introduced by
The parameter $email is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1419
    public function checkLogFailures(/** @scrutinizer ignore-unused */ $email, $secondsBack, $maxFailures)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $secondsBack is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1419
    public function checkLogFailures($email, /** @scrutinizer ignore-unused */ $secondsBack, $maxFailures)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $maxFailures is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1419
    public function checkLogFailures($email, $secondsBack, /** @scrutinizer ignore-unused */ $maxFailures)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1420
    {
1421
    }
1422
1423
    /**
1424
     * Raw initialization of the be_user with uid=$uid
1425
     * This will circumvent all login procedures and select a be_users record from the
1426
     * database and set the content of ->user to the record selected.
1427
     * Thus the BE_USER object will appear like if a user was authenticated - however without
1428
     * a session id and the fields from the session table of course.
1429
     * Will check the users for disabled, start/endtime, etc. ($this->user_where_clause())
1430
     *
1431
     * @param int $uid The UID of the backend user to set in ->user
1432
     * @internal
1433
     * @see SC_mod_tools_be_user_index::compareUsers(), \TYPO3\CMS\Setup\Controller\SetupModuleController::simulateUser(), freesite_admin::startCreate()
1434
     */
1435
    public function setBeUserByUid($uid)
1436
    {
1437
        $this->user = $this->getRawUserByUid($uid);
1438
    }
1439
1440
    /**
1441
     * Raw initialization of the be_user with username=$name
1442
     *
1443
     * @param string $name The username to look up.
1444
     * @see \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::setBeUserByUid()
1445
     * @internal
1446
     */
1447
    public function setBeUserByName($name)
1448
    {
1449
        $this->user = $this->getRawUserByName($name);
1450
    }
1451
1452
    /**
1453
     * Fetching raw user record with uid=$uid
1454
     *
1455
     * @param int $uid The UID of the backend user to set in ->user
1456
     * @return array user record or FALSE
1457
     * @internal
1458
     */
1459
    public function getRawUserByUid($uid)
1460
    {
1461
        $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
1462
        $query->setRestrictions($this->userConstraints());
1463
        $query->select('*')
1464
            ->from($this->user_table)
1465
            ->where($query->expr()->eq('uid', $query->createNamedParameter($uid, \PDO::PARAM_INT)));
1466
1467
        return $query->execute()->fetch();
1468
    }
1469
1470
    /**
1471
     * Fetching raw user record with username=$name
1472
     *
1473
     * @param string $name The username to look up.
1474
     * @return array user record or FALSE
1475
     * @see \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::getUserByUid()
1476
     * @internal
1477
     */
1478
    public function getRawUserByName($name)
1479
    {
1480
        $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
1481
        $query->setRestrictions($this->userConstraints());
1482
        $query->select('*')
1483
            ->from($this->user_table)
1484
            ->where($query->expr()->eq('username', $query->createNamedParameter($name, \PDO::PARAM_STR)));
1485
1486
        return $query->execute()->fetch();
1487
    }
1488
1489
    /**
1490
     * Get a user from DB by username
1491
     * provided for usage from services
1492
     *
1493
     * @param array $dbUser User db table definition: $this->db_user
1494
     * @param string $username user name
1495
     * @param string $extraWhere Additional WHERE clause: " AND ...
1496
     * @return mixed User array or FALSE
1497
     */
1498
    public function fetchUserRecord($dbUser, $username, $extraWhere = '')
1499
    {
1500
        $user = false;
1501
        if ($username || $extraWhere) {
1502
            $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($dbUser['table']);
1503
            $query->getRestrictions()->removeAll()
1504
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1505
1506
            $constraints = array_filter([
1507
                QueryHelper::stripLogicalOperatorPrefix($dbUser['check_pid_clause']),
1508
                QueryHelper::stripLogicalOperatorPrefix($dbUser['enable_clause']),
1509
                QueryHelper::stripLogicalOperatorPrefix($extraWhere),
1510
            ]);
1511
1512
            if (!empty($username)) {
1513
                array_unshift(
1514
                    $constraints,
1515
                    $query->expr()->eq(
1516
                        $dbUser['username_column'],
1517
                        $query->createNamedParameter($username, \PDO::PARAM_STR)
1518
                    )
1519
                );
1520
            }
1521
1522
            $user = $query->select('*')
1523
                ->from($dbUser['table'])
1524
                ->where(...$constraints)
1525
                ->execute()
1526
                ->fetch();
1527
        }
1528
1529
        return $user;
1530
    }
1531
1532
    /**
1533
     * @internal
1534
     * @return string
1535
     */
1536
    public function getSessionId() : string
1537
    {
1538
        return $this->id;
1539
    }
1540
1541
    /**
1542
     * @internal
1543
     * @return string
1544
     */
1545
    public function getLoginType() : string
1546
    {
1547
        return $this->loginType;
1548
    }
1549
1550
    /**
1551
     * Returns initialized session backend. Returns same session backend if called multiple times
1552
     *
1553
     * @return SessionBackendInterface
1554
     */
1555
    protected function getSessionBackend()
1556
    {
1557
        if (!isset($this->sessionBackend)) {
1558
            $this->sessionBackend = GeneralUtility::makeInstance(SessionManager::class)->getSessionBackend($this->loginType);
1559
        }
1560
        return $this->sessionBackend;
1561
    }
1562
}
1563