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