TYPO3 CMS  TYPO3_8-7
FrontendUserAuthentication.php
Go to the documentation of this file.
1 <?php
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
21 
26 {
33  public $formfield_permanent = 'permalogin';
34 
39  protected $sessionDataLifetime = 86400;
40 
49  public $sessionTimeout = 6000;
50 
54  public $usergroup_column = 'usergroup';
55 
59  public $usergroup_table = 'fe_groups';
60 
64  public $groupData = [
65  'title' => [],
66  'uid' => [],
67  'pid' => []
68  ];
69 
74  public $TSdataArray = [];
75 
79  public $userTS = [];
80 
84  public $userTSUpdated = false;
85 
89  public $sesData_change = false;
90 
94  public $userData_change = false;
95 
99  public $is_permanent = false;
100 
104  protected $loginHidden = false;
105 
109  public function __construct()
110  {
111  parent::__construct();
112 
113  // Disable cookie by default, will be activated if saveSessionData() is called,
114  // a user is logging-in or an existing session is found
115  $this->dontSetCookie = true;
116 
117  $this->name = self::getCookieName();
118  $this->get_name = 'ftu';
119  $this->loginType = 'FE';
120  $this->user_table = 'fe_users';
121  $this->username_column = 'username';
122  $this->userident_column = 'password';
123  $this->userid_column = 'uid';
124  $this->lastLogin_column = 'lastlogin';
125  $this->enablecolumns = [
126  'deleted' => 'deleted',
127  'disabled' => 'disable',
128  'starttime' => 'starttime',
129  'endtime' => 'endtime'
130  ];
131  $this->formfield_uname = 'user';
132  $this->formfield_uident = 'pass';
133  $this->formfield_status = 'logintype';
134  $this->sendNoCacheHeaders = false;
135  $this->getFallBack = true;
136  $this->getMethodEnabled = true;
137  $this->lockIP = $GLOBALS['TYPO3_CONF_VARS']['FE']['lockIP'];
138  $this->checkPid = $GLOBALS['TYPO3_CONF_VARS']['FE']['checkFeUserPid'];
139  $this->lifetime = (int)$GLOBALS['TYPO3_CONF_VARS']['FE']['lifetime'];
140  }
141 
147  public static function getCookieName()
148  {
149  $configuredCookieName = trim($GLOBALS['TYPO3_CONF_VARS']['FE']['cookieName']);
150  if (empty($configuredCookieName)) {
151  $configuredCookieName = 'fe_typo_user';
152  }
153  return $configuredCookieName;
154  }
155 
161  public function start()
162  {
163  if ((int)$this->sessionTimeout > 0 && $this->sessionTimeout < $this->lifetime) {
164  // If server session timeout is non-zero but less than client session timeout: Copy this value instead.
165  $this->sessionTimeout = $this->lifetime;
166  }
167  $this->sessionDataLifetime = (int)$GLOBALS['TYPO3_CONF_VARS']['FE']['sessionDataLifetime'];
168  if ($this->sessionDataLifetime <= 0) {
169  $this->sessionDataLifetime = 86400;
170  }
171  parent::start();
172  }
173 
180  public function getNewSessionRecord($tempuser)
181  {
182  $insertFields = parent::getNewSessionRecord($tempuser);
183  $insertFields['ses_permanent'] = $this->is_permanent ? 1 : 0;
184  return $insertFields;
185  }
186 
193  public function isSetSessionCookie()
194  {
195  return ($this->newSessionID || $this->forceSetCookie)
196  && ((int)$this->lifetime === 0 || !isset($this->user['ses_permanent']) || !$this->user['ses_permanent']);
197  }
198 
205  public function isRefreshTimeBasedCookie()
206  {
207  return $this->lifetime > 0 && isset($this->user['ses_permanent']) && $this->user['ses_permanent'];
208  }
209 
216  public function getLoginFormData()
217  {
218  $loginData = parent::getLoginFormData();
219  if ($GLOBALS['TYPO3_CONF_VARS']['FE']['permalogin'] == 0 || $GLOBALS['TYPO3_CONF_VARS']['FE']['permalogin'] == 1) {
220  if ($this->getMethodEnabled) {
221  $isPermanent = GeneralUtility::_GP($this->formfield_permanent);
222  } else {
223  $isPermanent = GeneralUtility::_POST($this->formfield_permanent);
224  }
225  if (strlen($isPermanent) != 1) {
226  $isPermanent = $GLOBALS['TYPO3_CONF_VARS']['FE']['permalogin'];
227  } elseif (!$isPermanent) {
228  // To make sure the user gets a session cookie and doesn't keep a possibly existing time based cookie,
229  // we need to force setting the session cookie here
230  $this->forceSetCookie = true;
231  }
232  $isPermanent = (bool)$isPermanent;
233  } elseif ($GLOBALS['TYPO3_CONF_VARS']['FE']['permalogin'] == 2) {
234  $isPermanent = true;
235  } else {
236  $isPermanent = false;
237  }
238  $loginData['permanent'] = $isPermanent;
239  $this->is_permanent = $isPermanent;
240  return $loginData;
241  }
242 
251  public function createUserSession($tempuser)
252  {
253  // At this point we do not know if we need to set a session or a permanent cookie
254  // So we force the cookie to be set after authentication took place, which will
255  // then call setSessionCookie(), which will set a cookie with correct settings.
256  $this->dontSetCookie = false;
257  return parent::createUserSession($tempuser);
258  }
259 
267  public function fetchGroupData()
268  {
269  $this->TSdataArray = [];
270  $this->userTS = [];
271  $this->userTSUpdated = false;
272  $this->groupData = [
273  'title' => [],
274  'uid' => [],
275  'pid' => []
276  ];
277  // Setting default configuration:
278  $this->TSdataArray[] = $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultUserTSconfig'];
279  // Get the info data for auth services
280  $authInfo = $this->getAuthInfoArray();
281  if ($this->writeDevLog) {
282  if (is_array($this->user)) {
283  GeneralUtility::devLog('Get usergroups for user: ' . GeneralUtility::arrayToLogString($this->user, [$this->userid_column, $this->username_column]), __CLASS__);
284  } else {
285  GeneralUtility::devLog('Get usergroups for "anonymous" user', __CLASS__);
286  }
287  }
288  $groupDataArr = [];
289  // Use 'auth' service to find the groups for the user
290  $serviceChain = '';
291  $subType = 'getGroups' . $this->loginType;
292  while (is_object($serviceObj = GeneralUtility::makeInstanceService('auth', $subType, $serviceChain))) {
293  $serviceChain .= ',' . $serviceObj->getServiceKey();
294  $serviceObj->initAuth($subType, [], $authInfo, $this);
295  $groupData = $serviceObj->getGroups($this->user, $groupDataArr);
296  if (is_array($groupData) && !empty($groupData)) {
297  // Keys in $groupData should be unique ids of the groups (like "uid") so this function will override groups.
298  $groupDataArr = $groupData + $groupDataArr;
299  }
300  unset($serviceObj);
301  }
302  if ($this->writeDevLog && $serviceChain) {
303  GeneralUtility::devLog($subType . ' auth services called: ' . $serviceChain, __CLASS__);
304  }
305  if ($this->writeDevLog && empty($groupDataArr)) {
306  GeneralUtility::devLog('No usergroups found by services', __CLASS__);
307  }
308  if ($this->writeDevLog && !empty($groupDataArr)) {
309  GeneralUtility::devLog(count($groupDataArr) . ' usergroup records found by services', __CLASS__);
310  }
311  // Use 'auth' service to check the usergroups if they are really valid
312  foreach ($groupDataArr as $groupData) {
313  // By default a group is valid
314  $validGroup = true;
315  $serviceChain = '';
316  $subType = 'authGroups' . $this->loginType;
317  while (is_object($serviceObj = GeneralUtility::makeInstanceService('auth', $subType, $serviceChain))) {
318  $serviceChain .= ',' . $serviceObj->getServiceKey();
319  $serviceObj->initAuth($subType, [], $authInfo, $this);
320  if (!$serviceObj->authGroup($this->user, $groupData)) {
321  $validGroup = false;
322  if ($this->writeDevLog) {
323  GeneralUtility::devLog($subType . ' auth service did not auth group: ' . GeneralUtility::arrayToLogString($groupData, 'uid,title'), __CLASS__, 2);
324  }
325  break;
326  }
327  unset($serviceObj);
328  }
329  unset($serviceObj);
330  if ($validGroup && (string)$groupData['uid'] !== '') {
331  $this->groupData['title'][$groupData['uid']] = $groupData['title'];
332  $this->groupData['uid'][$groupData['uid']] = $groupData['uid'];
333  $this->groupData['pid'][$groupData['uid']] = $groupData['pid'];
334  $this->groupData['TSconfig'][$groupData['uid']] = $groupData['TSconfig'];
335  }
336  }
337  if (!empty($this->groupData) && !empty($this->groupData['TSconfig'])) {
338  // TSconfig: collect it in the order it was collected
339  foreach ($this->groupData['TSconfig'] as $TSdata) {
340  $this->TSdataArray[] = $TSdata;
341  }
342  $this->TSdataArray[] = $this->user['TSconfig'];
343  // Sort information
344  ksort($this->groupData['title']);
345  ksort($this->groupData['uid']);
346  ksort($this->groupData['pid']);
347  }
348  return !empty($this->groupData['uid']) ? count($this->groupData['uid']) : 0;
349  }
350 
357  public function getUserTSconf()
358  {
359  if (!$this->userTSUpdated) {
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  $this->userTSUpdated = true;
367  }
368  return $this->userTS;
369  }
370 
371  /*****************************************
372  *
373  * Session data management functions
374  *
375  ****************************************/
383  public function storeSessionData()
384  {
385  // Saves UC and SesData if changed.
386  if ($this->userData_change) {
387  $this->writeUC();
388  }
389 
390  if ($this->sesData_change && $this->id) {
391  if (empty($this->sessionData)) {
392  // Remove session-data
393  $this->removeSessionData();
394  // Remove cookie if not logged in as the session data is removed as well
395  if (empty($this->user['uid']) && !$this->loginHidden && $this->isCookieSet()) {
396  $this->removeCookie($this->name);
397  }
398  } elseif (!$this->isExistingSessionRecord($this->id)) {
399  $sessionRecord = $this->getNewSessionRecord([]);
400  $sessionRecord['ses_anonymous'] = 1;
401  $sessionRecord['ses_data'] = serialize($this->sessionData);
402  $updatedSession = $this->getSessionBackend()->set($this->id, $sessionRecord);
403  $this->user = array_merge($this->user ?? [], $updatedSession);
404  // Now set the cookie (= fix the session)
405  $this->setSessionCookie();
406  } else {
407  // Update session data
408  $insertFields = [
409  'ses_data' => serialize($this->sessionData)
410  ];
411  $updatedSession = $this->getSessionBackend()->update($this->id, $insertFields);
412  $this->user = array_merge($this->user ?? [], $updatedSession);
413  }
414  }
415  }
416 
420  public function removeSessionData()
421  {
422  if (!empty($this->sessionData)) {
423  $this->sesData_change = true;
424  }
425  $this->sessionData = [];
426 
427  if ($this->isExistingSessionRecord($this->id)) {
428  // Remove session record if $this->user is empty is if session is anonymous
429  if ((empty($this->user) && !$this->loginHidden) || $this->user['ses_anonymous']) {
430  $this->getSessionBackend()->remove($this->id);
431  } else {
432  $this->getSessionBackend()->update($this->id, [
433  'ses_data' => ''
434  ]);
435  }
436  }
437  }
438 
447  protected function performLogoff()
448  {
449  $oldSession = [];
450  $sessionData = [];
451  try {
452  // Session might not be loaded at this point, so fetch it
453  $oldSession = $this->getSessionBackend()->get($this->id);
454  $sessionData = unserialize($oldSession['ses_data']);
455  } catch (SessionNotFoundException $e) {
456  // Leave uncaught, will unset cookie later in this method
457  }
458 
459  if (!empty($sessionData) && $GLOBALS['TYPO3_CONF_VARS']['SYS']['features']['security.frontend.keepSessionDataOnLogout']) {
460  // Regenerate session as anonymous
461  $this->regenerateSessionId($oldSession, true);
462  $this->user = null;
463  } else {
464  parent::performLogoff();
465  if ($this->isCookieSet()) {
466  $this->removeCookie($this->name);
467  }
468  }
469  }
470 
480  protected function regenerateSessionId(array $existingSessionRecord = [], bool $anonymous = false)
481  {
482  if (empty($existingSessionRecord)) {
483  $existingSessionRecord = $this->getSessionBackend()->get($this->id);
484  }
485  $existingSessionRecord['ses_anonymous'] = (int)$anonymous;
486  if ($anonymous) {
487  $existingSessionRecord['ses_userid'] = 0;
488  }
489  parent::regenerateSessionId($existingSessionRecord, $anonymous);
490  // We force the cookie to be set later in the authentication process
491  $this->dontSetCookie = false;
492  }
493 
503  public function getKey($type, $key)
504  {
505  if (!$key) {
506  return null;
507  }
508  $value = null;
509  switch ($type) {
510  case 'user':
511  $value = $this->uc[$key];
512  break;
513  case 'ses':
514  $value = $this->getSessionData($key);
515  break;
516  }
517  return $value;
518  }
519 
531  public function setKey($type, $key, $data)
532  {
533  if (!$key) {
534  return;
535  }
536  switch ($type) {
537  case 'user':
538  if ($this->user['uid']) {
539  if ($data === null) {
540  unset($this->uc[$key]);
541  } else {
542  $this->uc[$key] = $data;
543  }
544  $this->userData_change = true;
545  }
546  break;
547  case 'ses':
548  $this->setSessionData($key, $data);
549  break;
550  }
551  }
552 
560  public function setSessionData($key, $data)
561  {
562  $this->sesData_change = true;
563  if ($data === null) {
564  unset($this->sessionData[$key]);
565  return;
566  }
567  parent::setSessionData($key, $data);
568  }
569 
576  public function setAndSaveSessionData($key, $data)
577  {
578  $this->setSessionData($key, $data);
579  $this->storeSessionData();
580  }
581 
591  public function record_registration($recs, $maxSizeOfSessionData = 0)
592  {
594  // Storing value ONLY if there is a confirmed cookie set,
595  // otherwise a shellscript could easily be spamming the fe_sessions table
596  // with bogus content and thus bloat the database
597  if (!$maxSizeOfSessionData || $this->isCookieSet()) {
598  if ($recs['clear_all']) {
599  $this->setKey('ses', 'recs', []);
600  }
601  $change = 0;
602  $recs_array = $this->getKey('ses', 'recs');
603  foreach ($recs as $table => $data) {
604  if (is_array($data)) {
605  foreach ($data as $rec_id => $value) {
606  if ($value != $recs_array[$table][$rec_id]) {
607  $recs_array[$table][$rec_id] = $value;
608  $change = 1;
609  }
610  }
611  }
612  }
613  if ($change && (!$maxSizeOfSessionData || strlen(serialize($recs_array)) < $maxSizeOfSessionData)) {
614  $this->setKey('ses', 'recs', $recs_array);
615  }
616  }
617  }
618 
624  public function gc()
625  {
626  $this->getSessionBackend()->collectGarbage($this->gc_time, $this->sessionDataLifetime);
627  }
628 
635  public function hideActiveLogin()
636  {
637  $this->user = null;
638  $this->loginHidden = true;
639  }
640 }
static devLog($msg, $extKey, $severity=0, $dataVar=false)
static arrayToLogString(array $arr, $valueList=[], $valueLength=20)
static makeInstance($className,... $constructorArguments)
static makeInstanceService($serviceType, $serviceSubType='', $excludeServiceKeys=[])
regenerateSessionId(array $existingSessionRecord=[], bool $anonymous=false)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']