AbstractUserAuthentication::getCookieDomain()   A
last analyzed

Complexity

Conditions 6
Paths 10

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 15
dl 0
loc 22
rs 9.2222
c 0
b 0
f 0
cc 6
nc 10
nop 0
1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Core\Authentication;
17
18
use Psr\Http\Message\ResponseInterface;
19
use Psr\Http\Message\ServerRequestInterface;
20
use Psr\Log\LoggerAwareInterface;
21
use Psr\Log\LoggerAwareTrait;
22
use Symfony\Component\HttpFoundation\Cookie;
23
use TYPO3\CMS\Core\Authentication\Mfa\MfaProviderRegistry;
24
use TYPO3\CMS\Core\Authentication\Mfa\MfaRequiredException;
25
use TYPO3\CMS\Core\Core\Environment;
26
use TYPO3\CMS\Core\Crypto\Random;
27
use TYPO3\CMS\Core\Database\Connection;
28
use TYPO3\CMS\Core\Database\ConnectionPool;
29
use TYPO3\CMS\Core\Database\Query\Restriction\DefaultRestrictionContainer;
30
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
31
use TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction;
32
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
33
use TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionContainerInterface;
34
use TYPO3\CMS\Core\Database\Query\Restriction\RootLevelRestriction;
35
use TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction;
36
use TYPO3\CMS\Core\Exception;
37
use TYPO3\CMS\Core\Http\CookieHeaderTrait;
38
use TYPO3\CMS\Core\Http\ServerRequestFactory;
39
use TYPO3\CMS\Core\Session\Backend\Exception\SessionNotFoundException;
40
use TYPO3\CMS\Core\Session\UserSession;
41
use TYPO3\CMS\Core\Session\UserSessionManager;
42
use TYPO3\CMS\Core\SysLog\Action\Login as SystemLogLoginAction;
43
use TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
44
use TYPO3\CMS\Core\SysLog\Type as SystemLogType;
45
use TYPO3\CMS\Core\Utility\GeneralUtility;
46
47
/**
48
 * Authentication of users in TYPO3
49
 *
50
 * This class is used to authenticate a login user.
51
 * The class is used by both the frontend and backend.
52
 * In both cases this class is a parent class to BackendUserAuthentication and FrontendUserAuthentication
53
 *
54
 * See Inside TYPO3 for more information about the API of the class and internal variables.
55
 */
56
abstract class AbstractUserAuthentication implements LoggerAwareInterface
57
{
58
    use LoggerAwareTrait;
59
    use CookieHeaderTrait;
60
61
    /**
62
     * Session/Cookie name
63
     * @var string
64
     */
65
    public $name = '';
66
67
    /**
68
     * Table in database with user data
69
     * @var string
70
     */
71
    public $user_table = '';
72
73
    /**
74
     * Table in database with user groups
75
     * @var string
76
     */
77
    public $usergroup_table = '';
78
79
    /**
80
     * Column for login-name
81
     * @var string
82
     */
83
    public $username_column = '';
84
85
    /**
86
     * Column for password
87
     * @var string
88
     */
89
    public $userident_column = '';
90
91
    /**
92
     * Column for user-id
93
     * @var string
94
     */
95
    public $userid_column = '';
96
97
    /**
98
     * Column for user group information
99
     * @var string
100
     */
101
    public $usergroup_column = '';
102
103
    /**
104
     * Column name for last login timestamp
105
     * @var string
106
     */
107
    public $lastLogin_column = '';
108
109
    /**
110
     * Enable field columns of user table
111
     * @var array
112
     */
113
    public $enablecolumns = [
114
        'rootLevel' => '',
115
        // Boolean: If TRUE, 'AND pid=0' will be a part of the query...
116
        'disabled' => '',
117
        'starttime' => '',
118
        'endtime' => '',
119
        'deleted' => '',
120
    ];
121
122
    /**
123
     * Form field with login-name
124
     * @var string
125
     */
126
    public $formfield_uname = '';
127
128
    /**
129
     * Form field with password
130
     * @var string
131
     */
132
    public $formfield_uident = '';
133
134
    /**
135
     * Form field with status: *'login', 'logout'. If empty login is not verified.
136
     * @var string
137
     */
138
    public $formfield_status = '';
139
140
    /**
141
     * Lifetime for the session-cookie (on the client)
142
     *
143
     * If >0: permanent cookie with given lifetime
144
     * If 0: session-cookie
145
     * Session-cookie means the browser will remove it when the browser is closed.
146
     *
147
     * @var int
148
     */
149
    protected $lifetime = 0;
150
151
    /**
152
     * Decides if the writelog() function is called at login and logout
153
     * @var bool
154
     */
155
    public $writeStdLog = false;
156
157
    /**
158
     * Log failed login attempts
159
     * @var bool
160
     */
161
    public $writeAttemptLog = false;
162
163
    /**
164
     * If set, the user-record must be stored at the page defined by $checkPid_value
165
     * @var bool
166
     */
167
    public $checkPid = true;
168
169
    /**
170
     * The page id the user record must be stored at, can also hold a comma separated list of pids
171
     * @var int|string
172
     */
173
    public $checkPid_value = 0;
174
175
    /**
176
     * Will be set to TRUE if the login session is actually written during auth-check.
177
     * @var bool
178
     */
179
    public $loginSessionStarted = false;
180
181
    /**
182
     * @var array|null contains user- AND session-data from database (joined tables)
183
     * @internal
184
     */
185
    public $user;
186
187
    /**
188
     * This array will hold the groups that the user is a member of
189
     */
190
    public array $userGroups = [];
191
192
    /**
193
     * Will prevent the setting of the session cookie
194
     * @var bool
195
     */
196
    public $dontSetCookie = false;
197
198
    /**
199
     * Login type, used for services.
200
     * @var string
201
     */
202
    public $loginType = '';
203
204
    /**
205
     * @var array
206
     */
207
    public $uc;
208
209
    protected ?UserSession $userSession = null;
210
211
    protected UserSessionManager $userSessionManager;
212
213
    /**
214
     * If set, this cookie will be set to the response.
215
     *
216
     * @var Cookie|null
217
     */
218
    protected ?Cookie $setCookie = null;
219
220
    /**
221
     * Initialize some important variables
222
     *
223
     * @throws Exception
224
     */
225
    public function __construct()
226
    {
227
        // Backend or frontend login - used for auth services
228
        if (empty($this->loginType)) {
229
            throw new Exception('No loginType defined, must be set explicitly by subclass', 1476045345);
230
        }
231
        $this->lifetime = (int)($GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['lifetime'] ?? 0);
232
    }
233
234
    /**
235
     * Currently needed for various unit tests, until start() and checkAuthentication() methods
236
     * are smaller and extracted from this class.
237
     *
238
     * @param UserSessionManager|null $userSessionManager
239
     * @internal
240
     */
241
    public function initializeUserSessionManager(?UserSessionManager $userSessionManager = null): void
242
    {
243
        $this->userSessionManager = $userSessionManager ?? UserSessionManager::create($this->loginType);
244
        $this->userSession = $this->userSessionManager->createAnonymousSession();
245
    }
246
247
    /**
248
     * Starts a user session
249
     * Typical configurations will:
250
     * a) check if session cookie was set and if not, set one,
251
     * b) check if a password/username was sent and if so, try to authenticate the user
252
     * c) Lookup a session attached to a user and check timeout etc.
253
     * d) Garbage collection, setting of no-cache headers.
254
     * If a user is authenticated the database record of the user (array) will be set in the ->user internal variable.
255
     *
256
     * @param ServerRequestInterface|null $request @todo: Make mandatory in v12.
257
     */
258
    public function start(ServerRequestInterface $request = null)
259
    {
260
        $request = $request ?? $GLOBALS['TYPO3_REQUEST'] ?? ServerRequestFactory::fromGlobals();
261
        $this->logger->debug('## Beginning of auth logging.');
0 ignored issues
show
Bug introduced by
The method debug() 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

261
        $this->logger->/** @scrutinizer ignore-call */ 
262
                       debug('## Beginning of auth logging.');

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...
262
        // Make certain that NO user is set initially
263
        $this->user = null;
264
265
        if (!isset($this->userSessionManager)) {
266
            $this->initializeUserSessionManager();
267
        }
268
        $this->userSession = $this->userSessionManager->createFromRequestOrAnonymous($request, $this->name);
269
270
        // Load user session, check to see if anyone has submitted login-information and if so authenticate
271
        // the user with the session. $this->user[uid] may be used to write log...
272
        try {
273
            $this->checkAuthentication($request);
274
        } catch (MfaRequiredException $mfaRequiredException) {
275
            // Ensure the cookie is still set to keep the user session available
276
            if (!$this->dontSetCookie || $this->isRefreshTimeBasedCookie()) {
277
                $this->setSessionCookie();
278
            }
279
            throw $mfaRequiredException;
280
        }
281
        // Set cookie if generally enabled or if the current session is a non-session cookie (FE permalogin)
282
        if (!$this->dontSetCookie || $this->isRefreshTimeBasedCookie()) {
283
            $this->setSessionCookie();
284
        }
285
        // Hook for alternative ways of filling the $this->user array (is used by the "timtaw" extension)
286
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postUserLookUp'] ?? [] as $funcName) {
287
            $_params = [
288
                'pObj' => $this,
289
            ];
290
            GeneralUtility::callUserFunction($funcName, $_params, $this);
291
        }
292
    }
