MDL-17942 $CFG->respectsessionsettings obsoleted by db sessions - can not be used...
[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
88fdd846 416 $sql = "SELECT s.*, u.auth, u.username
dd9e22f8 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) {
88fdd846 428 if (!empty($record->userid) and $record->username !== 'guest') { // skips not logged in and guests
429 foreach ($authplugins as $authplugin) {
430 if ($authplugin->ignore_timeout_hook($record->userid, $records->auth, $record->sid, $record->timecreated, $record->timemodified)) {
431 continue;
432 }
dd9e22f8 433 }
434 }
435 $this->database->delete_records('sessions', array('id'=>$record->id));
436 }
437 } catch (dml_exception $ex) {
438 error_log('Error gc-ing sessions');
439 }
440 }
441
442 public function handler_open($save_path, $session_name) {
0a2092a3 443 return true;
444 }
445
446 public function handler_close() {
447 $this->record = null;
448 return true;
449 }
450
451 public function handler_read($sid) {
452 global $CFG;
453
0a2092a3 454 if ($this->record and $this->record->sid != $sid) {
455 error_log('Weird error reading session - mismatched sid');
456 return '';
457 }
458
459 try {
3b1a9849 460 if ($record = $this->database->get_record('sessions', array('sid'=>$sid))) {
461 $this->database->get_session_lock($record->id);
dd9e22f8 462
3b1a9849 463 } else {
0a2092a3 464 $record = new object();
465 $record->state = 0;
466 $record->sid = $sid;
467 $record->sessdata = null;
17d93489 468 $record->sessdatahash = null;
0a2092a3 469 $record->userid = 0;
470 $record->timecreated = $record->timemodified = time();
471 $record->firstip = $record->lastip = getremoteaddr();
0a2092a3 472 $record->id = $this->database->insert_record_raw('sessions', $record);
473
3b1a9849 474 $this->database->get_session_lock($record->id);
0a2092a3 475 }
476 } catch (dml_exception $ex) {
477 if (!empty($CFG->rolesactive)) {
478 error_log('Can not read or insert database sessions');
479 }
480 return '';
481 }
482
3b1a9849 483 // verify timeout
484 if ($record->timemodified + $CFG->sessiontimeout < time()) {
dd9e22f8 485 $ignoretimeout = false;
88fdd846 486 if (!empty($record->userid)) { // skips not logged in
487 if ($user = $this->database->get_record('user', array('id'=>$record->userid))) {
488 if ($user->username !== 'guest') {
489 $authsequence = get_enabled_auth_plugins(); // auths, in sequence
490 foreach($authsequence as $authname) {
491 $authplugin = get_auth_plugin($authname);
492 if ($authplugin->ignore_timeout_hook($user->id, $user->auth, $record->sid, $record->timecreated, $record->timemodified)) {
493 $ignoretimeout = true;
494 break;
495 }
496 }
497 }
dd9e22f8 498 }
499 }
500 if ($ignoretimeout) {
501 //refresh session
502 $record->timemodified = time();
503 try {
504 $this->database->update_record('sessions', $record);
505 } catch (dml_exception $ex) {
506 error_log('Can not refresh database session');
507 return '';
508 }
509 } else {
510 //time out session
511 $record->state = 0;
512 $record->sessdata = null;
513 $record->sessdatahash = null;
514 $record->userid = 0;
515 $record->timecreated = $record->timemodified = time();
516 $record->firstip = $record->lastip = getremoteaddr();
517 try {
518 $this->database->update_record('sessions', $record);
519 } catch (dml_exception $ex) {
520 error_log('Can not time out database session');
521 return '';
522 }
eee3bd3f 523 }
eee3bd3f 524 }
525
3b1a9849 526 if ($record->sessdatahash !== null) {
527 if (md5($record->sessdata) !== $record->sessdatahash) {
528 // probably this is caused by misconfigured mysql - the allowed request size might be too small
529 try {
530 $this->database->delete_records('sessions', array('sid'=>$record->sid));
531 } catch (dml_exception $ignored) {
532 }
533 print_error('dbsessionbroken', 'error');
534 }
535
536 $data = base64_decode($record->sessdata);
537 } else {
538 $data = '';
539 }
540
0a2092a3 541 unset($record->sessdata); // conserve memory
542 $this->record = $record;
543
544 return $data;
545 }
546
547 public function handler_write($sid, $session_data) {
548 global $USER;
549
550 if (!$this->record) {
551 error_log('Weird error writing session');
552 return true;
553 }
554
3b1a9849 555 $this->database->release_session_lock($this->record->id);
7f79aaea 556
0a2092a3 557 $this->record->sid = $sid; // it might be regenerated
558 $this->record->sessdata = base64_encode($session_data); // there might be some binary mess :-(
17d93489 559 $this->record->sessdatahash = md5($this->record->sessdata);
0a2092a3 560 $this->record->userid = empty($USER->realuser) ? $USER->id : $USER->realuser;
561 $this->record->timemodified = time();
562 $this->record->lastip = getremoteaddr();
563
eee3bd3f 564 // TODO: verify session changed before doing update
565
0a2092a3 566 try {
567 $this->database->update_record_raw('sessions', $this->record);
568 } catch (dml_exception $ex) {
569 error_log('Can not write session to database.');
570 }
17d93489 571
0a2092a3 572 return true;
573 }
574
575 public function handler_destroy($sid) {
576 if (!$this->record or $this->record->sid != $sid) {
577 error_log('Weird error destroying session - mismatched sid');
578 return true;
579 }
580
3b1a9849 581 $this->database->release_session_lock($this->record->id);
7f79aaea 582
0a2092a3 583 try {
584 $this->database->delete_records('sessions', array('sid'=>$this->record->sid));
585 } catch (dml_exception $ex) {
586 error_log('Can not destroy database session.');
587 }
588
589 return true;
590 }
591
3b1a9849 592 public function handler_gc($ignored_maxlifetime) {
dd9e22f8 593 $this->gc();
0a2092a3 594 return true;
57f7b7ce 595 }
0a2092a3 596
0ad6b20c 597}
57f7b7ce 598
0ad6b20c 599/**
600 * Makes sure that $USER->sesskey exists, if $USER itself exists. It sets a new sesskey
601 * if one does not already exist, but does not overwrite existing sesskeys. Returns the
602 * sesskey string if $USER exists, or boolean false if not.
603 *
604 * @uses $USER
605 * @return string
606 */
607function sesskey() {
608 global $USER;
57f7b7ce 609
0ad6b20c 610 if (empty($USER->sesskey)) {
611 $USER->sesskey = random_string(10);
612 }
57f7b7ce 613
0ad6b20c 614 return $USER->sesskey;
615}
57f7b7ce 616
57f7b7ce 617
0ad6b20c 618/**
619 * For security purposes, this function will check that the currently
620 * given sesskey (passed as a parameter to the script or this function)
621 * matches that of the current user.
622 *
623 * @param string $sesskey optionally provided sesskey
624 * @return bool
625 */
626function confirm_sesskey($sesskey=NULL) {
627 global $USER;
57f7b7ce 628
eb85959b 629 if (!empty($USER->ignoresesskey)) {
0ad6b20c 630 return true;
631 }
57f7b7ce 632
0ad6b20c 633 if (empty($sesskey)) {
634 $sesskey = required_param('sesskey', PARAM_RAW); // Check script parameters
57f7b7ce 635 }
636
eb85959b 637 return (sesskey() === $sesskey);
0ad6b20c 638}
639
640/**
641 * Sets a moodle cookie with a weakly encrypted string
642 *
643 * @uses $CFG
644 * @uses DAYSECS
645 * @uses HOURSECS
646 * @param string $thing The string to encrypt and place in a cookie
647 */
648function set_moodle_cookie($thing) {
649 global $CFG;
650
0a2092a3 651 if (NO_MOODLE_COOKIES) {
652 return;
653 }
654
0ad6b20c 655 if ($thing == 'guest') { // Ignore guest account
656 return;
57f7b7ce 657 }
658
0ad6b20c 659 $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
660
661 $days = 60;
662 $seconds = DAYSECS*$days;
663
664 // no need to set secure or http cookie only here - it is not secret
665 setcookie($cookiename, '', time() - HOURSECS, $CFG->sessioncookiepath, $CFG->sessioncookiedomain);
666 setcookie($cookiename, rc4encrypt($thing), time()+$seconds, $CFG->sessioncookiepath, $CFG->sessioncookiedomain);
667}
668
669/**
670 * Gets a moodle cookie with a weakly encrypted string
671 *
672 * @uses $CFG
673 * @return string
674 */
675function get_moodle_cookie() {
676 global $CFG;
677
0a2092a3 678 if (NO_MOODLE_COOKIES) {
679 return '';
680 }
681
0ad6b20c 682 $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
683
684 if (empty($_COOKIE[$cookiename])) {
685 return '';
686 } else {
687 $thing = rc4decrypt($_COOKIE[$cookiename]);
688 return ($thing == 'guest') ? '': $thing; // Ignore guest account
57f7b7ce 689 }
0ad6b20c 690}
57f7b7ce 691
b7b64ff2 692/**
693 * Setup $USER object - called during login, loginas, etc.
694 * Preloads capabilities and checks enrolment plugins
695 *
696 * @param object $user full user record object
697 * @return void
698 */
699function session_set_user($user) {
700 $_SESSION['USER'] = $user;
dd9e22f8 701 unset($_SESSION['USER']->description); // conserve memory
702 if (!isset($_SESSION['USER']->access)) {
703 // check enrolments and load caps only once
704 check_enrolment_plugins($_SESSION['USER']);
705 load_all_capabilities();
706 }
eb85959b 707 sesskey(); // init session key
b7b64ff2 708}
709
542797b4 710/**
711 * Is current $USER logged-in-as somebody else?
712 * @return bool
713 */
b7b64ff2 714function session_is_loggedinas() {
85f6b737 715 return !empty($_SESSION['USER']->realuser);
542797b4 716}
717
6132768e 718/**
719 * Returns the $USER object ignoring current login-as session
720 * @return object user object
721 */
b7b64ff2 722function session_get_realuser() {
723 if (session_is_loggedinas()) {
6132768e 724 return $_SESSION['REALUSER'];
725 } else {
726 return $_SESSION['USER'];
727 }
728}
729
542797b4 730/**
731 * Login as another user - no security checks here.
732 * @param int $userid
733 * @param object $context
734 * @return void
735 */
8d1964c4 736function session_loginas($userid, $context) {
b7b64ff2 737 if (session_is_loggedinas()) {
8d1964c4 738 return;
739 }
740
85f6b737 741 // switch to fresh new $SESSION
742 $_SESSION['REALSESSION'] = $_SESSION['SESSION'];
6132768e 743 $_SESSION['SESSION'] = new object();
8d1964c4 744
85f6b737 745 /// Create the new $USER object with all details and reload needed capabilitites
746 $_SESSION['REALUSER'] = $_SESSION['USER'];
b7b64ff2 747 $user = get_complete_user_data('id', $userid);
748 $user->realuser = $_SESSION['REALUSER']->id;
749 $user->loginascontext = $context;
750 session_set_user($user);
8d1964c4 751}
752
542797b4 753/**
754 * Terminate login-as session
755 * @return void
756 */
8d1964c4 757function session_unloginas() {
b7b64ff2 758 if (!session_is_loggedinas()) {
8d1964c4 759 return;
760 }
761
6132768e 762 $_SESSION['SESSION'] = $_SESSION['REALSESSION'];
763 unset($_SESSION['REALSESSION']);
8d1964c4 764
6132768e 765 $_SESSION['USER'] = $_SESSION['REALUSER'];
766 unset($_SESSION['REALUSER']);
8d1964c4 767}
768
e8b7114d 769/**
770 * Sets up current user and course enviroment (lang, etc.) in cron.
771 * Do not use outside of cron script!
772 *
773 * @param object $user full user object, null means default cron user (admin)
774 * @param $course full course record, null means $SITE
775 * @return void
776 */
777function cron_setup_user($user=null, $course=null) {
778 global $CFG, $SITE;
779
780 static $cronuser = null;
781 static $cronsession = null;
782
783 if (empty($cronuser)) {
784 /// ignore admins timezone, language and locale - use site deafult instead!
785 $cronuser = get_admin();
786 $cronuser->timezone = $CFG->timezone;
dd9e22f8 787 $cronuser->lang = '';
788 $cronuser->theme = '';
789 unset($cronuser->description);
e8b7114d 790
791 $cronsession = array();
792 }
793
794 if (!$user) {
795 // cached default cron user (==modified admin for now)
796 session_set_user($cronuser);
797 $_SESSION['SESSION'] = $cronsession;
798
799 } else {
800 // emulate real user session - needed for caps in cron
801 if ($_SESSION['USER']->id != $user->id) {
802 session_set_user($user);
803 $_SESSION['SESSION'] = array();
804 }
805 }
806
807 if ($course) {
808 course_setup($course);
809 } else {
810 course_setup($SITE);
811 }
812
813 // TODO: it should be possible to improve perf by caching some limited number of users here ;-)
814
815}
816
0ad6b20c 817/**
818* Enable cookieless sessions by including $CFG->usesid=true;
819* in config.php.
820* Based on code from php manual by Richard at postamble.co.uk
821* 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.
822* 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.
823* This doesn't require trans_sid to be turned on but this is recommended for better performance
824* you should put :
825* session.use_trans_sid = 1
826* in your php.ini file and make sure that you don't have a line like this in your php.ini
827* session.use_only_cookies = 1
828* @author Richard at postamble.co.uk and Jamie Pratt
829* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
830*/
831/**
832* You won't call this function directly. This function is used to process
833* text buffered by php in an output buffer. All output is run through this function
834* before it is ouput.
835* @param string $buffer is the output sent from php
836* @return string the output sent to the browser
837*/
838function sid_ob_rewrite($buffer){
839 $replacements = array(
840 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*")([^"]*)(")/i',
841 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*\')([^\']*)(\')/i');
842
843 $buffer = preg_replace_callback($replacements, 'sid_rewrite_link_tag', $buffer);
844 $buffer = preg_replace('/<form\s[^>]*>/i',
845 '\0<input type="hidden" name="' . session_name() . '" value="' . session_id() . '"/>', $buffer);
846
847 return $buffer;
848}
849/**
850* You won't call this function directly. This function is used to process
851* text buffered by php in an output buffer. All output is run through this function
852* before it is ouput.
853* This function only processes absolute urls, it is used when we decide that
854* php is processing other urls itself but needs some help with internal absolute urls still.
855* @param string $buffer is the output sent from php
856* @return string the output sent to the browser
857*/
858function sid_ob_rewrite_absolute($buffer){
859 $replacements = array(
860 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*")((?:http|https)[^"]*)(")/i',
861 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*\')((?:http|https)[^\']*)(\')/i');
862
863 $buffer = preg_replace_callback($replacements, 'sid_rewrite_link_tag', $buffer);
864 $buffer = preg_replace('/<form\s[^>]*>/i',
865 '\0<input type="hidden" name="' . session_name() . '" value="' . session_id() . '"/>', $buffer);
866 return $buffer;
867}
57f7b7ce 868
0ad6b20c 869/**
870* A function to process link, a and script tags found
871* by preg_replace_callback in {@link sid_ob_rewrite($buffer)}.
872*/
873function sid_rewrite_link_tag($matches){
874 $url = $matches[4];
875 $url = sid_process_url($url);
876 return $matches[1].$url.$matches[5];
877}
878
879/**
880* You can call this function directly. This function is used to process
881* urls to add a moodle session id to the url for internal links.
882* @param string $url is a url
883* @return string the processed url
884*/
885function sid_process_url($url) {
886 global $CFG;
887
888 if ((preg_match('/^(http|https):/i', $url)) // absolute url
889 && ((stripos($url, $CFG->wwwroot)!==0) && stripos($url, $CFG->httpswwwroot)!==0)) { // and not local one
890 return $url; //don't attach sessid to non local urls
891 }
892 if ($url[0]=='#' || (stripos($url, 'javascript:')===0)) {
893 return $url; //don't attach sessid to anchors
894 }
895 if (strpos($url, session_name())!==FALSE) {
896 return $url; //don't attach sessid to url that already has one sessid
897 }
898 if (strpos($url, "?")===FALSE) {
899 $append = "?".strip_tags(session_name() . '=' . session_id());
900 } else {
901 $append = "&amp;".strip_tags(session_name() . '=' . session_id());
57f7b7ce 902 }
0ad6b20c 903 //put sessid before any anchor
904 $p = strpos($url, "#");
905 if ($p!==FALSE){
906 $anch = substr($url, $p);
907 $url = substr($url, 0, $p).$append.$anch ;
908 } else {
909 $url .= $append ;
910 }
911 return $url;
912}
57f7b7ce 913
0ad6b20c 914/**
915* Call this function before there has been any output to the browser to
916* buffer output and add session ids to all internal links.
917*/
918function sid_start_ob(){
919 global $CFG;
920 //don't attach sess id for bots
921
922 if (!empty($_SERVER['HTTP_USER_AGENT'])) {
923 if (!empty($CFG->opentogoogle)) {
924 if (strpos($_SERVER['HTTP_USER_AGENT'], 'Googlebot') !== false) {
925 @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
926 $CFG->usesid=false;
927 return;
57f7b7ce 928 }
0ad6b20c 929 if (strpos($_SERVER['HTTP_USER_AGENT'], 'google.com') !== false) {
57f7b7ce 930 @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
931 $CFG->usesid=false;
932 return;
933 }
934 }
0ad6b20c 935 if (strpos($_SERVER['HTTP_USER_AGENT'], 'W3C_Validator') !== false) {
936 @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
937 $CFG->usesid=false;
938 return;
939 }
940 }
57f7b7ce 941
0ad6b20c 942 @ini_set('session.use_trans_sid', '1'); // try and turn on trans_sid
57f7b7ce 943
0ad6b20c 944 if (ini_get('session.use_trans_sid') != 0) {
945 // use trans sid as its available
946 ini_set('url_rewriter.tags', 'a=href,area=href,script=src,link=href,frame=src,form=fakeentry');
947 ob_start('sid_ob_rewrite_absolute');
948 } else {
949 //rewrite all links ourselves
950 ob_start('sid_ob_rewrite');
57f7b7ce 951 }
e6e13284 952}
0ad6b20c 953