TYPO3CMS  8
 All Classes Namespaces Files Functions Variables Pages
FrontendUserAuthentication.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Frontend\Authentication;
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
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 
98  public $sesData = [];
99 
103  public $sesData_change = false;
104 
108  public $userData_change = false;
109 
113  public $is_permanent = false;
114 
118  protected $sessionDataTimestamp = null;
119 
123  protected $loginHidden = false;
124 
128  public function __construct()
129  {
130  parent::__construct();
131 
132  // Disable cookie by default, will be activated if saveSessionData() is called,
133  // a user is logging-in or an existing session is found
134  $this->dontSetCookie = true;
135 
136  $this->session_table = 'fe_sessions';
137  $this->name = self::getCookieName();
138  $this->get_name = 'ftu';
139  $this->loginType = 'FE';
140  $this->user_table = 'fe_users';
141  $this->username_column = 'username';
142  $this->userident_column = 'password';
143  $this->userid_column = 'uid';
144  $this->lastLogin_column = 'lastlogin';
145  $this->enablecolumns = [
146  'deleted' => 'deleted',
147  'disabled' => 'disable',
148  'starttime' => 'starttime',
149  'endtime' => 'endtime'
150  ];
151  $this->formfield_uname = 'user';
152  $this->formfield_uident = 'pass';
153  $this->formfield_status = 'logintype';
154  $this->sendNoCacheHeaders = false;
155  $this->getFallBack = true;
156  $this->getMethodEnabled = true;
157  $this->lockIP = $GLOBALS['TYPO3_CONF_VARS']['FE']['lockIP'];
158  $this->checkPid = $GLOBALS['TYPO3_CONF_VARS']['FE']['checkFeUserPid'];
159  $this->lifetime = (int)$GLOBALS['TYPO3_CONF_VARS']['FE']['lifetime'];
160  }
161 
167  public static function getCookieName()
168  {
169  $configuredCookieName = trim($GLOBALS['TYPO3_CONF_VARS']['FE']['cookieName']);
170  if (empty($configuredCookieName)) {
171  $configuredCookieName = 'fe_typo_user';
172  }
173  return $configuredCookieName;
174  }
175 
182  public function start()
183  {
184  if ((int)$this->sessionTimeout > 0 && $this->sessionTimeout < $this->lifetime) {
185  // If server session timeout is non-zero but less than client session timeout: Copy this value instead.
186  $this->sessionTimeout = $this->lifetime;
187  }
188  $this->sessionDataLifetime = (int)$GLOBALS['TYPO3_CONF_VARS']['FE']['sessionDataLifetime'];
189  if ($this->sessionDataLifetime <= 0) {
190  $this->sessionDataLifetime = 86400;
191  }
192  parent::start();
193  }
194 
201  public function getNewSessionRecord($tempuser)
202  {
203  $insertFields = parent::getNewSessionRecord($tempuser);
204  $insertFields['ses_permanent'] = $this->is_permanent ? 1 : 0;
205  return $insertFields;
206  }
207 
214  public function isSetSessionCookie()
215  {
216  return ($this->newSessionID || $this->forceSetCookie)
217  && ($this->lifetime == 0 || !isset($this->user['ses_permanent']) || !$this->user['ses_permanent']);
218  }
219 
226  public function isRefreshTimeBasedCookie()
227  {
228  return $this->lifetime > 0 && isset($this->user['ses_permanent']) && $this->user['ses_permanent'];
229  }
230 
237  public function getLoginFormData()
238  {
239  $loginData = parent::getLoginFormData();
240  if ($GLOBALS['TYPO3_CONF_VARS']['FE']['permalogin'] == 0 || $GLOBALS['TYPO3_CONF_VARS']['FE']['permalogin'] == 1) {
241  if ($this->getMethodEnabled) {
242  $isPermanent = GeneralUtility::_GP($this->formfield_permanent);
243  } else {
244  $isPermanent = GeneralUtility::_POST($this->formfield_permanent);
245  }
246  if (strlen($isPermanent) != 1) {
247  $isPermanent = $GLOBALS['TYPO3_CONF_VARS']['FE']['permalogin'];
248  } elseif (!$isPermanent) {
249  // To make sure the user gets a session cookie and doesn't keep a possibly existing time based cookie,
250  // we need to force setting the session cookie here
251  $this->forceSetCookie = true;
252  }
253  $isPermanent = (bool)$isPermanent;
254  } elseif ($GLOBALS['TYPO3_CONF_VARS']['FE']['permalogin'] == 2) {
255  $isPermanent = true;
256  } else {
257  $isPermanent = false;
258  }
259  $loginData['permanent'] = $isPermanent;
260  $this->is_permanent = $isPermanent;
261  return $loginData;
262  }
263 
272  public function createUserSession($tempuser)
273  {
274  // At this point we do not know if we need to set a session or a "permanant" cookie
275  // So we force the cookie to be set after authentication took place, which will
276  // then call setSessionCookie(), which will set a cookie with correct settings.
277  $this->dontSetCookie = false;
278  return parent::createUserSession($tempuser);
279  }
280 
288  public function fetchGroupData()
289  {
290  $this->TSdataArray = [];
291  $this->userTS = [];
292  $this->userTSUpdated = false;
293  $this->groupData = [
294  'title' => [],
295  'uid' => [],
296  'pid' => []
297  ];
298  // Setting default configuration:
299  $this->TSdataArray[] = $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultUserTSconfig'];
300  // Get the info data for auth services
301  $authInfo = $this->getAuthInfoArray();
302  if ($this->writeDevLog) {
303  if (is_array($this->user)) {
304  GeneralUtility::devLog('Get usergroups for user: ' . GeneralUtility::arrayToLogString($this->user, [$this->userid_column, $this->username_column]), __CLASS__);
305  } else {
306  GeneralUtility::devLog('Get usergroups for "anonymous" user', __CLASS__);
307  }
308  }
309  $groupDataArr = [];
310  // Use 'auth' service to find the groups for the user
311  $serviceChain = '';
312  $subType = 'getGroups' . $this->loginType;
313  while (is_object($serviceObj = GeneralUtility::makeInstanceService('auth', $subType, $serviceChain))) {
314  $serviceChain .= ',' . $serviceObj->getServiceKey();
315  $serviceObj->initAuth($subType, [], $authInfo, $this);
316  $groupData = $serviceObj->getGroups($this->user, $groupDataArr);
317  if (is_array($groupData) && !empty($groupData)) {
318  // Keys in $groupData should be unique ids of the groups (like "uid") so this function will override groups.
319  $groupDataArr = $groupData + $groupDataArr;
320  }
321  unset($serviceObj);
322  }
323  if ($this->writeDevLog && $serviceChain) {
324  GeneralUtility::devLog($subType . ' auth services called: ' . $serviceChain, __CLASS__);
325  }
326  if ($this->writeDevLog && empty($groupDataArr)) {
327  GeneralUtility::devLog('No usergroups found by services', __CLASS__);
328  }
329  if ($this->writeDevLog && !empty($groupDataArr)) {
330  GeneralUtility::devLog(count($groupDataArr) . ' usergroup records found by services', __CLASS__);
331  }
332  // Use 'auth' service to check the usergroups if they are really valid
333  foreach ($groupDataArr as $groupData) {
334  // By default a group is valid
335  $validGroup = true;
336  $serviceChain = '';
337  $subType = 'authGroups' . $this->loginType;
338  while (is_object($serviceObj = GeneralUtility::makeInstanceService('auth', $subType, $serviceChain))) {
339  $serviceChain .= ',' . $serviceObj->getServiceKey();
340  $serviceObj->initAuth($subType, [], $authInfo, $this);
341  if (!$serviceObj->authGroup($this->user, $groupData)) {
342  $validGroup = false;
343  if ($this->writeDevLog) {
344  GeneralUtility::devLog($subType . ' auth service did not auth group: ' . GeneralUtility::arrayToLogString($groupData, 'uid,title'), __CLASS__, 2);
345  }
346  break;
347  }
348  unset($serviceObj);
349  }
350  unset($serviceObj);
351  if ($validGroup && (string)$groupData['uid'] !== '') {
352  $this->groupData['title'][$groupData['uid']] = $groupData['title'];
353  $this->groupData['uid'][$groupData['uid']] = $groupData['uid'];
354  $this->groupData['pid'][$groupData['uid']] = $groupData['pid'];
355  $this->groupData['TSconfig'][$groupData['uid']] = $groupData['TSconfig'];
356  }
357  }
358  if (!empty($this->groupData) && !empty($this->groupData['TSconfig'])) {
359  // TSconfig: collect it in the order it was collected
360  foreach ($this->groupData['TSconfig'] as $TSdata) {
361  $this->TSdataArray[] = $TSdata;
362  }
363  $this->TSdataArray[] = $this->user['TSconfig'];
364  // Sort information
365  ksort($this->groupData['title']);
366  ksort($this->groupData['uid']);
367  ksort($this->groupData['pid']);
368  }
369  return !empty($this->groupData['uid']) ? count($this->groupData['uid']) : 0;
370  }
371 
378  public function getUserTSconf()
379  {
380  if (!$this->userTSUpdated) {
381  // Parsing the user TS (or getting from cache)
382  $this->TSdataArray = TypoScriptParser::checkIncludeLines_array($this->TSdataArray);
383  $userTS = implode(LF . '[GLOBAL]' . LF, $this->TSdataArray);
384  $parseObj = GeneralUtility::makeInstance(TypoScriptParser::class);
385  $parseObj->parse($userTS);
386  $this->userTS = $parseObj->setup;
387  $this->userTSUpdated = true;
388  }
389  return $this->userTS;
390  }
391 
392  /*****************************************
393  *
394  * Session data management functions
395  *
396  ****************************************/
406  public function fetchSessionData()
407  {
408  // Gets SesData if any AND if not already selected by session fixation check in ->isExistingSessionRecord()
409  if ($this->id && empty($this->sesData)) {
410  $sesDataRow = GeneralUtility::makeInstance(ConnectionPool::class)
411  ->getConnectionForTable('fe_session_data')->select(
412  ['*'],
413  'fe_session_data',
414  ['hash' => $this->id]
415  )->fetch();
416  if ($sesDataRow !== null) {
417  $this->sesData = unserialize($sesDataRow['content']);
418  $this->sessionDataTimestamp = $sesDataRow['tstamp'];
419  }
420  }
421  }
422 
432  public function storeSessionData()
433  {
434  // Saves UC and SesData if changed.
435  if ($this->userData_change) {
436  $this->writeUC('');
437  }
438  $databaseConnection = GeneralUtility::makeInstance(ConnectionPool::class)
439  ->getConnectionForTable('fe_session_data');
440  if ($this->sesData_change && $this->id) {
441  if (empty($this->sesData)) {
442  // Remove session-data
443  $this->removeSessionData();
444  // Remove cookie if not logged in as the session data is removed as well
445  if (empty($this->user['uid']) && !$this->loginHidden && $this->isCookieSet()) {
446  $this->removeCookie($this->name);
447  }
448  } elseif ($this->sessionDataTimestamp === null) {
449  // Write new session-data
450  $insertFields = [
451  'hash' => $this->id,
452  'content' => serialize($this->sesData),
453  'tstamp' => $GLOBALS['EXEC_TIME']
454  ];
455  $this->sessionDataTimestamp = $GLOBALS['EXEC_TIME'];
456  $databaseConnection->insert('fe_session_data', $insertFields);
457  // Now set the cookie (= fix the session)
458  $this->setSessionCookie();
459  } else {
460  // Update session data
461  $updateFields = [
462  'content' => serialize($this->sesData),
463  'tstamp' => $GLOBALS['EXEC_TIME']
464  ];
465  $this->sessionDataTimestamp = $GLOBALS['EXEC_TIME'];
466  $databaseConnection->update('fe_session_data', $updateFields, ['hash' => $this->id]);
467  }
468  }
469  }
470 
476  public function removeSessionData()
477  {
478  $this->sessionDataTimestamp = null;
479  GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('fe_session_data')->delete(
480  'fe_session_data',
481  ['hash' => $this->id]
482  );
483  }
484 
493  public function logoff()
494  {
495  parent::logoff();
496  // Remove the cookie on log-off, but only if we do not have an anonymous session
497  if (!$this->isExistingSessionRecord($this->id) && $this->isCookieSet()) {
498  $this->removeCookie($this->name);
499  }
500  }
501 
506  protected function regenerateSessionId()
507  {
508  $oldSessionId = $this->id;
509  parent::regenerateSessionId();
510  // Update session data with new ID
511  GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('fe_session_data')
512  ->update(
513  'fe_session_data',
514  ['hash' => $this->id],
515  ['hash' => $oldSessionId]
516  );
517 
518  // We force the cookie to be set later in the authentication process
519  $this->dontSetCookie = false;
520  }
521 
528  public function gc()
529  {
530  $timeoutTimeStamp = (int)($GLOBALS['EXEC_TIME'] - $this->sessionDataLifetime);
531  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('fe_session_data');
532  $queryBuilder->delete('fe_session_data')
533  ->where(
534  $queryBuilder->expr()->lt(
535  'tstamp',
536  $queryBuilder->createNamedParameter($timeoutTimeStamp, \PDO::PARAM_INT)
537  )
538  )
539  ->execute();
540  parent::gc();
541  }
542 
552  public function getKey($type, $key)
553  {
554  if (!$key) {
555  return null;
556  }
557  $value = null;
558  switch ($type) {
559  case 'user':
560  $value = $this->uc[$key];
561  break;
562  case 'ses':
563  $value = $this->sesData[$key];
564  break;
565  }
566  return $value;
567  }
568 
581  public function setKey($type, $key, $data)
582  {
583  if (!$key) {
584  return;
585  }
586  switch ($type) {
587  case 'user':
588  if ($this->user['uid']) {
589  if ($data === null) {
590  unset($this->uc[$key]);
591  } else {
592  $this->uc[$key] = $data;
593  }
594  $this->userData_change = true;
595  }
596  break;
597  case 'ses':
598  if ($data === null) {
599  unset($this->sesData[$key]);
600  } else {
601  $this->sesData[$key] = $data;
602  }
603  $this->sesData_change = true;
604  break;
605  }
606  }
607 
615  public function getSessionData($key)
616  {
617  return $this->getKey('ses', $key);
618  }
619 
627  public function setAndSaveSessionData($key, $data)
628  {
629  $this->setKey('ses', $key, $data);
630  $this->storeSessionData();
631  }
632 
642  public function record_registration($recs, $maxSizeOfSessionData = 0)
643  {
644  // Storing value ONLY if there is a confirmed cookie set,
645  // otherwise a shellscript could easily be spamming the fe_sessions table
646  // with bogus content and thus bloat the database
647  if (!$maxSizeOfSessionData || $this->isCookieSet()) {
648  if ($recs['clear_all']) {
649  $this->setKey('ses', 'recs', []);
650  }
651  $change = 0;
652  $recs_array = $this->getKey('ses', 'recs');
653  foreach ($recs as $table => $data) {
654  if (is_array($data)) {
655  foreach ($data as $rec_id => $value) {
656  if ($value != $recs_array[$table][$rec_id]) {
657  $recs_array[$table][$rec_id] = $value;
658  $change = 1;
659  }
660  }
661  }
662  }
663  if ($change && (!$maxSizeOfSessionData || strlen(serialize($recs_array)) < $maxSizeOfSessionData)) {
664  $this->setKey('ses', 'recs', $recs_array);
665  }
666  }
667  }
668 
678  public function isExistingSessionRecord($id)
679  {
680  // Perform check in parent function
681  $count = parent::isExistingSessionRecord($id);
682  // Check if there are any fe_session_data records for the session ID the client claims to have
683  if ($count == false) {
684  $sesDataRow = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('fe_session_data')
685  ->select(['content', 'tstamp'], 'fe_session_data', ['hash' => $id])->fetch();
686 
687  if ($sesDataRow !== null) {
688  $count = true;
689  $this->sesData = unserialize($sesDataRow['content']);
690  $this->sessionDataTimestamp = $sesDataRow['tstamp'];
691  }
692  }
693  return $count;
694  }
695 
704  public function hideActiveLogin()
705  {
706  $this->user = null;
707  $this->loginHidden = true;
708  }
709 }
static arrayToLogString(array $arr, $valueList=[], $valueLength=20)
if(TYPO3_MODE=== 'BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static makeInstance($className,...$constructorArguments)
static makeInstanceService($serviceType, $serviceSubType= '', $excludeServiceKeys=[])
static devLog($msg, $extKey, $severity=0, $dataVar=false)