293
294
    /**
295
     * Used to apply a cookie to a PSR-7 Response.
296
     *
297
     * @param ResponseInterface $response
298
     * @return ResponseInterface
299
     */
300
    public function appendCookieToResponse(ResponseInterface $response): ResponseInterface
301
    {
302
        if ($this->setCookie !== null) {
303
            $response = $response->withAddedHeader('Set-Cookie', $this->setCookie->__toString());
304
        }
305
        return $response;
306
    }
307
308
    /**
309
     * Sets the session cookie for the current disposal.
310
     */
311
    protected function setSessionCookie()
312
    {
313
        $isRefreshTimeBasedCookie = $this->isRefreshTimeBasedCookie();
314
        if ($this->isSetSessionCookie() || $isRefreshTimeBasedCookie) {
315
            // Get the domain to be used for the cookie (if any):
316
            $cookieDomain = $this->getCookieDomain();
317
            // If no cookie domain is set, use the base path:
318
            $cookiePath = $cookieDomain ? '/' : GeneralUtility::getIndpEnv('TYPO3_SITE_PATH');
319
            // If the cookie lifetime is set, use it:
320
            $cookieExpire = $isRefreshTimeBasedCookie ? $GLOBALS['EXEC_TIME'] + $this->lifetime : 0;
321
            // Valid options are "strict", "lax" or "none", whereas "none" only works in HTTPS requests (default & fallback is "strict")
322
            $cookieSameSite = $this->sanitizeSameSiteCookieValue(
323
                strtolower($GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieSameSite'] ?? Cookie::SAMESITE_STRICT)
324
            );
325
            // Use the secure option when the current request is served by a secure connection:
326
            // SameSite "none" needs the secure option (only allowed on HTTPS)
327
            $isSecure = $cookieSameSite === Cookie::SAMESITE_NONE || GeneralUtility::getIndpEnv('TYPO3_SSL');
328
            $sessionId = $this->userSession->getIdentifier();
0 ignored issues
show
Bug introduced by
The method getIdentifier() 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

328
            /** @scrutinizer ignore-call */ 
329
            $sessionId = $this->userSession->getIdentifier();

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...
329
            $this->setCookie = new Cookie(
330
                $this->name,
331
                $sessionId,
332
                $cookieExpire,
333
                $cookiePath,
334
                $cookieDomain,
335
                $isSecure,
336
                true,
337
                false,
338
                $cookieSameSite
339
            );
340
            $message = $isRefreshTimeBasedCookie ? 'Updated Cookie: {session}, {domain}' : 'Set Cookie: {session}, {domain}';
341
            $this->logger->debug($message, [
342
                'session' => sha1($sessionId),
343
                'domain' => $cookieDomain,
344
            ]);
345
        }
346
    }
347
348
    /**
349
     * Gets the domain to be used on setting cookies.
350
     * The information is taken from the value in $GLOBALS['TYPO3_CONF_VARS']['SYS']['cookieDomain'].
351
     *
352
     * @return string The domain to be used on setting cookies
353
     */
354
    protected function getCookieDomain()
355
    {
356
        $result = '';
357
        $cookieDomain = $GLOBALS['TYPO3_CONF_VARS']['SYS']['cookieDomain'];
358
        // If a specific cookie domain is defined for a given application type, use that domain
359
        if (!empty($GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieDomain'])) {
360
            $cookieDomain = $GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieDomain'];
361
        }
362
        if ($cookieDomain) {
363
            if ($cookieDomain[0] === '/') {
364
                $match = [];
365
                $matchCnt = @preg_match($cookieDomain, GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY'), $match);
366
                if ($matchCnt === false) {
367
                    $this->logger->critical('The regular expression for the cookie domain ({domain}) contains errors. The session is not shared across sub-domains.', ['domain' => $cookieDomain]);
368
                } elseif ($matchCnt) {
369
                    $result = $match[0];
370
                }
371
            } else {
372
                $result = $cookieDomain;
373
            }
374
        }
375
        return $result;
376
    }
377
378
    /**
379
     * Get the value of a specified cookie.
380
     *
381
     * @param string $cookieName The cookie ID
382
     * @return string The value stored in the cookie
383
     */
384
    protected function getCookie($cookieName)
385
    {
386
        return isset($_COOKIE[$cookieName]) ? stripslashes($_COOKIE[$cookieName]) : '';
387
    }
388
389
    /**
390
     * Determine whether a session cookie needs to be set (lifetime=0)
391
     *
392
     * @return bool
393
     * @internal
394
     */
395
    public function isSetSessionCookie()
396
    {
397
        return $this->userSession->isNew() && $this->lifetime === 0;
398
    }
399
400
    /**
401
     * Determine whether a non-session cookie needs to be set (lifetime>0)
402
     *
403
     * @return bool
404
     * @internal
405
     */
406
    public function isRefreshTimeBasedCookie()
407
    {
408
        return $this->lifetime > 0;
409
    }
410
411
    /**
412
     * "auth" services configuration array from $GLOBALS['TYPO3_CONF_VARS']['SVCONF']['auth']
413
     * @return array
414
     */
415
    protected function getAuthServiceConfiguration(): array
416
    {
417
        if (is_array($GLOBALS['TYPO3_CONF_VARS']['SVCONF']['auth']['setup'] ?? null)) {
418
            return $GLOBALS['TYPO3_CONF_VARS']['SVCONF']['auth']['setup'];
419
        }
420
        return [];
421
    }
422
423
    /**
424
     * Checks if a submission of username and password is present or use other authentication by auth services
425
     *
426
     * @param ServerRequestInterface|null $request @todo: Make mandatory in v12.
427
     * @throws MfaRequiredException
428
     * @internal
429
     */
430
    public function checkAuthentication(ServerRequestInterface $request = null)
431
    {
432
        $request = $request ?? $GLOBALS['TYPO3_REQUEST'] ?? ServerRequestFactory::fromGlobals();
433
        $authConfiguration = $this->getAuthServiceConfiguration();
434
        if (!empty($authConfiguration)) {
435
            $this->logger->debug('Authentication Service Configuration found.', ['auth_configuration' => $authConfiguration]);
436
        }
437
        // No user for now - will be searched by service below
438
        $tempuserArr = [];
439
        $tempuser = false;
440
        // User is not authenticated by default
441
        $authenticated = false;
442
        // User want to login with passed login data (name/password)
443
        $activeLogin = false;
444
        $this->logger->debug('Login type: {type}', ['type' => $this->loginType]);
445
        // The info array provide additional information for auth services
446
        $authInfo = $this->getAuthInfoArray();
447
        // Get Login/Logout data submitted by a form or params
448
        $loginData = $this->getLoginFormData();
449
        $this->logger->debug('Login data', $this->removeSensitiveLoginDataForLoggingInfo($loginData));
450
        // Active logout (eg. with "logout" button)
451
        if ($loginData['status'] === LoginType::LOGOUT) {
452
            if ($this->writeStdLog) {
453
                // $type,$action,$error,$details_nr,$details,$data,$tablename,$recuid,$recpid
454
                $this->writelog(SystemLogType::LOGIN, SystemLogLoginAction::LOGOUT, SystemLogErrorClassification::MESSAGE, 2, 'User %s logged out', [$this->user['username']], '', 0, 0);
455
            }
456
            $this->logger->info('User logged out. Id: {session}', ['session' => sha1($this->userSession->getIdentifier())]);
457
            $this->logoff();
458
        }
459
        // Determine whether we need to skip session update.
460
        // This is used mainly for checking session timeout in advance without refreshing the current session's timeout.
461
        $skipSessionUpdate = (bool)($request->getQueryParams()['skipSessionUpdate'] ?? false);
462
        $haveSession = false;
463
        $anonymousSession = false;
464
        if (!$this->userSession->isNew()) {
465
            // Read user data if this is bound to a user
466
            // However, if the user data is not valid, or the session has timed out we'll recreate a new anonymous session
467
            if ($this->userSession->getUserId() > 0) {
468
                $authInfo['user'] = $this->fetchValidUserFromSessionOrDestroySession($skipSessionUpdate);
469
                if (is_array($authInfo['user'])) {
470
                    $authInfo['userSession'] = $authInfo['user'];
471
                } else {
472
                    $authInfo['userSession'] = false;
473
                }
474
            }
475
            $authInfo['session'] = $this->userSession;
476
            $haveSession = !$this->userSession->isNew();
477
            $anonymousSession = $haveSession && $this->userSession->isAnonymous();
478
        }
479
480
        // Active login (eg. with login form).
481
        if (!$haveSession && $loginData['status'] === LoginType::LOGIN) {
482
            $activeLogin = true;
483
            $this->logger->debug('Active login (eg. with login form)');
484
            // check referrer for submitted login values
485
            if ($this->formfield_status && $loginData['uident'] && $loginData['uname']) {
486
                // Delete old user session if any
487
                $this->logoff();
488
            }
489
            // Refuse login for _CLI users, if not processing a CLI request type
490
            // (although we shouldn't be here in case of a CLI request type)
491
            if (stripos($loginData['uname'], '_CLI_') === 0 && !Environment::isCli()) {
492
                throw new \RuntimeException('TYPO3 Fatal Error: You have tried to login using a CLI user. Access prohibited!', 1270853931);
493
            }
494
        }
495
496
        // Cause elevation of privilege, make sure regenerateSessionId is called later on
497
        if ($anonymousSession && $loginData['status'] === LoginType::LOGIN) {
498
            $activeLogin = true;
499
        }
500
501
        if ($haveSession) {
502
            $this->logger->debug('User found in session', [
503
                $this->userid_column => $authInfo['user'][$this->userid_column] ?? null,
504
                $this->username_column => $authInfo['user'][$this->username_column] ?? null,
505
            ]);
506
        } else {
507
            $this->logger->debug('No user session found');
508
        }
509
510
        // Fetch user if ...
511
        if (
512
            $activeLogin || !empty($authConfiguration[$this->loginType . '_alwaysFetchUser'])
513
            || !$haveSession && !empty($authConfiguration[$this->loginType . '_fetchUserIfNoSession'])
514
        ) {
515
            // Use 'auth' service to find the user
516
            // First found user will be used
517
            $subType = 'getUser' . $this->loginType;
518
            /** @var AuthenticationService $serviceObj */
519
            foreach ($this->getAuthServices($subType, $loginData, $authInfo) as $serviceObj) {
520
                $row = $serviceObj->getUser();
521
                if (is_array($row)) {
522
                    $tempuserArr[] = $row;
523
                    $this->logger->debug('User found', [
524
                        $this->userid_column => $row[$this->userid_column],
525
                        $this->username_column => $row[$this->username_column],
526
                    ]);
527
                    // User found, just stop to search for more if not configured to go on
528
                    if (empty($authConfiguration[$this->loginType . '_fetchAllUsers'])) {
529
                        break;
530
                    }
531
                }
532
            }
533
534
            if (!empty($authConfiguration[$this->loginType . '_alwaysFetchUser'])) {
535
                $this->logger->debug($this->loginType . '_alwaysFetchUser option is enabled');
536
            }
537
            if (empty($tempuserArr)) {
538
                $this->logger->debug('No user found by services');
539
            } else {
540
                $this->logger->debug('{count} user records found by services', ['count' => count($tempuserArr)]);
541
            }
542
        }
543
544
        // If no new user was set we use the already found user session
545
        if (empty($tempuserArr) && $haveSession && !$anonymousSession) {
546
            // Check if the previous services returned a proper user
547
            if (is_array($authInfo['user'] ?? null)) {
548
                $tempuserArr[] = $authInfo['user'];
549
                $tempuser = $authInfo['user'];
550
                // User is authenticated because we found a user session
551
                $authenticated = true;
552
                $this->logger->debug('User session used', [
553
                    $this->userid_column => $authInfo['user'][$this->userid_column] ?? '',
554
                    $this->username_column => $authInfo['user'][$this->username_column] ?? '',
555
                ]);
556
            }
557
        }
558
        // Re-auth user when 'auth'-service option is set
559
        if (!empty($authConfiguration[$this->loginType . '_alwaysAuthUser'])) {
560
            $authenticated = false;
561
            $this->logger->debug('alwaysAuthUser option is enabled');
562
        }
563
        // Authenticate the user if needed
564
        if (!empty($tempuserArr) && !$authenticated) {
565
            foreach ($tempuserArr as $tempuser) {
566
                // Use 'auth' service to authenticate the user
567
                // If one service returns FALSE then authentication failed
568
                // a service might return 100 which means there's no reason to stop but the user can't be authenticated by that service
569
                $this->logger->debug('Auth user', $this->removeSensitiveLoginDataForLoggingInfo($tempuser, true));
570
                $subType = 'authUser' . $this->loginType;
571
572
                /** @var AuthenticationService $serviceObj */
573
                foreach ($this->getAuthServices($subType, $loginData, $authInfo) as $serviceObj) {
574
                    if (($ret = $serviceObj->authUser($tempuser)) > 0) {
575
                        // If the service returns >=200 then no more checking is needed - useful for IP checking without password
576
                        if ((int)$ret >= 200) {
577
                            $authenticated = true;
578
                            break;
579
                        }
580
                        if ((int)$ret >= 100) {
581
                        } else {
582
                            $authenticated = true;
583
                        }
584
                    } else {
585
                        $authenticated = false;
586
                        break;
587
                    }
588
                }
589
590
                if ($authenticated) {
591
                    // Leave foreach() because a user is authenticated
592
                    break;
593
                }
594
            }
595
        }
596
597
        // If user is authenticated a valid user is in $tempuser
598
        if ($authenticated) {
599
            // Insert session record if needed:
600
            if (!$haveSession
601
                || $anonymousSession
602
                || (int)($tempuser['uid'] ?? 0) !== $this->userSession->getUserId()
603
            ) {
604
                $sessionData = $this->userSession->getData();
605
                // Create a new session with a fixated user
606
                $this->userSession = $this->createUserSession($tempuser);
607
608
                // Preserve session data on login
609
                if ($anonymousSession || $haveSession) {
610
                    $this->userSession->overrideData($sessionData);
611
                }
612
613
                $this->user = array_merge($tempuser, $this->user ?? []);
614
615
                // The login session is started.
616
                $this->loginSessionStarted = true;
617
                if (is_array($this->user)) {
0 ignored issues
show
introduced by
The condition is_array($this->user) is always true.
Loading history...
618
                    $this->logger->debug('User session finally read', [
619
                        $this->userid_column => $this->user[$this->userid_column],
620
                        $this->username_column => $this->user[$this->username_column],
621
                    ]);
622
                }
623
            } else {
624
                // if we come here the current session is for sure not anonymous as this is a pre-condition for $authenticated = true
625
                $this->user = $authInfo['user'];
626
            }
627
628
            if ($activeLogin && !$this->userSession->isNew()) {
629
                $this->regenerateSessionId();
630
            }
631
632
            // Check if multi-factor authentication is required
633
            $this->evaluateMfaRequirements();
634
635
            if ($activeLogin) {
636
                // User logged in - write that to the log!
637
                if ($this->writeStdLog) {
638
                    $this->writelog(SystemLogType::LOGIN, SystemLogLoginAction::LOGIN, SystemLogErrorClassification::MESSAGE, 1, 'User %s logged in from ###IP###', [$tempuser[$this->username_column]], '', '', '');
639
                }
640
                $this->logger->info('User {username} logged in from {ip}', [
641
                    'username' => $tempuser[$this->username_column],
642
                    'ip' => GeneralUtility::getIndpEnv('REMOTE_ADDR'),
643
                ]);
644
            } else {
645
                $this->logger->debug('User {username} authenticated from {ip}', [
646
                    'username' => $tempuser[$this->username_column],
647
                    'ip' => GeneralUtility::getIndpEnv('REMOTE_ADDR'),
648
                ]);
649
            }
650
        } else {
651
            // Mark the current login attempt as failed
652
            if (empty($tempuserArr) && $activeLogin) {
653
                $this->logger->debug('Login failed', [
654
                    'loginData' => $this->removeSensitiveLoginDataForLoggingInfo($loginData),
655
                ]);
656
            } elseif (!empty($tempuserArr)) {
657
                $this->logger->debug('Login failed', [
658
                    $this->userid_column => $tempuser[$this->userid_column],
659
                    $this->username_column => $tempuser[$this->username_column],
660
                ]);
661
            }
662
663
            // If there were a login failure, check to see if a warning email should be sent
664
            if ($activeLogin) {
665
                $this->handleLoginFailure();
666
            }
667
        }
668
    }
669
670
    /**
671
     * This method checks if the user is authenticated but has not succeeded in
672
     * passing his MFA challenge. This method can therefore only be used if a user
673
     * has been authenticated against his first authentication method (username+password
674
     * or any other authentication token).
675
     *
676
     * @throws MfaRequiredException
677
     * @internal
678
     */
679
    protected function evaluateMfaRequirements(): void
680
    {
681
        // MFA has been validated already, nothing to do
682
        if ($this->getSessionData('mfa')) {
683
            return;
684
        }
685
        // If the user session does not contain the 'mfa' key - indicating that MFA is already
686
        // passed - get the first provider for authentication, which is either the default provider
687
        // or the first active provider (based on the providers configured ordering).
688
        $provider = GeneralUtility::makeInstance(MfaProviderRegistry::class)->getFirstAuthenticationAwareProvider($this);
689
        // Throw an exception (hopefully caught in a middleware) when an active provider for the user exists
690
        if ($provider !== null) {
691
            throw new MfaRequiredException($provider, 1613687097);
692
        }
693
    }
694
695
    /**
696
     * Whether the user is required to set up MFA
697
     *
698
     * @return bool
699
     * @internal
700
     */
701
    public function isMfaSetupRequired(): bool
702
    {
703
        return false;
704
    }
705
706
    /**
707
     * Implement functionality when there was a failed login
708
     */
709
    protected function handleLoginFailure(): void
710
    {
711
        $_params = [];
712
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postLoginFailureProcessing'] ?? [] as $hookIdentifier => $_funcRef) {
713
            GeneralUtility::callUserFunction($_funcRef, $_params, $this);
714
        }
715
    }
716
717
    /**
718
     * Creates a new session ID.
719
     *
720
     * @return string The new session ID
721
     * @deprecated since TYPO3 v11.0, will be removed in TYPO3 v12, is kept because it is used in Testing Framework
722
     */
723
    public function createSessionId()
724
    {
725
        return GeneralUtility::makeInstance(Random::class)->generateRandomHexString(32);
726
    }
727
728
    /**
729
     * Initializes authentication services to be used in a foreach loop
730
     *
731
     * @param string $subType e.g. getUserFE
732
     * @param array $loginData
733
     * @param array $authInfo
734
     * @return \Traversable A generator of service objects
735
     */
736
    protected function getAuthServices(string $subType, array $loginData, array $authInfo): \Traversable
737
    {
738
        $serviceChain = [];
739
        while (is_object($serviceObj = GeneralUtility::makeInstanceService('auth', $subType, $serviceChain))) {
740
            $serviceChain[] = $serviceObj->getServiceKey();
741
            $serviceObj->initAuth($subType, $loginData, $authInfo, $this);
742
            yield $serviceObj;
743
        }
744
        if (!empty($serviceChain)) {
745
            $this->logger->debug('{subtype} auth services called: {chain}', [
746
                'subtype' => $subType,
747
                'chain' => implode(',', $serviceChain),
748
            ]);
749
        }
750
    }
751
752
    /**
753
     * Regenerate the session ID and transfer the session to new ID
754
     * Call this method whenever a user proceeds to a higher authorization level
755
     * e.g. when an anonymous session is now authenticated.
756
     */
757
    protected function regenerateSessionId()
758
    {
759
        $this->userSession = $this->userSessionManager->regenerateSession($this->userSession->getIdentifier());
760
    }
761
762
    /*************************
763
     *
764
     * User Sessions
765
     *
766
     *************************/
767
768
    /**
769
     * Creates a user session record and returns its values.
770
     *
771
     * @param array $tempuser User data array
772
     * @return UserSession The session data for the newly created session.
773
     */
774
    public function createUserSession(array $tempuser): UserSession
775
    {
776
        // Needed for testing framework
777
        if (!isset($this->userSessionManager)) {
778
            $this->initializeUserSessionManager();
779
        }
780
        $tempUserId = (int)($tempuser[$this->userid_column] ?? 0);
781
        $session = $this->userSessionManager->elevateToFixatedUserSession($this->userSession, $tempUserId);
0 ignored issues
show
Bug introduced by
It seems like $this->userSession can also be of type null; however, parameter $session of TYPO3\CMS\Core\Session\U...eToFixatedUserSession() does only seem to accept TYPO3\CMS\Core\Session\UserSession, 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

781
        $session = $this->userSessionManager->elevateToFixatedUserSession(/** @scrutinizer ignore-type */ $this->userSession, $tempUserId);
Loading history...
782
        // Updating lastLogin_column carrying information about last login.
783
        $this->updateLoginTimestamp($tempUserId);
784
        return $session;
785
    }
786
787
    /**
788
     * Updates the last login column in the user with the given id
789
     *
790
     * @param int $userId
791
     */
792
    protected function updateLoginTimestamp(int $userId)
793
    {
794
        if ($this->lastLogin_column) {
795
            $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->user_table);
796
            $connection->update(
797
                $this->user_table,
798
                [$this->lastLogin_column => $GLOBALS['EXEC_TIME']],
799
                [$this->userid_column => $userId]
800
            );
801
            $this->user[$this->lastLogin_column] = $GLOBALS['EXEC_TIME'];
802
        }
803
    }
804
805
    /**
806
     * Read the user session from db.
807
     *
808
     * @param bool $skipSessionUpdate
809
     * @return array|bool User session data, false if $userSession->getIdentifier() does not represent valid session
810
     * @deprecated since TYPO3 v11, will be removed in TYPO3 v12.
811
     */
812
    public function fetchUserSession($skipSessionUpdate = false)
813
    {
814
        try {
815
            $session = $this->userSessionManager->createSessionFromStorage($this->userSession->getIdentifier());
816
        } catch (SessionNotFoundException $e) {
817
            return false;
818
        }
819
        $this->userSession = $session;
820
        // Session is anonymous so no need to fetch user
821
        if ($session->isAnonymous()) {
822
            return $session->toArray();
823
        }
824
825
        // Fetch the user from the DB
826
        $userRecord = $this->fetchValidUserFromSessionOrDestroySession($skipSessionUpdate);
827
        return is_array($userRecord) ? $userRecord : false;
828
    }
829
830
    /**
831
     * If the session is bound to a user, this method fetches the user record, and returns it.
832
     * If the session has a timeout, the session date is extended if needed. Also the ìs_online
833
     * flag is updated for the user.
834
     *
835
     * However, if the session has expired the session is removed and the request is treated as an anonymous session.
836
     *
837
     * @param bool $skipSessionUpdate
838
     * @return array|null
839
     */
840
    protected function fetchValidUserFromSessionOrDestroySession(bool $skipSessionUpdate = false): ?array
841
    {
842
        if ($this->userSession->isAnonymous()) {
843
            return null;
844
        }
845
        // Fetch the user from the DB
846
        $userRecord = $this->getRawUserByUid($this->userSession->getUserId() ?? 0);
847
        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...
848
            // A user was found
849
            $userRecord['is_online'] = $this->userSession->getLastUpdated();
850
            if (!$this->userSessionManager->hasExpired($this->userSession)) {
0 ignored issues
show
Bug introduced by
It seems like $this->userSession can also be of type null; however, parameter $session of TYPO3\CMS\Core\Session\U...onManager::hasExpired() does only seem to accept TYPO3\CMS\Core\Session\UserSession, 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

850
            if (!$this->userSessionManager->hasExpired(/** @scrutinizer ignore-type */ $this->userSession)) {
Loading history...
851
                if (!$skipSessionUpdate) {
852
                    $this->userSession = $this->userSessionManager->updateSessionTimestamp($this->userSession);
0 ignored issues
show
Bug introduced by
It seems like $this->userSession can also be of type null; however, parameter $session of TYPO3\CMS\Core\Session\U...pdateSessionTimestamp() does only seem to accept TYPO3\CMS\Core\Session\UserSession, 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

852
                    $this->userSession = $this->userSessionManager->updateSessionTimestamp(/** @scrutinizer ignore-type */ $this->userSession);
Loading history...
853
                }
854
            } else {
855
                // Delete any user set...
856
                $this->logoff();
857
                $userRecord = false;
858
                $this->userSession = $this->userSessionManager->createAnonymousSession();
859
            }
860
        }
861
        return is_array($userRecord) ? $userRecord : null;
862
    }
863
864
    /**
865
     * Regenerates the session ID and sets the cookie again.
866
     *
867
     * @internal
868
     */
869
    public function enforceNewSessionId()
870
    {
871
        $this->regenerateSessionId();
872
        $this->setSessionCookie();
873
    }
874
875
    /**
876
     * Log out current user!
877
     * Removes the current session record, sets the internal ->user array to a blank string;
878
     * Thereby the current user (if any) is effectively logged out!
879
     */
880
    public function logoff()
881
    {
882
        $this->logger->debug('logoff: ses_id = {session}', ['session' => sha1($this->userSession->getIdentifier())]);
883
884
        $_params = [];
885
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'] ?? [] as $_funcRef) {
886
            if ($_funcRef) {
887
                GeneralUtility::callUserFunction($_funcRef, $_params, $this);
888
            }
889
        }
890
        $this->performLogoff();
0 ignored issues
show
Bug introduced by
The method performLogoff() 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

890
        $this->/** @scrutinizer ignore-call */ 
891
               performLogoff();

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...
891
892
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_post_processing'] ?? [] as $_funcRef) {
893
            if ($_funcRef) {
894
                GeneralUtility::callUserFunction($_funcRef, $_params, $this);
895
            }
896
        }
897
    }
898
899
    /**
900
     * Perform the logoff action. Called from logoff() as a way to allow subclasses to override
901
     * what happens when a user logs off, without needing to reproduce the hook calls and logging
902
     * that happens in the public logoff() API method.
903
     */
904
    protected function performLogoff()
905
    {
906
        if ($this->userSession) {
907
            $this->userSessionManager->removeSession($this->userSession);
908
        }
909
        $this->userSession = $this->userSessionManager->createAnonymousSession();
910
        $this->user = null;
911
        if ($this->isCookieSet()) {
912
            $this->removeCookie($this->name);
913
        }
914
    }
915
916
    /**
917
     * Empty / unset the cookie
918
     *
919
     * @param string|null $cookieName usually, this is $this->name
920
     */
921
    public function removeCookie($cookieName = null)
922
    {
923
        $cookieName = $cookieName ?? $this->name;
924
        $cookieDomain = $this->getCookieDomain();
925
        // If no cookie domain is set, use the base path
926
        $cookiePath = $cookieDomain ? '/' : GeneralUtility::getIndpEnv('TYPO3_SITE_PATH');
927
        $this->setCookie = new Cookie(
928
            $cookieName,
929
            '',
930
            -1,
931
            $cookiePath,
932
            $cookieDomain
933
        );
934
    }
