[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Session manager class. 19 * 20 * @package core 21 * @copyright 2013 Petr Skoda {@link http://skodak.org} 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 namespace core\session; 26 27 defined('MOODLE_INTERNAL') || die(); 28 29 /** 30 * Session manager, this is the public Moodle API for sessions. 31 * 32 * Following PHP functions MUST NOT be used directly: 33 * - session_start() - not necessary, lib/setup.php starts session automatically, 34 * use define('NO_MOODLE_COOKIE', true) if session not necessary. 35 * - session_write_close() - use \core\session\manager::write_close() instead. 36 * - session_destroy() - use require_logout() instead. 37 * 38 * @package core 39 * @copyright 2013 Petr Skoda {@link http://skodak.org} 40 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 41 */ 42 class manager { 43 /** @var handler $handler active session handler instance */ 44 protected static $handler; 45 46 /** @var bool $sessionactive Is the session active? */ 47 protected static $sessionactive = null; 48 49 /** 50 * Start user session. 51 * 52 * Note: This is intended to be called only from lib/setup.php! 53 */ 54 public static function start() { 55 global $CFG, $DB; 56 57 if (isset(self::$sessionactive)) { 58 debugging('Session was already started!', DEBUG_DEVELOPER); 59 return; 60 } 61 62 self::load_handler(); 63 64 // Init the session handler only if everything initialised properly in lib/setup.php file 65 // and the session is actually required. 66 if (empty($DB) or empty($CFG->version) or !defined('NO_MOODLE_COOKIES') or NO_MOODLE_COOKIES or CLI_SCRIPT) { 67 self::$sessionactive = false; 68 self::init_empty_session(); 69 return; 70 } 71 72 try { 73 self::$handler->init(); 74 self::prepare_cookies(); 75 $isnewsession = empty($_COOKIE[session_name()]); 76 77 if (!self::$handler->start()) { 78 // Could not successfully start/recover session. 79 throw new \core\session\exception(get_string('servererror')); 80 } 81 82 self::initialise_user_session($isnewsession); 83 self::check_security(); 84 85 // Link global $USER and $SESSION, 86 // this is tricky because PHP does not allow references to references 87 // and global keyword uses internally once reference to the $GLOBALS array. 88 // The solution is to use the $GLOBALS['USER'] and $GLOBALS['$SESSION'] 89 // as the main storage of data and put references to $_SESSION. 90 $GLOBALS['USER'] = $_SESSION['USER']; 91 $_SESSION['USER'] =& $GLOBALS['USER']; 92 $GLOBALS['SESSION'] = $_SESSION['SESSION']; 93 $_SESSION['SESSION'] =& $GLOBALS['SESSION']; 94 95 } catch (\Exception $ex) { 96 self::init_empty_session(); 97 self::$sessionactive = false; 98 throw $ex; 99 } 100 101 self::$sessionactive = true; 102 } 103 104 /** 105 * Returns current page performance info. 106 * 107 * @return array perf info 108 */ 109 public static function get_performance_info() { 110 if (!session_id()) { 111 return array(); 112 } 113 114 self::load_handler(); 115 $size = display_size(strlen(session_encode())); 116 $handler = get_class(self::$handler); 117 118 $info = array(); 119 $info['size'] = $size; 120 $info['html'] = "<span class=\"sessionsize\">Session ($handler): $size</span> "; 121 $info['txt'] = "Session ($handler): $size "; 122 123 return $info; 124 } 125 126 /** 127 * Create handler instance. 128 */ 129 protected static function load_handler() { 130 global $CFG, $DB; 131 132 if (self::$handler) { 133 return; 134 } 135 136 // Find out which handler to use. 137 if (PHPUNIT_TEST) { 138 $class = '\core\session\file'; 139 140 } else if (!empty($CFG->session_handler_class)) { 141 $class = $CFG->session_handler_class; 142 143 } else if (!empty($CFG->dbsessions) and $DB->session_lock_supported()) { 144 $class = '\core\session\database'; 145 146 } else { 147 $class = '\core\session\file'; 148 } 149 self::$handler = new $class(); 150 } 151 152 /** 153 * Empty current session, fill it with not-logged-in user info. 154 * 155 * This is intended for installation scripts, unit tests and other 156 * special areas. Do NOT use for logout and session termination 157 * in normal requests! 158 */ 159 public static function init_empty_session() { 160 global $CFG; 161 162 if (isset($GLOBALS['SESSION']->notifications)) { 163 // Backup notifications. These should be preserved across session changes until the user fetches and clears them. 164 $notifications = $GLOBALS['SESSION']->notifications; 165 } 166 $GLOBALS['SESSION'] = new \stdClass(); 167 168 $GLOBALS['USER'] = new \stdClass(); 169 $GLOBALS['USER']->id = 0; 170 171 if (!empty($notifications)) { 172 // Restore notifications. 173 $GLOBALS['SESSION']->notifications = $notifications; 174 } 175 if (isset($CFG->mnet_localhost_id)) { 176 $GLOBALS['USER']->mnethostid = $CFG->mnet_localhost_id; 177 } else { 178 // Not installed yet, the future host id will be most probably 1. 179 $GLOBALS['USER']->mnethostid = 1; 180 } 181 182 // Link global $USER and $SESSION. 183 $_SESSION = array(); 184 $_SESSION['USER'] =& $GLOBALS['USER']; 185 $_SESSION['SESSION'] =& $GLOBALS['SESSION']; 186 } 187 188 /** 189 * Make sure all cookie and session related stuff is configured properly before session start. 190 */ 191 protected static function prepare_cookies() { 192 global $CFG; 193 194 if (!isset($CFG->cookiesecure) or (!is_https() and empty($CFG->sslproxy))) { 195 $CFG->cookiesecure = 0; 196 } 197 198 if (!isset($CFG->cookiehttponly)) { 199 $CFG->cookiehttponly = 0; 200 } 201 202 // Set sessioncookie variable if it isn't already. 203 if (!isset($CFG->sessioncookie)) { 204 $CFG->sessioncookie = ''; 205 } 206 $sessionname = 'MoodleSession'.$CFG->sessioncookie; 207 208 // Make sure cookie domain makes sense for this wwwroot. 209 if (!isset($CFG->sessioncookiedomain)) { 210 $CFG->sessioncookiedomain = ''; 211 } else if ($CFG->sessioncookiedomain !== '') { 212 $host = parse_url($CFG->wwwroot, PHP_URL_HOST); 213 if ($CFG->sessioncookiedomain !== $host) { 214 if (substr($CFG->sessioncookiedomain, 0, 1) === '.') { 215 if (!preg_match('|^.*'.preg_quote($CFG->sessioncookiedomain, '|').'$|', $host)) { 216 // Invalid domain - it must be end part of host. 217 $CFG->sessioncookiedomain = ''; 218 } 219 } else { 220 if (!preg_match('|^.*\.'.preg_quote($CFG->sessioncookiedomain, '|').'$|', $host)) { 221 // Invalid domain - it must be end part of host. 222 $CFG->sessioncookiedomain = ''; 223 } 224 } 225 } 226 } 227 228 // Make sure the cookiepath is valid for this wwwroot or autodetect if not specified. 229 if (!isset($CFG->sessioncookiepath)) { 230 $CFG->sessioncookiepath = ''; 231 } 232 if ($CFG->sessioncookiepath !== '/') { 233 $path = parse_url($CFG->wwwroot, PHP_URL_PATH).'/'; 234 if ($CFG->sessioncookiepath === '') { 235 $CFG->sessioncookiepath = $path; 236 } else { 237 if (strpos($path, $CFG->sessioncookiepath) !== 0 or substr($CFG->sessioncookiepath, -1) !== '/') { 238 $CFG->sessioncookiepath = $path; 239 } 240 } 241 } 242 243 // Discard session ID from POST, GET and globals to tighten security, 244 // this is session fixation prevention. 245 unset($GLOBALS[$sessionname]); 246 unset($_GET[$sessionname]); 247 unset($_POST[$sessionname]); 248 unset($_REQUEST[$sessionname]); 249 250 // Compatibility hack for non-browser access to our web interface. 251 if (!empty($_COOKIE[$sessionname]) && $_COOKIE[$sessionname] == "deleted") { 252 unset($_COOKIE[$sessionname]); 253 } 254 255 // Set configuration. 256 session_name($sessionname); 257 session_set_cookie_params(0, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly); 258 ini_set('session.use_trans_sid', '0'); 259 ini_set('session.use_only_cookies', '1'); 260 ini_set('session.hash_function', '0'); // For now MD5 - we do not have room for sha-1 in sessions table. 261 ini_set('session.use_strict_mode', '0'); // We have custom protection in session init. 262 ini_set('session.serialize_handler', 'php'); // We can move to 'php_serialize' after we require PHP 5.5.4 form Moodle. 263 264 // Moodle does normal session timeouts, this is for leftovers only. 265 ini_set('session.gc_probability', 1); 266 ini_set('session.gc_divisor', 1000); 267 ini_set('session.gc_maxlifetime', 60*60*24*4); 268 } 269 270 /** 271 * Initialise $_SESSION, handles google access 272 * and sets up not-logged-in user properly. 273 * 274 * WARNING: $USER and $SESSION are set up later, do not use them yet! 275 * 276 * @param bool $newsid is this a new session in first http request? 277 */ 278 protected static function initialise_user_session($newsid) { 279 global $CFG, $DB; 280 281 $sid = session_id(); 282 if (!$sid) { 283 // No session, very weird. 284 error_log('Missing session ID, session not started!'); 285 self::init_empty_session(); 286 return; 287 } 288 289 if (!$record = $DB->get_record('sessions', array('sid'=>$sid), 'id, sid, state, userid, lastip, timecreated, timemodified')) { 290 if (!$newsid) { 291 if (!empty($_SESSION['USER']->id)) { 292 // This should not happen, just log it, we MUST not produce any output here! 293 error_log("Cannot find session record $sid for user ".$_SESSION['USER']->id.", creating new session."); 294 } 295 // Prevent session fixation attacks. 296 session_regenerate_id(true); 297 } 298 $_SESSION = array(); 299 } 300 unset($sid); 301 302 if (isset($_SESSION['USER']->id)) { 303 if (!empty($_SESSION['USER']->realuser)) { 304 $userid = $_SESSION['USER']->realuser; 305 } else { 306 $userid = $_SESSION['USER']->id; 307 } 308 309 // Verify timeout first. 310 $maxlifetime = $CFG->sessiontimeout; 311 $timeout = false; 312 if (isguestuser($userid) or empty($userid)) { 313 // Ignore guest and not-logged in timeouts, there is very little risk here. 314 $timeout = false; 315 316 } else if ($record->timemodified < time() - $maxlifetime) { 317 $timeout = true; 318 $authsequence = get_enabled_auth_plugins(); // Auths, in sequence. 319 foreach ($authsequence as $authname) { 320 $authplugin = get_auth_plugin($authname); 321 if ($authplugin->ignore_timeout_hook($_SESSION['USER'], $record->sid, $record->timecreated, $record->timemodified)) { 322 $timeout = false; 323 break; 324 } 325 } 326 } 327 328 if ($timeout) { 329 session_regenerate_id(true); 330 $_SESSION = array(); 331 $DB->delete_records('sessions', array('id'=>$record->id)); 332 333 } else { 334 // Update session tracking record. 335 336 $update = new \stdClass(); 337 $updated = false; 338 339 if ($record->userid != $userid) { 340 $update->userid = $record->userid = $userid; 341 $updated = true; 342 } 343 344 $ip = getremoteaddr(); 345 if ($record->lastip != $ip) { 346 $update->lastip = $record->lastip = $ip; 347 $updated = true; 348 } 349 350 $updatefreq = empty($CFG->session_update_timemodified_frequency) ? 20 : $CFG->session_update_timemodified_frequency; 351 352 if ($record->timemodified == $record->timecreated) { 353 // Always do first update of existing record. 354 $update->timemodified = $record->timemodified = time(); 355 $updated = true; 356 357 } else if ($record->timemodified < time() - $updatefreq) { 358 // Update the session modified flag only once every 20 seconds. 359 $update->timemodified = $record->timemodified = time(); 360 $updated = true; 361 } 362 363 if ($updated) { 364 $update->id = $record->id; 365 $DB->update_record('sessions', $update); 366 } 367 368 return; 369 } 370 } else { 371 if ($record) { 372 // This happens when people switch session handlers... 373 session_regenerate_id(true); 374 $_SESSION = array(); 375 $DB->delete_records('sessions', array('id'=>$record->id)); 376 } 377 } 378 unset($record); 379 380 $timedout = false; 381 if (!isset($_SESSION['SESSION'])) { 382 $_SESSION['SESSION'] = new \stdClass(); 383 if (!$newsid) { 384 $timedout = true; 385 } 386 } 387 388 $user = null; 389 390 if (!empty($CFG->opentogoogle)) { 391 if (\core_useragent::is_web_crawler()) { 392 $user = guest_user(); 393 } 394 $referer = get_local_referer(false); 395 if (!empty($CFG->guestloginbutton) and !$user and !empty($referer)) { 396 // Automatically log in users coming from search engine results. 397 if (strpos($referer, 'google') !== false ) { 398 $user = guest_user(); 399 } else if (strpos($referer, 'altavista') !== false ) { 400 $user = guest_user(); 401 } 402 } 403 } 404 405 // Setup $USER and insert the session tracking record. 406 if ($user) { 407 self::set_user($user); 408 self::add_session_record($user->id); 409 } else { 410 self::init_empty_session(); 411 self::add_session_record(0); 412 } 413 414 if ($timedout) { 415 $_SESSION['SESSION']->has_timed_out = true; 416 } 417 } 418 419 /** 420 * Insert new empty session record. 421 * @param int $userid 422 * @return \stdClass the new record 423 */ 424 protected static function add_session_record($userid) { 425 global $DB; 426 $record = new \stdClass(); 427 $record->state = 0; 428 $record->sid = session_id(); 429 $record->sessdata = null; 430 $record->userid = $userid; 431 $record->timecreated = $record->timemodified = time(); 432 $record->firstip = $record->lastip = getremoteaddr(); 433 434 $record->id = $DB->insert_record('sessions', $record); 435 436 return $record; 437 } 438 439 /** 440 * Do various session security checks. 441 * 442 * WARNING: $USER and $SESSION are set up later, do not use them yet! 443 * @throws \core\session\exception 444 */ 445 protected static function check_security() { 446 global $CFG; 447 448 if (!empty($_SESSION['USER']->id) and !empty($CFG->tracksessionip)) { 449 // Make sure current IP matches the one for this session. 450 $remoteaddr = getremoteaddr(); 451 452 if (empty($_SESSION['USER']->sessionip)) { 453 $_SESSION['USER']->sessionip = $remoteaddr; 454 } 455 456 if ($_SESSION['USER']->sessionip != $remoteaddr) { 457 // This is a security feature - terminate the session in case of any doubt. 458 self::terminate_current(); 459 throw new exception('sessionipnomatch2', 'error'); 460 } 461 } 462 } 463 464 /** 465 * Login user, to be called from complete_user_login() only. 466 * @param \stdClass $user 467 */ 468 public static function login_user(\stdClass $user) { 469 global $DB; 470 471 // Regenerate session id and delete old session, 472 // this helps prevent session fixation attacks from the same domain. 473 474 $sid = session_id(); 475 session_regenerate_id(true); 476 $DB->delete_records('sessions', array('sid'=>$sid)); 477 self::add_session_record($user->id); 478 479 // Let enrol plugins deal with new enrolments if necessary. 480 enrol_check_plugins($user); 481 482 // Setup $USER object. 483 self::set_user($user); 484 } 485 486 /** 487 * Terminate current user session. 488 * @return void 489 */ 490 public static function terminate_current() { 491 global $DB; 492 493 if (!self::$sessionactive) { 494 self::init_empty_session(); 495 self::$sessionactive = false; 496 return; 497 } 498 499 try { 500 $DB->delete_records('external_tokens', array('sid'=>session_id(), 'tokentype'=>EXTERNAL_TOKEN_EMBEDDED)); 501 } catch (\Exception $ignored) { 502 // Probably install/upgrade - ignore this problem. 503 } 504 505 // Initialize variable to pass-by-reference to headers_sent(&$file, &$line). 506 $file = null; 507 $line = null; 508 if (headers_sent($file, $line)) { 509 error_log('Cannot terminate session properly - headers were already sent in file: '.$file.' on line '.$line); 510 } 511 512 // Write new empty session and make sure the old one is deleted. 513 $sid = session_id(); 514 session_regenerate_id(true); 515 $DB->delete_records('sessions', array('sid'=>$sid)); 516 self::init_empty_session(); 517 self::add_session_record($_SESSION['USER']->id); // Do not use $USER here because it may not be set up yet. 518 session_write_close(); 519 self::$sessionactive = false; 520 } 521 522 /** 523 * No more changes in session expected. 524 * Unblocks the sessions, other scripts may start executing in parallel. 525 */ 526 public static function write_close() { 527 if (version_compare(PHP_VERSION, '5.6.0', '>=')) { 528 // More control over whether session data 529 // is persisted or not. 530 if (self::$sessionactive && session_id()) { 531 // Write session and release lock only if 532 // indication session start was clean. 533 session_write_close(); 534 } else { 535 // Otherwise, if possibile lock exists want 536 // to clear it, but do not write session. 537 @session_abort(); 538 } 539 } else { 540 // Any indication session was started, attempt 541 // to close it. 542 if (self::$sessionactive || session_id()) { 543 session_write_close(); 544 } 545 } 546 self::$sessionactive = false; 547 } 548 549 /** 550 * Does the PHP session with given id exist? 551 * 552 * The session must exist both in session table and actual 553 * session backend and the session must not be timed out. 554 * 555 * Timeout evaluation is simplified, the auth hooks are not executed. 556 * 557 * @param string $sid 558 * @return bool 559 */ 560 public static function session_exists($sid) { 561 global $DB, $CFG; 562 563 if (empty($CFG->version)) { 564 // Not installed yet, do not try to access database. 565 return false; 566 } 567 568 // Note: add sessions->state checking here if it gets implemented. 569 if (!$record = $DB->get_record('sessions', array('sid' => $sid), 'id, userid, timemodified')) { 570 return false; 571 } 572 573 if (empty($record->userid) or isguestuser($record->userid)) { 574 // Ignore guest and not-logged-in timeouts, there is very little risk here. 575 } else if ($record->timemodified < time() - $CFG->sessiontimeout) { 576 return false; 577 } 578 579 // There is no need the existence of handler storage in public API. 580 self::load_handler(); 581 return self::$handler->session_exists($sid); 582 } 583 584 /** 585 * Fake last access for given session, this prevents session timeout. 586 * @param string $sid 587 */ 588 public static function touch_session($sid) { 589 global $DB; 590 591 // Timeouts depend on core sessions table only, no need to update anything in external stores. 592 593 $sql = "UPDATE {sessions} SET timemodified = :now WHERE sid = :sid"; 594 $DB->execute($sql, array('now'=>time(), 'sid'=>$sid)); 595 } 596 597 /** 598 * Terminate all sessions unconditionally. 599 */ 600 public static function kill_all_sessions() { 601 global $DB; 602 603 self::terminate_current(); 604 605 self::load_handler(); 606 self::$handler->kill_all_sessions(); 607 608 try { 609 $DB->delete_records('sessions'); 610 } catch (\dml_exception $ignored) { 611 // Do not show any warnings - might be during upgrade/installation. 612 } 613 } 614 615 /** 616 * Terminate give session unconditionally. 617 * @param string $sid 618 */ 619 public static function kill_session($sid) { 620 global $DB; 621 622 self::load_handler(); 623 624 if ($sid === session_id()) { 625 self::write_close(); 626 } 627 628 self::$handler->kill_session($sid); 629 630 $DB->delete_records('sessions', array('sid'=>$sid)); 631 } 632 633 /** 634 * Terminate all sessions of given user unconditionally. 635 * @param int $userid 636 * @param string $keepsid keep this sid if present 637 */ 638 public static function kill_user_sessions($userid, $keepsid = null) { 639 global $DB; 640 641 $sessions = $DB->get_records('sessions', array('userid'=>$userid), 'id DESC', 'id, sid'); 642 foreach ($sessions as $session) { 643 if ($keepsid and $keepsid === $session->sid) { 644 continue; 645 } 646 self::kill_session($session->sid); 647 } 648 } 649 650 /** 651 * Terminate other sessions of current user depending 652 * on $CFG->limitconcurrentlogins restriction. 653 * 654 * This is expected to be called right after complete_user_login(). 655 * 656 * NOTE: 657 * * Do not use from SSO auth plugins, this would not work. 658 * * Do not use from web services because they do not have sessions. 659 * 660 * @param int $userid 661 * @param string $sid session id to be always keep, usually the current one 662 * @return void 663 */ 664 public static function apply_concurrent_login_limit($userid, $sid = null) { 665 global $CFG, $DB; 666 667 // NOTE: the $sid parameter is here mainly to allow testing, 668 // in most cases it should be current session id. 669 670 if (isguestuser($userid) or empty($userid)) { 671 // This applies to real users only! 672 return; 673 } 674 675 if (empty($CFG->limitconcurrentlogins) or $CFG->limitconcurrentlogins < 0) { 676 return; 677 } 678 679 $count = $DB->count_records('sessions', array('userid' => $userid)); 680 681 if ($count <= $CFG->limitconcurrentlogins) { 682 return; 683 } 684 685 $i = 0; 686 $select = "userid = :userid"; 687 $params = array('userid' => $userid); 688 if ($sid) { 689 if ($DB->record_exists('sessions', array('sid' => $sid, 'userid' => $userid))) { 690 $select .= " AND sid <> :sid"; 691 $params['sid'] = $sid; 692 $i = 1; 693 } 694 } 695 696 $sessions = $DB->get_records_select('sessions', $select, $params, 'timecreated DESC', 'id, sid'); 697 foreach ($sessions as $session) { 698 $i++; 699 if ($i <= $CFG->limitconcurrentlogins) { 700 continue; 701 } 702 self::kill_session($session->sid); 703 } 704 } 705 706 /** 707 * Set current user. 708 * 709 * @param \stdClass $user record 710 */ 711 public static function set_user(\stdClass $user) { 712 $GLOBALS['USER'] = $user; 713 unset($GLOBALS['USER']->description); // Conserve memory. 714 unset($GLOBALS['USER']->password); // Improve security. 715 if (isset($GLOBALS['USER']->lang)) { 716 // Make sure it is a valid lang pack name. 717 $GLOBALS['USER']->lang = clean_param($GLOBALS['USER']->lang, PARAM_LANG); 718 } 719 720 // Relink session with global $USER just in case it got unlinked somehow. 721 $_SESSION['USER'] =& $GLOBALS['USER']; 722 723 // Init session key. 724 sesskey(); 725 } 726 727 /** 728 * Periodic timed-out session cleanup. 729 */ 730 public static function gc() { 731 global $CFG, $DB; 732 733 // This may take a long time... 734 \core_php_time_limit::raise(); 735 736 $maxlifetime = $CFG->sessiontimeout; 737 738 try { 739 // Kill all sessions of deleted and suspended users without any hesitation. 740 $rs = $DB->get_recordset_select('sessions', "userid IN (SELECT id FROM {user} WHERE deleted <> 0 OR suspended <> 0)", array(), 'id DESC', 'id, sid'); 741 foreach ($rs as $session) { 742 self::kill_session($session->sid); 743 } 744 $rs->close(); 745 746 // Kill sessions of users with disabled plugins. 747 $auth_sequence = get_enabled_auth_plugins(true); 748 $auth_sequence = array_flip($auth_sequence); 749 unset($auth_sequence['nologin']); // No login means user cannot login. 750 $auth_sequence = array_flip($auth_sequence); 751 752 list($notplugins, $params) = $DB->get_in_or_equal($auth_sequence, SQL_PARAMS_QM, '', false); 753 $rs = $DB->get_recordset_select('sessions', "userid IN (SELECT id FROM {user} WHERE auth $notplugins)", $params, 'id DESC', 'id, sid'); 754 foreach ($rs as $session) { 755 self::kill_session($session->sid); 756 } 757 $rs->close(); 758 759 // Now get a list of time-out candidates - real users only. 760 $sql = "SELECT u.*, s.sid, s.timecreated AS s_timecreated, s.timemodified AS s_timemodified 761 FROM {user} u 762 JOIN {sessions} s ON s.userid = u.id 763 WHERE s.timemodified < :purgebefore AND u.id <> :guestid"; 764 $params = array('purgebefore' => (time() - $maxlifetime), 'guestid'=>$CFG->siteguest); 765 766 $authplugins = array(); 767 foreach ($auth_sequence as $authname) { 768 $authplugins[$authname] = get_auth_plugin($authname); 769 } 770 $rs = $DB->get_recordset_sql($sql, $params); 771 foreach ($rs as $user) { 772 foreach ($authplugins as $authplugin) { 773 /** @var \auth_plugin_base $authplugin*/ 774 if ($authplugin->ignore_timeout_hook($user, $user->sid, $user->s_timecreated, $user->s_timemodified)) { 775 continue; 776 } 777 } 778 self::kill_session($user->sid); 779 } 780 $rs->close(); 781 782 // Delete expired sessions for guest user account, give them larger timeout, there is no security risk here. 783 $params = array('purgebefore' => (time() - ($maxlifetime * 5)), 'guestid'=>$CFG->siteguest); 784 $rs = $DB->get_recordset_select('sessions', 'userid = :guestid AND timemodified < :purgebefore', $params, 'id DESC', 'id, sid'); 785 foreach ($rs as $session) { 786 self::kill_session($session->sid); 787 } 788 $rs->close(); 789 790 // Delete expired sessions for userid = 0 (not logged in), better kill them asap to release memory. 791 $params = array('purgebefore' => (time() - $maxlifetime)); 792 $rs = $DB->get_recordset_select('sessions', 'userid = 0 AND timemodified < :purgebefore', $params, 'id DESC', 'id, sid'); 793 foreach ($rs as $session) { 794 self::kill_session($session->sid); 795 } 796 $rs->close(); 797 798 // Cleanup letfovers from the first browser access because it may set multiple cookies and then use only one. 799 $params = array('purgebefore' => (time() - 60*3)); 800 $rs = $DB->get_recordset_select('sessions', 'userid = 0 AND timemodified = timecreated AND timemodified < :purgebefore', $params, 'id ASC', 'id, sid'); 801 foreach ($rs as $session) { 802 self::kill_session($session->sid); 803 } 804 $rs->close(); 805 806 } catch (\Exception $ex) { 807 debugging('Error gc-ing sessions: '.$ex->getMessage(), DEBUG_NORMAL, $ex->getTrace()); 808 } 809 } 810 811 /** 812 * Is current $USER logged-in-as somebody else? 813 * @return bool 814 */ 815 public static function is_loggedinas() { 816 return !empty($GLOBALS['USER']->realuser); 817 } 818 819 /** 820 * Returns the $USER object ignoring current login-as session 821 * @return \stdClass user object 822 */ 823 public static function get_realuser() { 824 if (self::is_loggedinas()) { 825 return $_SESSION['REALUSER']; 826 } else { 827 return $GLOBALS['USER']; 828 } 829 } 830 831 /** 832 * Login as another user - no security checks here. 833 * @param int $userid 834 * @param \context $context 835 * @return void 836 */ 837 public static function loginas($userid, \context $context) { 838 global $USER; 839 840 if (self::is_loggedinas()) { 841 return; 842 } 843 844 // Switch to fresh new $_SESSION. 845 $_SESSION = array(); 846 $_SESSION['REALSESSION'] = clone($GLOBALS['SESSION']); 847 $GLOBALS['SESSION'] = new \stdClass(); 848 $_SESSION['SESSION'] =& $GLOBALS['SESSION']; 849 850 // Create the new $USER object with all details and reload needed capabilities. 851 $_SESSION['REALUSER'] = clone($GLOBALS['USER']); 852 $user = get_complete_user_data('id', $userid); 853 $user->realuser = $_SESSION['REALUSER']->id; 854 $user->loginascontext = $context; 855 856 // Let enrol plugins deal with new enrolments if necessary. 857 enrol_check_plugins($user); 858 859 // Create event before $USER is updated. 860 $event = \core\event\user_loggedinas::create( 861 array( 862 'objectid' => $USER->id, 863 'context' => $context, 864 'relateduserid' => $userid, 865 'other' => array( 866 'originalusername' => fullname($USER, true), 867 'loggedinasusername' => fullname($user, true) 868 ) 869 ) 870 ); 871 // Set up global $USER. 872 \core\session\manager::set_user($user); 873 $event->trigger(); 874 } 875 876 /** 877 * Add a JS session keepalive to the page. 878 * 879 * A JS session keepalive script will be called to update the session modification time every $frequency seconds. 880 * 881 * Upon failure, the specified error message will be shown to the user. 882 * 883 * @param string $identifier The string identifier for the message to show on failure. 884 * @param string $component The string component for the message to show on failure. 885 * @param int $frequency The update frequency in seconds. 886 * @throws coding_exception IF the frequency is longer than the session lifetime. 887 */ 888 public static function keepalive($identifier = 'sessionerroruser', $component = 'error', $frequency = null) { 889 global $CFG, $PAGE; 890 891 if ($frequency) { 892 if ($frequency > $CFG->sessiontimeout) { 893 // Sanity check the frequency. 894 throw new \coding_exception('Keepalive frequency is longer than the session lifespan.'); 895 } 896 } else { 897 // A frequency of sessiontimeout / 3 allows for one missed request whilst still preserving the session. 898 $frequency = $CFG->sessiontimeout / 3; 899 } 900 901 // Add the session keepalive script to the list of page output requirements. 902 $sessionkeepaliveurl = new \moodle_url('/lib/sessionkeepalive_ajax.php'); 903 $PAGE->requires->string_for_js($identifier, $component); 904 $PAGE->requires->yui_module('moodle-core-checknet', 'M.core.checknet.init', array(array( 905 // The JS config takes this is milliseconds rather than seconds. 906 'frequency' => $frequency * 1000, 907 'message' => array($identifier, $component), 908 'uri' => $sessionkeepaliveurl->out(), 909 ))); 910 } 911 912 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |