MDL-17837 - mimic from 19_STABLE - apply some limits to the dropdown of users in...
[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 }
93f66983 121 }
122
57f7b7ce 123 /**
124 * Terminates active moodle session
125 */
126 public function terminate() {
127 global $CFG, $SESSION, $USER;
128
0ad6b20c 129 $_SESSION = array();
57f7b7ce 130
0ad6b20c 131 $SESSION = new object();
57f7b7ce 132 $USER = new object();
133 $USER->id = 0;
134 if (isset($CFG->mnet_localhost_id)) {
135 $USER->mnethostid = $CFG->mnet_localhost_id;
136 }
137
0ad6b20c 138 // Initialize variable to pass-by-reference to headers_sent(&$file, &$line)
139 $file = null;
140 $line = null;
141 if (headers_sent($file, $line)) {
142 error_log('Can not terminate session properly - headers were already sent in file: '.$file.' on line '.$line);
57f7b7ce 143 }
57f7b7ce 144
9bda43e6 145 // now let's try to get a new session id and destroy the old one
146 @session_regenerate_id(true);
147
148 // close the session
0ad6b20c 149 @session_write_close();
57f7b7ce 150 }
151
152 /**
153 * Prepare cookies and varions system settings
154 */
b7b64ff2 155 protected function prepare_cookies() {
57f7b7ce 156 global $CFG, $nomoodlecookie;
157
158 if (!defined('NO_MOODLE_COOKIES')) {
a91b910e 159 if (CLI_SCRIPT) {
160 // CLI scripts can not have session
161 define('NO_MOODLE_COOKIES', true);
162 } else if (isset($nomoodlecookie)) {
57f7b7ce 163 // backwards compatibility only
164 define('NO_MOODLE_COOKIES', $nomoodlecookie);
57f7b7ce 165 } else {
166 define('NO_MOODLE_COOKIES', false);
167 }
168 }
a91b910e 169 unset($nomoodlecookie); // cleanup
57f7b7ce 170
11e7b506 171 if (!isset($CFG->cookiesecure) or (strpos($CFG->wwwroot, 'https://') !== 0 and empty($CFG->sslproxy))) {
57f7b7ce 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
e8b7114d 394/**
395 * Sets up current user and course enviroment (lang, etc.) in cron.
396 * Do not use outside of cron script!
397 *
398 * @param object $user full user object, null means default cron user (admin)
399 * @param $course full course record, null means $SITE
400 * @return void
401 */
402function cron_setup_user($user=null, $course=null) {
403 global $CFG, $SITE;
404
405 static $cronuser = null;
406 static $cronsession = null;
407
408 if (empty($cronuser)) {
409 /// ignore admins timezone, language and locale - use site deafult instead!
410 $cronuser = get_admin();
411 $cronuser->timezone = $CFG->timezone;
412 $cronuser->lang = '';
413 $cronuser->theme = '';
414
415 $cronsession = array();
416 }
417
418 if (!$user) {
419 // cached default cron user (==modified admin for now)
420 session_set_user($cronuser);
421 $_SESSION['SESSION'] = $cronsession;
422
423 } else {
424 // emulate real user session - needed for caps in cron
425 if ($_SESSION['USER']->id != $user->id) {
426 session_set_user($user);
427 $_SESSION['SESSION'] = array();
428 }
429 }
430
431 if ($course) {
432 course_setup($course);
433 } else {
434 course_setup($SITE);
435 }
436
437 // TODO: it should be possible to improve perf by caching some limited number of users here ;-)
438
439}
440
0ad6b20c 441/**
442* Enable cookieless sessions by including $CFG->usesid=true;
443* in config.php.
444* Based on code from php manual by Richard at postamble.co.uk
445* 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.
446* 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.
447* This doesn't require trans_sid to be turned on but this is recommended for better performance
448* you should put :
449* session.use_trans_sid = 1
450* in your php.ini file and make sure that you don't have a line like this in your php.ini
451* session.use_only_cookies = 1
452* @author Richard at postamble.co.uk and Jamie Pratt
453* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
454*/
455/**
456* You won't call this function directly. This function is used to process
457* text buffered by php in an output buffer. All output is run through this function
458* before it is ouput.
459* @param string $buffer is the output sent from php
460* @return string the output sent to the browser
461*/
462function sid_ob_rewrite($buffer){
463 $replacements = array(
464 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*")([^"]*)(")/i',
465 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*\')([^\']*)(\')/i');
466
467 $buffer = preg_replace_callback($replacements, 'sid_rewrite_link_tag', $buffer);
468 $buffer = preg_replace('/<form\s[^>]*>/i',
469 '\0<input type="hidden" name="' . session_name() . '" value="' . session_id() . '"/>', $buffer);
470
471 return $buffer;
472}
473/**
474* You won't call this function directly. This function is used to process
475* text buffered by php in an output buffer. All output is run through this function
476* before it is ouput.
477* This function only processes absolute urls, it is used when we decide that
478* php is processing other urls itself but needs some help with internal absolute urls still.
479* @param string $buffer is the output sent from php
480* @return string the output sent to the browser
481*/
482function sid_ob_rewrite_absolute($buffer){
483 $replacements = array(
484 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*")((?:http|https)[^"]*)(")/i',
485 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*\')((?:http|https)[^\']*)(\')/i');
486
487 $buffer = preg_replace_callback($replacements, 'sid_rewrite_link_tag', $buffer);
488 $buffer = preg_replace('/<form\s[^>]*>/i',
489 '\0<input type="hidden" name="' . session_name() . '" value="' . session_id() . '"/>', $buffer);
490 return $buffer;
491}
57f7b7ce 492
0ad6b20c 493/**
494* A function to process link, a and script tags found
495* by preg_replace_callback in {@link sid_ob_rewrite($buffer)}.
496*/
497function sid_rewrite_link_tag($matches){
498 $url = $matches[4];
499 $url = sid_process_url($url);
500 return $matches[1].$url.$matches[5];
501}
502
503/**
504* You can call this function directly. This function is used to process
505* urls to add a moodle session id to the url for internal links.
506* @param string $url is a url
507* @return string the processed url
508*/
509function sid_process_url($url) {
510 global $CFG;
511
512 if ((preg_match('/^(http|https):/i', $url)) // absolute url
513 && ((stripos($url, $CFG->wwwroot)!==0) && stripos($url, $CFG->httpswwwroot)!==0)) { // and not local one
514 return $url; //don't attach sessid to non local urls
515 }
516 if ($url[0]=='#' || (stripos($url, 'javascript:')===0)) {
517 return $url; //don't attach sessid to anchors
518 }
519 if (strpos($url, session_name())!==FALSE) {
520 return $url; //don't attach sessid to url that already has one sessid
521 }
522 if (strpos($url, "?")===FALSE) {
523 $append = "?".strip_tags(session_name() . '=' . session_id());
524 } else {
525 $append = "&amp;".strip_tags(session_name() . '=' . session_id());
57f7b7ce 526 }
0ad6b20c 527 //put sessid before any anchor
528 $p = strpos($url, "#");
529 if ($p!==FALSE){
530 $anch = substr($url, $p);
531 $url = substr($url, 0, $p).$append.$anch ;
532 } else {
533 $url .= $append ;
534 }
535 return $url;
536}
57f7b7ce 537
0ad6b20c 538/**
539* Call this function before there has been any output to the browser to
540* buffer output and add session ids to all internal links.
541*/
542function sid_start_ob(){
543 global $CFG;
544 //don't attach sess id for bots
545
546 if (!empty($_SERVER['HTTP_USER_AGENT'])) {
547 if (!empty($CFG->opentogoogle)) {
548 if (strpos($_SERVER['HTTP_USER_AGENT'], 'Googlebot') !== false) {
549 @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
550 $CFG->usesid=false;
551 return;
57f7b7ce 552 }
0ad6b20c 553 if (strpos($_SERVER['HTTP_USER_AGENT'], 'google.com') !== false) {
57f7b7ce 554 @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
555 $CFG->usesid=false;
556 return;
557 }
558 }
0ad6b20c 559 if (strpos($_SERVER['HTTP_USER_AGENT'], 'W3C_Validator') !== false) {
560 @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
561 $CFG->usesid=false;
562 return;
563 }
564 }
57f7b7ce 565
0ad6b20c 566 @ini_set('session.use_trans_sid', '1'); // try and turn on trans_sid
57f7b7ce 567
0ad6b20c 568 if (ini_get('session.use_trans_sid') != 0) {
569 // use trans sid as its available
570 ini_set('url_rewriter.tags', 'a=href,area=href,script=src,link=href,frame=src,form=fakeentry');
571 ob_start('sid_ob_rewrite_absolute');
572 } else {
573 //rewrite all links ourselves
574 ob_start('sid_ob_rewrite');
57f7b7ce 575 }
e6e13284 576}
0ad6b20c 577