935
936
    /**
937
     * Returns whether this request is going to set a cookie
938
     * or a cookie was already found in the system
939
     *
940
     * @return bool Returns TRUE if a cookie is set
941
     */
942
    public function isCookieSet()
943
    {
944
        return isset($this->setCookie) || $this->getCookie($this->name);
945
    }
946
947
    /*************************
948
     *
949
     * SQL Functions
950
     *
951
     *************************/
952
    /**
953
     * This returns the restrictions needed to select the user respecting
954
     * enable columns and flags like deleted, hidden, starttime, endtime
955
     * and rootLevel
956
     *
957
     * @return QueryRestrictionContainerInterface
958
     * @internal
959
     */
960
    protected function userConstraints(): QueryRestrictionContainerInterface
961
    {
962
        $restrictionContainer = GeneralUtility::makeInstance(DefaultRestrictionContainer::class);
963
964
        if (empty($this->enablecolumns['disabled'])) {
965
            $restrictionContainer->removeByType(HiddenRestriction::class);
966
        }
967
968
        if (empty($this->enablecolumns['deleted'])) {
969
            $restrictionContainer->removeByType(DeletedRestriction::class);
970
        }
971
972
        if (empty($this->enablecolumns['starttime'])) {
973
            $restrictionContainer->removeByType(StartTimeRestriction::class);
974
        }
975
976
        if (empty($this->enablecolumns['endtime'])) {
977
            $restrictionContainer->removeByType(EndTimeRestriction::class);
978
        }
979
980
        if (!empty($this->enablecolumns['rootLevel'])) {
981
            $restrictionContainer->add(GeneralUtility::makeInstance(RootLevelRestriction::class, [$this->user_table]));
982
        }
983
984
        return $restrictionContainer;
985
    }
