MDL-17775 Chat ajax update.php not using sessions anymore
[moodle.git] / lib / sessionlib.php
CommitLineData
57f7b7ce 1<?php //$Id$
2
542797b4 3/**
4 * Factory method returning moodle_session object.
5 * @return moodle_session
6 */
b7b64ff2 7function session_get_instance() {
0ad6b20c 8 static $session = null;
9
10 if (is_null($session)) {
11 $session = new moodle_session();
12 }
13
14 return $session;
15}
16
57f7b7ce 17/**
18 * Class handling all session and cookies related stuff.
19 */
20class moodle_session {
b7b64ff2 21 public function __construct() {
57f7b7ce 22 global $CFG;
57f7b7ce 23 $this->prepare_cookies();
24 $this->init_session_storage();
25
26 if (!empty($CFG->usesid) && empty($_COOKIE['MoodleSession'.$CFG->sessioncookie])) {
0ad6b20c 27 sid_start_ob();
57f7b7ce 28 }
29
0ad6b20c 30 if (NO_MOODLE_COOKIES) {
31 $_SESSION = array();
32 $_SESSION['SESSION'] = new object();
33 $_SESSION['USER'] = new object();
34
35 } else {
57f7b7ce 36 session_name('MoodleSession'.$CFG->sessioncookie);
e6e13284 37 session_set_cookie_params(0, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
57f7b7ce 38 @session_start();
39 if (!isset($_SESSION['SESSION'])) {
40 $_SESSION['SESSION'] = new object();
57f7b7ce 41 }
42 if (!isset($_SESSION['USER'])) {
43 $_SESSION['USER'] = new object();
44 }
57f7b7ce 45 }
57f7b7ce 46
b7b64ff2 47 $this->check_user_initialised();
9bda43e6 48
49 $this->check_security();
b7b64ff2 50 }
51
52 /**
53 * Initialise $USER object, handles google access.
54 *
55 * @return void
56 */
57 protected function check_user_initialised() {
58 if (isset($_SESSION['USER']->id)) {
59 // already set up $USER
60 return;
61 }
62
63 $user = null;
64
65 if (!empty($CFG->opentogoogle) and !NO_MOODLE_COOKIES) {
66 if (!empty($_SERVER['HTTP_USER_AGENT'])) {
67 // allow web spiders in as guest users
68 if (strpos($_SERVER['HTTP_USER_AGENT'], 'Googlebot') !== false ) {
69 $user = guest_user();
70 } else if (strpos($_SERVER['HTTP_USER_AGENT'], 'google.com') !== false ) { // Google
71 $user = guest_user();
72 } else if (strpos($_SERVER['HTTP_USER_AGENT'], 'Yahoo! Slurp') !== false ) { // Yahoo
73 $user = guest_user();
74 } else if (strpos($_SERVER['HTTP_USER_AGENT'], '[ZSEBOT]') !== false ) { // Zoomspider
75 $user = guest_user();
76 } else if (strpos($_SERVER['HTTP_USER_AGENT'], 'MSNBOT') !== false ) { // MSN Search
77 $user = guest_user();
78 }
79 }
1b813f5c 80 if (!empty($CFG->guestloginbutton) and !$user and !empty($_SERVER['HTTP_REFERER'])) {
b7b64ff2 81 // automaticaly log in users coming from search engine results
82 if (strpos($_SERVER['HTTP_REFERER'], 'google') !== false ) {
83 $user = guest_user();
84 } else if (strpos($_SERVER['HTTP_REFERER'], 'altavista') !== false ) {
85 $user = guest_user();
86 }
87 }
88 }
89
90 if (!$user) {
91 $user = new object();
92 $user->id = 0; // to enable proper function of $CFG->notloggedinroleid hack
0ad6b20c 93 if (isset($CFG->mnet_localhost_id)) {
b7b64ff2 94 $user->mnethostid = $CFG->mnet_localhost_id;
57f7b7ce 95 }
96 }
b7b64ff2 97 session_set_user($user);
57f7b7ce 98 }
99
9bda43e6 100 /**
101 * Does various session security checks
102 * @global void
103 */
93f66983 104 protected function check_security() {
105 global $CFG;
106
9bda43e6 107 if (!empty($_SESSION['USER']->id) and !empty($CFG->tracksessionip)) {
108 /// Make sure current IP matches the one for this session
93f66983 109 $remoteaddr = getremoteaddr();
110
111 if (empty($_SESSION['USER']->sessionip)) {
112 $_SESSION['USER']->sessionip = $remoteaddr;
113 }
114
115 if ($_SESSION['USER']->sessionip != $remoteaddr) {
9bda43e6 116 // this is a security feature - terminate the session in case of any doubt
117 $this->terminate();
118 print_error('sessionipnomatch2', 'error');
93f66983 119 }
120 }
121
9bda43e6 122 // TODO: add wwwroot check here
123
93f66983 124 }
125
57f7b7ce 126 /**
127 * Terminates active moodle session
128 */
129 public function terminate() {
130 global $CFG, $SESSION, $USER;
131
0ad6b20c 132 $_SESSION = array();
57f7b7ce 133
0ad6b20c 134 $SESSION = new object();
57f7b7ce 135 $USER = new object();
136 $USER->id = 0;
137 if (isset($CFG->mnet_localhost_id)) {
138 $USER->mnethostid = $CFG->mnet_localhost_id;
139 }
140
0ad6b20c 141 // Initialize variable to pass-by-reference to headers_sent(&$file, &$line)
142 $file = null;
143 $line = null;
144 if (headers_sent($file, $line)) {
145 error_log('Can not terminate session properly - headers were already sent in file: '.$file.' on line '.$line);
57f7b7ce 146 }
57f7b7ce 147
9bda43e6 148 // now let's try to get a new session id and destroy the old one
149 @session_regenerate_id(true);
150
151 // close the session
0ad6b20c 152 @session_write_close();
57f7b7ce 153 }
154
155 /**
156 * Prepare cookies and varions system settings
157 */
b7b64ff2 158 protected function prepare_cookies() {
57f7b7ce 159 global $CFG, $nomoodlecookie;
160
161 if (!defined('NO_MOODLE_COOKIES')) {
162 if (isset($nomoodlecookie)) {
163 // backwards compatibility only
164 define('NO_MOODLE_COOKIES', $nomoodlecookie);
165 unset($nomoodlecookie);
166 } else {
167 define('NO_MOODLE_COOKIES', false);
168 }
169 }
170
171 if (!isset($CFG->cookiesecure) or strpos($CFG->wwwroot, 'https://') !== 0) {
172 $CFG->cookiesecure = 0;
173 }
174
175 if (!isset($CFG->cookiehttponly)) {
176 $CFG->cookiehttponly = 0;
177 }
178
179 /// Set sessioncookie and sessioncookiepath variable if it isn't already
180 if (!isset($CFG->sessioncookie)) {
181 $CFG->sessioncookie = '';
182 }
e6e13284 183 if (!isset($CFG->sessioncookiedomain)) {
184 $CFG->sessioncookiedomain = '';
185 }
57f7b7ce 186 if (!isset($CFG->sessioncookiepath)) {
187 $CFG->sessioncookiepath = '/';
188 }
189
190 //discard session ID from POST, GET and globals to tighten security,
191 //this session fixation prevention can not be used in cookieless mode
192 if (empty($CFG->usesid)) {
193 unset(${'MoodleSession'.$CFG->sessioncookie});
194 unset($_GET['MoodleSession'.$CFG->sessioncookie]);
195 unset($_POST['MoodleSession'.$CFG->sessioncookie]);
b7b64ff2 196 unset($_REQUEST['MoodleSession'.$CFG->sessioncookie]);
57f7b7ce 197 }
198 //compatibility hack for Moodle Cron, cookies not deleted, but set to "deleted" - should not be needed with NO_MOODLE_COOKIES in cron.php now
199 if (!empty($_COOKIE['MoodleSession'.$CFG->sessioncookie]) && $_COOKIE['MoodleSession'.$CFG->sessioncookie] == "deleted") {
200 unset($_COOKIE['MoodleSession'.$CFG->sessioncookie]);
201 }
57f7b7ce 202 }
203
204 /**
205 * Inits session storage.
206 */
b7b64ff2 207 protected function init_session_storage() {
57f7b7ce 208 global $CFG;
209
210 /// Set up session handling
211 if(empty($CFG->respectsessionsettings)) {
212 if (true) { /// File-based sessions
213 // Some distros disable GC by setting probability to 0
214 // overriding the PHP default of 1
215 // (gc_probability is divided by gc_divisor, which defaults to 1000)
216 if (ini_get('session.gc_probability') == 0) {
217 ini_set('session.gc_probability', 1);
218 }
219
220 if (!empty($CFG->sessiontimeout)) {
221 ini_set('session.gc_maxlifetime', $CFG->sessiontimeout);
222 }
223
224 if (!file_exists($CFG->dataroot .'/sessions')) {
225 make_upload_directory('sessions');
226 }
227 ini_set('session.save_path', $CFG->dataroot .'/sessions');
228
229 } else { /// Database sessions
230 // TODO: implement proper database session storage
231 }
232 }
233 }
0ad6b20c 234}
57f7b7ce 235
0ad6b20c 236/**
237 * Makes sure that $USER->sesskey exists, if $USER itself exists. It sets a new sesskey
238 * if one does not already exist, but does not overwrite existing sesskeys. Returns the
239 * sesskey string if $USER exists, or boolean false if not.
240 *
241 * @uses $USER
242 * @return string
243 */
244function sesskey() {
245 global $USER;
57f7b7ce 246
0ad6b20c 247 if (empty($USER->sesskey)) {
248 $USER->sesskey = random_string(10);
249 }
57f7b7ce 250
0ad6b20c 251 return $USER->sesskey;
252}
57f7b7ce 253
57f7b7ce 254
0ad6b20c 255/**
256 * For security purposes, this function will check that the currently
257 * given sesskey (passed as a parameter to the script or this function)
258 * matches that of the current user.
259 *
260 * @param string $sesskey optionally provided sesskey
261 * @return bool
262 */
263function confirm_sesskey($sesskey=NULL) {
264 global $USER;
57f7b7ce 265
eb85959b 266 if (!empty($USER->ignoresesskey)) {
0ad6b20c 267 return true;
268 }
57f7b7ce 269
0ad6b20c 270 if (empty($sesskey)) {
271 $sesskey = required_param('sesskey', PARAM_RAW); // Check script parameters
57f7b7ce 272 }
273
eb85959b 274 return (sesskey() === $sesskey);
0ad6b20c 275}
276
277/**
278 * Sets a moodle cookie with a weakly encrypted string
279 *
280 * @uses $CFG
281 * @uses DAYSECS
282 * @uses HOURSECS
283 * @param string $thing The string to encrypt and place in a cookie
284 */
285function set_moodle_cookie($thing) {
286 global $CFG;
287
288 if ($thing == 'guest') { // Ignore guest account
289 return;
57f7b7ce 290 }
291
0ad6b20c 292 $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
293
294 $days = 60;
295 $seconds = DAYSECS*$days;
296
297 // no need to set secure or http cookie only here - it is not secret
298 setcookie($cookiename, '', time() - HOURSECS, $CFG->sessioncookiepath, $CFG->sessioncookiedomain);
299 setcookie($cookiename, rc4encrypt($thing), time()+$seconds, $CFG->sessioncookiepath, $CFG->sessioncookiedomain);
300}
301
302/**
303 * Gets a moodle cookie with a weakly encrypted string
304 *
305 * @uses $CFG
306 * @return string
307 */
308function get_moodle_cookie() {
309 global $CFG;
310
311 $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
312
313 if (empty($_COOKIE[$cookiename])) {
314 return '';
315 } else {
316 $thing = rc4decrypt($_COOKIE[$cookiename]);
317 return ($thing == 'guest') ? '': $thing; // Ignore guest account
57f7b7ce 318 }
0ad6b20c 319}
57f7b7ce 320
b7b64ff2 321/**
322 * Setup $USER object - called during login, loginas, etc.
323 * Preloads capabilities and checks enrolment plugins
324 *
325 * @param object $user full user record object
326 * @return void
327 */
328function session_set_user($user) {
329 $_SESSION['USER'] = $user;
330 check_enrolment_plugins($_SESSION['USER']);
331 load_all_capabilities();
eb85959b 332 sesskey(); // init session key
b7b64ff2 333}
334
542797b4 335/**
336 * Is current $USER logged-in-as somebody else?
337 * @return bool
338 */
b7b64ff2 339function session_is_loggedinas() {
85f6b737 340 return !empty($_SESSION['USER']->realuser);
542797b4 341}
342
6132768e 343/**
344 * Returns the $USER object ignoring current login-as session
345 * @return object user object
346 */
b7b64ff2 347function session_get_realuser() {
348 if (session_is_loggedinas()) {
6132768e 349 return $_SESSION['REALUSER'];
350 } else {
351 return $_SESSION['USER'];
352 }
353}
354
542797b4 355/**
356 * Login as another user - no security checks here.
357 * @param int $userid
358 * @param object $context
359 * @return void
360 */
8d1964c4 361function session_loginas($userid, $context) {
b7b64ff2 362 if (session_is_loggedinas()) {
8d1964c4 363 return;
364 }
365
85f6b737 366 // switch to fresh new $SESSION
367 $_SESSION['REALSESSION'] = $_SESSION['SESSION'];
6132768e 368 $_SESSION['SESSION'] = new object();
8d1964c4 369
85f6b737 370 /// Create the new $USER object with all details and reload needed capabilitites
371 $_SESSION['REALUSER'] = $_SESSION['USER'];
b7b64ff2 372 $user = get_complete_user_data('id', $userid);
373 $user->realuser = $_SESSION['REALUSER']->id;
374 $user->loginascontext = $context;
375 session_set_user($user);
8d1964c4 376}
377
542797b4 378/**
379 * Terminate login-as session
380 * @return void
381 */
8d1964c4 382function session_unloginas() {
b7b64ff2 383 if (!session_is_loggedinas()) {
8d1964c4 384 return;
385 }
386
6132768e 387 $_SESSION['SESSION'] = $_SESSION['REALSESSION'];
388 unset($_SESSION['REALSESSION']);
8d1964c4 389
6132768e 390 $_SESSION['USER'] = $_SESSION['REALUSER'];
391 unset($_SESSION['REALUSER']);
8d1964c4 392}
393
0ad6b20c 394/**
395* Enable cookieless sessions by including $CFG->usesid=true;
396* in config.php.
397* Based on code from php manual by Richard at postamble.co.uk
398* Attempts to use cookies if cookies not present then uses session ids attached to all urls and forms to pass session id from page to page.
399* If site is open to google, google is given guest access as usual and there are no sessions. No session ids will be attached to urls for googlebot.
400* This doesn't require trans_sid to be turned on but this is recommended for better performance
401* you should put :
402* session.use_trans_sid = 1
403* in your php.ini file and make sure that you don't have a line like this in your php.ini
404* session.use_only_cookies = 1
405* @author Richard at postamble.co.uk and Jamie Pratt
406* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
407*/
408/**
409* You won't call this function directly. This function is used to process
410* text buffered by php in an output buffer. All output is run through this function
411* before it is ouput.
412* @param string $buffer is the output sent from php
413* @return string the output sent to the browser
414*/
415function sid_ob_rewrite($buffer){
416 $replacements = array(
417 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*")([^"]*)(")/i',
418 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*\')([^\']*)(\')/i');
419
420 $buffer = preg_replace_callback($replacements, 'sid_rewrite_link_tag', $buffer);
421 $buffer = preg_replace('/<form\s[^>]*>/i',
422 '\0<input type="hidden" name="' . session_name() . '" value="' . session_id() . '"/>', $buffer);
423
424 return $buffer;
425}
426/**
427* You won't call this function directly. This function is used to process
428* text buffered by php in an output buffer. All output is run through this function
429* before it is ouput.
430* This function only processes absolute urls, it is used when we decide that
431* php is processing other urls itself but needs some help with internal absolute urls still.
432* @param string $buffer is the output sent from php
433* @return string the output sent to the browser
434*/
435function sid_ob_rewrite_absolute($buffer){
436 $replacements = array(
437 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*")((?:http|https)[^"]*)(")/i',
438 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*\')((?:http|https)[^\']*)(\')/i');
439
440 $buffer = preg_replace_callback($replacements, 'sid_rewrite_link_tag', $buffer);
441 $buffer = preg_replace('/<form\s[^>]*>/i',
442 '\0<input type="hidden" name="' . session_name() . '" value="' . session_id() . '"/>', $buffer);
443 return $buffer;
444}
57f7b7ce 445
0ad6b20c 446/**
447* A function to process link, a and script tags found
448* by preg_replace_callback in {@link sid_ob_rewrite($buffer)}.
449*/
450function sid_rewrite_link_tag($matches){
451 $url = $matches[4];
452 $url = sid_process_url($url);
453 return $matches[1].$url.$matches[5];
454}
455
456/**
457* You can call this function directly. This function is used to process
458* urls to add a moodle session id to the url for internal links.
459* @param string $url is a url
460* @return string the processed url
461*/
462function sid_process_url($url) {
463 global $CFG;
464
465 if ((preg_match('/^(http|https):/i', $url)) // absolute url
466 && ((stripos($url, $CFG->wwwroot)!==0) && stripos($url, $CFG->httpswwwroot)!==0)) { // and not local one
467 return $url; //don't attach sessid to non local urls
468 }
469 if ($url[0]=='#' || (stripos($url, 'javascript:')===0)) {
470 return $url; //don't attach sessid to anchors
471 }
472 if (strpos($url, session_name())!==FALSE) {
473 return $url; //don't attach sessid to url that already has one sessid
474 }
475 if (strpos($url, "?")===FALSE) {
476 $append = "?".strip_tags(session_name() . '=' . session_id());
477 } else {
478 $append = "&amp;".strip_tags(session_name() . '=' . session_id());
57f7b7ce 479 }
0ad6b20c 480 //put sessid before any anchor
481 $p = strpos($url, "#");
482 if ($p!==FALSE){
483 $anch = substr($url, $p);
484 $url = substr($url, 0, $p).$append.$anch ;
485 } else {
486 $url .= $append ;
487 }
488 return $url;
489}
57f7b7ce 490
0ad6b20c 491/**
492* Call this function before there has been any output to the browser to
493* buffer output and add session ids to all internal links.
494*/
495function sid_start_ob(){
496 global $CFG;
497 //don't attach sess id for bots
498
499 if (!empty($_SERVER['HTTP_USER_AGENT'])) {
500 if (!empty($CFG->opentogoogle)) {
501 if (strpos($_SERVER['HTTP_USER_AGENT'], 'Googlebot') !== false) {
502 @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
503 $CFG->usesid=false;
504 return;
57f7b7ce 505 }
0ad6b20c 506 if (strpos($_SERVER['HTTP_USER_AGENT'], 'google.com') !== false) {
57f7b7ce 507 @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
508 $CFG->usesid=false;
509 return;
510 }
511 }
0ad6b20c 512 if (strpos($_SERVER['HTTP_USER_AGENT'], 'W3C_Validator') !== false) {
513 @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
514 $CFG->usesid=false;
515 return;
516 }
517 }
57f7b7ce 518
0ad6b20c 519 @ini_set('session.use_trans_sid', '1'); // try and turn on trans_sid
57f7b7ce 520
0ad6b20c 521 if (ini_get('session.use_trans_sid') != 0) {
522 // use trans sid as its available
523 ini_set('url_rewriter.tags', 'a=href,area=href,script=src,link=href,frame=src,form=fakeentry');
524 ob_start('sid_ob_rewrite_absolute');
525 } else {
526 //rewrite all links ourselves
527 ob_start('sid_ob_rewrite');
57f7b7ce 528 }
e6e13284 529}
0ad6b20c 530