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