AbstractUserAuthentication::writelog()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Importance

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

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Core\Authentication;
17
18
use Psr\Http\Message\ResponseInterface;
19
use Psr\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