986
987
    /*************************
988
     *
989
     * Session and Configuration Handling
990
     *
991
     *************************/
992
    /**
993
     * This writes $variable to the user-record. This is a way of providing session-data.
994
     * You can fetch the data again through $this->uc in this class!
995
     * If $variable is not an array, $this->uc is saved!
996
     *
997
     * @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  @deprecated will be removed in TYPO3 v12.0.
998
     */
999
    public function writeUC($variable = '')
1000
    {
1001
        if ($variable !== '') {
1002
            trigger_error('Calling ' . __CLASS__ . '->writeUC() with an input argument will stop working with TYPO3 12.0. Setting the "uc" as array can be done via $user->uc = $myValue.', E_USER_DEPRECATED);
1003
        }
1004
        if (is_array($this->user) && $this->user[$this->userid_column]) {
1005
            if (!is_array($variable)) {
1006
                $variable = $this->uc;
1007
            }
1008
            $this->logger->debug('writeUC: {userid_column}={value}', [
1009
                'userid_column' => $this->userid_column,
1010
                'value' => $this->user[$this->userid_column],
1011
            ]);
1012
            GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->user_table)->update(
1013
                $this->user_table,
1014
                ['uc' => serialize($variable)],
1015
                [$this->userid_column => (int)$this->user[$this->userid_column]],
1016
                ['uc' => Connection::PARAM_LOB]
1017
            );
1018
        }
