[FEATURE] Add hook for TSFE fetch_the_id() post processing
[Packages/TYPO3.CMS.git] / typo3 / sysext / cms / tslib / class.tslib_feuserauth.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 1999-2011 Kasper Skårhøj (kasperYYYY@typo3.com)
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
18 *
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27
28 /**
29 * Front End session user. Login and session data
30 * Included from index_ts.php
31 *
32 * Revised for TYPO3 3.6 June/2003 by Kasper Skårhøj
33 *
34 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
35 * @author René Fritz <r.fritz@colorcube.de>
36 */
37
38 /**
39 * Extension class for Front End User Authentication.
40 *
41 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
42 * @author René Fritz <r.fritz@colorcube.de>
43 * @package TYPO3
44 * @subpackage tslib
45 */
46 class tslib_feUserAuth extends t3lib_userAuth {
47 // formfield with 0 or 1 // 1 = permanent login enabled // 0 = session is valid for a browser session only
48 var $formfield_permanent = 'permalogin';
49 // Lifetime of session data in seconds.
50 protected $sessionDataLifetime = 86400;
51
52 var $usergroup_column = 'usergroup';
53 var $usergroup_table = 'fe_groups';
54 var $groupData = Array(
55 'title' =>Array(),
56 'uid' =>Array(),
57 'pid' =>Array()
58 );
59 // Used to accumulate the TSconfig data of the user
60 var $TSdataArray = array();
61 var $userTS = array();
62 var $userTSUpdated = 0;
63 var $showHiddenRecords = 0;
64
65 // Session and user data:
66 /*
67 There are two types of data that can be stored: UserData and Session-Data. Userdata is for the login-user, and session-data for anyone viewing the pages.
68 'Keys' are keys in the internal dataarray of the data. When you get or set a key in one of the data-spaces (user or session) you decide the type of the variable (not object though)
69 'Reserved' keys are:
70 - 'recs': Array: Used to 'register' records, eg in a shopping basket. Structure: [recs][tablename][record_uid]=number
71 - sys: Reserved for TypoScript standard code.
72 */
73 var $sesData = array();
74 var $sesData_change = 0;
75 var $userData_change = 0;
76 protected $sessionDataTimestamp = NULL;
77
78 /**
79 * Default constructor.
80 */
81 public function __construct() {
82 $this->session_table = 'fe_sessions';
83 $this->name = self::getCookieName();
84 $this->get_name = 'ftu';
85 $this->loginType = 'FE';
86
87 $this->user_table = 'fe_users';
88 $this->username_column = 'username';
89 $this->userident_column = 'password';
90 $this->userid_column = 'uid';
91 $this->lastLogin_column = 'lastlogin';
92
93 $this->enablecolumns = array(
94 'deleted' => 'deleted',
95 'disabled' => 'disable',
96 'starttime' => 'starttime',
97 'endtime' => 'endtime',
98 );
99
100 $this->formfield_uname = 'user';
101 $this->formfield_uident = 'pass';
102 $this->formfield_chalvalue = 'challenge';
103 $this->formfield_status = 'logintype';
104 $this->security_level = '';
105
106 $this->auth_timeout_field = 6000;
107 $this->sendNoCacheHeaders = FALSE;
108 $this->getFallBack = TRUE;
109 $this->getMethodEnabled = TRUE;
110 }
111
112 /**
113 * Returns the configured cookie name
114 *
115 * @return string
116 */
117 public static function getCookieName() {
118 $configuredCookieName = trim($GLOBALS['TYPO3_CONF_VARS']['FE']['cookieName']);
119 if (empty($configuredCookieName)) {
120 $configuredCookieName = 'fe_typo_user';
121 }
122 return $configuredCookieName;
123 }
124
125 /**
126 * Starts a user session
127 *
128 * @return void
129 * @see t3lib_userAuth::start()
130 */
131 function start() {
132 if (intval($this->auth_timeout_field)>0 && intval($this->auth_timeout_field) < $this->lifetime) {
133 // If server session timeout is non-zero but less than client session timeout: Copy this value instead.
134 $this->auth_timeout_field = $this->lifetime;
135 }
136
137 $this->sessionDataLifetime = intval($GLOBALS['TYPO3_CONF_VARS']['FE']['sessionDataLifetime']);
138 if ($this->sessionDataLifetime <= 0) {
139 $this->sessionDataLifetime = 86400;
140 }
141
142 parent::start();
143 }
144
145 /**
146 * Returns a new session record for the current user for insertion into the DB.
147 *
148 * @return array User session record
149 */
150 function getNewSessionRecord($tempuser) {
151 $insertFields = parent::getNewSessionRecord($tempuser);
152 $insertFields['ses_permanent'] = $this->is_permanent;
153
154 return $insertFields;
155 }
156
157 /**
158 * Determine whether a session cookie needs to be set (lifetime=0)
159 *
160 * @return boolean
161 * @internal
162 */
163 function isSetSessionCookie() {
164 $retVal = ($this->newSessionID || $this->forceSetCookie) && ($this->lifetime==0 || !$this->user['ses_permanent']);
165 return $retVal;
166 }
167
168 /**
169 * Determine whether a non-session cookie needs to be set (lifetime>0)
170 *
171 * @return boolean
172 * @internal
173 */
174 function isRefreshTimeBasedCookie() {
175 return $this->lifetime > 0 && $this->user['ses_permanent'];
176 }
177
178 /**
179 * Returns an info array with Login/Logout data submitted by a form or params
180 *
181 * @return array
182 * @see t3lib_userAuth::getLoginFormData()
183 */
184 function getLoginFormData() {
185 $loginData = parent::getLoginFormData();
186 if ($GLOBALS['TYPO3_CONF_VARS']['FE']['permalogin'] == 0 || $GLOBALS['TYPO3_CONF_VARS']['FE']['permalogin'] == 1) {
187 if ($this->getMethodEnabled) {
188 $isPermanent = t3lib_div::_GP($this->formfield_permanent);
189 } else {
190 $isPermanent = t3lib_div::_POST($this->formfield_permanent);
191 }
192 if (strlen($isPermanent) != 1) {
193 $isPermanent = $GLOBALS['TYPO3_CONF_VARS']['FE']['permalogin'];
194 } elseif (!$isPermanent) {
195 // To make sure the user gets a session cookie and doesn't keep a possibly existing time based cookie,
196 // we need to force seeting the session cookie here
197 $this->forceSetCookie = TRUE;
198 }
199 $isPermanent = $isPermanent?1:0;
200 } elseif ($GLOBALS['TYPO3_CONF_VARS']['FE']['permalogin'] == 2) {
201 $isPermanent = 1;
202 } else {
203 $isPermanent = 0;
204 }
205 $loginData['permanent'] = $isPermanent;
206 $this->is_permanent = $isPermanent;
207
208 return $loginData;
209 }
210
211 /**
212 * Will select all fe_groups records that the current fe_user is member of - and which groups are also allowed in the current domain.
213 * It also accumulates the TSconfig for the fe_user/fe_groups in ->TSdataArray
214 *
215 * @return integer Returns the number of usergroups for the frontend users (if the internal user record exists and the usergroup field contains a value)
216 */
217 function fetchGroupData() {
218 $this->TSdataArray = array();
219 $this->userTS = array();
220 $this->userTSUpdated = 0;
221 $this->groupData = Array(
222 'title' => Array(),
223 'uid' => Array(),
224 'pid' => Array()
225 );
226
227 // Setting default configuration:
228 $this->TSdataArray[]=$GLOBALS['TYPO3_CONF_VARS']['FE']['defaultUserTSconfig'];
229
230 // Get the info data for auth services
231 $authInfo = $this->getAuthInfoArray();
232
233 if ($this->writeDevLog) {
234 if (is_array($this->user)) {
235 t3lib_div::devLog('Get usergroups for user: '.t3lib_div::arrayToLogString($this->user, array($this->userid_column, $this->username_column)), 'tslib_feUserAuth');
236 } else {
237 t3lib_div::devLog('Get usergroups for "anonymous" user', 'tslib_feUserAuth');
238 }
239 }
240
241 $groupDataArr = array();
242
243 // Use 'auth' service to find the groups for the user
244 $serviceChain='';
245 $subType = 'getGroups'.$this->loginType;
246 while (is_object($serviceObj = t3lib_div::makeInstanceService('auth', $subType, $serviceChain))) {
247 $serviceChain.=','.$serviceObj->getServiceKey();
248 $serviceObj->initAuth($subType, array(), $authInfo, $this);
249
250 $groupData = $serviceObj->getGroups($this->user, $groupDataArr);
251 if (is_array($groupData) && count($groupData)) {
252 // Keys in $groupData should be unique ids of the groups (like "uid") so this function will override groups.
253 $groupDataArr = t3lib_div::array_merge($groupDataArr, $groupData);
254 }
255 unset($serviceObj);
256 }
257 if ($this->writeDevLog && $serviceChain) {
258 t3lib_div::devLog($subType.' auth services called: '.$serviceChain, 'tslib_feUserAuth');
259 }
260 if ($this->writeDevLog && !count($groupDataArr)) {
261 t3lib_div::devLog('No usergroups found by services', 'tslib_feUserAuth');
262 }
263 if ($this->writeDevLog && count($groupDataArr)) {
264 t3lib_div::devLog(count($groupDataArr).' usergroup records found by services', 'tslib_feUserAuth');
265 }
266
267 // Use 'auth' service to check the usergroups if they are really valid
268 foreach ($groupDataArr as $groupData) {
269 // By default a group is valid
270 $validGroup = TRUE;
271
272 $serviceChain='';
273 $subType = 'authGroups'.$this->loginType;
274 while (is_object($serviceObj = t3lib_div::makeInstanceService('auth', $subType, $serviceChain))) {
275 $serviceChain.=','.$serviceObj->getServiceKey();
276 $serviceObj->initAuth($subType, array(), $authInfo, $this);
277
278 if (!$serviceObj->authGroup($this->user, $groupData)) {
279 $validGroup = FALSE;
280 if ($this->writeDevLog) {
281 t3lib_div::devLog($subType.' auth service did not auth group: '.t3lib_div::arrayToLogString($groupData, 'uid,title'), 'tslib_feUserAuth', 2);
282 }
283
284 break;
285 }
286 unset($serviceObj);
287 }
288 unset($serviceObj);
289
290 if ($validGroup) {
291 $this->groupData['title'][$groupData['uid']] = $groupData['title'];
292 $this->groupData['uid'][$groupData['uid']] = $groupData['uid'];
293 $this->groupData['pid'][$groupData['uid']] = $groupData['pid'];
294 $this->groupData['TSconfig'][$groupData['uid']] = $groupData['TSconfig'];
295 }
296 }
297
298 if (count($this->groupData) && count($this->groupData['TSconfig'])) {
299 // TSconfig: collect it in the order it was collected
300 foreach($this->groupData['TSconfig'] as $TSdata) {
301 $this->TSdataArray[]=$TSdata;
302 }
303
304 $this->TSdataArray[] = $this->user['TSconfig'];
305
306 // Sort information
307 ksort($this->groupData['title']);
308 ksort($this->groupData['uid']);
309 ksort($this->groupData['pid']);
310 }
311
312 return count($this->groupData['uid']) ? count($this->groupData['uid']) : 0;
313 }
314
315 /**
316 * Returns the parsed TSconfig for the fe_user
317 * First time this function is called it will parse the TSconfig and store it in $this->userTS. Subsequent requests will not re-parse the TSconfig but simply return what is already in $this->userTS
318 *
319 * @return array TSconfig array for the fe_user
320 */
321 function getUserTSconf() {
322 if (!$this->userTSUpdated) {
323 // Parsing the user TS (or getting from cache)
324 $this->TSdataArray = t3lib_TSparser::checkIncludeLines_array($this->TSdataArray);
325 $userTS = implode(LF . '[GLOBAL]' . LF, $this->TSdataArray);
326 $parseObj = t3lib_div::makeInstance('t3lib_TSparser');
327 $parseObj->parse($userTS);
328 $this->userTS = $parseObj->setup;
329
330 $this->userTSUpdated = 1;
331 }
332 return $this->userTS;
333 }
334
335 /*****************************************
336 *
337 * Session data management functions
338 *
339 ****************************************/
340
341 /**
342 * Fetches the session data for the user (from the fe_session_data table) based on the ->id of the current user-session.
343 * The session data is restored to $this->sesData
344 * 1/100 calls will also do a garbage collection.
345 *
346 * @return void
347 * @access private
348 * @see storeSessionData()
349 */
350 function fetchSessionData() {
351 // Gets SesData if any AND if not already selected by session fixation check in ->isExistingSessionRecord()
352 if ($this->id && !count($this->sesData)) {
353 $statement = $GLOBALS['TYPO3_DB']->prepare_SELECTquery(
354 '*',
355 'fe_session_data',
356 'hash = :hash'
357 );
358 $statement->execute(array(':hash' => $this->id));
359 if (($sesDataRow = $statement->fetch()) !== FALSE) {
360 $this->sesData = unserialize($sesDataRow['content']);
361 $this->sessionDataTimestamp = $sesDataRow['tstamp'];
362 }
363 $statement->free();
364 }
365 }
366
367 /**
368 * Will write UC and session data.
369 * If the flag $this->userData_change has been set, the function ->writeUC is called (which will save persistent user session data)
370 * If the flag $this->sesData_change has been set, the fe_session_data table is updated with the content of $this->sesData
371 * If the $this->sessionDataTimestamp is NULL there was no session record yet, so we need to insert it into the database
372 *
373 * @return void
374 * @see fetchSessionData(), getKey(), setKey()
375 */
376 function storeSessionData() {
377 // Saves UC and SesData if changed.
378 if ($this->userData_change) {
379 $this->writeUC('');
380 }
381
382 if ($this->sesData_change && $this->id) {
383 if ($this->sessionDataTimestamp === NULL) {
384 // Write new session-data
385 $insertFields = array(
386 'hash' => $this->id,
387 'content' => serialize($this->sesData),
388 'tstamp' => $GLOBALS['EXEC_TIME']
389 );
390 $this->sessionDataTimestamp = $GLOBALS['EXEC_TIME'];
391 $GLOBALS['TYPO3_DB']->exec_INSERTquery('fe_session_data', $insertFields);
392 } else {
393 // Update session data
394 $updateFields = array(
395 'content' => serialize($this->sesData),
396 'tstamp' => $GLOBALS['EXEC_TIME'],
397 );
398 $this->sessionDataTimestamp = $GLOBALS['EXEC_TIME'];
399 $GLOBALS['TYPO3_DB']->exec_UPDATEquery('fe_session_data', 'hash=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->id, 'fe_session_data'), $updateFields);
400 }
401 }
402 }
403
404 /**
405 * Removes data of the current session.
406 *
407 * @return void
408 */
409 public function removeSessionData() {
410 $GLOBALS['TYPO3_DB']->exec_DELETEquery(
411 'fe_session_data',
412 'hash=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->id, 'fe_session_data')
413 );
414 }
415
416 /**
417 * Executes the garbage collection of session data and session.
418 * The lifetime of session data is defined by $TYPO3_CONF_VARS['FE']['sessionDataLifetime'].
419 *
420 * @return void
421 */
422 public function gc() {
423 $timeoutTimeStamp = intval($GLOBALS['EXEC_TIME'] - $this->sessionDataLifetime);
424 $GLOBALS['TYPO3_DB']->exec_DELETEquery('fe_session_data', 'tstamp < ' . $timeoutTimeStamp);
425
426 parent::gc();
427 }
428
429 /**
430 * Returns session data for the fe_user; Either persistent data following the fe_users uid/profile (requires login) or current-session based (not available when browse is closed, but does not require login)
431 *
432 * @param string $type Session data type; Either "user" (persistent, bound to fe_users profile) or "ses" (temporary, bound to current session cookie)
433 * @param string $key Key from the data array to return; The session data (in either case) is an array ($this->uc / $this->sesData) and this value determines which key to return the value for.
434 * @return mixed Returns whatever value there was in the array for the key, $key
435 * @see setKey()
436 */
437 function getKey($type, $key) {
438 if ($key) {
439 switch($type) {
440 case 'user':
441 return $this->uc[$key];
442 break;
443 case 'ses':
444 return $this->sesData[$key];
445 break;
446 }
447 }
448 }
449
450 /**
451 * Saves session data, either persistent or bound to current session cookie. Please see getKey() for more details.
452 * When a value is set the flags $this->userData_change or $this->sesData_change will be set so that the final call to ->storeSessionData() will know if a change has occurred and needs to be saved to the database.
453 * Notice: The key "recs" is already used by the function record_registration() which stores table/uid=value pairs in that key. This is used for the shopping basket among other things.
454 * Notice: Simply calling this function will not save the data to the database! The actual saving is done in storeSessionData() which is called as some of the last things in index_ts.php. So if you exit before this point, nothing gets saved of course! And the solution is to call $GLOBALS['TSFE']->storeSessionData(); before you exit.
455 *
456 * @param string $type Session data type; Either "user" (persistent, bound to fe_users profile) or "ses" (temporary, bound to current session cookie)
457 * @param string $key Key from the data array to store incoming data in; The session data (in either case) is an array ($this->uc / $this->sesData) and this value determines in which key the $data value will be stored.
458 * @param mixed $data The data value to store in $key
459 * @return void
460 * @see setKey(), storeSessionData(), record_registration()
461 */
462 function setKey($type, $key, $data) {
463 if ($key) {
464 switch($type) {
465 case 'user':
466 if ($this->user['uid']) {
467 $this->uc[$key]=$data;
468 $this->userData_change=1;
469 }
470 break;
471 case 'ses':
472 $this->sesData[$key]=$data;
473 $this->sesData_change=1;
474 break;
475 }
476 }
477 }
478
479 /**
480 * Returns the session data stored for $key.
481 * The data will last only for this login session since it is stored in the session table.
482 *
483 * @param string $key
484 * @return mixed
485 */
486 public function getSessionData($key) {
487 return $this->getKey('ses', $key);
488 }
489
490 /**
491 * Saves the tokens so that they can be used by a later incarnation of this class.
492 *
493 * @param string $key
494 * @param mixed $data
495 * @return void
496 */
497 public function setAndSaveSessionData($key, $data) {
498 $this->setKey('ses', $key, $data);
499 $this->storeSessionData();
500 }
501
502 /**
503 * Registration of records/"shopping basket" in session data
504 * This will take the input array, $recs, and merge into the current "recs" array found in the session data.
505 * If a change in the recs storage happens (which it probably does) the function setKey() is called in order to store the array again.
506 *
507 * @param array $recs The data array to merge into/override the current recs values. The $recs array is constructed as [table]][uid] = scalar-value (eg. string/integer).
508 * @param integer $maxSizeOfSessionData The maximum size of stored session data. If zero, no limit is applied and even confirmation of cookie session is discarded.
509 * @return void
510 */
511 function record_registration($recs, $maxSizeOfSessionData = 0) {
512
513 // Storing value ONLY if there is a confirmed cookie set (->cookieID),
514 // otherwise a shellscript could easily be spamming the fe_sessions table
515 // with bogus content and thus bloat the database
516 if (!$maxSizeOfSessionData || $this->cookieId) {
517 if ($recs['clear_all']) {
518 $this->setKey('ses', 'recs', array());
519 }
520 $change=0;
521 $recs_array=$this->getKey('ses', 'recs');
522 foreach ($recs as $table => $data) {
523 if (is_array($data)) {
524 foreach ($data as $rec_id => $value) {
525 if ($value != $recs_array[$table][$rec_id]) {
526 $recs_array[$table][$rec_id] = $value;
527 $change=1;
528 }
529 }
530 }
531 }
532 if ($change && (!$maxSizeOfSessionData || strlen(serialize($recs_array))<$maxSizeOfSessionData)) {
533 $this->setKey('ses', 'recs', $recs_array);
534 }
535 }
536 }
537
538 /**
539 * Determine whether there's an according session record to a given session_id
540 * in the database. Don't care if session record is still valid or not.
541 *
542 * This calls the parent function but additionally tries to look up the session ID in the "fe_session_data" table.
543 *
544 * @param integer $id Claimed Session ID
545 * @return boolean Returns TRUE if a corresponding session was found in the database
546 */
547 function isExistingSessionRecord($id) {
548 // Perform check in parent function
549 $count = parent::isExistingSessionRecord($id);
550
551 // Check if there are any fe_session_data records for the session ID the client claims to have
552 if ($count == FALSE) {
553 $statement = $GLOBALS['TYPO3_DB']->prepare_SELECTquery(
554 'content,tstamp',
555 'fe_session_data',
556 'hash = :hash'
557 );
558 $res = $statement->execute(array(':hash' => $id));
559 if ($res !== FALSE) {
560 if ($sesDataRow = $statement->fetch()) {
561 $count = TRUE;
562 $this->sesData = unserialize($sesDataRow['content']);
563 $this->sessionDataTimestamp = $sesDataRow['tstamp'];
564 }
565 $statement->free();
566 }
567 }
568
569 return $count;
570 }
571 }
572 ?>