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