1019
    }
1020
1021
    /**
1022
     * Sets $theUC as the internal variable ->uc IF $theUC is an array.
1023
     * If $theUC is FALSE, the 'uc' content from the ->user array will be unserialized and restored in ->uc
1024
     *
1025
     * @param mixed $theUC If an array, then set as ->uc, otherwise load from user record @deprecated will be removed in TYPO3 v12.0.
1026
     */
1027
    public function unpack_uc($theUC = '')
1028
    {
1029
        if ($theUC !== '') {
1030
            trigger_error('Calling ' . __CLASS__ . '->unpack_uc() with an input argument will stop working with TYPO3 12.0. Setting the "uc" as array can be done via $user->uc = $myValue.', E_USER_DEPRECATED);
1031
        }
1032
        if (!$theUC && isset($this->user['uc'])) {
1033
            $theUC = unserialize($this->user['uc'], ['allowed_classes' => false]);
1034
        }
1035
        if (is_array($theUC)) {
1036
            $this->uc = $theUC;
1037
        }
1038
    }
1039
1040
    /**
1041
     * Stores data for a module.
1042
     * The data is stored with the session id so you can even check upon retrieval
1043
     * if the module data is from a previous session or from the current session.
1044
     *
1045
     * @param string $module Is the name of the module ($MCONF['name'])
1046
     * @param mixed $data Is the data you want to store for that module (array, string, ...)
1047
     * @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.
1048
     */
1049
    public function pushModuleData($module, $data, $noSave = 0)
1050
    {
1051
        $sessionHash = GeneralUtility::hmac(
1052
            $this->userSession->getIdentifier(),
1053
            'core-session-hash'
1054
        );
1055
        $this->uc['moduleData'][$module] = $data;
1056
        $this->uc['moduleSessionID'][$module] = $sessionHash;
1057
        if (!$noSave) {
1058
            $this->writeUC();
0 ignored issues
show
Deprecated Code introduced by
The function TYPO3\CMS\Core\Authentic...thentication::writeUC() has been deprecated. ( Ignorable by Annotation )

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

1058
            /** @scrutinizer ignore-deprecated */ $this->writeUC();
Loading history...
1059
        }
1060
    }
