MDL-17942 yet more session refactoring
[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() {
5e9dd017 8 global $CFG, $DB;
0a2092a3 9
0ad6b20c 10 static $session = null;
11
12 if (is_null($session)) {
e8656bef 13 if (empty($CFG->sessiontimeout)) {
14 $CFG->sessiontimeout = 7200;
15 }
16
ad76d184 17 if (defined('SESSION_CUSTOM_CLASS')) {
e8656bef 18 // this is a hook for webservices, key based login, etc.
0a2092a3 19 if (defined('SESSION_CUSTOM_FILE')) {
20 require_once($CFG->dirroot.SESSION_CUSTOM_FILE);
21 }
ad76d184 22 $session_class = SESSION_CUSTOM_CLASS;
0a2092a3 23 $session = new $session_class();
24
35d6a2a4 25 } else if ((!isset($CFG->dbsessions) or $CFG->dbsessions) and $DB->session_lock_supported()) {
0a2092a3 26 // default recommended session type
27 $session = new database_session();
28
29 } else {
30 // legacy limited file based storage - some features and auth plugins will not work, sorry
31 $session = new legacy_file_session();
32 }
0ad6b20c 33 }
34
35 return $session;
36}
37
0a2092a3 38interface moodle_session {
56949c17 39 /**
40 * Terminate current session
41 * @return void
42 */
43 public function terminate_current();
44
45 /**
46 * No more changes in session expected.
47 * Unblocks the sesions, other scripts may start executing in parallel.
48 * @return void
49 */
50 public function write_close();
0a2092a3 51}
52
57f7b7ce 53/**
54 * Class handling all session and cookies related stuff.
55 */
0a2092a3 56abstract class session_stub implements moodle_session {
b7b64ff2 57 public function __construct() {
57f7b7ce 58 global $CFG;
57f7b7ce 59
0a2092a3 60 if (!defined('NO_MOODLE_COOKIES')) {
61 if (CLI_SCRIPT) {
62 // CLI scripts can not have session
63 define('NO_MOODLE_COOKIES', true);
64 } else {
65 define('NO_MOODLE_COOKIES', false);
66 }
57f7b7ce 67 }
68
0ad6b20c 69 if (NO_MOODLE_COOKIES) {
0a2092a3 70 // session not used at all
45871c08 71 $CFG->usesid = 0;
0a2092a3 72
0ad6b20c 73 $_SESSION = array();
74 $_SESSION['SESSION'] = new object();
0a2092a3 75 $_SESSION['USER'] = new object();
0ad6b20c 76
77 } else {
0a2092a3 78 $this->prepare_cookies();
79 $this->init_session_storage();
80
35d6a2a4 81 $newsession = empty($_COOKIE['MoodleSession'.$CFG->sessioncookie]);
82
83 if (!empty($CFG->usesid) && $newsession) {
0a2092a3 84 sid_start_ob();
45871c08 85 } else {
86 $CFG->usesid = 0;
87 ini_set('session.use_trans_sid', '0');
0a2092a3 88 }
89
57f7b7ce 90 session_name('MoodleSession'.$CFG->sessioncookie);
e6e13284 91 session_set_cookie_params(0, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
57f7b7ce 92 @session_start();
93 if (!isset($_SESSION['SESSION'])) {
94 $_SESSION['SESSION'] = new object();
35d6a2a4 95 if (!$newsession and !empty($CFG->rolesactive)) {
96 $_SESSION['SESSION']->has_timed_out = true;
97 }
57f7b7ce 98 }
99 if (!isset($_SESSION['USER'])) {
100 $_SESSION['USER'] = new object();
101 }
57f7b7ce 102 }
57f7b7ce 103
b7b64ff2 104 $this->check_user_initialised();
9bda43e6 105
106 $this->check_security();
b7b64ff2 107 }
108
56949c17 109 /**
110 * Terminates active moodle session
111 */
112 public function terminate_current() {
113 global $CFG, $SESSION, $USER;
114
115 if (NO_MOODLE_COOKIES) {
116 return;
117 }
118
119 $_SESSION = array();
35d6a2a4 120 $_SESSION['SESSION'] = new object();
121 $_SESSION['USER'] = new object();
122 $_SESSION['USER']->id = 0;
56949c17 123 if (isset($CFG->mnet_localhost_id)) {
35d6a2a4 124 $_SESSION['USER']->mnethostid = $CFG->mnet_localhost_id;
56949c17 125 }
126
35d6a2a4 127 $SESSION = $_SESSION['SESSION']; // this may not work properly
128 $USER = $_SESSION['USER']; // this may not work properly
129
56949c17 130 // Initialize variable to pass-by-reference to headers_sent(&$file, &$line)
131 $file = null;
132 $line = null;
133 if (headers_sent($file, $line)) {
134 error_log('Can not terminate session properly - headers were already sent in file: '.$file.' on line '.$line);
135 }
136
35d6a2a4 137 // now let's try to get a new session id
138 session_regenerate_id();
56949c17 139
140 // close the session
35d6a2a4 141 session_write_close();
56949c17 142 }
143
144 /**
145 * No more changes in session expected.
146 * Unblocks the sesions, other scripts may start executing in parallel.
147 * @return void
148 */
149 public function write_close() {
150 if (NO_MOODLE_COOKIES) {
151 return;
152 }
153
154 session_write_close();
155 }
156
b7b64ff2 157 /**
dd9e22f8 158 * Initialise $USER object, handles google access
159 * and sets up not logged in user properly.
b7b64ff2 160 *
161 * @return void
162 */
163 protected function check_user_initialised() {
164 if (isset($_SESSION['USER']->id)) {
165 // already set up $USER
166 return;
167 }
168
169 $user = null;
170
171 if (!empty($CFG->opentogoogle) and !NO_MOODLE_COOKIES) {
172 if (!empty($_SERVER['HTTP_USER_AGENT'])) {
173 // allow web spiders in as guest users
174 if (strpos($_SERVER['HTTP_USER_AGENT'], 'Googlebot') !== false ) {
175 $user = guest_user();
176 } else if (strpos($_SERVER['HTTP_USER_AGENT'], 'google.com') !== false ) { // Google
177 $user = guest_user();
178 } else if (strpos($_SERVER['HTTP_USER_AGENT'], 'Yahoo! Slurp') !== false ) { // Yahoo
179 $user = guest_user();
180 } else if (strpos($_SERVER['HTTP_USER_AGENT'], '[ZSEBOT]') !== false ) { // Zoomspider
181 $user = guest_user();
182 } else if (strpos($_SERVER['HTTP_USER_AGENT'], 'MSNBOT') !== false ) { // MSN Search
183 $user = guest_user();
184 }
185 }
1b813f5c 186 if (!empty($CFG->guestloginbutton) and !$user and !empty($_SERVER['HTTP_REFERER'])) {
b7b64ff2 187 // automaticaly log in users coming from search engine results
188 if (strpos($_SERVER['HTTP_REFERER'], 'google') !== false ) {
189 $user = guest_user();
190 } else if (strpos($_SERVER['HTTP_REFERER'], 'altavista') !== false ) {
191 $user = guest_user();
192 }
193 }
194 }
195
196 if (!$user) {
197 $user = new object();
198 $user->id = 0; // to enable proper function of $CFG->notloggedinroleid hack
0ad6b20c 199 if (isset($CFG->mnet_localhost_id)) {
b7b64ff2 200 $user->mnethostid = $CFG->mnet_localhost_id;
0a2092a3 201 } else {
202 $user->mnethostid = 1;
57f7b7ce 203 }
204 }
b7b64ff2 205 session_set_user($user);
57f7b7ce 206 }
207
9bda43e6 208 /**
209 * Does various session security checks
210 * @global void
211 */
93f66983 212 protected function check_security() {
213 global $CFG;
214
0a2092a3 215 if (NO_MOODLE_COOKIES) {
216 return;
217 }
218
9bda43e6 219 if (!empty($_SESSION['USER']->id) and !empty($CFG->tracksessionip)) {
220 /// Make sure current IP matches the one for this session
93f66983 221 $remoteaddr = getremoteaddr();
222
223 if (empty($_SESSION['USER']->sessionip)) {
224 $_SESSION['USER']->sessionip = $remoteaddr;
225 }
226
227 if ($_SESSION['USER']->sessionip != $remoteaddr) {
9bda43e6 228 // this is a security feature - terminate the session in case of any doubt
56949c17 229 $this->terminate_current();
9bda43e6 230 print_error('sessionipnomatch2', 'error');
93f66983 231 }
232 }
93f66983 233 }
234
57f7b7ce 235 /**
236 * Prepare cookies and varions system settings
237 */
b7b64ff2 238 protected function prepare_cookies() {
0a2092a3 239 global $CFG;
57f7b7ce 240
11e7b506 241 if (!isset($CFG->cookiesecure) or (strpos($CFG->wwwroot, 'https://') !== 0 and empty($CFG->sslproxy))) {
57f7b7ce 242 $CFG->cookiesecure = 0;
243 }
244
245 if (!isset($CFG->cookiehttponly)) {
246 $CFG->cookiehttponly = 0;
247 }
248
249 /// Set sessioncookie and sessioncookiepath variable if it isn't already
250 if (!isset($CFG->sessioncookie)) {
251 $CFG->sessioncookie = '';
252 }
e6e13284 253 if (!isset($CFG->sessioncookiedomain)) {
254 $CFG->sessioncookiedomain = '';
255 }
57f7b7ce 256 if (!isset($CFG->sessioncookiepath)) {
257 $CFG->sessioncookiepath = '/';
258 }
259
260 //discard session ID from POST, GET and globals to tighten security,
261 //this session fixation prevention can not be used in cookieless mode
262 if (empty($CFG->usesid)) {
263 unset(${'MoodleSession'.$CFG->sessioncookie});
264 unset($_GET['MoodleSession'.$CFG->sessioncookie]);
265 unset($_POST['MoodleSession'.$CFG->sessioncookie]);
b7b64ff2 266 unset($_REQUEST['MoodleSession'.$CFG->sessioncookie]);
57f7b7ce 267 }
268 //compatibility hack for Moodle Cron, cookies not deleted, but set to "deleted" - should not be needed with NO_MOODLE_COOKIES in cron.php now
269 if (!empty($_COOKIE['MoodleSession'.$CFG->sessioncookie]) && $_COOKIE['MoodleSession'.$CFG->sessioncookie] == "deleted") {
270 unset($_COOKIE['MoodleSession'.$CFG->sessioncookie]);
271 }
57f7b7ce 272 }
273
274 /**
275 * Inits session storage.
276 */
f61a032a 277 protected abstract function init_session_storage();
278
279}
280
281/**
282 * Legacy moodle sessions stored in files, not recommended any more.
283 */
0a2092a3 284class legacy_file_session extends session_stub {
b7b64ff2 285 protected function init_session_storage() {
57f7b7ce 286 global $CFG;
287
0a2092a3 288 ini_set('session.save_handler', 'files');
289
f61a032a 290 // Some distros disable GC by setting probability to 0
291 // overriding the PHP default of 1
292 // (gc_probability is divided by gc_divisor, which defaults to 1000)
293 if (ini_get('session.gc_probability') == 0) {
294 ini_set('session.gc_probability', 1);
295 }
57f7b7ce 296
3b1a9849 297 ini_set('session.gc_maxlifetime', $CFG->sessiontimeout);
57f7b7ce 298
f61a032a 299 if (!file_exists($CFG->dataroot .'/sessions')) {
300 make_upload_directory('sessions');
301 }
302 if (!is_writable($CFG->dataroot .'/sessions/')) {
303 print_error('sessionnotwritable', 'error');
57f7b7ce 304 }
f61a032a 305 ini_set('session.save_path', $CFG->dataroot .'/sessions');
306 }
307}
308
309/**
310 * Recommended moodle session storage.
311 */
0a2092a3 312class database_session extends session_stub {
313 protected $record = null;
314 protected $database = null;
315
dd9e22f8 316 public function __construct() {
317 global $DB;
318 $this->database = $DB;
319 parent::__construct();
320 }
321
f61a032a 322 protected function init_session_storage() {
323 global $CFG;
324
dd9e22f8 325 // gc only from CRON - individual user timeouts now checked during each access
326 ini_set('session.gc_probability', 0);
0a2092a3 327
ef159e5f 328 ini_set('session.gc_maxlifetime', $CFG->sessiontimeout);
0a2092a3 329
330 $result = session_set_save_handler(array($this, 'handler_open'),
331 array($this, 'handler_close'),
332 array($this, 'handler_read'),
333 array($this, 'handler_write'),
334 array($this, 'handler_destroy'),
335 array($this, 'handler_gc'));
336 if (!$result) {
eee3bd3f 337 print_error('dbsessionhandlerproblem', 'error');
0a2092a3 338 }
339 }
340
dd9e22f8 341 public function handler_open($save_path, $session_name) {
0a2092a3 342 return true;
343 }
344
345 public function handler_close() {
346 $this->record = null;
347 return true;
348 }
349
350 public function handler_read($sid) {
351 global $CFG;
352
0a2092a3 353 if ($this->record and $this->record->sid != $sid) {
354 error_log('Weird error reading session - mismatched sid');
355 return '';
356 }
357
358 try {
3b1a9849 359 if ($record = $this->database->get_record('sessions', array('sid'=>$sid))) {
360 $this->database->get_session_lock($record->id);
dd9e22f8 361
3b1a9849 362 } else {
0a2092a3 363 $record = new object();
364 $record->state = 0;
365 $record->sid = $sid;
366 $record->sessdata = null;
17d93489 367 $record->sessdatahash = null;
0a2092a3 368 $record->userid = 0;
369 $record->timecreated = $record->timemodified = time();
370 $record->firstip = $record->lastip = getremoteaddr();
0a2092a3 371 $record->id = $this->database->insert_record_raw('sessions', $record);
372
3b1a9849 373 $this->database->get_session_lock($record->id);
0a2092a3 374 }
375 } catch (dml_exception $ex) {
376 if (!empty($CFG->rolesactive)) {
377 error_log('Can not read or insert database sessions');
378 }
379 return '';
380 }
381
3b1a9849 382 // verify timeout
383 if ($record->timemodified + $CFG->sessiontimeout < time()) {
dd9e22f8 384 $ignoretimeout = false;
88fdd846 385 if (!empty($record->userid)) { // skips not logged in
386 if ($user = $this->database->get_record('user', array('id'=>$record->userid))) {
387 if ($user->username !== 'guest') {
388 $authsequence = get_enabled_auth_plugins(); // auths, in sequence
389 foreach($authsequence as $authname) {
390 $authplugin = get_auth_plugin($authname);
e8656bef 391 if ($authplugin->ignore_timeout_hook($user, $record->sid, $record->timecreated, $record->timemodified)) {
88fdd846 392 $ignoretimeout = true;
393 break;
394 }
395 }
396 }
dd9e22f8 397 }
398 }
399 if ($ignoretimeout) {
400 //refresh session
401 $record->timemodified = time();
402 try {
403 $this->database->update_record('sessions', $record);
404 } catch (dml_exception $ex) {
405 error_log('Can not refresh database session');
406 return '';
407 }
408 } else {
409 //time out session
410 $record->state = 0;
411 $record->sessdata = null;
412 $record->sessdatahash = null;
413 $record->userid = 0;
414 $record->timecreated = $record->timemodified = time();
415 $record->firstip = $record->lastip = getremoteaddr();
416 try {
417 $this->database->update_record('sessions', $record);
418 } catch (dml_exception $ex) {
419 error_log('Can not time out database session');
420 return '';
421 }
eee3bd3f 422 }
eee3bd3f 423 }
424
3b1a9849 425 if ($record->sessdatahash !== null) {
426 if (md5($record->sessdata) !== $record->sessdatahash) {
427 // probably this is caused by misconfigured mysql - the allowed request size might be too small
428 try {
429 $this->database->delete_records('sessions', array('sid'=>$record->sid));
430 } catch (dml_exception $ignored) {
431 }
432 print_error('dbsessionbroken', 'error');
433 }
434
435 $data = base64_decode($record->sessdata);
436 } else {
437 $data = '';
438 }
439
0a2092a3 440 unset($record->sessdata); // conserve memory
441 $this->record = $record;
442
443 return $data;
444 }
445
446 public function handler_write($sid, $session_data) {
447 global $USER;
448
449 if (!$this->record) {
450 error_log('Weird error writing session');
451 return true;
452 }
453
3b1a9849 454 $this->database->release_session_lock($this->record->id);
7f79aaea 455
0a2092a3 456 $this->record->sid = $sid; // it might be regenerated
457 $this->record->sessdata = base64_encode($session_data); // there might be some binary mess :-(
17d93489 458 $this->record->sessdatahash = md5($this->record->sessdata);
0a2092a3 459 $this->record->userid = empty($USER->realuser) ? $USER->id : $USER->realuser;
460 $this->record->timemodified = time();
461 $this->record->lastip = getremoteaddr();
462
eee3bd3f 463 // TODO: verify session changed before doing update
464
0a2092a3 465 try {
466 $this->database->update_record_raw('sessions', $this->record);
467 } catch (dml_exception $ex) {
468 error_log('Can not write session to database.');
469 }
17d93489 470
0a2092a3 471 return true;
472 }
473
474 public function handler_destroy($sid) {
475 if (!$this->record or $this->record->sid != $sid) {
476 error_log('Weird error destroying session - mismatched sid');
477 return true;
478 }
479
3b1a9849 480 $this->database->release_session_lock($this->record->id);
7f79aaea 481
0a2092a3 482 try {
483 $this->database->delete_records('sessions', array('sid'=>$this->record->sid));
484 } catch (dml_exception $ex) {
485 error_log('Can not destroy database session.');
486 }
487
488 return true;
489 }
490
3b1a9849 491 public function handler_gc($ignored_maxlifetime) {
dd9e22f8 492 $this->gc();
0a2092a3 493 return true;
57f7b7ce 494 }
e8656bef 495}
496
497/**
498 * Terminates all sessions, auth hooks are not executed.
499 * Useful in ugrade scripts.
500 */
501function session_kill_all() {
502 global $CFG, $DB;
503
504 try {
505 // do not show any warnings - might be during upgrade/installation
506 $DB->delete_records('sessions');
507 } catch (dml_exception $ignored) {
508 }
0a2092a3 509
e8656bef 510 $sessiondir = "$CFG->dataroot/sessions/";
511 if (is_dir($sessiondir)) {
512 // TODO: delete all files, watch out some might be locked
513 }
514}
515
516/**
517 * Terminates one sessions, auth hooks are not executed.
518 *
519 * @param string $sid session id
520 */
521function session_kill($sid) {
522 global $CFG, $DB;
523
524 try {
525 // do not show any warnings - might be during upgrade/installation
526 $$DB->delete_records('sessions', array('sid'=>$sid));
527 } catch (dml_exception $ignored) {
528 }
529
530 $sessionfile = clean_param("$CFG->dataroot/sessions/$sid", PARAM_FILE);
531 if (file_exists($sessionfile)) {
532 // TODO: delete file, watch out might be locked
533 }
534}
535
536/**
537 * Terminates all sessions of one user, auth hooks are not executed.
538 * NOTE: This can not work for file based sessions!
539 *
540 * @param int $userid user id
541 */
542function session_kill_user($userid) {
543 global $CFG, $DB;
544
545 try {
546 // do not show any warnings - might be during upgrade/installation
547 $$DB->delete_records('sessions', array('userid'=>$userid));
548 } catch (dml_exception $ignored) {
549 }
550}
551
552/**
553 * Session garbage collection
554 * - verify timeout for all users
555 * - kill sessions of all deleted users
556 * - kill sessions of users with disabled plugins or 'nologin' plugin
557 *
558 * NOTE: this can not work when legacy file sessions used!
559 */
560function session_gc() {
561 global $CFG, $DB;
562
563 $maxlifetime = $CFG->sessiontimeout;
564
565 if (empty($CFG->rolesactive)) {
566 return;
567 }
568
569 try {
570 /// kill all sessions of deleted users
571 $DB->delete_records_select('sessions', "userid IN (SELECT id FROM {user} WHERE deleted <> 0)");
572
573 /// kill sessions of users with disabled plugins
574 $auth_sequence = get_enabled_auth_plugins(true);
575 $auth_sequence = array_flip($auth_sequence);
576 unset($auth_sequence['nologin']); // no login allowed
577 $auth_sequence = array_flip($auth_sequence);
578 $notplugins = null;
579 list($notplugins, $params) = $DB->get_in_or_equal($auth_sequence, SQL_PARAMS_QM, '', false);
580 $DB->delete_records_select('sessions', "userid IN (SELECT id FROM {user} WHERE auth $notplugins)", $params);
581
582 /// now get a list of time-out candidates
583 $sql = "SELECT u.*, s.sid, s.timecreated AS s_timecreated, s.timemodified AS s_timemodified
584 FROM {user} u
585 JOIN {sessions} s ON s.userid = u.id
586 WHERE s.timemodified + ? < ? AND u.username <> 'guest'";
587 $params = array($maxlifetime, time());
588
589 $authplugins = array();
590 foreach($auth_sequence as $authname) {
591 $authplugins[$authname] = get_auth_plugin($authname);
592 }
593 $rs = $DB->get_recordset_sql($sql, $params);
594 foreach ($rs as $user) {
595 foreach ($authplugins as $authplugin) {
596 if ($authplugin->ignore_timeout_hook($user, $user->sid, $user->s_timecreated, $user->s_timemodified)) {
597 continue;
598 }
599 }
600 $DB->delete_records('sessions', array('sid'=>$user->sid));
601 }
602 $rs->close();
603 } catch (dml_exception $ex) {
604 error_log('Error gc-ing sessions');
605 }
0ad6b20c 606}
57f7b7ce 607
0ad6b20c 608/**
609 * Makes sure that $USER->sesskey exists, if $USER itself exists. It sets a new sesskey
610 * if one does not already exist, but does not overwrite existing sesskeys. Returns the
611 * sesskey string if $USER exists, or boolean false if not.
612 *
613 * @uses $USER
614 * @return string
615 */
616function sesskey() {
617 global $USER;
57f7b7ce 618
0ad6b20c 619 if (empty($USER->sesskey)) {
620 $USER->sesskey = random_string(10);
621 }
57f7b7ce 622
0ad6b20c 623 return $USER->sesskey;
624}
57f7b7ce 625
57f7b7ce 626
0ad6b20c 627/**
628 * For security purposes, this function will check that the currently
629 * given sesskey (passed as a parameter to the script or this function)
630 * matches that of the current user.
631 *
632 * @param string $sesskey optionally provided sesskey
633 * @return bool
634 */
635function confirm_sesskey($sesskey=NULL) {
636 global $USER;
57f7b7ce 637
eb85959b 638 if (!empty($USER->ignoresesskey)) {
0ad6b20c 639 return true;
640 }
57f7b7ce 641
0ad6b20c 642 if (empty($sesskey)) {
643 $sesskey = required_param('sesskey', PARAM_RAW); // Check script parameters
57f7b7ce 644 }
645
eb85959b 646 return (sesskey() === $sesskey);
0ad6b20c 647}
648
649/**
650 * Sets a moodle cookie with a weakly encrypted string
651 *
652 * @uses $CFG
653 * @uses DAYSECS
654 * @uses HOURSECS
655 * @param string $thing The string to encrypt and place in a cookie
656 */
657function set_moodle_cookie($thing) {
658 global $CFG;
659
0a2092a3 660 if (NO_MOODLE_COOKIES) {
661 return;
662 }
663
0ad6b20c 664 if ($thing == 'guest') { // Ignore guest account
665 return;
57f7b7ce 666 }
667
0ad6b20c 668 $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
669
670 $days = 60;
671 $seconds = DAYSECS*$days;
672
673 // no need to set secure or http cookie only here - it is not secret
674 setcookie($cookiename, '', time() - HOURSECS, $CFG->sessioncookiepath, $CFG->sessioncookiedomain);
675 setcookie($cookiename, rc4encrypt($thing), time()+$seconds, $CFG->sessioncookiepath, $CFG->sessioncookiedomain);
676}
677
678/**
679 * Gets a moodle cookie with a weakly encrypted string
680 *
681 * @uses $CFG
682 * @return string
683 */
684function get_moodle_cookie() {
685 global $CFG;
686
0a2092a3 687 if (NO_MOODLE_COOKIES) {
688 return '';
689 }
690
0ad6b20c 691 $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
692
693 if (empty($_COOKIE[$cookiename])) {
694 return '';
695 } else {
696 $thing = rc4decrypt($_COOKIE[$cookiename]);
697 return ($thing == 'guest') ? '': $thing; // Ignore guest account
57f7b7ce 698 }
0ad6b20c 699}
57f7b7ce 700
e8656bef 701
b7b64ff2 702/**
703 * Setup $USER object - called during login, loginas, etc.
704 * Preloads capabilities and checks enrolment plugins
705 *
706 * @param object $user full user record object
707 * @return void
708 */
709function session_set_user($user) {
710 $_SESSION['USER'] = $user;
dd9e22f8 711 unset($_SESSION['USER']->description); // conserve memory
712 if (!isset($_SESSION['USER']->access)) {
713 // check enrolments and load caps only once
714 check_enrolment_plugins($_SESSION['USER']);
715 load_all_capabilities();
716 }
eb85959b 717 sesskey(); // init session key
b7b64ff2 718}
719
542797b4 720/**
721 * Is current $USER logged-in-as somebody else?
722 * @return bool
723 */
b7b64ff2 724function session_is_loggedinas() {
85f6b737 725 return !empty($_SESSION['USER']->realuser);
542797b4 726}
727
6132768e 728/**
729 * Returns the $USER object ignoring current login-as session
730 * @return object user object
731 */
b7b64ff2 732function session_get_realuser() {
733 if (session_is_loggedinas()) {
6132768e 734 return $_SESSION['REALUSER'];
735 } else {
736 return $_SESSION['USER'];
737 }
738}
739
542797b4 740/**
741 * Login as another user - no security checks here.
742 * @param int $userid
743 * @param object $context
744 * @return void
745 */
8d1964c4 746function session_loginas($userid, $context) {
b7b64ff2 747 if (session_is_loggedinas()) {
8d1964c4 748 return;
749 }
750
85f6b737 751 // switch to fresh new $SESSION
752 $_SESSION['REALSESSION'] = $_SESSION['SESSION'];
6132768e 753 $_SESSION['SESSION'] = new object();
8d1964c4 754
85f6b737 755 /// Create the new $USER object with all details and reload needed capabilitites
756 $_SESSION['REALUSER'] = $_SESSION['USER'];
b7b64ff2 757 $user = get_complete_user_data('id', $userid);
758 $user->realuser = $_SESSION['REALUSER']->id;
759 $user->loginascontext = $context;
760 session_set_user($user);
8d1964c4 761}
762
542797b4 763/**
764 * Terminate login-as session
765 * @return void
766 */
8d1964c4 767function session_unloginas() {
b7b64ff2 768 if (!session_is_loggedinas()) {
8d1964c4 769 return;
770 }
771
6132768e 772 $_SESSION['SESSION'] = $_SESSION['REALSESSION'];
773 unset($_SESSION['REALSESSION']);
8d1964c4 774
6132768e 775 $_SESSION['USER'] = $_SESSION['REALUSER'];
776 unset($_SESSION['REALUSER']);
8d1964c4 777}
778
e8b7114d 779/**
780 * Sets up current user and course enviroment (lang, etc.) in cron.
781 * Do not use outside of cron script!
782 *
783 * @param object $user full user object, null means default cron user (admin)
784 * @param $course full course record, null means $SITE
785 * @return void
786 */
787function cron_setup_user($user=null, $course=null) {
788 global $CFG, $SITE;
789
790 static $cronuser = null;
791 static $cronsession = null;
792
793 if (empty($cronuser)) {
794 /// ignore admins timezone, language and locale - use site deafult instead!
795 $cronuser = get_admin();
796 $cronuser->timezone = $CFG->timezone;
dd9e22f8 797 $cronuser->lang = '';
798 $cronuser->theme = '';
799 unset($cronuser->description);
e8b7114d 800
801 $cronsession = array();
802 }
803
804 if (!$user) {
805 // cached default cron user (==modified admin for now)
806 session_set_user($cronuser);
807 $_SESSION['SESSION'] = $cronsession;
808
809 } else {
810 // emulate real user session - needed for caps in cron
811 if ($_SESSION['USER']->id != $user->id) {
812 session_set_user($user);
813 $_SESSION['SESSION'] = array();
814 }
815 }
816
817 if ($course) {
818 course_setup($course);
819 } else {
820 course_setup($SITE);
821 }
822
823 // TODO: it should be possible to improve perf by caching some limited number of users here ;-)
824
825}
826
0ad6b20c 827/**
828* Enable cookieless sessions by including $CFG->usesid=true;
829* in config.php.
830* Based on code from php manual by Richard at postamble.co.uk
831* 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.
832* 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.
833* This doesn't require trans_sid to be turned on but this is recommended for better performance
834* you should put :
835* session.use_trans_sid = 1
836* in your php.ini file and make sure that you don't have a line like this in your php.ini
837* session.use_only_cookies = 1
838* @author Richard at postamble.co.uk and Jamie Pratt
839* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
840*/
841/**
842* You won't call this function directly. This function is used to process
843* text buffered by php in an output buffer. All output is run through this function
844* before it is ouput.
845* @param string $buffer is the output sent from php
846* @return string the output sent to the browser
847*/
848function sid_ob_rewrite($buffer){
849 $replacements = array(
850 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*")([^"]*)(")/i',
851 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*\')([^\']*)(\')/i');
852
853 $buffer = preg_replace_callback($replacements, 'sid_rewrite_link_tag', $buffer);
854 $buffer = preg_replace('/<form\s[^>]*>/i',
855 '\0<input type="hidden" name="' . session_name() . '" value="' . session_id() . '"/>', $buffer);
856
857 return $buffer;
858}
859/**
860* You won't call this function directly. This function is used to process
861* text buffered by php in an output buffer. All output is run through this function
862* before it is ouput.
863* This function only processes absolute urls, it is used when we decide that
864* php is processing other urls itself but needs some help with internal absolute urls still.
865* @param string $buffer is the output sent from php
866* @return string the output sent to the browser
867*/
868function sid_ob_rewrite_absolute($buffer){
869 $replacements = array(
870 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*")((?:http|https)[^"]*)(")/i',
871 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*\')((?:http|https)[^\']*)(\')/i');
872
873 $buffer = preg_replace_callback($replacements, 'sid_rewrite_link_tag', $buffer);
874 $buffer = preg_replace('/<form\s[^>]*>/i',
875 '\0<input type="hidden" name="' . session_name() . '" value="' . session_id() . '"/>', $buffer);
876 return $buffer;
877}
57f7b7ce 878
0ad6b20c 879/**
880* A function to process link, a and script tags found
881* by preg_replace_callback in {@link sid_ob_rewrite($buffer)}.
882*/
883function sid_rewrite_link_tag($matches){
884 $url = $matches[4];
885 $url = sid_process_url($url);
886 return $matches[1].$url.$matches[5];
887}
888
889/**
890* You can call this function directly. This function is used to process
891* urls to add a moodle session id to the url for internal links.
892* @param string $url is a url
893* @return string the processed url
894*/
895function sid_process_url($url) {
896 global $CFG;
897
898 if ((preg_match('/^(http|https):/i', $url)) // absolute url
899 && ((stripos($url, $CFG->wwwroot)!==0) && stripos($url, $CFG->httpswwwroot)!==0)) { // and not local one
900 return $url; //don't attach sessid to non local urls
901 }
902 if ($url[0]=='#' || (stripos($url, 'javascript:')===0)) {
903 return $url; //don't attach sessid to anchors
904 }
905 if (strpos($url, session_name())!==FALSE) {
906 return $url; //don't attach sessid to url that already has one sessid
907 }
908 if (strpos($url, "?")===FALSE) {
909 $append = "?".strip_tags(session_name() . '=' . session_id());
910 } else {
911 $append = "&amp;".strip_tags(session_name() . '=' . session_id());
57f7b7ce 912 }
0ad6b20c 913 //put sessid before any anchor
914 $p = strpos($url, "#");
915 if ($p!==FALSE){
916 $anch = substr($url, $p);
917 $url = substr($url, 0, $p).$append.$anch ;
918 } else {
919 $url .= $append ;
920 }
921 return $url;
922}
57f7b7ce 923
0ad6b20c 924/**
925* Call this function before there has been any output to the browser to
926* buffer output and add session ids to all internal links.
927*/
928function sid_start_ob(){
929 global $CFG;
930 //don't attach sess id for bots
931
932 if (!empty($_SERVER['HTTP_USER_AGENT'])) {
933 if (!empty($CFG->opentogoogle)) {
934 if (strpos($_SERVER['HTTP_USER_AGENT'], 'Googlebot') !== false) {
935 @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
936 $CFG->usesid=false;
937 return;
57f7b7ce 938 }
0ad6b20c 939 if (strpos($_SERVER['HTTP_USER_AGENT'], 'google.com') !== false) {
57f7b7ce 940 @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
941 $CFG->usesid=false;
942 return;
943 }
944 }
0ad6b20c 945 if (strpos($_SERVER['HTTP_USER_AGENT'], 'W3C_Validator') !== false) {
946 @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
947 $CFG->usesid=false;
948 return;
949 }
950 }
57f7b7ce 951
0ad6b20c 952 @ini_set('session.use_trans_sid', '1'); // try and turn on trans_sid
57f7b7ce 953
0ad6b20c 954 if (ini_get('session.use_trans_sid') != 0) {
955 // use trans sid as its available
956 ini_set('url_rewriter.tags', 'a=href,area=href,script=src,link=href,frame=src,form=fakeentry');
957 ob_start('sid_ob_rewrite_absolute');
958 } else {
959 //rewrite all links ourselves
960 ob_start('sid_ob_rewrite');
57f7b7ce 961 }
e6e13284 962}
0ad6b20c 963