dab73e90ecd53fed5ab0d79876aeba06896a47a4
[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\Crypto\Random;
19 use TYPO3\CMS\Core\Database\Connection;
20 use TYPO3\CMS\Core\Database\ConnectionPool;
21 use TYPO3\CMS\Core\Database\DatabaseConnection;
22 use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
23 use TYPO3\CMS\Core\Database\Query\QueryHelper;
24 use TYPO3\CMS\Core\Exception;
25 use TYPO3\CMS\Core\Utility\GeneralUtility;
26 use TYPO3\CMS\Core\Utility\MathUtility;
27
28 /**
29 * Authentication of users in TYPO3
30 *
31 * This class is used to authenticate a login user.
32 * The class is used by both the frontend and backend.
33 * In both cases this class is a parent class to BackendUserAuthentication and FrontenUserAuthentication
34 *
35 * See Inside TYPO3 for more information about the API of the class and internal variables.
36 */
37 abstract class AbstractUserAuthentication
38 {
39 /**
40 * Table to use for session data
41 * @var string
42 */
43 public $session_table = '';
44
45 /**
46 * Session/Cookie name
47 * @var string
48 */
49 public $name = '';
50
51 /**
52 * Session/GET-var name
53 * @var string
54 */
55 public $get_name = '';
56
57 /**
58 * Table in database with user data
59 * @var string
60 */
61 public $user_table = '';
62
63 /**
64 * Table in database with user groups
65 * @var string
66 */
67 public $usergroup_table = '';
68
69 /**
70 * Column for login-name
71 * @var string
72 */
73 public $username_column = '';
74
75 /**
76 * Column for password
77 * @var string
78 */
79 public $userident_column = '';
80
81 /**
82 * Column for user-id
83 * @var string
84 */
85 public $userid_column = '';
86
87 /**
88 * Column for user group information
89 * @var string
90 */
91 public $usergroup_column = '';
92
93 /**
94 * Column name for last login timestamp
95 * @var string
96 */
97 public $lastLogin_column = '';
98
99 /**
100 * Enable field columns of user table
101 * @var array
102 */
103 public $enablecolumns = array(
104 'rootLevel' => '',
105 // Boolean: If TRUE, 'AND pid=0' will be a part of the query...
106 'disabled' => '',
107 'starttime' => '',
108 'endtime' => '',
109 'deleted' => ''
110 );
111
112 /**
113 * @var bool
114 */
115 public $showHiddenRecords = false;
116
117 /**
118 * Form field with login-name
119 * @var string
120 */
121 public $formfield_uname = '';
122
123 /**
124 * Form field with password
125 * @var string
126 */
127 public $formfield_uident = '';
128
129 /**
130 * Form field with status: *'login', 'logout'. If empty login is not verified.
131 * @var string
132 */
133 public $formfield_status = '';
134
135 /**
136 * Session timeout (on the server)
137 *
138 * If >0: session-timeout in seconds.
139 * If 0: no timeout.
140 *
141 * @var int
142 */
143 public $sessionTimeout = 0;
144
145 /**
146 * Name for a field to fetch the server session timeout from.
147 * If not empty this is a field name from the user table where the timeout can be found.
148 * @var string
149 */
150 public $auth_timeout_field = '';
151
152 /**
153 * Lifetime for the session-cookie (on the client)
154 *
155 * If >0: permanent cookie with given lifetime
156 * If 0: session-cookie
157 * Session-cookie means the browser will remove it when the browser is closed.
158 *
159 * @var int
160 */
161 public $lifetime = 0;
162
163 /**
164 * GarbageCollection
165 * Purge all server session data older than $gc_time seconds.
166 * 0 = default to $this->sessionTimeout or use 86400 seconds (1 day) if $this->sessionTimeout == 0
167 * @var int
168 */
169 public $gc_time = 0;
170
171 /**
172 * Probability for g arbage collection to be run (in percent)
173 * @var int
174 */
175 public $gc_probability = 1;
176
177 /**
178 * Decides if the writelog() function is called at login and logout
179 * @var bool
180 */
181 public $writeStdLog = false;
182
183 /**
184 * Log failed login attempts
185 * @var bool
186 */
187 public $writeAttemptLog = false;
188
189 /**
190 * Send no-cache headers
191 * @var bool
192 */
193 public $sendNoCacheHeaders = true;
194
195 /**
196 * If this is set, authentication is also accepted by $_GET.
197 * Notice that the identification is NOT 128bit MD5 hash but reduced.
198 * This is done in order to minimize the size for mobile-devices, such as WAP-phones
199 * @var bool
200 */
201 public $getFallBack = false;
202
203 /**
204 * The ident-hash is normally 32 characters and should be!
205 * But if you are making sites for WAP-devices or other low-bandwidth stuff,
206 * you may shorten the length.
207 * Never let this value drop below 6!
208 * A length of 6 would give you more than 16 mio possibilities.
209 * @var int
210 */
211 public $hash_length = 32;
212
213 /**
214 * Setting this flag TRUE lets user-authentication happen from GET_VARS if
215 * POST_VARS are not set. Thus you may supply username/password with the URL.
216 * @var bool
217 */
218 public $getMethodEnabled = false;
219
220 /**
221 * If set to 4, the session will be locked to the user's IP address (all four numbers).
222 * Reducing this to 1-3 means that only the given number of parts of the IP address is used.
223 * @var int
224 */
225 public $lockIP = 4;
226
227 /**
228 * Keyword list (comma separated list with no spaces!)
229 * Each keyword indicates some information that can be included in a hash made to lock down user sessions.
230 * Configurable by $GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['lockHashKeyWords']
231 * @var string
232 */
233 public $lockHashKeyWords = 'useragent';
234
235 /**
236 * @var string
237 */
238 public $warningEmail = '';
239
240 /**
241 * Time span (in seconds) within the number of failed logins are collected
242 * @var int
243 */
244 public $warningPeriod = 3600;
245
246 /**
247 * The maximum accepted number of warnings before an email to $warningEmail is sent
248 * @var int
249 */
250 public $warningMax = 3;
251
252 /**
253 * If set, the user-record must be stored at the page defined by $checkPid_value
254 * @var bool
255 */
256 public $checkPid = true;
257
258 /**
259 * The page id the user record must be stored at
260 * @var int
261 */
262 public $checkPid_value = 0;
263
264 /**
265 * session_id (MD5-hash)
266 * @var string
267 * @internal
268 */
269 public $id;
270
271 /**
272 * Indicates if an authentication was started but failed
273 * @var bool
274 */
275 public $loginFailure = false;
276
277 /**
278 * Will be set to TRUE if the login session is actually written during auth-check.
279 * @var bool
280 */
281 public $loginSessionStarted = false;
282
283 /**
284 * @var array|NULL contains user- AND session-data from database (joined tables)
285 * @internal
286 */
287 public $user = null;
288
289 /**
290 * Will be added to the url (eg. '&login=ab7ef8d...')
291 * GET-auth-var if getFallBack is TRUE. Should be inserted in links!
292 * @var string
293 * @internal
294 */
295 public $get_URL_ID = '';
296
297 /**
298 * Will be set to TRUE if a new session ID was created
299 * @var bool
300 */
301 public $newSessionID = false;
302
303 /**
304 * Will force the session cookie to be set every time (lifetime must be 0)
305 * @var bool
306 */
307 public $forceSetCookie = false;
308
309 /**
310 * Will prevent the setting of the session cookie (takes precedence over forceSetCookie)
311 * @var bool
312 */
313 public $dontSetCookie = false;
314
315 /**
316 * @var bool
317 */
318 protected $cookieWasSetOnCurrentRequest = false;
319
320 /**
321 * Login type, used for services.
322 * @var string
323 */
324 public $loginType = '';
325
326 /**
327 * "auth" services configuration array from $GLOBALS['TYPO3_CONF_VARS']['SVCONF']['auth']
328 * @var array
329 */
330 public $svConfig = array();
331
332 /**
333 * Write messages to the devlog
334 * @var bool
335 */
336 public $writeDevLog = false;
337
338 /**
339 * @var array
340 */
341 public $uc;
342
343 /**
344 * Initialize some important variables
345 */
346 public function __construct()
347 {
348 // This function has to stay even if it's empty
349 // Implementations of that abstract class might call parent::__construct();
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 GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->session_table)->update(
828 $this->session_table,
829 ['ses_id' => $this->id],
830 ['ses_id' => $oldSessionId, 'ses_name' => $this->name]
831 );
832 $this->newSessionID = true;
833 }
834
835 /*************************
836 *
837 * User Sessions
838 *
839 *************************/
840 /**
841 * Creates a user session record and returns its values.
842 *
843 * @param array $tempuser User data array
844 *
845 * @return array The session data for the newly created session.
846 */
847 public function createUserSession($tempuser)
848 {
849 if ($this->writeDevLog) {
850 GeneralUtility::devLog('Create session ses_id = ' . $this->id, AbstractUserAuthentication::class);
851 }
852 // Delete session entry first
853 $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->session_table);
854 $connection->delete(
855 $this->session_table,
856 ['ses_id' => $this->id, 'ses_name' => $this->name]
857 );
858
859 // Re-create session entry
860 $insertFields = $this->getNewSessionRecord($tempuser);
861 $inserted = (bool)$connection->insert($this->session_table, $insertFields);
862 if (!$inserted) {
863 $message = 'Session data could not be written to DB. Error: ' . $connection->errorInfo();
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 $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->user_table);
872 $connection->update(
873 $this->user_table,
874 [$this->lastLogin_column => $GLOBALS['EXEC_TIME']],
875 [$this->userid_column => $tempuser[$this->userid_column]]
876 );
877 }
878
879 return $inserted ? $insertFields : [];
880 }
881
882 /**
883 * Returns a new session record for the current user for insertion into the DB.
884 * This function is mainly there as a wrapper for inheriting classes to override it.
885 *
886 * @param array $tempuser
887 * @return array User session record
888 */
889 public function getNewSessionRecord($tempuser)
890 {
891 return array(
892 'ses_id' => $this->id,
893 'ses_name' => $this->name,
894 'ses_iplock' => $tempuser['disableIPlock'] ? '[DISABLED]' : $this->ipLockClause_remoteIPNumber($this->lockIP),
895 'ses_hashlock' => $this->hashLockClause_getHashInt(),
896 'ses_userid' => $tempuser[$this->userid_column],
897 'ses_tstamp' => $GLOBALS['EXEC_TIME'],
898 'ses_data' => ''
899 );
900 }
901
902 /**
903 * Read the user session from db.
904 *
905 * @param bool $skipSessionUpdate
906 * @return array User session data
907 */
908 public function fetchUserSession($skipSessionUpdate = false)
909 {
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 $user = $this->fetchUserSessionFromDB();
916
917 if ($user) {
918 // A user was found
919 $user['ses_tstamp'] = (int)$user['ses_tstamp'];
920
921 if (!empty($this->auth_timeout_field)) {
922 // Get timeout-time from usertable
923 $timeout = (int)$user[$this->auth_timeout_field];
924 } else {
925 $timeout = $this->sessionTimeout;
926 }
927 // If timeout > 0 (TRUE) and current time has not exceeded the latest sessions-time plus the timeout in seconds then accept user
928 // Use a gracetime-value to avoid updating a session-record too often
929 if ($timeout > 0 && $GLOBALS['EXEC_TIME'] < $user['ses_tstamp'] + $timeout) {
930 $sessionUpdateGracePeriod = 61;
931 if (!$skipSessionUpdate && $GLOBALS['EXEC_TIME'] > ($user['ses_tstamp'] + $sessionUpdateGracePeriod)) {
932 GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->session_table)->update(
933 $this->session_table,
934 ['ses_tstamp' => $GLOBALS['EXEC_TIME']],
935 ['ses_id' => $this->id, 'ses_name' => $this->name]
936 );
937 // Make sure that the timestamp is also updated in the array
938 $user['ses_tstamp'] = $GLOBALS['EXEC_TIME'];
939 }
940 } else {
941 // Delete any user set...
942 $this->logoff();
943 $user = false;
944 }
945 }
946 return $user;
947 }
948
949 /**
950 * Log out current user!
951 * Removes the current session record, sets the internal ->user array to a blank string;
952 * Thereby the current user (if any) is effectively logged out!
953 *
954 * @return void
955 */
956 public function logoff()
957 {
958 if ($this->writeDevLog) {
959 GeneralUtility::devLog('logoff: ses_id = ' . $this->id, AbstractUserAuthentication::class);
960 }
961 // Release the locked records
962 BackendUtility::lockRecords();
963 // Hook for pre-processing the logoff() method, requested and implemented by andreas.otto@dkd.de:
964 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'])) {
965 $_params = array();
966 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'] as $_funcRef) {
967 if ($_funcRef) {
968 GeneralUtility::callUserFunction($_funcRef, $_params, $this);
969 }
970 }
971 }
972
973 GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->session_table)->delete(
974 $this->session_table,
975 ['ses_id' => $this->id, 'ses_name' => $this->name]
976 );
977
978 $this->user = null;
979 // Hook for post-processing the logoff() method, requested and implemented by andreas.otto@dkd.de:
980 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_post_processing'])) {
981 $_params = array();
982 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_post_processing'] as $_funcRef) {
983 if ($_funcRef) {
984 GeneralUtility::callUserFunction($_funcRef, $_params, $this);
985 }
986 }
987 }
988 }
989
990 /**
991 * Empty / unset the cookie
992 *
993 * @param string $cookieName usually, this is $this->name
994 * @return void
995 */
996 public function removeCookie($cookieName)
997 {
998 $cookieDomain = $this->getCookieDomain();
999 // If no cookie domain is set, use the base path
1000 $cookiePath = $cookieDomain ? '/' : GeneralUtility::getIndpEnv('TYPO3_SITE_PATH');
1001 setcookie($cookieName, null, -1, $cookiePath, $cookieDomain);
1002 }
1003
1004 /**
1005 * Determine whether there's an according session record to a given session_id
1006 * in the database. Don't care if session record is still valid or not.
1007 *
1008 * @param int $id Claimed Session ID
1009 * @return bool Returns TRUE if a corresponding session was found in the database
1010 */
1011 public function isExistingSessionRecord($id)
1012 {
1013 $conn = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->session_table);
1014 $count = $conn->count(
1015 '*',
1016 $this->session_table,
1017 ['ses_id' => $id]
1018 );
1019
1020 return (bool)$count;
1021 }
1022
1023 /**
1024 * Returns whether this request is going to set a cookie
1025 * or a cookie was already found in the system
1026 *
1027 * @return bool Returns TRUE if a cookie is set
1028 */
1029 public function isCookieSet()
1030 {
1031 return $this->cookieWasSetOnCurrentRequest || $this->getCookie($this->name);
1032 }
1033
1034 /*************************
1035 *
1036 * SQL Functions
1037 *
1038 *************************/
1039 /**
1040 * The session_id is used to find user in the database.
1041 * Two tables are joined: The session-table with user_id of the session and the usertable with its primary key
1042 * if the client is flash (e.g. from a flash application inside TYPO3 that does a server request)
1043 * then don't evaluate with the hashLockClause, as the client/browser is included in this hash
1044 * and thus, the flash request would be rejected
1045 *
1046 * @return array|false
1047 * @access private
1048 */
1049 protected function fetchUserSessionFromDB()
1050 {
1051 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1052 ->getQueryBuilderForTable($this->session_table);
1053 $queryBuilder->select('*')
1054 ->from($this->session_table)
1055 ->from($this->user_table)
1056 ->where(
1057 $queryBuilder->expr()->eq(
1058 $this->session_table . '.ses_id',
1059 $queryBuilder->createNamedParameter($this->id)
1060 )
1061 )
1062 ->andWhere(
1063 $queryBuilder->expr()->eq(
1064 $this->session_table . '.ses_name',
1065 $queryBuilder->createNamedParameter($this->name)
1066 )
1067 )
1068 // Condition on which to join the session and user table
1069 ->andWhere(
1070 $queryBuilder->expr()->eq(
1071 $this->session_table . '.ses_userid',
1072 $queryBuilder->quoteIdentifier($this->user_table . '.' . $this->userid_column)
1073 )
1074 )
1075 ->andWhere(
1076 $queryBuilder->expr()->eq(
1077 $this->session_table . '.ses_hashlock',
1078 $queryBuilder->createNamedParameter($this->hashLockClause_getHashInt())
1079 )
1080 )
1081 ->andWhere($this->userConstraints($queryBuilder->expr()));
1082
1083 if ($this->lockIP) {
1084 $queryBuilder->andWhere(
1085 $queryBuilder->expr()->in(
1086 $this->session_table . '.ses_iplock',
1087 $queryBuilder->createNamedParameter(
1088 [$this->ipLockClause_remoteIPNumber($this->lockIP), '[DISABLED]'],
1089 Connection::PARAM_STR_ARRAY // Automatically expand the array into multiple named parameters
1090 )
1091 )
1092 );
1093 }
1094
1095 // Force the fetch mode to ensure we get back an array independently of the default fetch mode.
1096 return $queryBuilder->execute()->fetch(\PDO::FETCH_ASSOC);
1097 }
1098
1099 /**
1100 * @param ExpressionBuilder $expressionBuilder
1101 * @param string $tableAlias
1102 * @return \Doctrine\DBAL\Query\Expression\CompositeExpression
1103 * @internal
1104 */
1105 protected function userConstraints(
1106 ExpressionBuilder $expressionBuilder,
1107 string $tableAlias = ''
1108 ): \Doctrine\DBAL\Query\Expression\CompositeExpression {
1109 if ($tableAlias === '') {
1110 $tableAlias = $this->user_table;
1111 }
1112
1113 $constraints = $expressionBuilder->andX();
1114 if ($this->enablecolumns['rootLevel']) {
1115 $constraints->add(
1116 $expressionBuilder->eq($tableAlias . '.pid', 0)
1117 );
1118 }
1119 if ($this->enablecolumns['disabled']) {
1120 $constraints->add(
1121 $expressionBuilder->eq($tableAlias . '.' . $this->enablecolumns['disabled'], 0)
1122 );
1123 }
1124 if ($this->enablecolumns['deleted']) {
1125 $constraints->add(
1126 $expressionBuilder->eq($tableAlias . '.' . $this->enablecolumns['deleted'], 0)
1127 );
1128 }
1129 if ($this->enablecolumns['starttime']) {
1130 $constraints->add(
1131 $expressionBuilder->lte($tableAlias . '.' . $this->enablecolumns['starttime'], $GLOBALS['EXEC_TIME'])
1132 );
1133 }
1134 if ($this->enablecolumns['endtime']) {
1135 $constraints->add(
1136 $expressionBuilder->orX(
1137 $expressionBuilder->eq($tableAlias . '.' . $this->enablecolumns['endtime'], 0),
1138 $expressionBuilder->gt($tableAlias . '.' . $this->enablecolumns['endtime'], $GLOBALS['EXEC_TIME'])
1139 )
1140 );
1141 }
1142
1143 return $constraints;
1144 }
1145
1146 /**
1147 * This returns the where-clause needed to select the user
1148 * with respect flags like deleted, hidden, starttime, endtime
1149 *
1150 * @return string
1151 * @access private
1152 * @deprecated since TYPO3 v8, will be removed in TYPO3 v9
1153 */
1154 protected function user_where_clause()
1155 {
1156 GeneralUtility::logDeprecatedFunction();
1157
1158 $whereClause = '';
1159 if ($this->enablecolumns['rootLevel']) {
1160 $whereClause .= 'AND ' . $this->user_table . '.pid=0 ';
1161 }
1162 if ($this->enablecolumns['disabled']) {
1163 $whereClause .= ' AND ' . $this->user_table . '.' . $this->enablecolumns['disabled'] . '=0';
1164 }
1165 if ($this->enablecolumns['deleted']) {
1166 $whereClause .= ' AND ' . $this->user_table . '.' . $this->enablecolumns['deleted'] . '=0';
1167 }
1168 if ($this->enablecolumns['starttime']) {
1169 $whereClause .= ' AND (' . $this->user_table . '.' . $this->enablecolumns['starttime'] . '<=' . $GLOBALS['EXEC_TIME'] . ')';
1170 }
1171 if ($this->enablecolumns['endtime']) {
1172 $whereClause .= ' AND (' . $this->user_table . '.' . $this->enablecolumns['endtime'] . '=0 OR '
1173 . $this->user_table . '.' . $this->enablecolumns['endtime'] . '>' . $GLOBALS['EXEC_TIME'] . ')';
1174 }
1175 return $whereClause;
1176 }
1177
1178 /**
1179 * This returns the where prepared statement-clause needed to lock a user to the IP address
1180 *
1181 * @return array
1182 * @access private
1183 * @deprecated since TYPO3 v8, will be removed in TYPO3 v9
1184 */
1185 protected function ipLockClause()
1186 {
1187 GeneralUtility::logDeprecatedFunction();
1188 $statementClause = array(
1189 'where' => '',
1190 'parameters' => array()
1191 );
1192 if ($this->lockIP) {
1193 $statementClause['where'] = 'AND (
1194 ' . $this->session_table . '.ses_iplock = :ses_iplock
1195 OR ' . $this->session_table . '.ses_iplock=\'[DISABLED]\'
1196 )';
1197 $statementClause['parameters'] = array(
1198 ':ses_iplock' => $this->ipLockClause_remoteIPNumber($this->lockIP)
1199 );
1200 }
1201 return $statementClause;
1202 }
1203
1204 /**
1205 * Returns the IP address to lock to.
1206 * The IP address may be partial based on $parts.
1207 *
1208 * @param int $parts 1-4: Indicates how many parts of the IP address to return. 4 means all, 1 means only first number.
1209 * @return string (Partial) IP address for REMOTE_ADDR
1210 * @access private
1211 */
1212 protected function ipLockClause_remoteIPNumber($parts)
1213 {
1214 $IP = GeneralUtility::getIndpEnv('REMOTE_ADDR');
1215 if ($parts >= 4) {
1216 return $IP;
1217 } else {
1218 $parts = MathUtility::forceIntegerInRange($parts, 1, 3);
1219 $IPparts = explode('.', $IP);
1220 for ($a = 4; $a > $parts; $a--) {
1221 unset($IPparts[$a - 1]);
1222 }
1223 return implode('.', $IPparts);
1224 }
1225 }
1226
1227 /**
1228 * VeriCode returns 10 first chars of a md5 hash of the session cookie AND the encryptionKey from TYPO3_CONF_VARS.
1229 * This code is used as an alternative verification when the JavaScript interface executes cmd's to
1230 * tce_db.php from eg. MSIE 5.0 because the proper referer is not passed with this browser...
1231 *
1232 * @return string
1233 */
1234 public function veriCode()
1235 {
1236 return substr(md5($this->id . $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey']), 0, 10);
1237 }
1238
1239 /**
1240 * This returns the where-clause needed to lock a user to a hash integer
1241 *
1242 * @return string
1243 * @access private
1244 */
1245 protected function hashLockClause()
1246 {
1247 return 'AND ' . $this->session_table . '.ses_hashlock=' . $this->hashLockClause_getHashInt();
1248 }
1249
1250 /**
1251 * Creates hash integer to lock user to. Depends on configured keywords
1252 *
1253 * @return int Hash integer
1254 * @access private
1255 */
1256 protected function hashLockClause_getHashInt()
1257 {
1258 $hashStr = '';
1259 if (GeneralUtility::inList($this->lockHashKeyWords, 'useragent')) {
1260 $hashStr .= ':' . GeneralUtility::getIndpEnv('HTTP_USER_AGENT');
1261 }
1262 return GeneralUtility::md5int($hashStr);
1263 }
1264
1265 /*************************
1266 *
1267 * Session and Configuration Handling
1268 *
1269 *************************/
1270 /**
1271 * This writes $variable to the user-record. This is a way of providing session-data.
1272 * You can fetch the data again through $this->uc in this class!
1273 * If $variable is not an array, $this->uc is saved!
1274 *
1275 * @param array|string $variable An array you want to store for the user as session data. If $variable is not supplied (is null), the internal variable, ->uc, is stored by default
1276 * @return void
1277 */
1278 public function writeUC($variable = '')
1279 {
1280 if (is_array($this->user) && $this->user[$this->userid_column]) {
1281 if (!is_array($variable)) {
1282 $variable = $this->uc;
1283 }
1284 if ($this->writeDevLog) {
1285 GeneralUtility::devLog(
1286 'writeUC: ' . $this->userid_column . '=' . (int)$this->user[$this->userid_column],
1287 AbstractUserAuthentication::class
1288 );
1289 }
1290 GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->session_table)->update(
1291 $this->user_table,
1292 ['uc' => serialize($variable)],
1293 [$this->userid_column => (int)$this->user[$this->userid_column]]
1294 );
1295 }
1296 }
1297
1298 /**
1299 * Sets $theUC as the internal variable ->uc IF $theUC is an array.
1300 * If $theUC is FALSE, the 'uc' content from the ->user array will be unserialized and restored in ->uc
1301 *
1302 * @param mixed $theUC If an array, then set as ->uc, otherwise load from user record
1303 * @return void
1304 */
1305 public function unpack_uc($theUC = '')
1306 {
1307 if (!$theUC && isset($this->user['uc'])) {
1308 $theUC = unserialize($this->user['uc']);
1309 }
1310 if (is_array($theUC)) {
1311 $this->uc = $theUC;
1312 }
1313 }
1314
1315 /**
1316 * Stores data for a module.
1317 * The data is stored with the session id so you can even check upon retrieval
1318 * if the module data is from a previous session or from the current session.
1319 *
1320 * @param string $module Is the name of the module ($MCONF['name'])
1321 * @param mixed $data Is the data you want to store for that module (array, string, ...)
1322 * @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.
1323 * @return void
1324 */
1325 public function pushModuleData($module, $data, $noSave = 0)
1326 {
1327 $this->uc['moduleData'][$module] = $data;
1328 $this->uc['moduleSessionID'][$module] = $this->id;
1329 if (!$noSave) {
1330 $this->writeUC();
1331 }
1332 }
1333
1334 /**
1335 * Gets module data for a module (from a loaded ->uc array)
1336 *
1337 * @param string $module Is the name of the module ($MCONF['name'])
1338 * @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).
1339 * @return mixed The module data if available: $this->uc['moduleData'][$module];
1340 */
1341 public function getModuleData($module, $type = '')
1342 {
1343 if ($type != 'ses' || (isset($this->uc['moduleSessionID'][$module]) && $this->uc['moduleSessionID'][$module] == $this->id)) {
1344 return $this->uc['moduleData'][$module];
1345 }
1346 return null;
1347 }
1348
1349 /**
1350 * Returns the session data stored for $key.
1351 * The data will last only for this login session since it is stored in the session table.
1352 *
1353 * @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.
1354 * @return mixed
1355 */
1356 public function getSessionData($key)
1357 {
1358 $sesDat = unserialize($this->user['ses_data']);
1359 return $sesDat[$key];
1360 }
1361
1362 /**
1363 * Sets the session data ($data) for $key and writes all session data (from ->user['ses_data']) to the database.
1364 * The data will last only for this login session since it is stored in the session table.
1365 *
1366 * @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.
1367 * @param mixed $data The variable to store in index $key
1368 * @return void
1369 */
1370 public function setAndSaveSessionData($key, $data)
1371 {
1372 $sesDat = unserialize($this->user['ses_data']);
1373 $sesDat[$key] = $data;
1374 $this->user['ses_data'] = serialize($sesDat);
1375 if ($this->writeDevLog) {
1376 GeneralUtility::devLog('setAndSaveSessionData: ses_id = ' . $this->user['ses_id'], AbstractUserAuthentication::class);
1377 }
1378 GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->session_table)->update(
1379 $this->session_table,
1380 ['ses_data' => $this->user['ses_data']],
1381 ['ses_id' => $this->user['ses_id']]
1382 );
1383 }
1384
1385 /*************************
1386 *
1387 * Misc
1388 *
1389 *************************/
1390 /**
1391 * Returns an info array with Login/Logout data submitted by a form or params
1392 *
1393 * @return array
1394 * @internal
1395 */
1396 public function getLoginFormData()
1397 {
1398 $loginData = array();
1399 $loginData['status'] = GeneralUtility::_GP($this->formfield_status);
1400 if ($this->getMethodEnabled) {
1401 $loginData['uname'] = GeneralUtility::_GP($this->formfield_uname);
1402 $loginData['uident'] = GeneralUtility::_GP($this->formfield_uident);
1403 } else {
1404 $loginData['uname'] = GeneralUtility::_POST($this->formfield_uname);
1405 $loginData['uident'] = GeneralUtility::_POST($this->formfield_uident);
1406 }
1407 // Only process the login data if a login is requested
1408 if ($loginData['status'] === 'login') {
1409 $loginData = $this->processLoginData($loginData);
1410 }
1411 $loginData = array_map('trim', $loginData);
1412 return $loginData;
1413 }
1414
1415 /**
1416 * Processes Login data submitted by a form or params depending on the
1417 * passwordTransmissionStrategy
1418 *
1419 * @param array $loginData Login data array
1420 * @param string $passwordTransmissionStrategy Alternative passwordTransmissionStrategy. Used when authentication services wants to override the default.
1421 * @return array
1422 * @internal
1423 */
1424 public function processLoginData($loginData, $passwordTransmissionStrategy = '')
1425 {
1426 $loginSecurityLevel = trim($GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['loginSecurityLevel']) ?: 'normal';
1427 $passwordTransmissionStrategy = $passwordTransmissionStrategy ?: $loginSecurityLevel;
1428 if ($this->writeDevLog) {
1429 GeneralUtility::devLog('Login data before processing: ' . GeneralUtility::arrayToLogString($loginData), AbstractUserAuthentication::class);
1430 }
1431 $serviceChain = '';
1432 $subType = 'processLoginData' . $this->loginType;
1433 $authInfo = $this->getAuthInfoArray();
1434 $isLoginDataProcessed = false;
1435 $processedLoginData = $loginData;
1436 while (is_object($serviceObject = GeneralUtility::makeInstanceService('auth', $subType, $serviceChain))) {
1437 $serviceChain .= ',' . $serviceObject->getServiceKey();
1438 $serviceObject->initAuth($subType, $loginData, $authInfo, $this);
1439 $serviceResult = $serviceObject->processLoginData($processedLoginData, $passwordTransmissionStrategy);
1440 if (!empty($serviceResult)) {
1441 $isLoginDataProcessed = true;
1442 // If the service returns >=200 then no more processing is needed
1443 if ((int)$serviceResult >= 200) {
1444 unset($serviceObject);
1445 break;
1446 }
1447 }
1448 unset($serviceObject);
1449 }
1450 if ($isLoginDataProcessed) {
1451 $loginData = $processedLoginData;
1452 if ($this->writeDevLog) {
1453 GeneralUtility::devLog('Processed login data: ' . GeneralUtility::arrayToLogString($processedLoginData), AbstractUserAuthentication::class);
1454 }
1455 }
1456 return $loginData;
1457 }
1458
1459 /**
1460 * Returns an info array which provides additional information for auth services
1461 *
1462 * @return array
1463 * @internal
1464 */
1465 public function getAuthInfoArray()
1466 {
1467 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
1468 $expressionBuilder = $queryBuilder->expr();
1469 $authInfo = array();
1470 $authInfo['loginType'] = $this->loginType;
1471 $authInfo['refInfo'] = parse_url(GeneralUtility::getIndpEnv('HTTP_REFERER'));
1472 $authInfo['HTTP_HOST'] = GeneralUtility::getIndpEnv('HTTP_HOST');
1473 $authInfo['REMOTE_ADDR'] = GeneralUtility::getIndpEnv('REMOTE_ADDR');
1474 $authInfo['REMOTE_HOST'] = GeneralUtility::getIndpEnv('REMOTE_HOST');
1475 $authInfo['showHiddenRecords'] = $this->showHiddenRecords;
1476 // Can be overidden in localconf by SVCONF:
1477 $authInfo['db_user']['table'] = $this->user_table;
1478 $authInfo['db_user']['userid_column'] = $this->userid_column;
1479 $authInfo['db_user']['username_column'] = $this->username_column;
1480 $authInfo['db_user']['userident_column'] = $this->userident_column;
1481 $authInfo['db_user']['usergroup_column'] = $this->usergroup_column;
1482 $authInfo['db_user']['enable_clause'] = $this->userConstraints($expressionBuilder);
1483 if ($this->checkPid && $this->checkPid_value !== null) {
1484 $authInfo['db_user']['checkPidList'] = $this->checkPid_value;
1485 $authInfo['db_user']['check_pid_clause'] = $expressionBuilder->in(
1486 'pid',
1487 GeneralUtility::intExplode(',', $this->checkPid_value)
1488 );
1489 } else {
1490 $authInfo['db_user']['checkPidList'] = '';
1491 $authInfo['db_user']['check_pid_clause'] = '';
1492 }
1493 $authInfo['db_groups']['table'] = $this->usergroup_table;
1494 return $authInfo;
1495 }
1496
1497 /**
1498 * Check the login data with the user record data for builtin login methods
1499 *
1500 * @param array $user User data array
1501 * @param array $loginData Login data array
1502 * @param string $passwordCompareStrategy Alternative passwordCompareStrategy. Used when authentication services wants to override the default.
1503 * @return bool TRUE if login data matched
1504 */
1505 public function compareUident($user, $loginData, $passwordCompareStrategy = '')
1506 {
1507 return (string)$loginData['uident_text'] !== '' && (string)$loginData['uident_text'] === (string)$user[$this->userident_column];
1508 }
1509
1510 /**
1511 * Garbage collector, removing old expired sessions.
1512 *
1513 * @return void
1514 * @internal
1515 */
1516 public function gc()
1517 {
1518 $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->session_table);
1519 $query->delete($this->session_table)
1520 ->where(
1521 $query->expr()->lt(
1522 'ses_tstamp',
1523 $query->createNamedParameter((int)($GLOBALS['EXEC_TIME'] - $this->gc_time))
1524 )
1525 )
1526 ->andWhere(
1527 $query->expr()->eq(
1528 'ses_name',
1529 $query->createNamedParameter($this->name)
1530 )
1531 )
1532 ->execute();
1533 }
1534
1535 /**
1536 * DUMMY: Writes to log database table (in some extension classes)
1537 *
1538 * @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
1539 * @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 !!)
1540 * @param int $error flag. 0 = message, 1 = error (user problem), 2 = System Error (which should not happen), 3 = security notice (admin)
1541 * @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
1542 * @param string $details Default text that follows the message
1543 * @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...
1544 * @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.)
1545 * @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.)
1546 * @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.)
1547 * @return void
1548 */
1549 public function writelog($type, $action, $error, $details_nr, $details, $data, $tablename, $recuid, $recpid)
1550 {
1551 }
1552
1553 /**
1554 * DUMMY: Check login failures (in some extension classes)
1555 *
1556 * @param string $email Email address
1557 * @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
1558 * @param int $maxFailures Max allowed failures before a warning mail is sent
1559 * @return void
1560 * @ignore
1561 */
1562 public function checkLogFailures($email, $secondsBack, $maxFailures)
1563 {
1564 }
1565
1566 /**
1567 * Raw initialization of the be_user with uid=$uid
1568 * This will circumvent all login procedures and select a be_users record from the
1569 * database and set the content of ->user to the record selected.
1570 * Thus the BE_USER object will appear like if a user was authenticated - however without
1571 * a session id and the fields from the session table of course.
1572 * Will check the users for disabled, start/endtime, etc. ($this->user_where_clause())
1573 *
1574 * @param int $uid The UID of the backend user to set in ->user
1575 * @return void
1576 * @internal
1577 * @see SC_mod_tools_be_user_index::compareUsers(), \TYPO3\CMS\Setup\Controller\SetupModuleController::simulateUser(), freesite_admin::startCreate()
1578 */
1579 public function setBeUserByUid($uid)
1580 {
1581 $this->user = $this->getRawUserByUid($uid);
1582 }
1583
1584 /**
1585 * Raw initialization of the be_user with username=$name
1586 *
1587 * @param string $name The username to look up.
1588 * @return void
1589 * @see \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::setBeUserByUid()
1590 * @internal
1591 */
1592 public function setBeUserByName($name)
1593 {
1594 $this->user = $this->getRawUserByName($name);
1595 }
1596
1597 /**
1598 * Fetching raw user record with uid=$uid
1599 *
1600 * @param int $uid The UID of the backend user to set in ->user
1601 * @return array user record or FALSE
1602 * @internal
1603 */
1604 public function getRawUserByUid($uid)
1605 {
1606 $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
1607 $query->select('*')
1608 ->from($this->user_table)
1609 ->where($query->expr()->eq('uid', $query->createNamedParameter($uid)))
1610 ->andWhere($this->userConstraints($query->expr()));
1611
1612 return $query->execute()->fetch();
1613 }
1614
1615 /**
1616 * Fetching raw user record with username=$name
1617 *
1618 * @param string $name The username to look up.
1619 * @return array user record or FALSE
1620 * @see \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::getUserByUid()
1621 * @internal
1622 */
1623 public function getRawUserByName($name)
1624 {
1625 $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
1626 $query->select('*')
1627 ->from($this->user_table)
1628 ->where($query->expr()->eq('username', $query->createNamedParameter($name)))
1629 ->andWhere($this->userConstraints($query->expr()));
1630
1631 return $query->execute()->fetch();
1632 }
1633
1634 /*************************
1635 *
1636 * Create/update user - EXPERIMENTAL
1637 *
1638 *************************/
1639 /**
1640 * Get a user from DB by username
1641 * provided for usage from services
1642 *
1643 * @param array $dbUser User db table definition: $this->db_user
1644 * @param string $username user name
1645 * @param string $extraWhere Additional WHERE clause: " AND ...
1646 * @return mixed User array or FALSE
1647 */
1648 public function fetchUserRecord($dbUser, $username, $extraWhere = '')
1649 {
1650 $user = false;
1651 if ($username || $extraWhere) {
1652 $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($dbUser['table']);
1653
1654 $constraints = array_filter([
1655 QueryHelper::stripLogicalOperatorPrefix($dbUser['check_pid_clause']),
1656 QueryHelper::stripLogicalOperatorPrefix($dbUser['enable_clause']),
1657 QueryHelper::stripLogicalOperatorPrefix($extraWhere),
1658 ]);
1659
1660 if (!empty($username)) {
1661 array_unshift(
1662 $constraints,
1663 $query->expr()->eq($dbUser['username_column'], $query->createNamedParameter($username))
1664 );
1665 }
1666
1667 $user = $query->select('*')
1668 ->from($dbUser['table'])
1669 ->where(...$constraints)
1670 ->execute()
1671 ->fetch();
1672 }
1673
1674 return $user;
1675 }
1676
1677 /**
1678 * Get global database connection
1679 * @return DatabaseConnection
1680 */
1681 protected function getDatabaseConnection()
1682 {
1683 return $GLOBALS['TYPO3_DB'];
1684 }
1685 }