1061
1062
    /**
1063
     * Gets module data for a module (from a loaded ->uc array)
1064
     *
1065
     * @param string $module Is the name of the module ($MCONF['name'])
1066
     * @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).
1067
     * @return mixed The module data if available: $this->uc['moduleData'][$module];
1068
     */
1069
    public function getModuleData($module, $type = '')
1070
    {
1071
        $sessionHash = GeneralUtility::hmac(
1072
            $this->userSession->getIdentifier(),
1073
            'core-session-hash'
1074
        );
1075
        $sessionData = $this->uc['moduleData'][$module] ?? null;
1076
        $moduleSessionIdHash = $this->uc['moduleSessionID'][$module] ?? null;
1077
        if ($type !== 'ses'
1078
            || $sessionData !== null && $moduleSessionIdHash === $sessionHash
1079
            // @todo Fallback for non-hashed values in `moduleSessionID`, remove for TYPO3 v11.5 LTS
1080
            || $sessionData !== null && $moduleSessionIdHash === $this->userSession->getIdentifier()
1081
        ) {
1082
            return $sessionData;
1083
        }
1084
        return null;
1085
    }
1086
1087
    /**
1088
     * Returns the session data stored for $key.
1089
     * The data will last only for this login session since it is stored in the user session.
1090
     *
1091
     * @param string $key The key associated with the session data
1092
     * @return mixed
1093
     */
1094
    public function getSessionData($key)
1095
    {
1096
        return $this->userSession ? $this->userSession->get($key) : '';
1097
    }
1098
1099
    /**
1100
     * Set session data by key.
1101
     * The data will last only for this login session since it is stored in the user session.
1102
     *
1103
     * @param string $key A non empty string to store the data under
1104
     * @param mixed $data Data store store in session
1105
     */
1106
    public function setSessionData($key, $data)
1107
    {
1108
        $this->userSession->set($key, $data);
1109
    }
1110
1111
    /**
1112
     * Sets the session data ($data) for $key and writes all session data (from ->user['ses_data']) to the database.
1113
     * The data will last only for this login session since it is stored in the session table.
1114
     *
1115
     * @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.
1116
     * @param mixed $data The data to store in index $key
1117
     */
1118
    public function setAndSaveSessionData($key, $data)
1119
    {
1120
        $this->userSession->set($key, $data);
1121
        $this->logger->debug('setAndSaveSessionData: ses_id = {session}', ['session' => sha1($this->userSession->getIdentifier())]);
1122
        $this->userSession = $this->userSessionManager->updateSession($this->userSession);
0 ignored issues
show
Bug introduced by
It seems like $this->userSession can also be of type null; however, parameter $session of TYPO3\CMS\Core\Session\U...anager::updateSession() does only seem to accept TYPO3\CMS\Core\Session\UserSession, 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

1122
        $this->userSession = $this->userSessionManager->updateSession(/** @scrutinizer ignore-type */ $this->userSession);
Loading history...
1123
    }
1124
1125
    /*************************
1126
     *
1127
     * Misc
1128
     *
1129
     *************************/
1130
    /**
1131
     * Returns an info array with Login/Logout data submitted by a form or params
1132
     *
1133
     * @return array
1134
     * @internal
1135
     */
1136
    public function getLoginFormData()
1137
    {
1138
        $loginData = [
1139
            'status' => GeneralUtility::_GP($this->formfield_status),
1140
            'uname'  => GeneralUtility::_POST($this->formfield_uname),
1141
            'uident' => GeneralUtility::_POST($this->formfield_uident),
1142
        ];
1143
        // Only process the login data if a login is requested
1144
        if ($loginData['status'] === LoginType::LOGIN) {
1145
            $loginData = $this->processLoginData($loginData);
1146
        }
1147
        return $loginData;
1148
    }
1149
1150
    public function isActiveLogin(ServerRequestInterface $request): bool
1151
    {
1152
        $status = $request->getParsedBody()[$this->formfield_status] ?? $request->getQueryParams()[$this->formfield_status] ?? '';
1153
        return $status === LoginType::LOGIN;
1154
    }
1155
1156
    /**
1157
     * Processes Login data submitted by a form or params
1158
     *
1159
     * @param array $loginData Login data array
1160
     * @return array
1161
     * @internal
1162
     */
1163
    public function processLoginData($loginData)
1164
    {
1165
        $this->logger->debug('Login data before processing', $this->removeSensitiveLoginDataForLoggingInfo($loginData));
1166
        $subType = 'processLoginData' . $this->loginType;
1167
        $authInfo = $this->getAuthInfoArray();
1168
        $isLoginDataProcessed = false;
1169
        $processedLoginData = $loginData;
1170
        /** @var AuthenticationService $serviceObject */
1171
        foreach ($this->getAuthServices($subType, $loginData, $authInfo) as $serviceObject) {
1172
            $serviceResult = $serviceObject->processLoginData($processedLoginData, 'normal');
1173
            if (!empty($serviceResult)) {
1174
                $isLoginDataProcessed = true;
1175
                // If the service returns >=200 then no more processing is needed
1176
                if ((int)$serviceResult >= 200) {
1177
                    break;
1178
                }
1179
            }
1180
        }
1181
        if ($isLoginDataProcessed) {
1182
            $loginData = $processedLoginData;
1183
            $this->logger->debug('Processed login data', $this->removeSensitiveLoginDataForLoggingInfo($processedLoginData));
1184
        }
1185
        return $loginData;
1186
    }
1187
1188
    /**
1189
     * Removes any sensitive data from the incoming data (either from loginData, processedLogin data
1190
     * or the user record from the DB).
1191
     *
1192
     * No type hinting is added because it might be possible that the incoming data is of any other type.
1193
     *
1194
     * @param mixed|array $data
1195
     * @param bool $isUserRecord
1196
     * @return mixed
1197
     */
1198
    protected function removeSensitiveLoginDataForLoggingInfo($data, bool $isUserRecord = false)
1199
    {
1200
        if ($isUserRecord && is_array($data)) {
1201
            $fieldNames = ['uid', 'pid', 'tstamp', 'crdate', 'cruser_id', 'deleted', 'disabled', 'starttime', 'endtime', 'username', 'admin', 'usergroup', 'db_mountpoints', 'file_mountpoints', 'file_permissions', 'workspace_perms', 'lastlogin', 'workspace_id', 'category_perms'];
1202
            $data = array_intersect_key($data, array_combine($fieldNames, $fieldNames));
1203
        }
1204
        if (isset($data['uident'])) {
1205
            $data['uident'] = '********';
1206
        }
1207
        if (isset($data['uident_text'])) {
1208
            $data['uident_text'] = '********';
1209
        }
1210
        if (isset($data['password'])) {
1211
            $data['password'] = '********';
1212
        }
1213
        return $data;
1214
    }
