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