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