1215
1216
    /**
1217
     * Returns an info array which provides additional information for auth services
1218
     *
1219
     * @return array
1220
     * @internal
1221
     */
1222
    public function getAuthInfoArray()
1223
    {
1224
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
1225
        $expressionBuilder = $queryBuilder->expr();
1226
        $authInfo = [];
1227
        $authInfo['loginType'] = $this->loginType;
1228
        $authInfo['refInfo'] = parse_url(GeneralUtility::getIndpEnv('HTTP_REFERER'));
1229
        $authInfo['HTTP_HOST'] = GeneralUtility::getIndpEnv('HTTP_HOST');
1230
        $authInfo['REMOTE_ADDR'] = GeneralUtility::getIndpEnv('REMOTE_ADDR');
1231
        $authInfo['REMOTE_HOST'] = GeneralUtility::getIndpEnv('REMOTE_HOST');
1232
        // Can be overridden in localconf by SVCONF:
1233
        $authInfo['db_user']['table'] = $this->user_table;
1234
        $authInfo['db_user']['userid_column'] = $this->userid_column;
1235
        $authInfo['db_user']['username_column'] = $this->username_column;
1236
        $authInfo['db_user']['userident_column'] = $this->userident_column;
1237
        $authInfo['db_user']['enable_clause'] = $this->userConstraints()->buildExpression(
1238
            [$this->user_table => $this->user_table],
1239
            $expressionBuilder
1240
        );
1241
        if ($this->checkPid && $this->checkPid_value !== null) {
1242
            $authInfo['db_user']['checkPidList'] = $this->checkPid_value;
1243
            $authInfo['db_user']['check_pid_clause'] = $expressionBuilder->in(
1244
                'pid',
1245
                GeneralUtility::intExplode(',', (string)$this->checkPid_value)
1246
            );
1247
        } else {
1248
            $authInfo['db_user']['checkPidList'] = '';
1249
            $authInfo['db_user']['check_pid_clause'] = '';
1250
        }
1251
        return $authInfo;
1252
    }
1253
1254
    /**
1255
     * DUMMY: Writes to log database table (in some extension classes)
1256
     *
1257
     * @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
1258
     * @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 !!)
1259
     * @param int $error flag. 0 = message, 1 = error (user problem), 2 = System Error (which should not happen), 3 = security notice (admin)
1260
     * @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
1261
     * @param string $details Default text that follows the message
1262
     * @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...
1263
     * @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.)
1264
     * @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.)
1265
     * @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.)
1266
     */
1267
    public function writelog($type, $action, $error, $details_nr, $details, $data, $tablename, $recuid, $recpid)
1268
    {
1269
    }
1270
1271
    /**
1272
     * Raw initialization of the be_user with uid=$uid
1273
     * This will circumvent all login procedures and select a be_users record from the
1274
     * database and set the content of ->user to the record selected.
1275
     * Thus the BE_USER object will appear like if a user was authenticated - however without
1276
     * a session id and the fields from the session table of course.
1277
     * Will check the users for disabled, start/endtime, etc. ($this->user_where_clause())
1278
     *
1279
     * @param int $uid The UID of the backend user to set in ->user
1280
     * @internal
1281
     */
1282
    public function setBeUserByUid($uid)
1283
    {
1284
        $this->user = $this->getRawUserByUid($uid);
1285
    }
1286
1287
    /**
1288
     * Raw initialization of the be_user with username=$name
1289
     *
1290
     * @param string $name The username to look up.
1291
     * @see \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::setBeUserByUid()
1292
     * @internal
1293
     */
1294
    public function setBeUserByName($name)
1295
    {
1296
        $this->user = $this->getRawUserByName($name) ?: null;
1297
    }
1298
1299
    /**
1300
     * Fetching raw user record with uid=$uid
1301
     *
1302
     * @param int $uid The UID of the backend user to set in ->user
1303
     * @return array user record or FALSE
1304
     * @internal
1305
     */
1306
    public function getRawUserByUid($uid)
1307
    {
1308
        $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
1309
        $query->setRestrictions($this->userConstraints());
1310
        $query->select('*')
1311
            ->from($this->user_table)
1312
            ->where($query->expr()->eq('uid', $query->createNamedParameter($uid, \PDO::PARAM_INT)));
1313
1314
        return $query->execute()->fetchAssociative();
1315
    }
1316
1317
    /**
1318
     * Fetching raw user record with username=$name
1319
     *
1320
     * @param string $name The username to look up.
1321
     * @return array user record or FALSE
1322
     * @see \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::getUserByUid()
1323
     * @internal
1324
     */
1325
    public function getRawUserByName($name)
1326
    {
1327
        $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
1328
        $query->setRestrictions($this->userConstraints());
1329
        $query->select('*')
1330
            ->from($this->user_table)
1331
            ->where($query->expr()->eq('username', $query->createNamedParameter($name, \PDO::PARAM_STR)));
1332
1333
        return $query->execute()->fetchAssociative();
1334
    }
1335
1336
    /**
1337
     * @return UserSession
1338
     */
1339
    public function getSession(): UserSession
1340
    {
1341
        return $this->userSession;
1342
    }
1343
1344
    public function __isset(string $propertyName): bool
1345
    {
1346
        switch ($propertyName) {
1347
            case 'id':
1348
                trigger_error('Property id is removed in v11.', E_USER_DEPRECATED);
1349
                return isset($this->userSession);
1350
        }
1351
        return isset($this->propertyName);
0 ignored issues
show
Bug Best Practice introduced by
The property propertyName does not exist on TYPO3\CMS\Core\Authentic...tractUserAuthentication. Since you implemented __get, consider adding a @property annotation.
Loading history...
1352
    }
1353
1354
    public function __set(string $propertyName, $propertyValue)
1355
    {
1356
        switch ($propertyName) {
1357
            case 'id':
1358
                if (!isset($this->userSessionManager)) {
1359
                    $this->initializeUserSessionManager();
1360
                }
1361
                $this->userSession = UserSession::createNonFixated($propertyValue);
1362
                // No deprecation due to adaptions in testing framework to remove ->id = ...
1363
                break;
1364
        }
1365
        $this->$propertyName = $propertyValue;
1366
    }
1367
1368
    public function __get(string $propertyName)
1369
    {
1370
        switch ($propertyName) {
1371
            case 'id':
1372
                trigger_error('Property id is marked as protected now. Use ->getSession()->getIdentifier().', E_USER_DEPRECATED);
1373
                return $this->getSession()->getIdentifier();
1374
        }
1375
        return $this->$propertyName;
1376
    }
1377
1378
    public function __unset(string $propertyName): void
1379
    {
1380
        switch ($propertyName) {
1381
            case 'id':
1382
                trigger_error('Property id is marked as protected now. Use ->getSession()->getIdentifier().', E_USER_DEPRECATED);
1383
                return;
1384
        }
1385
        unset($this->$propertyName);
1386
    }
1387
}
1388