Passed
Push — master ( 794f3e...22b32d )
by
unknown
12:36
created

FrontendUserAuthentication::fetchGroupData()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 40
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 29
dl 0
loc 40
rs 9.456
c 0
b 0
f 0
cc 4
nc 8
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\Frontend\Authentication;
17
18
use TYPO3\CMS\Core\Authentication\AbstractUserAuthentication;
19
use TYPO3\CMS\Core\Authentication\GroupResolver;
20
use TYPO3\CMS\Core\Context\UserAspect;
21
use TYPO3\CMS\Core\Database\ConnectionPool;
22
use TYPO3\CMS\Core\Session\UserSession;
23
use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
24
use TYPO3\CMS\Core\Utility\GeneralUtility;
25
26
/**
27
 * Extension class for Front End User Authentication.
28
 */
29
class FrontendUserAuthentication extends AbstractUserAuthentication
30
{
31
    /**
32
     * Login type, used for services.
33
     * @var string
34
     */
35
    public $loginType = 'FE';
36
37
    /**
38
     * Form field with login-name
39
     * @var string
40
     */
41
    public $formfield_uname = 'user';
42
43
    /**
44
     * Form field with password
45
     * @var string
46
     */
47
    public $formfield_uident = 'pass';
48
49
    /**
50
     * Form field with status: *'login', 'logout'. If empty login is not verified.
51
     * @var string
52
     */
53
    public $formfield_status = 'logintype';
54
55
    /**
56
     * form field with 0 or 1
57
     * 1 = permanent login enabled
58
     * 0 = session is valid for a browser session only
59
     * @var string
60
     */
61
    public $formfield_permanent = 'permalogin';
62
63
    /**
64
     * Table in database with user data
65
     * @var string
66
     */
67
    public $user_table = 'fe_users';
68
69
    /**
70
     * Column for login-name
71
     * @var string
72
     */
73
    public $username_column = 'username';
74
75
    /**
76
     * Column for password
77
     * @var string
78
     */
79
    public $userident_column = 'password';
80
81
    /**
82
     * Column for user-id
83
     * @var string
84
     */
85
    public $userid_column = 'uid';
86
87
    /**
88
     * Column name for last login timestamp
89
     * @var string
90
     */
91
    public $lastLogin_column = 'lastlogin';
92
93
    /**
94
     * @var string
95
     */
96
    public $usergroup_column = 'usergroup';
97
98
    /**
99
     * @var string
100
     */
101
    public $usergroup_table = 'fe_groups';
102
103
    /**
104
     * Enable field columns of user table
105
     * @var array
106
     */
107
    public $enablecolumns = [
108
        'deleted' => 'deleted',
109
        'disabled' => 'disable',
110
        'starttime' => 'starttime',
111
        'endtime' => 'endtime'
112
    ];
113
114
    /**
115
     * @var array
116
     */
117
    public $groupData = [
118
        'title' => [],
119
        'uid' => [],
120
        'pid' => []
121
    ];
122
123
    /**
124
     * Used to accumulate the TSconfig data of the user
125
     * @var array
126
     */
127
    protected $TSdataArray = [];
128
129
    /**
130
     * @var array
131
     */
132
    protected $userTS = [];
133
134
    /**
135
     * @var bool
136
     */
137
    protected $userData_change = false;
138
139
    /**
140
     * @var bool
141
     */
142
    public $is_permanent = false;
143
144
    /**
145
     * @var bool
146
     */
147
    protected $loginHidden = false;
148
149
    /**
150
     * Will force the session cookie to be set every time (lifetime must be 0).
151
     * @var bool
152
     */
153
    protected $forceSetCookie = false;
154
155
    /**
156
     * Will prevent the setting of the session cookie (takes precedence over forceSetCookie)
157
     * Disable cookie by default, will be activated if saveSessionData() is called,
158
     * a user is logging-in or an existing session is found
159
     * @var bool
160
     */
161
    public $dontSetCookie = true;
162
163
    public function __construct()
164
    {
165
        $this->name = self::getCookieName();
166
        parent::__construct();
167
        $this->checkPid = $GLOBALS['TYPO3_CONF_VARS']['FE']['checkFeUserPid'];
168
    }
169
170
    /**
171
     * Returns the configured cookie name
172
     *
173
     * @return string
174
     */
175
    public static function getCookieName()
176
    {
177
        $configuredCookieName = trim($GLOBALS['TYPO3_CONF_VARS']['FE']['cookieName']);
178
        if (empty($configuredCookieName)) {
179
            $configuredCookieName = 'fe_typo_user';
180
        }
181
        return $configuredCookieName;
182
    }
183
184
    /**
185
     * Determine whether a session cookie needs to be set (lifetime=0)
186
     *
187
     * @return bool
188
     * @internal
189
     */
190
    public function isSetSessionCookie()
191
    {
192
        return ($this->userSession->isNew() || $this->forceSetCookie)
0 ignored issues
show
Bug introduced by
The method isNew() 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

192
        return ($this->userSession->/** @scrutinizer ignore-call */ isNew() || $this->forceSetCookie)

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...
193
            && ((int)$this->lifetime === 0 || !$this->userSession->isPermanent());
194
    }
195
196
    /**
197
     * Determine whether a non-session cookie needs to be set (lifetime>0)
198
     *
199
     * @return bool
200
     * @internal
201
     */
202
    public function isRefreshTimeBasedCookie()
203
    {
204
        return $this->lifetime > 0 && $this->userSession->isPermanent();
205
    }
206
207
    /**
208
     * Returns an info array with Login/Logout data submitted by a form or params
209
     *
210
     * @return array
211
     * @see AbstractUserAuthentication::getLoginFormData()
212
     */
213
    public function getLoginFormData()
214
    {
215
        $loginData = parent::getLoginFormData();
216
        if ($GLOBALS['TYPO3_CONF_VARS']['FE']['permalogin'] == 0 || $GLOBALS['TYPO3_CONF_VARS']['FE']['permalogin'] == 1) {
217
            $isPermanent = GeneralUtility::_POST($this->formfield_permanent);
218
            if (strlen($isPermanent) != 1) {
219
                $isPermanent = $GLOBALS['TYPO3_CONF_VARS']['FE']['permalogin'];
220
            } elseif (!$isPermanent) {
221
                // To make sure the user gets a session cookie and doesn't keep a possibly existing time based cookie,
222
                // we need to force setting the session cookie here
223
                $this->forceSetCookie = true;
224
            }
225
            $isPermanent = (bool)$isPermanent;
226
        } elseif ($GLOBALS['TYPO3_CONF_VARS']['FE']['permalogin'] == 2) {
227
            $isPermanent = true;
228
        } else {
229
            $isPermanent = false;
230
        }
231
        $loginData['permanent'] = $isPermanent;
232
        $this->is_permanent = $isPermanent;
233
        return $loginData;
234
    }
235
236
    /**
237
     * Creates a user session record and returns its values.
238
     * However, as the FE user cookie is normally not set, this has to be done
239
     * before the parent class is doing the rest.
240
     *
241
     * @param array $tempuser User data array
242
     * @return UserSession The session data for the newly created session.
243
     */
244
    public function createUserSession(array $tempuser): UserSession
245
    {
246
        // At this point we do not know if we need to set a session or a permanent cookie
247
        // So we force the cookie to be set after authentication took place, which will
248
        // then call setSessionCookie(), which will set a cookie with correct settings.
249
        $this->dontSetCookie = false;
250
        $tempUserId = (int)($tempuser[$this->userid_column] ?? 0);
251
        $session = $this->userSessionManager->elevateToFixatedUserSession(
252
            $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...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

252
            /** @scrutinizer ignore-type */ $this->userSession,
Loading history...
253
            $tempUserId,
254
            (bool)$this->is_permanent
255
        );
256
        // Updating lastLogin_column carrying information about last login.
257
        $this->updateLoginTimestamp($tempUserId);
258
        return $session;
259
    }
260
261
    /**
262
     * Will select all fe_groups records that the current fe_user is member of.
263
     *
264
     * It also accumulates the TSconfig for the fe_user/fe_groups in ->TSdataArray
265
     */
266
    public function fetchGroupData()
267
    {
268
        $this->TSdataArray = [];
269
        $this->userTS = [];
270
        $this->userGroups = [];
271
        $this->groupData = [
272
            'title' => [],
273
            'uid' => [],
274
            'pid' => []
275
        ];
276
        // Setting default configuration:
277
        $this->TSdataArray[] = $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultUserTSconfig'];
278
279
        $groupDataArr = [];
280
        if (is_array($this->user)) {
281
            $this->logger->debug('Get usergroups for user', [
282
                $this->userid_column => $this->user[$this->userid_column],
283
                $this->username_column => $this->user[$this->username_column]
284
            ]);
285
            $groupDataArr = GeneralUtility::makeInstance(GroupResolver::class)->resolveGroupsForUser($this->user, $this->usergroup_table);
286
        }
287
288
        if (empty($groupDataArr)) {
289
            $this->logger->debug('No usergroups found');
290
        } else {
291
            $this->logger->debug(count($groupDataArr) . ' usergroup records found');
292
        }
293
        foreach ($groupDataArr as $groupData) {
294
            $groupId = (int)$groupData['uid'];
295
            $this->groupData['title'][$groupId] = $groupData['title'];
296
            $this->groupData['uid'][$groupId] = $groupData['uid'];
297
            $this->groupData['pid'][$groupId] = $groupData['pid'];
298
            $this->TSdataArray[] = $groupData['TSconfig'];
299
            $this->userGroups[$groupId] = $groupData;
300
        }
301
        $this->TSdataArray[] = $this->user['TSconfig'] ?? '';
302
        // Sort information
303
        ksort($this->groupData['title']);
304
        ksort($this->groupData['uid']);
305
        ksort($this->groupData['pid']);
306
    }
307
308
    /**
309
     * Initializes the front-end user groups for the context API,
310
     * based on the user groups and the logged-in state.
311
     *
312
     * @param bool $respectUserGroups used with the $TSFE->loginAllowedInBranch flag to disable the inclusion of the users' groups
313
     * @return UserAspect
314
     */
315
    public function createUserAspect(bool $respectUserGroups = true): UserAspect
316
    {
317
        $userGroups = [0];
318
        $isUserAndGroupSet = is_array($this->user) && !empty($this->userGroups);
319
        if ($isUserAndGroupSet) {
320
            // group -2 is not an existing group, but denotes a 'default' group when a user IS logged in.
321
            // This is used to let elements be shown for all logged in users!
322
            $userGroups[] = -2;
323
            $groupsFromUserRecord = array_keys($this->userGroups);
324
        } else {
325
            // group -1 is not an existing group, but denotes a 'default' group when not logged in.
326
            // This is used to let elements be hidden, when a user is logged in!
327
            $userGroups[] = -1;
328
            if ($respectUserGroups) {
329
                // For cases where logins are not banned from a branch usergroups can be set based on IP masks so we should add the usergroups uids.
330
                $groupsFromUserRecord = array_keys($this->userGroups);
331
            } else {
332
                // Set to blank since we will NOT risk any groups being set when no logins are allowed!
333
                $groupsFromUserRecord = [];
334
            }
335
        }
336
        // Make unique and sort the groups
337
        $groupsFromUserRecord = array_unique($groupsFromUserRecord);
338
        if ($respectUserGroups && !empty($groupsFromUserRecord)) {
339
            sort($groupsFromUserRecord);
340
            $userGroups = array_merge($userGroups, $groupsFromUserRecord);
341
        }
342
343
        // For every 60 seconds the is_online timestamp for a logged-in user is updated
344
        if ($isUserAndGroupSet) {
345
            $this->updateOnlineTimestamp();
346
        }
347
348
        $this->logger->debug('Valid frontend usergroups: ' . implode(',', $userGroups));
349
        return GeneralUtility::makeInstance(UserAspect::class, $this, $userGroups);
350
    }
351
    /**
352
     * Returns the parsed TSconfig for the fe_user
353
     * The TSconfig will be cached in $this->userTS.
354
     *
355
     * @return array TSconfig array for the fe_user
356
     */
357
    public function getUserTSconf()
358
    {
359
        if ($this->userTS === [] && !empty($this->TSdataArray)) {
360
            // Parsing the user TS (or getting from cache)
361
            $this->TSdataArray = TypoScriptParser::checkIncludeLines_array($this->TSdataArray);
362
            $userTS = implode(LF . '[GLOBAL]' . LF, $this->TSdataArray);
363
            $parseObj = GeneralUtility::makeInstance(TypoScriptParser::class);
364
            $parseObj->parse($userTS);
365
            $this->userTS = $parseObj->setup;
366
        }
367
        return $this->userTS ?? [];
368
    }
369
370
    /*****************************************
371
     *
372
     * Session data management functions
373
     *
374
     ****************************************/
375
    /**
376
     * Will write UC and session data.
377
     * If the flag $this->userData_change has been set, the function ->writeUC is called (which will save persistent user session data)
378
     *
379
     * @see getKey()
380
     * @see setKey()
381
     */
382
    public function storeSessionData()
383
    {
384
        // Saves UC and SesData if changed.
385
        if ($this->userData_change) {
386
            $this->writeUC();
387
        }
388
389
        if ($this->userSession->dataWasUpdated()) {
390
            if (!$this->userSession->hasData()) {
391
                // Remove session-data
392
                $this->removeSessionData();
393
                // Remove cookie if not logged in as the session data is removed as well
394
                if (empty($this->user['uid']) && !$this->loginHidden && $this->isCookieSet()) {
395
                    $this->removeCookie($this->name);
396
                }
397
            } elseif (!$this->userSessionManager->isSessionPersisted($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...r::isSessionPersisted() 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

397
            } elseif (!$this->userSessionManager->isSessionPersisted(/** @scrutinizer ignore-type */ $this->userSession)) {
Loading history...
398
                // Create a new session entry in the backend
399
                $this->userSessionManager->fixateAnonymousSession($this->userSession, (bool)$this->is_permanent);
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...ixateAnonymousSession() 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

399
                $this->userSessionManager->fixateAnonymousSession(/** @scrutinizer ignore-type */ $this->userSession, (bool)$this->is_permanent);
Loading history...
400
                // Now set the cookie (= fix the session)
401
                $this->setSessionCookie();
402
            } else {
403
                // Update session data of an already fixated session
404
                $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

404
                $this->userSessionManager->updateSession(/** @scrutinizer ignore-type */ $this->userSession);
Loading history...
405
            }
406
        }
407
    }
408
409
    /**
410
     * Removes data of the current session.
411
     */
412
    public function removeSessionData()
413
    {
414
        $this->userSession->overrideData([]);
415
        if ($this->userSessionManager->isSessionPersisted($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...r::isSessionPersisted() 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

415
        if ($this->userSessionManager->isSessionPersisted(/** @scrutinizer ignore-type */ $this->userSession)) {
Loading history...
416
            // Remove session record if $this->user is empty is if session is anonymous
417
            if ((empty($this->user) && !$this->loginHidden) || $this->userSession->isAnonymous()) {
418
                $this->userSessionManager->removeSession($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::removeSession() 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

418
                $this->userSessionManager->removeSession(/** @scrutinizer ignore-type */ $this->userSession);
Loading history...
419
            } else {
420
                $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

420
                $this->userSessionManager->updateSession(/** @scrutinizer ignore-type */ $this->userSession);
Loading history...
421
            }
422
        }
423
    }
424
425
    /**
426
     * Regenerate the session ID and transfer the session to new ID
427
     * Call this method whenever a user proceeds to a higher authorization level
428
     * e.g. when an anonymous session is now authenticated.
429
     * Forces cookie to be set
430
     */
431
    protected function regenerateSessionId()
432
    {
433
        parent::regenerateSessionId();
434
        // We force the cookie to be set later in the authentication process
435
        $this->dontSetCookie = false;
436
    }
437
438
    /**
439
     * Returns session data for the fe_user; Either persistent data following the fe_users uid/profile (requires login)
440
     * or current-session based (not available when browse is closed, but does not require login)
441
     *
442
     * @param string $type Session data type; Either "user" (persistent, bound to fe_users profile) or "ses" (temporary, bound to current session cookie)
443
     * @param string $key Key from the data array to return; The session data (in either case) is an array ($this->uc / $this->sessionData) and this value determines which key to return the value for.
444
     * @return mixed Returns whatever value there was in the array for the key, $key
445
     * @see setKey()
446
     */
447
    public function getKey($type, $key)
448
    {
449
        if (!$key) {
450
            return null;
451
        }
452
        $value = null;
453
        switch ($type) {
454
            case 'user':
455
                $value = $this->uc[$key];
456
                break;
457
            case 'ses':
458
                $value = $this->getSessionData($key);
459
                break;
460
        }
461
        return $value;
462
    }
463
464
    /**
465
     * Saves session data, either persistent or bound to current session cookie. Please see getKey() for more details.
466
     * When a value is set the flag $this->userData_change will be set so that the final call to ->storeSessionData() will know if a change has occurred and needs to be saved to the database.
467
     * Notice: Simply calling this function will not save the data to the database! The actual saving is done in storeSessionData() which is called as some of the last things in \TYPO3\CMS\Frontend\Http\RequestHandler. So if you exit before this point, nothing gets saved of course! And the solution is to call $GLOBALS['TSFE']->storeSessionData(); before you exit.
468
     *
469
     * @param string $type Session data type; Either "user" (persistent, bound to fe_users profile) or "ses" (temporary, bound to current session cookie)
470
     * @param string $key Key from the data array to store incoming data in; The session data (in either case) is an array ($this->uc / $this->sessionData) and this value determines in which key the $data value will be stored.
471
     * @param mixed $data The data value to store in $key
472
     * @see setKey()
473
     * @see storeSessionData()
474
     */
475
    public function setKey($type, $key, $data)
476
    {
477
        if (!$key) {
478
            return;
479
        }
480
        switch ($type) {
481
            case 'user':
482
                if ($this->user['uid']) {
483
                    if ($data === null) {
484
                        unset($this->uc[$key]);
485
                    } else {
486
                        $this->uc[$key] = $data;
487
                    }
488
                    $this->userData_change = true;
489
                }
490
                break;
491
            case 'ses':
492
                $this->setSessionData($key, $data);
493
                break;
494
        }
495
    }
496
497
    /**
498
     * Saves the tokens so that they can be used by a later incarnation of this class.
499
     *
500
     * @param string $key
501
     * @param mixed $data
502
     */
503
    public function setAndSaveSessionData($key, $data)
504
    {
505
        $this->setSessionData($key, $data);
506
        $this->storeSessionData();
507
    }
508
509
    /**
510
     * Hide the current login
511
     *
512
     * This is used by the fe_login_mode feature for pages.
513
     * A current login is unset, but we remember that there has been one.
514
     */
515
    public function hideActiveLogin()
516
    {
517
        $this->user = null;
518
        $this->loginHidden = true;
519
    }
520
521
    /**
522
     * Update the field "is_online" every 60 seconds of a logged-in user
523
     *
524
     * @internal
525
     */
526
    public function updateOnlineTimestamp()
527
    {
528
        if (!is_array($this->user) || !$this->user['uid']
529
            || $this->user['is_online'] >= $GLOBALS['EXEC_TIME'] - 60) {
530
            return;
531
        }
532
        $dbConnection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->user_table);
533
        $dbConnection->update(
534
            $this->user_table,
535
            ['is_online' => $GLOBALS['EXEC_TIME']],
536
            ['uid' => (int)$this->user['uid']]
537
        );
538
        $this->user['is_online'] = $GLOBALS['EXEC_TIME'];
539
    }
540
}
541