[!!!][TASK] Cleanup dual-use of auth_timeout_field in AbstractUserAuthentication
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Authentication / AbstractUserAuthentication.php
1 <?php
2 namespace TYPO3\CMS\Core\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
17 use TYPO3\CMS\Backend\Utility\BackendUtility;
18 use TYPO3\CMS\Core\Database\DatabaseConnection;
19 use TYPO3\CMS\Core\Exception;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21 use TYPO3\CMS\Core\Utility\MathUtility;
22 use TYPO3\CMS\Core\Crypto\Random;
23
24 /**
25 * Authentication of users in TYPO3
26 *
27 * This class is used to authenticate a login user.
28 * The class is used by both the frontend and backend.
29 * In both cases this class is a parent class to BackendUserAuthentication and FrontenUserAuthentication
30 *
31 * See Inside TYPO3 for more information about the API of the class and internal variables.
32 */
33 abstract class AbstractUserAuthentication
34 {
35 /**
36 * Table to use for session data
37 * @var string
38 */
39 public $session_table = '';
40
41 /**
42 * Session/Cookie name
43 * @var string
44 */
45 public $name = '';
46
47 /**
48 * Session/GET-var name
49 * @var string
50 */
51 public $get_name = '';
52
53 /**
54 * Table in database with user data
55 * @var string
56 */
57 public $user_table = '';
58
59 /**
60 * Table in database with user groups
61 * @var string
62 */
63 public $usergroup_table = '';
64
65 /**
66 * Column for login-name
67 * @var string
68 */
69 public $username_column = '';
70
71 /**
72 * Column for password
73 * @var string
74 */
75 public $userident_column = '';
76
77 /**
78 * Column for user-id
79 * @var string
80 */
81 public $userid_column = '';
82
83 /**
84 * Column for user group information
85 * @var string
86 */
87 public $usergroup_column = '';
88
89 /**
90 * Column name for last login timestamp
91 * @var string
92 */
93 public $lastLogin_column = '';
94
95 /**
96 * Enable field columns of user table
97 * @var array
98 */
99 public $enablecolumns = array(
100 'rootLevel' => '',
101 // Boolean: If TRUE, 'AND pid=0' will be a part of the query...
102 'disabled' => '',
103 'starttime' => '',
104 'endtime' => '',
105 'deleted' => ''
106 );
107
108 /**
109 * @var bool
110 */
111 public $showHiddenRecords = false;
112
113 /**
114 * Form field with login-name
115 * @var string
116 */
117 public $formfield_uname = '';
118
119 /**
120 * Form field with password
121 * @var string
122 */
123 public $formfield_uident = '';
124
125 /**
126 * Form field with status: *'login', 'logout'. If empty login is not verified.
127 * @var string
128 */
129 public $formfield_status = '';
130
131 /**
132 * Session timeout (on the server)
133 *
134 * If >0: session-timeout in seconds.
135 * If 0: no timeout.
136 *
137 * @var int
138 */
139 public $sessionTimeout = 0;
140
141 /**
142 * Name for a field to fetch the server session timeout from.
143 * If not empty this is a field name from the user table where the timeout can be found.
144 * @var string
145 */
146 public $auth_timeout_field = '';
147
148 /**
149 * Lifetime for the session-cookie (on the client)
150 *
151 * If >0: permanent cookie with given lifetime
152 * If 0: session-cookie
153 * Session-cookie means the browser will remove it when the browser is closed.
154 *
155 * @var int
156 */
157 public $lifetime = 0;
158
159 /**
160 * GarbageCollection
161 * Purge all server session data older than $gc_time seconds.
162 * 0 = default to $this->sessionTimeout or use 86400 seconds (1 day) if $this->sessionTimeout == 0
163 * @var int
164 */
165 public $gc_time = 0;
166
167 /**
168 * Probability for g arbage collection to be run (in percent)
169 * @var int
170 */
171 public $gc_probability = 1;
172
173 /**
174 * Decides if the writelog() function is called at login and logout
175 * @var bool
176 */
177 public $writeStdLog = false;
178
179 /**
180 * Log failed login attempts
181 * @var bool
182 */
183 public $writeAttemptLog = false;
184
185 /**
186 * Send no-cache headers
187 * @var bool
188 */
189 public $sendNoCacheHeaders = true;
190
191 /**
192 * If this is set, authentication is also accepted by $_GET.
193 * Notice that the identification is NOT 128bit MD5 hash but reduced.
194 * This is done in order to minimize the size for mobile-devices, such as WAP-phones
195 * @var bool
196 */
197 public $getFallBack = false;
198
199 /**
200 * The ident-hash is normally 32 characters and should be!
201 * But if you are making sites for WAP-devices or other low-bandwidth stuff,
202 * you may shorten the length.
203 * Never let this value drop below 6!
204 * A length of 6 would give you more than 16 mio possibilities.
205 * @var int
206 */
207 public $hash_length = 32;
208
209 /**
210 * Setting this flag TRUE lets user-authentication happen from GET_VARS if
211 * POST_VARS are not set. Thus you may supply username/password with the URL.
212 * @var bool
213 */
214 public $getMethodEnabled = false;
215
216 /**
217 * If set to 4, the session will be locked to the user's IP address (all four numbers).
218 * Reducing this to 1-3 means that only the given number of parts of the IP address is used.
219 * @var int
220 */
221 public $lockIP = 4;
222
223 /**
224 * Keyword list (comma separated list with no spaces!)
225 * Each keyword indicates some information that can be included in a hash made to lock down user sessions.
226 * Configurable by $GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['lockHashKeyWords']
227 * @var string
228 */
229 public $lockHashKeyWords = 'useragent';
230
231 /**
232 * @var string
233 */
234 public $warningEmail = '';
235
236 /**
237 * Time span (in seconds) within the number of failed logins are collected
238 * @var int
239 */
240 public $warningPeriod = 3600;
241
242 /**
243 * The maximum accepted number of warnings before an email to $warningEmail is sent
244 * @var int
245 */
246 public $warningMax = 3;
247
248 /**
249 * If set, the user-record must be stored at the page defined by $checkPid_value
250 * @var bool
251 */
252 public $checkPid = true;
253
254 /**
255 * The page id the user record must be stored at
256 * @var int
257 */
258 public $checkPid_value = 0;
259
260 /**
261 * session_id (MD5-hash)
262 * @var string
263 * @internal
264 */
265 public $id;
266
267 /**
268 * Indicates if an authentication was started but failed
269 * @var bool
270 */
271 public $loginFailure = false;
272
273 /**
274 * Will be set to TRUE if the login session is actually written during auth-check.
275 * @var bool
276 */
277 public $loginSessionStarted = false;
278
279 /**
280 * @var array|NULL contains user- AND session-data from database (joined tables)
281 * @internal
282 */
283 public $user = null;
284
285 /**
286 * Will be added to the url (eg. '&login=ab7ef8d...')
287 * GET-auth-var if getFallBack is TRUE. Should be inserted in links!
288 * @var string
289 * @internal
290 */
291 public $get_URL_ID = '';
292
293 /**
294 * Will be set to TRUE if a new session ID was created
295 * @var bool
296 */
297 public $newSessionID = false;
298
299 /**
300 * Will force the session cookie to be set every time (lifetime must be 0)
301 * @var bool
302 */
303 public $forceSetCookie = false;
304
305 /**
306 * Will prevent the setting of the session cookie (takes precedence over forceSetCookie)
307 * @var bool
308 */
309 public $dontSetCookie = false;
310
311 /**
312 * @var bool
313 */
314 protected $cookieWasSetOnCurrentRequest = false;
315
316 /**
317 * Login type, used for services.
318 * @var string
319 */
320 public $loginType = '';
321
322 /**
323 * "auth" services configuration array from $GLOBALS['TYPO3_CONF_VARS']['SVCONF']['auth']
324 * @var array
325 */
326 public $svConfig = array();
327
328 /**
329 * Write messages to the devlog
330 * @var bool
331 */
332 public $writeDevLog = false;
333
334 /**
335 * @var array
336 */
337 public $uc;
338
339 /**
340 * @var DatabaseConnection
341 */
342 protected $db = null;
343
344 /**
345 * Initialize some important variables
346 */
347 public function __construct()
348 {
349 $this->db = $this->getDatabaseConnection();
350 }
351
352 /**
353 * Starts a user session
354 * Typical configurations will:
355 * a) check if session cookie was set and if not, set one,
356 * b) check if a password/username was sent and if so, try to authenticate the user
357 * c) Lookup a session attached to a user and check timeout etc.
358 * d) Garbage collection, setting of no-cache headers.
359 * If a user is authenticated the database record of the user (array) will be set in the ->user internal variable.
360 *
361 * @throws Exception
362 * @return void
363 */
364 public function start()
365 {
366 // Backend or frontend login - used for auth services
367 if (empty($this->loginType)) {
368 throw new Exception('No loginType defined, should be set explicitly by subclass');
369 }
370 // Enable dev logging if set
371 if ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['writeDevLog']) {
372 $this->writeDevLog = true;
373 }
374 if ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['writeDevLog' . $this->loginType]) {
375 $this->writeDevLog = true;
376 }
377 if (TYPO3_DLOG) {
378 $this->writeDevLog = true;
379 }
380 if ($this->writeDevLog) {
381 GeneralUtility::devLog('## Beginning of auth logging.', AbstractUserAuthentication::class);
382 }
383 // Init vars.
384 $mode = '';
385 $this->newSessionID = false;
386 // $id is set to ses_id if cookie is present. Else set to FALSE, which will start a new session
387 $id = $this->getCookie($this->name);
388 $this->svConfig = $GLOBALS['TYPO3_CONF_VARS']['SVCONF']['auth'];
389
390 // If fallback to get mode....
391 if (!$id && $this->getFallBack && $this->get_name) {
392 $id = isset($_GET[$this->get_name]) ? GeneralUtility::_GET($this->get_name) : '';
393 if (strlen($id) != $this->hash_length) {
394 $id = '';
395 }
396 $mode = 'get';
397 }
398
399 // If new session or client tries to fix session...
400 if (!$id || !$this->isExistingSessionRecord($id)) {
401 // New random session-$id is made
402 $id = $this->createSessionId();
403 // New session
404 $this->newSessionID = true;
405 }
406 // Internal var 'id' is set
407 $this->id = $id;
408 // If fallback to get mode....
409 if ($mode == 'get' && $this->getFallBack && $this->get_name) {
410 $this->get_URL_ID = '&' . $this->get_name . '=' . $id;
411 }
412 // Set session hashKey lock keywords from configuration; currently only 'useragent' can be used.
413 $this->lockHashKeyWords = $GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['lockHashKeyWords'];
414 // Make certain that NO user is set initially
415 $this->user = null;
416 // Set all possible headers that could ensure that the script is not cached on the client-side
417 if ($this->sendNoCacheHeaders && !(TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_CLI)) {
418 header('Expires: 0');
419 header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
420 $cacheControlHeader = 'no-cache, must-revalidate';
421 $pragmaHeader = 'no-cache';
422 // Prevent error message in IE when using a https connection
423 // see http://forge.typo3.org/issues/24125
424 $clientInfo = GeneralUtility::clientInfo();
425 if ($clientInfo['BROWSER'] === 'msie' && GeneralUtility::getIndpEnv('TYPO3_SSL')) {
426 // Some IEs can not handle no-cache
427 // see http://support.microsoft.com/kb/323308/en-us
428 $cacheControlHeader = 'must-revalidate';
429 // IE needs "Pragma: private" if SSL connection
430 $pragmaHeader = 'private';
431 }
432 header('Cache-Control: ' . $cacheControlHeader);
433 header('Pragma: ' . $pragmaHeader);
434 }
435 // Load user session, check to see if anyone has submitted login-information and if so authenticate
436 // the user with the session. $this->user[uid] may be used to write log...
437 $this->checkAuthentication();
438 // Setting cookies
439 if (!$this->dontSetCookie) {
440 $this->setSessionCookie();
441 }
442 // Hook for alternative ways of filling the $this->user array (is used by the "timtaw" extension)
443 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postUserLookUp'])) {
444 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postUserLookUp'] as $funcName) {
445 $_params = array(
446 'pObj' => &$this
447 );
448 GeneralUtility::callUserFunction($funcName, $_params, $this);
449 }
450 }
451 // Set $this->gc_time if not explicitly specified
452 if ($this->gc_time === 0) {
453 // Default to 86400 seconds (1 day) if $this->sessionTimeout is 0
454 $this->gc_time = $this->sessionTimeout === 0 ? 86400 : $this->sessionTimeout;
455 }
456 // If we're lucky we'll get to clean up old sessions
457 if (rand() % 100 <= $this->gc_probability) {
458 $this->gc();
459 }
460 }
461
462 /**
463 * Sets the session cookie for the current disposal.
464 *
465 * @return void
466 * @throws Exception
467 */
468 protected function setSessionCookie()
469 {
470 $isSetSessionCookie = $this->isSetSessionCookie();
471 $isRefreshTimeBasedCookie = $this->isRefreshTimeBasedCookie();
472 if ($isSetSessionCookie || $isRefreshTimeBasedCookie) {
473 $settings = $GLOBALS['TYPO3_CONF_VARS']['SYS'];
474 // Get the domain to be used for the cookie (if any):
475 $cookieDomain = $this->getCookieDomain();
476 // If no cookie domain is set, use the base path:
477 $cookiePath = $cookieDomain ? '/' : GeneralUtility::getIndpEnv('TYPO3_SITE_PATH');
478 // If the cookie lifetime is set, use it:
479 $cookieExpire = $isRefreshTimeBasedCookie ? $GLOBALS['EXEC_TIME'] + $this->lifetime : 0;
480 // Use the secure option when the current request is served by a secure connection:
481 $cookieSecure = (bool)$settings['cookieSecure'] && GeneralUtility::getIndpEnv('TYPO3_SSL');
482 // Deliver cookies only via HTTP and prevent possible XSS by JavaScript:
483 $cookieHttpOnly = (bool)$settings['cookieHttpOnly'];
484 // Do not set cookie if cookieSecure is set to "1" (force HTTPS) and no secure channel is used:
485 if ((int)$settings['cookieSecure'] !== 1 || GeneralUtility::getIndpEnv('TYPO3_SSL')) {
486 setcookie($this->name, $this->id, $cookieExpire, $cookiePath, $cookieDomain, $cookieSecure, $cookieHttpOnly);
487 $this->cookieWasSetOnCurrentRequest = true;
488 } else {
489 throw new Exception('Cookie was not set since HTTPS was forced in $TYPO3_CONF_VARS[SYS][cookieSecure].', 1254325546);
490 }
491 if ($this->writeDevLog) {
492 $devLogMessage = ($isRefreshTimeBasedCookie ? 'Updated Cookie: ' : 'Set Cookie: ') . $this->id;
493 GeneralUtility::devLog($devLogMessage . ($cookieDomain ? ', ' . $cookieDomain : ''), AbstractUserAuthentication::class);
494 }
495 }
496 }
497
498 /**
499 * Gets the domain to be used on setting cookies.
500 * The information is taken from the value in $GLOBALS['TYPO3_CONF_VARS']['SYS']['cookieDomain'].
501 *
502 * @return string The domain to be used on setting cookies
503 */
504 protected function getCookieDomain()
505 {
506 $result = '';
507 $cookieDomain = $GLOBALS['TYPO3_CONF_VARS']['SYS']['cookieDomain'];
508 // If a specific cookie domain is defined for a given TYPO3_MODE,
509 // use that domain
510 if (!empty($GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieDomain'])) {
511 $cookieDomain = $GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieDomain'];
512 }
513 if ($cookieDomain) {
514 if ($cookieDomain[0] == '/') {
515 $match = array();
516 $matchCnt = @preg_match($cookieDomain, GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY'), $match);
517 if ($matchCnt === false) {
518 GeneralUtility::sysLog('The regular expression for the cookie domain (' . $cookieDomain . ') contains errors. The session is not shared across sub-domains.', 'core', GeneralUtility::SYSLOG_SEVERITY_ERROR);
519 } elseif ($matchCnt) {
520 $result = $match[0];
521 }
522 } else {
523 $result = $cookieDomain;
524 }
525 }
526 return $result;
527 }
528
529 /**
530 * Get the value of a specified cookie.
531 *
532 * @param string $cookieName The cookie ID
533 * @return string The value stored in the cookie
534 */
535 protected function getCookie($cookieName)
536 {
537 return isset($_COOKIE[$cookieName]) ? stripslashes($_COOKIE[$cookieName]) : '';
538 }
539
540 /**
541 * Determine whether a session cookie needs to be set (lifetime=0)
542 *
543 * @return bool
544 * @internal
545 */
546 public function isSetSessionCookie()
547 {
548 return ($this->newSessionID || $this->forceSetCookie) && $this->lifetime == 0;
549 }
550
551 /**
552 * Determine whether a non-session cookie needs to be set (lifetime>0)
553 *
554 * @return bool
555 * @internal
556 */
557 public function isRefreshTimeBasedCookie()
558 {
559 return $this->lifetime > 0;
560 }
561
562 /**
563 * Checks if a submission of username and password is present or use other authentication by auth services
564 *
565 * @throws \RuntimeException
566 * @return void
567 * @internal
568 */
569 public function checkAuthentication()
570 {
571 // No user for now - will be searched by service below
572 $tempuserArr = array();
573 $tempuser = false;
574 // User is not authenticated by default
575 $authenticated = false;
576 // User want to login with passed login data (name/password)
577 $activeLogin = false;
578 // Indicates if an active authentication failed (not auto login)
579 $this->loginFailure = false;
580 if ($this->writeDevLog) {
581 GeneralUtility::devLog('Login type: ' . $this->loginType, AbstractUserAuthentication::class);
582 }
583 // The info array provide additional information for auth services
584 $authInfo = $this->getAuthInfoArray();
585 // Get Login/Logout data submitted by a form or params
586 $loginData = $this->getLoginFormData();
587 if ($this->writeDevLog) {
588 GeneralUtility::devLog('Login data: ' . GeneralUtility::arrayToLogString($loginData), AbstractUserAuthentication::class);
589 }
590 // Active logout (eg. with "logout" button)
591 if ($loginData['status'] == 'logout') {
592 if ($this->writeStdLog) {
593 // $type,$action,$error,$details_nr,$details,$data,$tablename,$recuid,$recpid
594 $this->writelog(255, 2, 0, 2, 'User %s logged out', array($this->user['username']), '', 0, 0);
595 }
596 // Logout written to log
597 if ($this->writeDevLog) {
598 GeneralUtility::devLog('User logged out. Id: ' . $this->id, AbstractUserAuthentication::class, -1);
599 }
600 $this->logoff();
601 }
602 // Active login (eg. with login form)
603 if ($loginData['status'] == 'login') {
604 $activeLogin = true;
605 if ($this->writeDevLog) {
606 GeneralUtility::devLog('Active login (eg. with login form)', AbstractUserAuthentication::class);
607 }
608 // check referer for submitted login values
609 if ($this->formfield_status && $loginData['uident'] && $loginData['uname']) {
610 $httpHost = GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY');
611 if (!$this->getMethodEnabled && ($httpHost != $authInfo['refInfo']['host'] && !$GLOBALS['TYPO3_CONF_VARS']['SYS']['doNotCheckReferer'])) {
612 throw new \RuntimeException('TYPO3 Fatal Error: Error: This host address ("' . $httpHost . '") and the referer host ("' . $authInfo['refInfo']['host'] . '") mismatches! ' .
613 'It is possible that the environment variable HTTP_REFERER is not passed to the script because of a proxy. ' .
614 'The site administrator can disable this check in the "All Configuration" section of the Install Tool (flag: TYPO3_CONF_VARS[SYS][doNotCheckReferer]).', 1270853930);
615 }
616 // Delete old user session if any
617 $this->logoff();
618 }
619 // Refuse login for _CLI users, if not processing a CLI request type
620 // (although we shouldn't be here in case of a CLI request type)
621 if (strtoupper(substr($loginData['uname'], 0, 5)) == '_CLI_' && !(TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_CLI)) {
622 throw new \RuntimeException('TYPO3 Fatal Error: You have tried to login using a CLI user. Access prohibited!', 1270853931);
623 }
624 }
625 // The following code makes auto-login possible (if configured). No submitted data needed
626 // Determine whether we need to skip session update.
627 // This is used mainly for checking session timeout without
628 // refreshing the session itself while checking.
629 $skipSessionUpdate = (bool)GeneralUtility::_GP('skipSessionUpdate');
630 $haveSession = false;
631 if (!$this->newSessionID) {
632 // Read user session
633 $authInfo['userSession'] = $this->fetchUserSession($skipSessionUpdate);
634 $haveSession = is_array($authInfo['userSession']);
635 }
636 if ($this->writeDevLog) {
637 if ($haveSession) {
638 GeneralUtility::devLog('User session found: ' . GeneralUtility::arrayToLogString($authInfo['userSession'], array($this->userid_column, $this->username_column)), AbstractUserAuthentication::class, 0);
639 } else {
640 GeneralUtility::devLog('No user session found.', AbstractUserAuthentication::class, 2);
641 }
642 if (is_array($this->svConfig['setup'])) {
643 GeneralUtility::devLog('SV setup: ' . GeneralUtility::arrayToLogString($this->svConfig['setup']), AbstractUserAuthentication::class, 0);
644 }
645 }
646 // Fetch user if ...
647 if (
648 $activeLogin || $this->svConfig['setup'][$this->loginType . '_alwaysFetchUser']
649 || !$haveSession && $this->svConfig['setup'][$this->loginType . '_fetchUserIfNoSession']
650 ) {
651 // Use 'auth' service to find the user
652 // First found user will be used
653 $serviceChain = '';
654 $subType = 'getUser' . $this->loginType;
655 while (is_object($serviceObj = GeneralUtility::makeInstanceService('auth', $subType, $serviceChain))) {
656 $serviceChain .= ',' . $serviceObj->getServiceKey();
657 $serviceObj->initAuth($subType, $loginData, $authInfo, $this);
658 if ($row = $serviceObj->getUser()) {
659 $tempuserArr[] = $row;
660 if ($this->writeDevLog) {
661 GeneralUtility::devLog('User found: ' . GeneralUtility::arrayToLogString($row, array($this->userid_column, $this->username_column)), AbstractUserAuthentication::class, 0);
662 }
663 // User found, just stop to search for more if not configured to go on
664 if (!$this->svConfig['setup'][$this->loginType . '_fetchAllUsers']) {
665 break;
666 }
667 }
668 unset($serviceObj);
669 }
670 unset($serviceObj);
671 if ($this->writeDevLog && $this->svConfig['setup'][$this->loginType . '_alwaysFetchUser']) {
672 GeneralUtility::devLog($this->loginType . '_alwaysFetchUser option is enabled', AbstractUserAuthentication::class);
673 }
674 if ($this->writeDevLog && $serviceChain) {
675 GeneralUtility::devLog($subType . ' auth services called: ' . $serviceChain, AbstractUserAuthentication::class);
676 }
677 if ($this->writeDevLog && empty($tempuserArr)) {
678 GeneralUtility::devLog('No user found by services', AbstractUserAuthentication::class);
679 }
680 if ($this->writeDevLog && !empty($tempuserArr)) {
681 GeneralUtility::devLog(count($tempuserArr) . ' user records found by services', AbstractUserAuthentication::class);
682 }
683 }
684 // If no new user was set we use the already found user session
685 if (empty($tempuserArr) && $haveSession) {
686 $tempuserArr[] = $authInfo['userSession'];
687 $tempuser = $authInfo['userSession'];
688 // User is authenticated because we found a user session
689 $authenticated = true;
690 if ($this->writeDevLog) {
691 GeneralUtility::devLog('User session used: ' . GeneralUtility::arrayToLogString($authInfo['userSession'], array($this->userid_column, $this->username_column)), AbstractUserAuthentication::class);
692 }
693 }
694 // Re-auth user when 'auth'-service option is set
695 if ($this->svConfig['setup'][$this->loginType . '_alwaysAuthUser']) {
696 $authenticated = false;
697 if ($this->writeDevLog) {
698 GeneralUtility::devLog('alwaysAuthUser option is enabled', AbstractUserAuthentication::class);
699 }
700 }
701 // Authenticate the user if needed
702 if (!empty($tempuserArr) && !$authenticated) {
703 foreach ($tempuserArr as $tempuser) {
704 // Use 'auth' service to authenticate the user
705 // If one service returns FALSE then authentication failed
706 // a service might return 100 which means there's no reason to stop but the user can't be authenticated by that service
707 if ($this->writeDevLog) {
708 GeneralUtility::devLog('Auth user: ' . GeneralUtility::arrayToLogString($tempuser), AbstractUserAuthentication::class);
709 }
710 $serviceChain = '';
711 $subType = 'authUser' . $this->loginType;
712 while (is_object($serviceObj = GeneralUtility::makeInstanceService('auth', $subType, $serviceChain))) {
713 $serviceChain .= ',' . $serviceObj->getServiceKey();
714 $serviceObj->initAuth($subType, $loginData, $authInfo, $this);
715 if (($ret = $serviceObj->authUser($tempuser)) > 0) {
716 // If the service returns >=200 then no more checking is needed - useful for IP checking without password
717 if ((int)$ret >= 200) {
718 $authenticated = true;
719 break;
720 } elseif ((int)$ret >= 100) {
721 } else {
722 $authenticated = true;
723 }
724 } else {
725 $authenticated = false;
726 break;
727 }
728 unset($serviceObj);
729 }
730 unset($serviceObj);
731 if ($this->writeDevLog && $serviceChain) {
732 GeneralUtility::devLog($subType . ' auth services called: ' . $serviceChain, AbstractUserAuthentication::class);
733 }
734 if ($authenticated) {
735 // Leave foreach() because a user is authenticated
736 break;
737 }
738 }
739 }
740 // If user is authenticated a valid user is in $tempuser
741 if ($authenticated) {
742 // Reset failure flag
743 $this->loginFailure = false;
744 // Insert session record if needed:
745 if (!($haveSession && ($tempuser['ses_id'] == $this->id || $tempuser['uid'] == $authInfo['userSession']['ses_userid']))) {
746 $sessionData = $this->createUserSession($tempuser);
747 if ($sessionData) {
748 $this->user = array_merge(
749 $tempuser,
750 $sessionData
751 );
752 }
753 // The login session is started.
754 $this->loginSessionStarted = true;
755 if ($this->writeDevLog && is_array($this->user)) {
756 GeneralUtility::devLog('User session finally read: ' . GeneralUtility::arrayToLogString($this->user, array($this->userid_column, $this->username_column)), AbstractUserAuthentication::class, -1);
757 }
758 } elseif ($haveSession) {
759 $this->user = $authInfo['userSession'];
760 }
761 if ($activeLogin && !$this->newSessionID) {
762 $this->regenerateSessionId();
763 }
764 // User logged in - write that to the log!
765 if ($this->writeStdLog && $activeLogin) {
766 $this->writelog(255, 1, 0, 1, 'User %s logged in from %s (%s)', array($tempuser[$this->username_column], GeneralUtility::getIndpEnv('REMOTE_ADDR'), GeneralUtility::getIndpEnv('REMOTE_HOST')), '', '', '', -1, '', $tempuser['uid']);
767 }
768 if ($this->writeDevLog && $activeLogin) {
769 GeneralUtility::devLog('User ' . $tempuser[$this->username_column] . ' logged in from ' . GeneralUtility::getIndpEnv('REMOTE_ADDR') . ' (' . GeneralUtility::getIndpEnv('REMOTE_HOST') . ')', AbstractUserAuthentication::class, -1);
770 }
771 if ($this->writeDevLog && !$activeLogin) {
772 GeneralUtility::devLog('User ' . $tempuser[$this->username_column] . ' authenticated from ' . GeneralUtility::getIndpEnv('REMOTE_ADDR') . ' (' . GeneralUtility::getIndpEnv('REMOTE_HOST') . ')', AbstractUserAuthentication::class, -1);
773 }
774 } elseif ($activeLogin || !empty($tempuserArr)) {
775 $this->loginFailure = true;
776 if ($this->writeDevLog && empty($tempuserArr) && $activeLogin) {
777 GeneralUtility::devLog('Login failed: ' . GeneralUtility::arrayToLogString($loginData), AbstractUserAuthentication::class, 2);
778 }
779 if ($this->writeDevLog && !empty($tempuserArr)) {
780 GeneralUtility::devLog('Login failed: ' . GeneralUtility::arrayToLogString($tempuser, array($this->userid_column, $this->username_column)), AbstractUserAuthentication::class, 2);
781 }
782 }
783 // If there were a login failure, check to see if a warning email should be sent:
784 if ($this->loginFailure && $activeLogin) {
785 if ($this->writeDevLog) {
786 GeneralUtility::devLog('Call checkLogFailures: ' . GeneralUtility::arrayToLogString(array('warningEmail' => $this->warningEmail, 'warningPeriod' => $this->warningPeriod, 'warningMax' => $this->warningMax)), AbstractUserAuthentication::class, -1);
787 }
788
789 // Hook to implement login failure tracking methods
790 if (
791 !empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postLoginFailureProcessing'])
792 && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postLoginFailureProcessing'])
793 ) {
794 $_params = array();
795 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postLoginFailureProcessing'] as $_funcRef) {
796 GeneralUtility::callUserFunction($_funcRef, $_params, $this);
797 }
798 } else {
799 // If no hook is implemented, wait for 5 seconds
800 sleep(5);
801 }
802
803 $this->checkLogFailures($this->warningEmail, $this->warningPeriod, $this->warningMax);
804 }
805 }
806
807 /**
808 * Creates a new session ID.
809 *
810 * @return string The new session ID
811 */
812 public function createSessionId()
813 {
814 return GeneralUtility::makeInstance(Random::class)->generateRandomHexString($this->hash_length);
815 }
816
817 /**
818 * Regenerate the session ID and transfer the session to new ID
819 * Call this method whenever a user proceeds to a higher authorization level
820 * e.g. when an anonymous session is now authenticated.
821 */
822 protected function regenerateSessionId()
823 {
824 $oldSessionId = $this->id;
825 $this->id = $this->createSessionId();
826 // Update session record with new ID
827 $this->db->exec_UPDATEquery(
828 $this->session_table,
829 'ses_id = ' . $this->db->fullQuoteStr($oldSessionId, $this->session_table)
830 . ' AND ses_name = ' . $this->db->fullQuoteStr($this->name, $this->session_table),
831 array('ses_id' => $this->id)
832 );
833 $this->newSessionID = true;
834 }
835
836 /*************************
837 *
838 * User Sessions
839 *
840 *************************/
841 /**
842 * Creates a user session record and returns its values.
843 *
844 * @param array $tempuser User data array
845 *
846 * @return array The session data for the newly created session.
847 */
848 public function createUserSession($tempuser)
849 {
850 if ($this->writeDevLog) {
851 GeneralUtility::devLog('Create session ses_id = ' . $this->id, AbstractUserAuthentication::class);
852 }
853 // Delete session entry first
854 $this->db->exec_DELETEquery(
855 $this->session_table,
856 'ses_id = ' . $this->db->fullQuoteStr($this->id, $this->session_table)
857 . ' AND ses_name = ' . $this->db->fullQuoteStr($this->name, $this->session_table)
858 );
859 // Re-create session entry
860 $insertFields = $this->getNewSessionRecord($tempuser);
861 $inserted = (bool)$this->db->exec_INSERTquery($this->session_table, $insertFields);
862 if (!$inserted) {
863 $message = 'Session data could not be written to DB. Error: ' . $this->db->sql_error();
864 GeneralUtility::sysLog($message, 'core', GeneralUtility::SYSLOG_SEVERITY_WARNING);
865 if ($this->writeDevLog) {
866 GeneralUtility::devLog($message, AbstractUserAuthentication::class, 2);
867 }
868 }
869 // Updating lastLogin_column carrying information about last login.
870 if ($this->lastLogin_column && $inserted) {
871 $this->db->exec_UPDATEquery(
872 $this->user_table,
873 $this->userid_column . '=' . $this->db->fullQuoteStr($tempuser[$this->userid_column], $this->user_table),
874 array($this->lastLogin_column => $GLOBALS['EXEC_TIME'])
875 );
876 }
877
878 return $inserted ? $insertFields : array();
879 }
880
881 /**
882 * Returns a new session record for the current user for insertion into the DB.
883 * This function is mainly there as a wrapper for inheriting classes to override it.
884 *
885 * @param array $tempuser
886 * @return array User session record
887 */
888 public function getNewSessionRecord($tempuser)
889 {
890 return array(
891 'ses_id' => $this->id,
892 'ses_name' => $this->name,
893 'ses_iplock' => $tempuser['disableIPlock'] ? '[DISABLED]' : $this->ipLockClause_remoteIPNumber($this->lockIP),
894 'ses_hashlock' => $this->hashLockClause_getHashInt(),
895 'ses_userid' => $tempuser[$this->userid_column],
896 'ses_tstamp' => $GLOBALS['EXEC_TIME'],
897 'ses_data' => ''
898 );
899 }
900
901 /**
902 * Read the user session from db.
903 *
904 * @param bool $skipSessionUpdate
905 * @return array User session data
906 */
907 public function fetchUserSession($skipSessionUpdate = false)
908 {
909 $user = false;
910 if ($this->writeDevLog) {
911 GeneralUtility::devLog('Fetch session ses_id = ' . $this->id, AbstractUserAuthentication::class);
912 }
913
914 // Fetch the user session from the DB
915 $statement = $this->fetchUserSessionFromDB();
916
917 if ($statement) {
918 $statement->execute();
919 $user = $statement->fetch();
920 $statement->free();
921 }
922 if ($user) {
923 // A user was found
924 $user['ses_tstamp'] = (int)$user['ses_tstamp'];
925
926 if (!empty($this->auth_timeout_field)) {
927 // Get timeout-time from usertable
928 $timeout = (int)$user[$this->auth_timeout_field];
929 } else {
930 $timeout = $this->sessionTimeout;
931 }
932 // If timeout > 0 (TRUE) and current time has not exceeded the latest sessions-time plus the timeout in seconds then accept user
933 // Use a gracetime-value to avoid updating a session-record too often
934 if ($timeout > 0 && $GLOBALS['EXEC_TIME'] < $user['ses_tstamp'] + $timeout) {
935 $sessionUpdateGracePeriod = 61;
936 if (!$skipSessionUpdate && $GLOBALS['EXEC_TIME'] > ($user['ses_tstamp'] + $sessionUpdateGracePeriod)) {
937 $this->db->exec_UPDATEquery($this->session_table, 'ses_id=' . $this->db->fullQuoteStr($this->id, $this->session_table)
938 . ' AND ses_name=' . $this->db->fullQuoteStr($this->name, $this->session_table), array('ses_tstamp' => $GLOBALS['EXEC_TIME']));
939 // Make sure that the timestamp is also updated in the array
940 $user['ses_tstamp'] = $GLOBALS['EXEC_TIME'];
941 }
942 } else {
943 // Delete any user set...
944 $this->logoff();
945 $user = false;
946 }
947 }
948 return $user;
949 }
950
951 /**
952 * Log out current user!
953 * Removes the current session record, sets the internal ->user array to a blank string;
954 * Thereby the current user (if any) is effectively logged out!
955 *
956 * @return void
957 */
958 public function logoff()
959 {
960 if ($this->writeDevLog) {
961 GeneralUtility::devLog('logoff: ses_id = ' . $this->id, AbstractUserAuthentication::class);
962 }
963 // Release the locked records
964 BackendUtility::lockRecords();
965 // Hook for pre-processing the logoff() method, requested and implemented by andreas.otto@dkd.de:
966 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'])) {
967 $_params = array();
968 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'] as $_funcRef) {
969 if ($_funcRef) {
970 GeneralUtility::callUserFunction($_funcRef, $_params, $this);
971 }
972 }
973 }
974 $this->db->exec_DELETEquery($this->session_table, 'ses_id = ' . $this->db->fullQuoteStr($this->id, $this->session_table) . '
975 AND ses_name = ' . $this->db->fullQuoteStr($this->name, $this->session_table));
976 $this->user = null;
977 // Hook for post-processing the logoff() method, requested and implemented by andreas.otto@dkd.de:
978 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_post_processing'])) {
979 $_params = array();
980 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_post_processing'] as $_funcRef) {
981 if ($_funcRef) {
982 GeneralUtility::callUserFunction($_funcRef, $_params, $this);
983 }
984 }
985 }
986 }
987
988 /**
989 * Empty / unset the cookie
990 *
991 * @param string $cookieName usually, this is $this->name
992 * @return void
993 */
994 public function removeCookie($cookieName)
995 {
996 $cookieDomain = $this->getCookieDomain();
997 // If no cookie domain is set, use the base path
998 $cookiePath = $cookieDomain ? '/' : GeneralUtility::getIndpEnv('TYPO3_SITE_PATH');
999 setcookie($cookieName, null, -1, $cookiePath, $cookieDomain);
1000 }
1001
1002 /**
1003 * Determine whether there's an according session record to a given session_id
1004 * in the database. Don't care if session record is still valid or not.
1005 *
1006 * @param int $id Claimed Session ID
1007 * @return bool Returns TRUE if a corresponding session was found in the database
1008 */
1009 public function isExistingSessionRecord($id)
1010 {
1011 $statement = $this->db->prepare_SELECTquery('COUNT(*)', $this->session_table, 'ses_id = :ses_id');
1012 $statement->execute(array(':ses_id' => $id));
1013 $row = $statement->fetch(\TYPO3\CMS\Core\Database\PreparedStatement::FETCH_NUM);
1014 $statement->free();
1015 return (bool)$row[0];
1016 }
1017
1018 /**
1019 * Returns whether this request is going to set a cookie
1020 * or a cookie was already found in the system
1021 *
1022 * @return bool Returns TRUE if a cookie is set
1023 */
1024 public function isCookieSet()
1025 {
1026 return $this->cookieWasSetOnCurrentRequest || $this->getCookie($this->name);
1027 }
1028
1029 /*************************
1030 *
1031 * SQL Functions
1032 *
1033 *************************/
1034 /**
1035 * The session_id is used to find user in the database.
1036 * Two tables are joined: The session-table with user_id of the session and the usertable with its primary key
1037 * if the client is flash (e.g. from a flash application inside TYPO3 that does a server request)
1038 * then don't evaluate with the hashLockClause, as the client/browser is included in this hash
1039 * and thus, the flash request would be rejected
1040 *
1041 * @return \TYPO3\CMS\Core\Database\PreparedStatement
1042 * @access private
1043 */
1044 protected function fetchUserSessionFromDB()
1045 {
1046 $statement = null;
1047 $ipLockClause = $this->ipLockClause();
1048 $statement = $this->db->prepare_SELECTquery('*', $this->session_table . ',' . $this->user_table, $this->session_table . '.ses_id = :ses_id
1049 AND ' . $this->session_table . '.ses_name = :ses_name
1050 AND ' . $this->session_table . '.ses_userid = ' . $this->user_table . '.' . $this->userid_column . '
1051 ' . $ipLockClause['where'] . '
1052 ' . $this->hashLockClause() . '
1053 ' . $this->user_where_clause());
1054 $statement->bindValues(array(
1055 ':ses_id' => $this->id,
1056 ':ses_name' => $this->name
1057 ));
1058 $statement->bindValues($ipLockClause['parameters']);
1059 return $statement;
1060 }
1061
1062 /**
1063 * This returns the where-clause needed to select the user
1064 * with respect flags like deleted, hidden, starttime, endtime
1065 *
1066 * @return string
1067 * @access private
1068 */
1069 protected function user_where_clause()
1070 {
1071 $whereClause = '';
1072 if ($this->enablecolumns['rootLevel']) {
1073 $whereClause .= 'AND ' . $this->user_table . '.pid=0 ';
1074 }
1075 if ($this->enablecolumns['disabled']) {
1076 $whereClause .= ' AND ' . $this->user_table . '.' . $this->enablecolumns['disabled'] . '=0';
1077 }
1078 if ($this->enablecolumns['deleted']) {
1079 $whereClause .= ' AND ' . $this->user_table . '.' . $this->enablecolumns['deleted'] . '=0';
1080 }
1081 if ($this->enablecolumns['starttime']) {
1082 $whereClause .= ' AND (' . $this->user_table . '.' . $this->enablecolumns['starttime'] . '<=' . $GLOBALS['EXEC_TIME'] . ')';
1083 }
1084 if ($this->enablecolumns['endtime']) {
1085 $whereClause .= ' AND (' . $this->user_table . '.' . $this->enablecolumns['endtime'] . '=0 OR '
1086 . $this->user_table . '.' . $this->enablecolumns['endtime'] . '>' . $GLOBALS['EXEC_TIME'] . ')';
1087 }
1088 return $whereClause;
1089 }
1090
1091 /**
1092 * This returns the where prepared statement-clause needed to lock a user to the IP address
1093 *
1094 * @return array
1095 * @access private
1096 */
1097 protected function ipLockClause()
1098 {
1099 $statementClause = array(
1100 'where' => '',
1101 'parameters' => array()
1102 );
1103 if ($this->lockIP) {
1104 $statementClause['where'] = 'AND (
1105 ' . $this->session_table . '.ses_iplock = :ses_iplock
1106 OR ' . $this->session_table . '.ses_iplock=\'[DISABLED]\'
1107 )';
1108 $statementClause['parameters'] = array(
1109 ':ses_iplock' => $this->ipLockClause_remoteIPNumber($this->lockIP)
1110 );
1111 }
1112 return $statementClause;
1113 }
1114
1115 /**
1116 * Returns the IP address to lock to.
1117 * The IP address may be partial based on $parts.
1118 *
1119 * @param int $parts 1-4: Indicates how many parts of the IP address to return. 4 means all, 1 means only first number.
1120 * @return string (Partial) IP address for REMOTE_ADDR
1121 * @access private
1122 */
1123 protected function ipLockClause_remoteIPNumber($parts)
1124 {
1125 $IP = GeneralUtility::getIndpEnv('REMOTE_ADDR');
1126 if ($parts >= 4) {
1127 return $IP;
1128 } else {
1129 $parts = MathUtility::forceIntegerInRange($parts, 1, 3);
1130 $IPparts = explode('.', $IP);
1131 for ($a = 4; $a > $parts; $a--) {
1132 unset($IPparts[$a - 1]);
1133 }
1134 return implode('.', $IPparts);
1135 }
1136 }
1137
1138 /**
1139 * VeriCode returns 10 first chars of a md5 hash of the session cookie AND the encryptionKey from TYPO3_CONF_VARS.
1140 * This code is used as an alternative verification when the JavaScript interface executes cmd's to
1141 * tce_db.php from eg. MSIE 5.0 because the proper referer is not passed with this browser...
1142 *
1143 * @return string
1144 */
1145 public function veriCode()
1146 {
1147 return substr(md5($this->id . $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey']), 0, 10);
1148 }
1149
1150 /**
1151 * This returns the where-clause needed to lock a user to a hash integer
1152 *
1153 * @return string
1154 * @access private
1155 */
1156 protected function hashLockClause()
1157 {
1158 return 'AND ' . $this->session_table . '.ses_hashlock=' . $this->hashLockClause_getHashInt();
1159 }
1160
1161 /**
1162 * Creates hash integer to lock user to. Depends on configured keywords
1163 *
1164 * @return int Hash integer
1165 * @access private
1166 */
1167 protected function hashLockClause_getHashInt()
1168 {
1169 $hashStr = '';
1170 if (GeneralUtility::inList($this->lockHashKeyWords, 'useragent')) {
1171 $hashStr .= ':' . GeneralUtility::getIndpEnv('HTTP_USER_AGENT');
1172 }
1173 return GeneralUtility::md5int($hashStr);
1174 }
1175
1176 /*************************
1177 *
1178 * Session and Configuration Handling
1179 *
1180 *************************/
1181 /**
1182 * This writes $variable to the user-record. This is a way of providing session-data.
1183 * You can fetch the data again through $this->uc in this class!
1184 * If $variable is not an array, $this->uc is saved!
1185 *
1186 * @param array|string $variable An array you want to store for the user as session data. If $variable is not supplied (is blank string), the internal variable, ->uc, is stored by default
1187 * @return void
1188 */
1189 public function writeUC($variable = '')
1190 {
1191 if (is_array($this->user) && $this->user[$this->userid_column]) {
1192 if (!is_array($variable)) {
1193 $variable = $this->uc;
1194 }
1195 if ($this->writeDevLog) {
1196 GeneralUtility::devLog('writeUC: ' . $this->userid_column . '=' . (int)$this->user[$this->userid_column], AbstractUserAuthentication::class);
1197 }
1198 $this->db->exec_UPDATEquery($this->user_table, $this->userid_column . '=' . (int)$this->user[$this->userid_column], array('uc' => serialize($variable)));
1199 }
1200 }
1201
1202 /**
1203 * Sets $theUC as the internal variable ->uc IF $theUC is an array.
1204 * If $theUC is FALSE, the 'uc' content from the ->user array will be unserialized and restored in ->uc
1205 *
1206 * @param mixed $theUC If an array, then set as ->uc, otherwise load from user record
1207 * @return void
1208 */
1209 public function unpack_uc($theUC = '')
1210 {
1211 if (!$theUC && isset($this->user['uc'])) {
1212 $theUC = unserialize($this->user['uc']);
1213 }
1214 if (is_array($theUC)) {
1215 $this->uc = $theUC;
1216 }
1217 }
1218
1219 /**
1220 * Stores data for a module.
1221 * The data is stored with the session id so you can even check upon retrieval
1222 * if the module data is from a previous session or from the current session.
1223 *
1224 * @param string $module Is the name of the module ($MCONF['name'])
1225 * @param mixed $data Is the data you want to store for that module (array, string, ...)
1226 * @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.
1227 * @return void
1228 */
1229 public function pushModuleData($module, $data, $noSave = 0)
1230 {
1231 $this->uc['moduleData'][$module] = $data;
1232 $this->uc['moduleSessionID'][$module] = $this->id;
1233 if (!$noSave) {
1234 $this->writeUC();
1235 }
1236 }
1237
1238 /**
1239 * Gets module data for a module (from a loaded ->uc array)
1240 *
1241 * @param string $module Is the name of the module ($MCONF['name'])
1242 * @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).
1243 * @return mixed The module data if available: $this->uc['moduleData'][$module];
1244 */
1245 public function getModuleData($module, $type = '')
1246 {
1247 if ($type != 'ses' || (isset($this->uc['moduleSessionID'][$module]) && $this->uc['moduleSessionID'][$module] == $this->id)) {
1248 return $this->uc['moduleData'][$module];
1249 }
1250 return null;
1251 }
1252
1253 /**
1254 * Returns the session data stored for $key.
1255 * The data will last only for this login session since it is stored in the session table.
1256 *
1257 * @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.
1258 * @return mixed
1259 */
1260 public function getSessionData($key)
1261 {
1262 $sesDat = unserialize($this->user['ses_data']);
1263 return $sesDat[$key];
1264 }
1265
1266 /**
1267 * Sets the session data ($data) for $key and writes all session data (from ->user['ses_data']) to the database.
1268 * The data will last only for this login session since it is stored in the session table.
1269 *
1270 * @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.
1271 * @param mixed $data The variable to store in index $key
1272 * @return void
1273 */
1274 public function setAndSaveSessionData($key, $data)
1275 {
1276 $sesDat = unserialize($this->user['ses_data']);
1277 $sesDat[$key] = $data;
1278 $this->user['ses_data'] = serialize($sesDat);
1279 if ($this->writeDevLog) {
1280 GeneralUtility::devLog('setAndSaveSessionData: ses_id = ' . $this->user['ses_id'], AbstractUserAuthentication::class);
1281 }
1282 $this->db->exec_UPDATEquery($this->session_table, 'ses_id=' . $this->db->fullQuoteStr($this->user['ses_id'], $this->session_table), array('ses_data' => $this->user['ses_data']));
1283 }
1284
1285 /*************************
1286 *
1287 * Misc
1288 *
1289 *************************/
1290 /**
1291 * Returns an info array with Login/Logout data submitted by a form or params
1292 *
1293 * @return array
1294 * @internal
1295 */
1296 public function getLoginFormData()
1297 {
1298 $loginData = array();
1299 $loginData['status'] = GeneralUtility::_GP($this->formfield_status);
1300 if ($this->getMethodEnabled) {
1301 $loginData['uname'] = GeneralUtility::_GP($this->formfield_uname);
1302 $loginData['uident'] = GeneralUtility::_GP($this->formfield_uident);
1303 } else {
1304 $loginData['uname'] = GeneralUtility::_POST($this->formfield_uname);
1305 $loginData['uident'] = GeneralUtility::_POST($this->formfield_uident);
1306 }
1307 // Only process the login data if a login is requested
1308 if ($loginData['status'] === 'login') {
1309 $loginData = $this->processLoginData($loginData);
1310 }
1311 $loginData = array_map('trim', $loginData);
1312 return $loginData;
1313 }
1314
1315 /**
1316 * Processes Login data submitted by a form or params depending on the
1317 * passwordTransmissionStrategy
1318 *
1319 * @param array $loginData Login data array
1320 * @param string $passwordTransmissionStrategy Alternative passwordTransmissionStrategy. Used when authentication services wants to override the default.
1321 * @return array
1322 * @internal
1323 */
1324 public function processLoginData($loginData, $passwordTransmissionStrategy = '')
1325 {
1326 $loginSecurityLevel = trim($GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['loginSecurityLevel']) ?: 'normal';
1327 $passwordTransmissionStrategy = $passwordTransmissionStrategy ?: $loginSecurityLevel;
1328 if ($this->writeDevLog) {
1329 GeneralUtility::devLog('Login data before processing: ' . GeneralUtility::arrayToLogString($loginData), AbstractUserAuthentication::class);
1330 }
1331 $serviceChain = '';
1332 $subType = 'processLoginData' . $this->loginType;
1333 $authInfo = $this->getAuthInfoArray();
1334 $isLoginDataProcessed = false;
1335 $processedLoginData = $loginData;
1336 while (is_object($serviceObject = GeneralUtility::makeInstanceService('auth', $subType, $serviceChain))) {
1337 $serviceChain .= ',' . $serviceObject->getServiceKey();
1338 $serviceObject->initAuth($subType, $loginData, $authInfo, $this);
1339 $serviceResult = $serviceObject->processLoginData($processedLoginData, $passwordTransmissionStrategy);
1340 if (!empty($serviceResult)) {
1341 $isLoginDataProcessed = true;
1342 // If the service returns >=200 then no more processing is needed
1343 if ((int)$serviceResult >= 200) {
1344 unset($serviceObject);
1345 break;
1346 }
1347 }
1348 unset($serviceObject);
1349 }
1350 if ($isLoginDataProcessed) {
1351 $loginData = $processedLoginData;
1352 if ($this->writeDevLog) {
1353 GeneralUtility::devLog('Processed login data: ' . GeneralUtility::arrayToLogString($processedLoginData), AbstractUserAuthentication::class);
1354 }
1355 }
1356 return $loginData;
1357 }
1358
1359 /**
1360 * Returns an info array which provides additional information for auth services
1361 *
1362 * @return array
1363 * @internal
1364 */
1365 public function getAuthInfoArray()
1366 {
1367 $authInfo = array();
1368 $authInfo['loginType'] = $this->loginType;
1369 $authInfo['refInfo'] = parse_url(GeneralUtility::getIndpEnv('HTTP_REFERER'));
1370 $authInfo['HTTP_HOST'] = GeneralUtility::getIndpEnv('HTTP_HOST');
1371 $authInfo['REMOTE_ADDR'] = GeneralUtility::getIndpEnv('REMOTE_ADDR');
1372 $authInfo['REMOTE_HOST'] = GeneralUtility::getIndpEnv('REMOTE_HOST');
1373 $authInfo['showHiddenRecords'] = $this->showHiddenRecords;
1374 // Can be overidden in localconf by SVCONF:
1375 $authInfo['db_user']['table'] = $this->user_table;
1376 $authInfo['db_user']['userid_column'] = $this->userid_column;
1377 $authInfo['db_user']['username_column'] = $this->username_column;
1378 $authInfo['db_user']['userident_column'] = $this->userident_column;
1379 $authInfo['db_user']['usergroup_column'] = $this->usergroup_column;
1380 $authInfo['db_user']['enable_clause'] = $this->user_where_clause();
1381 if ($this->checkPid && $this->checkPid_value !== null) {
1382 $authInfo['db_user']['checkPidList'] = $this->checkPid_value;
1383 $authInfo['db_user']['check_pid_clause'] = ' AND pid IN (' .
1384 $this->db->cleanIntList($this->checkPid_value) . ')';
1385 } else {
1386 $authInfo['db_user']['checkPidList'] = '';
1387 $authInfo['db_user']['check_pid_clause'] = '';
1388 }
1389 $authInfo['db_groups']['table'] = $this->usergroup_table;
1390 return $authInfo;
1391 }
1392
1393 /**
1394 * Check the login data with the user record data for builtin login methods
1395 *
1396 * @param array $user User data array
1397 * @param array $loginData Login data array
1398 * @param string $passwordCompareStrategy Alternative passwordCompareStrategy. Used when authentication services wants to override the default.
1399 * @return bool TRUE if login data matched
1400 */
1401 public function compareUident($user, $loginData, $passwordCompareStrategy = '')
1402 {
1403 return (string)$loginData['uident_text'] === (string)$user[$this->userident_column];
1404 }
1405
1406 /**
1407 * Garbage collector, removing old expired sessions.
1408 *
1409 * @return void
1410 * @internal
1411 */
1412 public function gc()
1413 {
1414 $this->db->exec_DELETEquery($this->session_table, 'ses_tstamp < ' . (int)($GLOBALS['EXEC_TIME'] - $this->gc_time) . ' AND ses_name = ' . $this->db->fullQuoteStr($this->name, $this->session_table));
1415 }
1416
1417 /**
1418 * DUMMY: Writes to log database table (in some extension classes)
1419 *
1420 * @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
1421 * @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 !!)
1422 * @param int $error flag. 0 = message, 1 = error (user problem), 2 = System Error (which should not happen), 3 = security notice (admin)
1423 * @param int $details_nr The message number. Specific for each $type and $action. in the future this will make it possible to translate errormessages to other languages
1424 * @param string $details Default text that follows the message
1425 * @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...
1426 * @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.)
1427 * @param int $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.)
1428 * @param int $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.)
1429 * @return void
1430 */
1431 public function writelog($type, $action, $error, $details_nr, $details, $data, $tablename, $recuid, $recpid)
1432 {
1433 }
1434
1435 /**
1436 * DUMMY: Check login failures (in some extension classes)
1437 *
1438 * @param string $email Email address
1439 * @param int $secondsBack Number of sections back in time to check. This is a kind of limit for how many failures an hour for instance
1440 * @param int $maxFailures Max allowed failures before a warning mail is sent
1441 * @return void
1442 * @ignore
1443 */
1444 public function checkLogFailures($email, $secondsBack, $maxFailures)
1445 {
1446 }
1447
1448 /**
1449 * Raw initialization of the be_user with uid=$uid
1450 * This will circumvent all login procedures and select a be_users record from the
1451 * database and set the content of ->user to the record selected.
1452 * Thus the BE_USER object will appear like if a user was authenticated - however without
1453 * a session id and the fields from the session table of course.
1454 * Will check the users for disabled, start/endtime, etc. ($this->user_where_clause())
1455 *
1456 * @param int $uid The UID of the backend user to set in ->user
1457 * @return void
1458 * @internal
1459 * @see SC_mod_tools_be_user_index::compareUsers(), \TYPO3\CMS\Setup\Controller\SetupModuleController::simulateUser(), freesite_admin::startCreate()
1460 */
1461 public function setBeUserByUid($uid)
1462 {
1463 $this->user = $this->getRawUserByUid($uid);
1464 }
1465
1466 /**
1467 * Raw initialization of the be_user with username=$name
1468 *
1469 * @param string $name The username to look up.
1470 * @return void
1471 * @see \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::setBeUserByUid()
1472 * @internal
1473 */
1474 public function setBeUserByName($name)
1475 {
1476 $this->user = $this->getRawUserByName($name);
1477 }
1478
1479 /**
1480 * Fetching raw user record with uid=$uid
1481 *
1482 * @param int $uid The UID of the backend user to set in ->user
1483 * @return array user record or FALSE
1484 * @internal
1485 */
1486 public function getRawUserByUid($uid)
1487 {
1488 $user = false;
1489 $dbres = $this->db->exec_SELECTquery('*', $this->user_table, 'uid=' . (int)$uid . ' ' . $this->user_where_clause());
1490 if ($dbres) {
1491 $user = $this->db->sql_fetch_assoc($dbres);
1492 $this->db->sql_free_result($dbres);
1493 }
1494 return $user;
1495 }
1496
1497 /**
1498 * Fetching raw user record with username=$name
1499 *
1500 * @param string $name The username to look up.
1501 * @return array user record or FALSE
1502 * @see \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::getUserByUid()
1503 * @internal
1504 */
1505 public function getRawUserByName($name)
1506 {
1507 $user = false;
1508 $dbres = $this->db->exec_SELECTquery('*', $this->user_table, 'username=' . $this->db->fullQuoteStr($name, $this->user_table) . ' ' . $this->user_where_clause());
1509 if ($dbres) {
1510 $user = $this->db->sql_fetch_assoc($dbres);
1511 $this->db->sql_free_result($dbres);
1512 }
1513 return $user;
1514 }
1515
1516 /*************************
1517 *
1518 * Create/update user - EXPERIMENTAL
1519 *
1520 *************************/
1521 /**
1522 * Get a user from DB by username
1523 * provided for usage from services
1524 *
1525 * @param array $dbUser User db table definition: $this->db_user
1526 * @param string $username user name
1527 * @param string $extraWhere Additional WHERE clause: " AND ...
1528 * @return mixed User array or FALSE
1529 */
1530 public function fetchUserRecord($dbUser, $username, $extraWhere = '')
1531 {
1532 $user = false;
1533 $usernameClause = $username ? $dbUser['username_column'] . '=' . $this->db->fullQuoteStr($username, $dbUser['table']) : '1=1';
1534 if ($username || $extraWhere) {
1535 // Look up the user by the username and/or extraWhere:
1536 $dbres = $this->db->exec_SELECTquery('*', $dbUser['table'], $usernameClause . $dbUser['check_pid_clause'] . $dbUser['enable_clause'] . $extraWhere);
1537 if ($dbres) {
1538 $user = $this->db->sql_fetch_assoc($dbres);
1539 $this->db->sql_free_result($dbres);
1540 }
1541 }
1542 return $user;
1543 }
1544
1545 /**
1546 * Get global database connection
1547 * @return DatabaseConnection
1548 */
1549 protected function getDatabaseConnection()
1550 {
1551 return $GLOBALS['TYPO3_DB'];
1552 }
1553 }