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