MDL-31501 rework user session architecture

List of changes:
 * New OOP API using PHP namespace \core\session\.
 * All handlers now update the sessions table consistently.
 * Experimental DB session support in Oracle.
 * Full support for session file handler (filesystem locking required).
 * New option for alternative session directory.
 * Official memcached session handler support.
 * Workaround for memcached version with non-functional gc.
 * Improved security - forced session id regeneration.
 * Improved compatibility with recent PHP releases.
 * Fixed borked CSS during install in debug mode.
 * Switched to file based sessions in new installs.
 * DB session setting disappears if DB does not support sessions.
 * DB session setting disappears if session handler specified in config.php.
 * Fast purging of sessions used in request only.
 * No legacy distinction -  file, database and memcached support the same functionality.
 * Session handler name included in performance info.
 * Fixed user_loggedin and user_loggedout event triggering.
 * Other minor bugfixing and improvements.
 * Fixed database session segfault if MUC disposed before $DB.

Limitations:
 * Session access time is now updated right after session start.
 * Support for $CFG->sessionlockloggedinonly was removed.
 * First request does not update userid in sessions table.
 * The timeouts may break badly if server hosting forces PHP.ini session settings.
 * The session GC is a lot slower, we do not rely on external session timeouts.
 * There cannot be any hooks triggered at the session write time.
 * File and memcached handlers do not support session lock acquire timeouts.
 * Some low level PHP session functions can not be used directly in Moodle code.
This commit is contained in:
Petr Škoda 2013-09-08 08:38:52 +02:00
parent 81881cb9d6
commit d79d5ac276
77 changed files with 2267 additions and 1350 deletions

View File

@ -51,7 +51,7 @@ switch ($action) {
if ($auth == $CFG->registerauth) {
set_config('registerauth', '');
}
session_gc(); // remove stale sessions
\core\session\manager::gc(); // Remove stale sessions.
break;
case 'enable':
@ -61,7 +61,7 @@ switch ($action) {
$authsenabled = array_unique($authsenabled);
set_config('auth', implode(',', $authsenabled));
}
session_gc(); // remove stale sessions
\core\session\manager::gc(); // Remove stale sessions.
break;
case 'down':

View File

@ -172,7 +172,7 @@ set_config('branch', $branch);
upgrade_noncore(true);
// log in as admin - we need doanything permission when applying defaults
session_set_user(get_admin());
\core\session\manager::set_user(get_admin());
// apply all default settings, just in case do it twice to fill all defaults
admin_apply_default_settings(NULL, false);

View File

@ -53,7 +53,7 @@ require_once($CFG->libdir.'/clilib.php');
require_once($CFG->libdir.'/cronlib.php');
// extra safety
session_get_instance()->write_close();
\core\session\manager::write_close();
// check if execution allowed
if (!empty($CFG->cronclionly)) {

View File

@ -163,7 +163,7 @@ if (!core_tables_exist()) {
$strinstallation = get_string('installation', 'install');
// remove current session content completely
session_get_instance()->terminate_current();
\core\session\manager::terminate_current();
if (empty($agreelicense)) {
$strlicense = get_string('license');

View File

@ -35,7 +35,9 @@ $ADMIN->add('server', $temp);
// "sessionhandling" settingpage
$temp = new admin_settingpage('sessionhandling', new lang_string('sessionhandling', 'admin'));
$temp->add(new admin_setting_configcheckbox('dbsessions', new lang_string('dbsessions', 'admin'), new lang_string('configdbsessions', 'admin'), 1));
if (empty($CFG->session_handler_class) and $DB->session_lock_supported()) {
$temp->add(new admin_setting_configcheckbox('dbsessions', new lang_string('dbsessions', 'admin'), new lang_string('configdbsessions', 'admin'), 0));
}
$temp->add(new admin_setting_configselect('sessiontimeout', new lang_string('sessiontimeout', 'admin'), new lang_string('configsessiontimeout', 'admin'), 7200, array(14400 => new lang_string('numhours', '', 4),
10800 => new lang_string('numhours', '', 3),
7200 => new lang_string('numhours', '', 2),

View File

@ -47,7 +47,7 @@ if (!$confirm) {
}
raise_memory_limit(MEMORY_EXTRA);
// Release session.
session_get_instance()->write_close();
\core\session\manager::write_close();
echo $renderer->header();
echo $renderer->heading(get_string('batchupgrade', 'tool_assignmentupgrade'));

View File

@ -52,7 +52,7 @@ require_once($CFG->libdir.'/dtllib.php');
function tool_dbtransfer_export_xml_database($description, $mdb) {
@set_time_limit(0);
session_get_instance()->write_close(); // Release session.
\core\session\manager::write_close(); // Release session.
header('Content-Type: application/xhtml+xml; charset=utf-8');
header('Content-Disposition: attachment; filename=database.xml');
@ -79,7 +79,7 @@ function tool_dbtransfer_export_xml_database($description, $mdb) {
function tool_dbtransfer_transfer_database(moodle_database $sourcedb, moodle_database $targetdb, progress_trace $feedback = null) {
@set_time_limit(0);
session_get_instance()->write_close(); // Release session.
\core\session\manager::write_close(); // Release session.
$var = new database_mover($sourcedb, $targetdb, true, $feedback);
$var->export_database(null);

View File

@ -90,7 +90,7 @@ if ($error = tool_generator_course_backend::check_shortname_available($shortname
}
// Switch to admin user account.
session_set_user(get_admin());
\core\session\manager::set_user(get_admin());
// Do backend code to generate course.
$backend = new tool_generator_course_backend($shortname, $size, $fixeddataset, empty($options['quiet']));

View File

@ -88,7 +88,7 @@ try {
}
// Switch to admin user account.
session_set_user(get_admin());
\core\session\manager::set_user(get_admin());
// Do backend code to generate site.
$backend = new tool_generator_site_backend($size, $options['bypasscheck'], $fixeddataset, empty($options['quiet']));

View File

@ -687,7 +687,7 @@ if ($formdata = $mform2->is_cancelled()) {
}
if ($dologout) {
session_kill_user($existinguser->id);
\core\session\manager::kill_user_sessions($existinguser->id);
}
} else {

View File

@ -82,10 +82,10 @@
die;
} else if (data_submitted() and !$user->deleted) {
if (delete_user($user)) {
session_gc(); // remove stale sessions
\core\session\manager::gc(); // Remove stale sessions.
redirect($returnurl);
} else {
session_gc(); // remove stale sessions
\core\session\manager::gc(); // Remove stale sessions.
echo $OUTPUT->header();
echo $OUTPUT->notification($returnurl, get_string('deletednot', '', fullname($user, true)));
}
@ -125,7 +125,7 @@
if (!is_siteadmin($user) and $USER->id != $user->id and $user->suspended != 1) {
$user->suspended = 1;
// Force logout.
session_kill_user($user->id);
\core\session\manager::kill_user_sessions($user->id);
user_update_user($user, false);
}
}

View File

@ -34,7 +34,7 @@ if ($confirm and confirm_sesskey()) {
}
}
$rs->close();
session_gc(); // remove stale sessions
\core\session\manager::gc(); // Remove stale sessions.
echo $OUTPUT->box_start('generalbox', 'notice');
if (!empty($notifications)) {
echo $notifications;

View File

@ -808,7 +808,7 @@ class auth_plugin_ldap extends auth_plugin_base {
$updateuser->suspended = 1;
user_update_user($updateuser, false);
echo "\t"; print_string('auth_dbsuspenduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)); echo "\n";
session_kill_user($user->id);
\core\session\manager::kill_user_sessions($user->id);
}
} else {
print_string('nouserentriestoremove', 'auth_ldap');

View File

@ -141,7 +141,7 @@ class auth_plugin_mnet extends auth_plugin_base {
global $CFG, $USER, $DB;
require_once $CFG->dirroot . '/mnet/xmlrpc/client.php';
if (session_is_loggedinas()) {
if (\core\session\manager::is_loggedinas()) {
print_error('notpermittedtojumpas', 'mnet');
}
@ -919,7 +919,7 @@ class auth_plugin_mnet extends auth_plugin_base {
$returnString .= "We failed to refresh the session for the following usernames: \n".implode("\n", $subArray)."\n\n";
} else {
foreach($results as $emigrant) {
session_touch($emigrant->session_id);
\core\session\manager::touch_session($emigrant->session_id);
}
}
}
@ -1076,7 +1076,7 @@ class auth_plugin_mnet extends auth_plugin_base {
array('useragent'=>$useragent, 'userid'=>$userid));
if (isset($remoteclient) && isset($remoteclient->id)) {
session_kill_user($userid);
\core\session\manager::kill_user_sessions($userid);
}
return $returnstring;
}
@ -1096,7 +1096,7 @@ class auth_plugin_mnet extends auth_plugin_base {
$session = $DB->get_record('mnet_session', array('username'=>$username, 'mnethostid'=>$remoteclient->id, 'useragent'=>$useragent));
$DB->delete_records('mnet_session', array('username'=>$username, 'mnethostid'=>$remoteclient->id, 'useragent'=>$useragent));
if (false != $session) {
session_kill($session->session_id);
\core\session\manager::kill_session($session->session_id);
return true;
}
return false;
@ -1113,7 +1113,7 @@ class auth_plugin_mnet extends auth_plugin_base {
global $CFG;
if (is_array($sessionArray)) {
while($session = array_pop($sessionArray)) {
session_kill($session->session_id);
\core\session\manager::kill_session($session->session_id);
}
return true;
}

View File

@ -48,7 +48,7 @@
&& $user = authenticate_user_login($frm->username, $frm->password)) {
enrol_check_plugins($user);
session_set_user($user);
\core\session\manager::set_user($user);
$USER->loggedin = true;
$USER->site = $CFG->wwwroot; // for added security, store the site in the

View File

@ -34,7 +34,7 @@ $PAGE->set_url('/badges/ajax.php');
$PAGE->set_context(context_system::instance());
// Unlock session during potentially long curl request.
session_get_instance()->write_close();
\core\session\manager::write_close();
$result = badges_check_backpack_accessibility();

View File

@ -81,7 +81,7 @@ function block_html_pluginfile($course, $birecord_or_cm, $context, $filearea, $a
$forcedownload = true;
}
session_get_instance()->write_close();
\core\session\manager::write_close();
send_stored_file($file, 60*60, 0, $forcedownload, $options);
}

View File

@ -25,7 +25,7 @@ class block_mnet_hosts extends block_list {
return false;
}
if (session_is_loggedinas()) {
if (\core\session\manager::is_loggedinas()) {
$this->content = new stdClass();
$this->content->footer = html_writer::tag('span',
get_string('notpermittedtojumpas', 'mnet'));

View File

@ -277,6 +277,6 @@ class core_calendar_type_testcase extends advanced_testcase {
*/
private function set_calendar_type($type) {
$this->user->calendartype = $type;
session_set_user($this->user);
\core\session\manager::set_user($this->user);
}
}

View File

@ -224,10 +224,22 @@ $CFG->admin = 'admin';
// RewriteRule (^.*/theme/yui_combo\.php)(/.*) $1?file=$2
//
//
// By default all user sessions should be using locking, uncomment
// the following setting to prevent locking for guests and not-logged-in
// accounts. This may improve performance significantly.
// $CFG->sessionlockloggedinonly = 1;
// Following settings may be used to select session driver, uncomment only one of the handlers.
// Database session handler (not compatible with MyISAM):
// $CFG->session_handler_class = '\core\session\database';
// $CFG->session_database_acquire_lock_timeout = 120;
//
// File session handler (file system locking required):
// $CFG->session_handler_class = '\core\session\file';
// $CFG->session_file_save_path = $CFG->dataroot.'/sessions';
//
// Memcached session handler (requires memcached server and extension):
// $CFG->session_handler_class = '\core\session\memcached';
// $CFG->session_memcached_save_path = '127.0.0.1:11211';
// $CFG->session_memcached_prefix = 'memc.sess.key.';
//
// Following setting allows you to alter how frequently is timemodified updated in sessions table.
// $CFG->session_update_timemodified_frequency = 20; // In seconds.
//
// If this setting is set to true, then Moodle will track the IP of the
// current user to make sure it hasn't changed during a session. This

View File

@ -11,7 +11,7 @@ $url = new moodle_url('/course/loginas.php', array('id'=>$id));
$PAGE->set_url($url);
// Reset user back to their real self if needed, for security reasons you need to log out and log in again.
if (session_is_loggedinas()) {
if (\core\session\manager::is_loggedinas()) {
require_sesskey();
require_logout();
@ -61,7 +61,7 @@ if (has_capability('moodle/user:loginas', $systemcontext)) {
}
// Login as this user and return to course home page.
session_loginas($userid, $context);
\core\session\manager::loginas($userid, $context);
$newfullname = fullname($USER, true);
$strloginas = get_string('loginas');

View File

@ -84,5 +84,5 @@ if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->get_filename() ==
// ========================================
// finally send the file
// ========================================
session_get_instance()->write_close(); // unlock session during fileserving
\core\session\manager::write_close(); // Unlock session during file serving.
send_stored_file($file, 0, false, true, array('preview' => $preview)); // force download - security first!

View File

@ -46,7 +46,7 @@ $PAGE->set_pagelayout('course');
$PAGE->set_url('/enrol/index.php', array('id'=>$course->id));
// do not allow enrols when in login-as session
if (session_is_loggedinas() and $USER->loginascontext->contextlevel == CONTEXT_COURSE) {
if (\core\session\manager::is_loggedinas() and $USER->loginascontext->contextlevel == CONTEXT_COURSE) {
print_error('loginasnoenrol', '', $CFG->wwwroot.'/course/view.php?id='.$USER->loginascontext->instanceid);
}

View File

@ -111,7 +111,7 @@ if ($file->get_filename() == '.') {
// ========================================
// finally send the file
// ========================================
session_get_instance()->write_close(); // unlock session during fileserving
\core\session\manager::write_close(); // Unlock session during file serving.
send_stored_file($file, $lifetime, $CFG->filteruploadedfiles, $forcedownload);

View File

@ -466,6 +466,7 @@ $string['servicedonotexist'] = 'The service does not exist';
$string['sessionwaiterr'] = 'Timed out while waiting for session lock.<br />Wait for your current requests to finish and try again later.';
$string['sessioncookiesdisable'] = 'Incorrect use of require_key_login() - session cookies must be disabled!';
$string['sessiondiskfull'] = 'The session partition is full. It is not possible to login at this time.<br /><br />Please notify server administrator.';
$string['sessionhandlerproblem'] = 'Session handler is misconfigured';
$string['sessionerroruser'] = 'Your session has timed out. Please login again.';
$string['sessionerroruser2'] = 'A server error that affects your login session was detected. Please login again or restart your browser.';
$string['sessionipnomatch'] = 'Sorry, but your IP number seems to have changed from when you first logged in. This security feature prevents crackers stealing your identity while logged in to this site. Normal users should not be seeing this message - please ask the site administrator for help.';

View File

@ -618,9 +618,7 @@ function login_is_lockedout($user) {
function login_attempt_valid($user) {
global $CFG;
$event = \core\event\user_loggedin::create(array('objectid' => $user->id, 'other' => array('username' => $user->username)));
$event->add_record_snapshot('user', $user);
$event->trigger();
// Note: user_loggedin event is triggered in complete_user_login().
if ($user->mnethostid != $CFG->mnet_localhost_id) {
return;

View File

@ -96,7 +96,7 @@ class user_loggedin extends \core\event\base {
/**
* Custom validation.
*
* @throws coding_exception when validation does not pass.
* @throws \coding_exception when validation does not pass.
* @return void
*/
protected function validate_data() {

View File

@ -38,6 +38,7 @@ class user_loggedout extends base {
* Initialise required event data properties.
*/
protected function init() {
$this->context = \context_system::instance();
$this->data['objecttable'] = 'user';
$this->data['crud'] = 'r';
$this->data['level'] = self::LEVEL_OTHER;

View File

@ -0,0 +1,315 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Database based session handler.
*
* @package core
* @copyright 2013 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\session;
defined('MOODLE_INTERNAL') || die();
/**
* Database based session handler.
*
* @package core
* @copyright 2013 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class database extends handler {
/** @var \stdClass $record session record */
protected $recordid = null;
/** @var \moodle_database $database session database */
protected $database = null;
/** @var bool $failed session read/init failed, do not write back to DB */
protected $failed = false;
/** @var string $lasthash hash of the session data content */
protected $lasthash = null;
/** @var int $acquiretimeout how long to wait for session lock */
protected $acquiretimeout = 120;
/**
* Create new instance of handler.
*/
public function __construct() {
global $DB, $CFG;
// Note: we store the reference here because we need to modify database in shutdown handler.
$this->database = $DB;
if (!empty($CFG->session_database_acquire_lock_timeout)) {
$this->acquiretimeout = (int)$CFG->session_database_acquire_lock_timeout;
}
}
/**
* Init session handler.
*/
public function init() {
if (!$this->database->session_lock_supported()) {
throw new exception('sessionhandlerproblem', 'error', '', null, 'Database does not support session locking');
}
$result = session_set_save_handler(array($this, 'handler_open'),
array($this, 'handler_close'),
array($this, 'handler_read'),
array($this, 'handler_write'),
array($this, 'handler_destroy'),
array($this, 'handler_gc'));
if (!$result) {
throw new exception('dbsessionhandlerproblem', 'error');
}
register_shutdown_function(array($this, 'handler_shutdown'));
}
/**
* Check for existing session with id $sid.
*
* Note: this verifies the storage backend only, not the actual session records.
*
* @param string $sid
* @return bool true if session found.
*/
public function session_exists($sid) {
try {
return $this->database->record_exists('sessions', array('sid'=>$sid, 'state'=>0));
} catch (\dml_exception $ex) {
return false;
}
}
/**
* Kill all active sessions, the core sessions table is
* purged afterwards.
*/
public function kill_all_sessions() {
// Nothing to do, the sessions table is cleared from core.
return;
}
/**
* Kill one session, the session record is removed afterwards.
* @param string $sid
*/
public function kill_session($sid) {
// Nothing to do, the sessions table is purged afterwards.
return;
}
/**
* Open session handler.
*
* {@see http://php.net/manual/en/function.session-set-save-handler.php}
*
* @param string $save_path
* @param string $session_name
* @return bool success
*/
public function handler_open($save_path, $session_name) {
// Note: we use the already open database.
return true;
}
/**
* Close session handler.
*
* {@see http://php.net/manual/en/function.session-set-save-handler.php}
*
* @return bool success
*/
public function handler_close() {
if ($this->recordid) {
try {
$this->database->release_session_lock($this->recordid);
} catch (\Exception $ex) {
// Ignore any problems.
}
}
$this->recordid = null;
$this->lasthash = null;
return true;
}
/**
* Read session handler.
*
* {@see http://php.net/manual/en/function.session-set-save-handler.php}
*
* @param string $sid
* @return string
*/
public function handler_read($sid) {
try {
if (!$record = $this->database->get_record('sessions', array('sid'=>$sid), 'id')) {
// Let's cheat and skip locking if this is the first access,
// do not create the record here, let the manager do it after session init.
$this->failed = false;
$this->recordid = null;
$this->lasthash = sha1('');
return '';
}
if ($this->recordid and $this->recordid != $record->id) {
error_log('Second session read with different record id detected, cannot read session');
$this->failed = true;
$this->recordid = null;
return '';
}
if (!$this->recordid) {
// Lock session if exists and not already locked.
$this->database->get_session_lock($record->id, $this->acquiretimeout);
$this->recordid = $record->id;
}
} catch (\dml_sessionwait_exception $ex) {
// This is a fatal error, better inform users.
// It should not happen very often - all pages that need long time to execute
// should close session immediately after access control checks.
error_log('Cannot obtain session lock for sid: '.$sid);
$this->failed = true;
throw $ex;
} catch (\Exception $ex) {
// Do not rethrow exceptions here, this should not happen.
error_log('Unknown exception when starting database session : '.$sid.' - '.$ex->getMessage());
$this->failed = true;
$this->recordid = null;
return '';
}
// Finally read the full session data because we know we have the lock now.
if (!$record = $this->database->get_record('sessions', array('id'=>$record->id), 'id, sessdata')) {
// Ignore - something else just deleted the session record.
$this->failed = true;
$this->recordid = null;
return '';
}
$this->failed = false;
if (is_null($record->sessdata)) {
$data = '';
$this->lasthash = sha1('');
} else {
$data = base64_decode($record->sessdata);
$this->lasthash = sha1($record->sessdata);
}
return $data;
}
/**
* Write session handler.
*
* {@see http://php.net/manual/en/function.session-set-save-handler.php}
*
* NOTE: Do not write to output or throw any exceptions!
* Hopefully the next page is going to display nice error or it recovers...
*
* @param string $sid
* @param string $session_data
* @return bool success
*/
public function handler_write($sid, $session_data) {
if ($this->failed) {
// Do not write anything back - we failed to start the session properly.
return false;
}
$sessdata = base64_encode($session_data); // There might be some binary mess :-(
$hash = sha1($sessdata);
if ($hash === $this->lasthash) {
return true;
}
try {
if ($this->recordid) {
$this->database->set_field('sessions', 'sessdata', $sessdata, array('id'=>$this->recordid));
} else {
// This happens in the first request when session record was just created in manager.
$this->database->set_field('sessions', 'sessdata', $sessdata, array('sid'=>$sid));
}
} catch (\Exception $ex) {
// Do not rethrow exceptions here, this should not happen.
error_log('Unknown exception when writing database session data : '.$sid.' - '.$ex->getMessage());
}
return true;
}
/**
* Destroy session handler.
*
* {@see http://php.net/manual/en/function.session-set-save-handler.php}
*
* @param string $sid
* @return bool success
*/
public function handler_destroy($sid) {
if (!$session = $this->database->get_record('sessions', array('sid'=>$sid), 'id, sid')) {
if ($sid == session_id()) {
$this->recordid = null;
$this->lasthash = null;
}
return true;
}
if ($this->recordid and $session->id == $this->recordid) {
try {
$this->database->release_session_lock($this->recordid);
} catch (\Exception $ex) {
// Ignore problems.
}
$this->recordid = null;
$this->lasthash = null;
}
$this->database->delete_records('sessions', array('id'=>$session->id));
return true;
}
/**
* GC session handler.
*
* {@see http://php.net/manual/en/function.session-set-save-handler.php}
*
* @param int $ignored_maxlifetime moodle uses special timeout rules
* @return bool success
*/
public function handler_gc($ignored_maxlifetime) {
// This should do something only if cron is not running properly...
if (!$stalelifetime = ini_get('session.gc_maxlifetime')) {
return true;
}
$params = array('purgebefore' => (time() - $stalelifetime));
$this->database->delete_records_select('sessions', 'userid = 0 AND timemodified < :purgebefore', $params);
return true;
}
/**
* This makes sure the session is written to disk at the end of request.
*/
public function handler_shutdown() {
$this->database->dispose();
}
}

View File

@ -0,0 +1,34 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Session exception.
*
* @package core
* @copyright 2013 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\session;
defined('MOODLE_INTERNAL') || die();
/**
* Session related exception class.
* @package core
*/
class exception extends \moodle_exception {
}

View File

@ -0,0 +1,120 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* File based session handler.
*
* @package core
* @copyright 2013 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\session;
defined('MOODLE_INTERNAL') || die();
/**
* File based session handler.
*
* @package core
* @copyright 2013 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class file extends handler {
/** @var string session dir */
protected $sessiondir;
/**
* Create new instance of handler.
*/
public function __construct() {
global $CFG;
if (!empty($CFG->session_file_save_path)) {
$this->sessiondir = $CFG->session_file_save_path;
} else {
$this->sessiondir = "$CFG->dataroot/sessions";
}
}
/**
* Init session handler.
*/
public function init() {
if (preg_match('/^[0-9]+;/', $this->sessiondir)) {
throw new exception('sessionhandlerproblem', 'error', '', null, 'Multilevel session directories are not supported');
}
// Make sure session directory exists and is writable.
make_writable_directory($this->sessiondir, false);
if (!is_writable($this->sessiondir)) {
throw new exception('sessionhandlerproblem', 'error', '', null, 'Session directory is not writable');
}
// Need to disable debugging since disk_free_space()
// will fail on very large partitions (see MDL-19222).
$freespace = @disk_free_space($this->sessiondir);
if (!($freespace > 2048) and $freespace !== false) {
throw new exception('sessiondiskfull', 'error');
}
// NOTE: we cannot set any lock acquiring timeout here - bad luck.
ini_set('session.save_handler', 'files');
ini_set('session.save_path', $this->sessiondir);
}
/**
* Check for existing session with id $sid.
*
* Note: this verifies the storage backend only, not the actual session records.
*
* @param string $sid
* @return bool true if session found.
*/
public function session_exists($sid) {
$sid = clean_param($sid, PARAM_FILE);
if (!$sid) {
return false;
}
$sessionfile = "$this->sessiondir/sess_$sid";
return file_exists($sessionfile);
}
/**
* Kill all active sessions, the core sessions table is
* purged afterwards.
*/
public function kill_all_sessions() {
if (is_dir($this->sessiondir)) {
foreach (glob("$this->sessiondir/sess_*") as $filename) {
@unlink($filename);
}
}
}
/**
* Kill one session, the session record is removed afterwards.
* @param string $sid
*/
public function kill_session($sid) {
$sid = clean_param($sid, PARAM_FILE);
if (!$sid) {
return;
}
$sessionfile = "$this->sessiondir/sess_$sid";
if (file_exists($sessionfile)) {
@unlink($sessionfile);
}
}
}

View File

@ -0,0 +1,63 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Session handler base.
*
* @package core
* @copyright 2013 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\session;
defined('MOODLE_INTERNAL') || die();
/**
* Session handler base.
*
* @package core
* @copyright 2013 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class handler {
/**
* Init session handler.
*/
public abstract function init();
/**
* Check for existing session with id $sid.
*
* Note: this verifies the storage backend only, not the actual session records.
*
* @param string $sid
* @return bool true if session found.
*/
public abstract function session_exists($sid);
/**
* Kill all active sessions, the core sessions table is
* purged afterwards.
*/
public abstract function kill_all_sessions();
/**
* Kill one session, the session record is removed afterwards.
* @param string $sid
*/
public abstract function kill_session($sid);
}

View File

@ -0,0 +1,754 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Session manager class.
*
* @package core
* @copyright 2013 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\session;
defined('MOODLE_INTERNAL') || die();
/**
* Session manager, this is the public Moodle API for sessions.
*
* Following PHP functions MUST NOT be used directly:
* - session_start() - not necessary, lib/setup.php starts session automatically,
* use define('NO_MOODLE_COOKIE', true) if session not necessary.
* - session_write_close() - use \core\session\manager::write_close() instead.
* - session_destroy() - use require_logout() instead.
*
* @package core
* @copyright 2013 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class manager {
/** @var handler $handler active session handler instance */
protected static $handler;
/** @var bool $sessionactive Is the session active? */
protected static $sessionactive = null;
/**
* Start user session.
*
* Note: This is intended to be called only from lib/setup.php!
*/
public static function start() {
global $CFG, $DB;
if (isset(self::$sessionactive)) {
debugging('Session was already started!', DEBUG_DEVELOPER);
return;
}
self::load_handler();
// Init the session handler only if everything initialised properly in lib/setup.php file
// and the session is actually required.
if (empty($DB) or empty($CFG->version) or !defined('NO_MOODLE_COOKIES') or NO_MOODLE_COOKIES or CLI_SCRIPT) {
self::$sessionactive = false;
self::init_empty_session();
return;
}
try {
self::$handler->init();
self::prepare_cookies();
$newsid = empty($_COOKIE[session_name()]);
session_start();
self::initialise_user_session($newsid);
self::check_security();
} catch (\Exception $ex) {
@session_write_close();
self::init_empty_session();
self::$sessionactive = false;
throw $ex;
}
self::$sessionactive = true;
}
/**
* Returns current page performance info.
*
* @return array perf info
*/
public static function get_performance_info() {
if (!session_id()) {
return array();
}
self::load_handler();
$size = display_size(strlen(session_encode()));
$handler = get_class(self::$handler);
$info = array();
$info['size'] = $size;
$info['html'] = "<span class=\"sessionsize\">Session ($handler): $size</span> ";
$info['txt'] = "Session ($handler): $size ";
return $info;
}
/**
* Create handler instance.
*/
protected static function load_handler() {
global $CFG, $DB;
if (self::$handler) {
return;
}
// Find out which handler to use.
if (PHPUNIT_TEST) {
$class = '\core\session\file';
} else if (!empty($CFG->session_handler_class)) {
$class = $CFG->session_handler_class;
} else if (!empty($CFG->dbsessions) and $DB->session_lock_supported()) {
$class = '\core\session\database';
} else {
$class = '\core\session\file';
}
self::$handler = new $class();
}
/**
* Empty current session, fill it with not-logged-in user info.
*/
protected static function init_empty_session() {
global $CFG;
// Session not used at all.
$_SESSION = array();
$_SESSION['SESSION'] = new \stdClass();
$_SESSION['USER'] = new \stdClass();
$_SESSION['USER']->id = 0;
if (isset($CFG->mnet_localhost_id)) {
$_SESSION['USER']->mnethostid = $CFG->mnet_localhost_id;
} else {
// Not installed yet, the future host id will be most probably 1.
$_SESSION['USER']->mnethostid = 1;
}
if (PHPUNIT_TEST) {
// Phpunit tests use reversed reference.
global $USER, $SESSION;
$USER = $_SESSION['USER'];
$SESSION = $_SESSION['SESSION'];
$_SESSION['USER'] =& $USER;
$_SESSION['SESSION'] =& $SESSION;
}
}
/**
* Make sure all cookie and session related stuff is configured properly before session start.
*/
protected static function prepare_cookies() {
global $CFG;
if (!isset($CFG->cookiesecure) or (strpos($CFG->wwwroot, 'https://') !== 0 and empty($CFG->sslproxy))) {
$CFG->cookiesecure = 0;
}
if (!isset($CFG->cookiehttponly)) {
$CFG->cookiehttponly = 0;
}
// Set sessioncookie variable if it isn't already.
if (!isset($CFG->sessioncookie)) {
$CFG->sessioncookie = '';
}
$sessionname = 'MoodleSession'.$CFG->sessioncookie;
// Make sure cookie domain makes sense for this wwwroot.
if (!isset($CFG->sessioncookiedomain)) {
$CFG->sessioncookiedomain = '';
} else if ($CFG->sessioncookiedomain !== '') {
$host = parse_url($CFG->wwwroot, PHP_URL_HOST);
if ($CFG->sessioncookiedomain !== $host) {
if (substr($CFG->sessioncookiedomain, 0, 1) === '.') {
if (!preg_match('|^.*'.preg_quote($CFG->sessioncookiedomain, '|').'$|', $host)) {
// Invalid domain - it must be end part of host.
$CFG->sessioncookiedomain = '';
}
} else {
if (!preg_match('|^.*\.'.preg_quote($CFG->sessioncookiedomain, '|').'$|', $host)) {
// Invalid domain - it must be end part of host.
$CFG->sessioncookiedomain = '';
}
}
}
}
// Make sure the cookiepath is valid for this wwwroot or autodetect if not specified.
if (!isset($CFG->sessioncookiepath)) {
$CFG->sessioncookiepath = '';
}
if ($CFG->sessioncookiepath !== '/') {
$path = parse_url($CFG->wwwroot, PHP_URL_PATH).'/';
if ($CFG->sessioncookiepath === '') {
$CFG->sessioncookiepath = $path;
} else {
if (strpos($path, $CFG->sessioncookiepath) !== 0 or substr($CFG->sessioncookiepath, -1) !== '/') {
$CFG->sessioncookiepath = $path;
}
}
}
// Discard session ID from POST, GET and globals to tighten security,
// this is session fixation prevention.
unset($GLOBALS[$sessionname]);
unset($_GET[$sessionname]);
unset($_POST[$sessionname]);
unset($_REQUEST[$sessionname]);
// Compatibility hack for non-browser access to our web interface.
if (!empty($_COOKIE[$sessionname]) && $_COOKIE[$sessionname] == "deleted") {
unset($_COOKIE[$sessionname]);
}
// Set configuration.
session_name($sessionname);
session_set_cookie_params(0, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
ini_set('session.use_trans_sid', '0');
ini_set('session.use_only_cookies', '1');
ini_set('session.hash_function', '0'); // For now MD5 - we do not have room for sha-1 in sessions table.
ini_set('session.use_strict_mode', '0'); // We have custom protection in session init.
ini_set('session.serialize_handler', 'php'); // We can move to 'php_serialize' after we require PHP 5.5.4 form Moodle.
// Moodle does normal session timeouts, this is for leftovers only.
ini_set('session.gc_probability', 1);
ini_set('session.gc_divisor', 1000);
ini_set('session.gc_maxlifetime', 60*60*24*4);
}
/**
* Initialise $USER and $SESSION objects, handles google access
* and sets up not-logged-in user properly.
*
* @param bool $newsid is this a new session in first http request?
*/
protected static function initialise_user_session($newsid) {
global $CFG, $DB;
$sid = session_id();
if (!$sid) {
// No session, very weird.
error_log('Missing session ID, session not started!');
self::init_empty_session();
return;
}
if (!$record = $DB->get_record('sessions', array('sid'=>$sid), 'id, sid, state, userid, lastip, timecreated, timemodified')) {
if (!$newsid) {
if (!empty($_SESSION['USER']->id)) {
// This should not happen, just log it, we MUST not produce any output here!
error_log("Cannot find session record $sid for user ".$_SESSION['USER']->id.", creating new session.");
}
}
session_regenerate_id(true);
$_SESSION = array();
}
unset($sid);
if (isset($_SESSION['USER']->id)) {
if (!empty($_SESSION['USER']->realuser)) {
$userid = $_SESSION['USER']->realuser;
} else {
$userid = $_SESSION['USER']->id;
}
// Verify timeout first.
$maxlifetime = $CFG->sessiontimeout;
$timeout = false;
if (isguestuser($userid) or empty($userid)) {
// Ignore guest and not-logged in timeouts, there is very little risk here.
$timeout = false;
} else if ($record->timemodified < time() - $maxlifetime) {
$timeout = true;
$authsequence = get_enabled_auth_plugins(); // Auths, in sequence.
foreach ($authsequence as $authname) {
$authplugin = get_auth_plugin($authname);
if ($authplugin->ignore_timeout_hook($_SESSION['USER'], $record->sid, $record->timecreated, $record->timemodified)) {
$timeout = false;
break;
}
}
}
if ($timeout) {
session_regenerate_id(true);
$_SESSION = array();
$DB->delete_records('sessions', array('id'=>$record->id));
} else {
// Update session tracking record.
$update = new \stdClass();
$updated = false;
if ($record->userid != $userid) {
$update->userid = $record->userid = $userid;
$updated = true;
}
$ip = getremoteaddr();
if ($record->lastip != $ip) {
$update->lastip = $record->lastip = $ip;
$updated = true;
}
$updatefreq = empty($CFG->session_update_timemodified_frequency) ? 20 : $CFG->session_update_timemodified_frequency;
if ($record->timemodified == $record->timecreated) {
// Always do first update of existing record.
$update->timemodified = $record->timemodified = time();
$updated = true;
} else if ($record->timemodified < time() - $updatefreq) {
// Update the session modified flag only once every 20 seconds.
$update->timemodified = $record->timemodified = time();
$updated = true;
}
if ($updated) {
$update->id = $record->id;
$DB->update_record('sessions', $update);
}
return;
}
} else {
if ($record) {
// This happens when people switch session handlers...
session_regenerate_id(true);
$_SESSION = array();
$DB->delete_records('sessions', array('id'=>$record->id));
}
}
unset($record);
$timedout = false;
if (!isset($_SESSION['SESSION'])) {
$_SESSION['SESSION'] = new \stdClass();
if (!$newsid) {
$timedout = true;
}
}
$user = null;
if (!empty($CFG->opentogoogle)) {
if (is_web_crawler()) {
$user = guest_user();
}
if (!empty($CFG->guestloginbutton) and !$user and !empty($_SERVER['HTTP_REFERER'])) {
// Automatically log in users coming from search engine results.
if (strpos($_SERVER['HTTP_REFERER'], 'google') !== false ) {
$user = guest_user();
} else if (strpos($_SERVER['HTTP_REFERER'], 'altavista') !== false ) {
$user = guest_user();
}
}
}
// Setup $USER and insert the session tracking record.
if ($user) {
self::set_user($user);
self::add_session_record($user->id);
} else {
self::init_empty_session();
self::add_session_record(0);
}
if ($timedout) {
$_SESSION['SESSION']->has_timed_out = true;
}
}
/**
* Insert new empty session record.
* @param int $userid
* @return \stdClass the new record
*/
protected static function add_session_record($userid) {
global $DB;
$record = new \stdClass();
$record->state = 0;
$record->sid = session_id();
$record->sessdata = null;
$record->userid = $userid;
$record->timecreated = $record->timemodified = time();
$record->firstip = $record->lastip = getremoteaddr();
$record->id = $DB->insert_record('sessions', $record);
return $record;
}
/**
* Do various session security checks.
*/
protected static function check_security() {
global $CFG;
if (!empty($_SESSION['USER']->id) and !empty($CFG->tracksessionip)) {
// Make sure current IP matches the one for this session.
$remoteaddr = getremoteaddr();
if (empty($_SESSION['USER']->sessionip)) {
$_SESSION['USER']->sessionip = $remoteaddr;
}
if ($_SESSION['USER']->sessionip != $remoteaddr) {
// This is a security feature - terminate the session in case of any doubt.
self::terminate_current();
throw new exception('sessionipnomatch2', 'error');
}
}
}
/**
* Login user, to be called from complete_user_login() only.
* @param \stdClass $user
*/
public static function login_user(\stdClass $user) {
global $DB;
// Regenerate session id and delete old session,
// this helps prevent session fixation attacks from the same domain.
$sid = session_id();
session_regenerate_id(true);
$DB->delete_records('sessions', array('sid'=>$sid));
self::add_session_record($user->id);
// Let enrol plugins deal with new enrolments if necessary.
enrol_check_plugins($user);
// Setup $USER object.
self::set_user($user);
}
/**
* Terminate current user session.
* @return void
*/
public static function terminate_current() {
global $DB;
if (!self::$sessionactive) {
self::init_empty_session();
self::$sessionactive = false;
return;
}
try {
$DB->delete_records('external_tokens', array('sid'=>session_id(), 'tokentype'=>EXTERNAL_TOKEN_EMBEDDED));
} catch (\Exception $ignored) {
// Probably install/upgrade - ignore this problem.
}
// Initialize variable to pass-by-reference to headers_sent(&$file, &$line).
$file = null;
$line = null;
if (headers_sent($file, $line)) {
error_log('Cannot terminate session properly - headers were already sent in file: '.$file.' on line '.$line);
}
// Write new empty session and make sure the old one is deleted.
$sid = session_id();
session_regenerate_id(true);
$DB->delete_records('sessions', array('sid'=>$sid));
self::init_empty_session();
self::add_session_record($_SESSION['USER']->id);
session_write_close();
self::$sessionactive = false;
}
/**
* No more changes in session expected.
* Unblocks the sessions, other scripts may start executing in parallel.
*/
public static function write_close() {
if (self::$sessionactive) {
session_write_close();
} else {
if (session_id()) {
@session_write_close();
}
}
self::$sessionactive = false;
}
/**
* Does the PHP session with given id exist?
*
* Note: this does not actually verify the presence of sessions record.
*
* @param string $sid
* @return bool
*/
public static function session_exists($sid) {
self::load_handler();
return self::$handler->session_exists($sid);
}
/**
* Fake last access for given session, this prevents session timeout.
* @param string $sid
*/
public static function touch_session($sid) {
global $DB;
// Timeouts depend on core sessions table only, no need to update anything in external stores.
$sql = "UPDATE {sessions} SET timemodified = :now WHERE sid = :sid";
$DB->execute($sql, array('now'=>time(), 'sid'=>$sid));
}
/**
* Terminate all sessions unconditionally.
*/
public static function kill_all_sessions() {
global $DB;
self::terminate_current();
self::load_handler();
self::$handler->kill_all_sessions();
try {
$DB->delete_records('sessions');
} catch (\dml_exception $ignored) {
// Do not show any warnings - might be during upgrade/installation.
}
}
/**
* Terminate give session unconditionally.
* @param string $sid
*/
public static function kill_session($sid) {
global $DB;
self::load_handler();
if ($sid === session_id()) {
self::write_close();
}
self::$handler->kill_session($sid);
$DB->delete_records('sessions', array('sid'=>$sid));
}
/**
* Terminate all sessions of given user unconditionally.
* @param int $userid
*/
public static function kill_user_sessions($userid) {
global $DB;
$sessions = $DB->get_records('sessions', array('userid'=>$userid), 'id DESC', 'id, sid');
foreach ($sessions as $session) {
self::kill_session($session->sid);
}
}
/**
* Set current user.
*
* @param \stdClass $user record
*/
public static function set_user(\stdClass $user) {
$_SESSION['USER'] = $user;
unset($_SESSION['USER']->description); // Conserve memory.
unset($_SESSION['USER']->password); // Improve security.
if (isset($_SESSION['USER']->lang)) {
// Make sure it is a valid lang pack name.
$_SESSION['USER']->lang = clean_param($_SESSION['USER']->lang, PARAM_LANG);
}
sesskey(); // Init session key.
if (PHPUNIT_TEST) {
// Phpunit tests use reversed reference.
global $USER;
$USER = $_SESSION['USER'];
$_SESSION['USER'] =& $USER;
}
}
/**
* Periodic timed-out session cleanup.
*/
public static function gc() {
global $CFG, $DB;
// This may take a long time...
set_time_limit(0);
$maxlifetime = $CFG->sessiontimeout;
try {
// Kill all sessions of deleted and suspended users without any hesitation.
$rs = $DB->get_recordset_select('sessions', "userid IN (SELECT id FROM {user} WHERE deleted <> 0 OR suspended <> 0)", array(), 'id DESC', 'id, sid');
foreach ($rs as $session) {
self::kill_session($session->sid);
}
$rs->close();
// Kill sessions of users with disabled plugins.
$auth_sequence = get_enabled_auth_plugins(true);
$auth_sequence = array_flip($auth_sequence);
unset($auth_sequence['nologin']); // No login means user cannot login.
$auth_sequence = array_flip($auth_sequence);
list($notplugins, $params) = $DB->get_in_or_equal($auth_sequence, SQL_PARAMS_QM, '', false);
$rs = $DB->get_recordset_select('sessions', "userid IN (SELECT id FROM {user} WHERE auth $notplugins)", $params, 'id DESC', 'id, sid');
foreach ($rs as $session) {
self::kill_session($session->sid);
}
$rs->close();
// Now get a list of time-out candidates - real users only.
$sql = "SELECT u.*, s.sid, s.timecreated AS s_timecreated, s.timemodified AS s_timemodified
FROM {user} u
JOIN {sessions} s ON s.userid = u.id
WHERE s.timemodified < :purgebefore AND u.id <> :guestid";
$params = array('purgebefore' => (time() - $maxlifetime), 'guestid'=>$CFG->siteguest);
$authplugins = array();
foreach ($auth_sequence as $authname) {
$authplugins[$authname] = get_auth_plugin($authname);
}
$rs = $DB->get_recordset_sql($sql, $params);
foreach ($rs as $user) {
foreach ($authplugins as $authplugin) {
/** @var \auth_plugin_base $authplugin*/
if ($authplugin->ignore_timeout_hook($user, $user->sid, $user->s_timecreated, $user->s_timemodified)) {
continue;
}
}
self::kill_session($user->sid);
}
$rs->close();
// Delete expired sessions for guest user account, give them larger timeout, there is no security risk here.
$params = array('purgebefore' => (time() - ($maxlifetime * 5)), 'guestid'=>$CFG->siteguest);
$rs = $DB->get_recordset_select('sessions', 'userid = :guestid AND timemodified < :purgebefore', $params, 'id DESC', 'id, sid');
foreach ($rs as $session) {
self::kill_session($session->sid);
}
$rs->close();
// Delete expired sessions for userid = 0 (not logged in), better kill them asap to release memory.
$params = array('purgebefore' => (time() - $maxlifetime));
$rs = $DB->get_recordset_select('sessions', 'userid = 0 AND timemodified < :purgebefore', $params, 'id DESC', 'id, sid');
foreach ($rs as $session) {
self::kill_session($session->sid);
}
$rs->close();
// Cleanup letfovers from the first browser access because it may set multiple cookies and then use only one.
$params = array('purgebefore' => (time() - 60*3));
$rs = $DB->get_recordset_select('sessions', 'userid = 0 AND timemodified = timecreated AND timemodified < :purgebefore', $params, 'id ASC', 'id, sid');
foreach ($rs as $session) {
self::kill_session($session->sid);
}
$rs->close();
} catch (\Exception $ex) {
debugging('Error gc-ing sessions: '.$ex->getMessage(), DEBUG_NORMAL, $ex->getTrace());
}
}
/**
* Is current $USER logged-in-as somebody else?
* @return bool
*/
public static function is_loggedinas() {
return !empty($_SESSION['USER']->realuser);
}
/**
* Returns the $USER object ignoring current login-as session
* @return \stdClass user object
*/
public static function get_realuser() {
if (self::is_loggedinas()) {
return $_SESSION['REALUSER'];
} else {
return $_SESSION['USER'];
}
}
/**
* Login as another user - no security checks here.
* @param int $userid
* @param \context $context
* @return void
*/
public static function loginas($userid, \context $context) {
global $USER;
if (self::is_loggedinas()) {
return;
}
// Switch to fresh new $SESSION.
$_SESSION['REALSESSION'] = $_SESSION['SESSION'];
$_SESSION['SESSION'] = new \stdClass();
// Create the new $USER object with all details and reload needed capabilities.
$_SESSION['REALUSER'] = $_SESSION['USER'];
$user = get_complete_user_data('id', $userid);
$user->realuser = $_SESSION['REALUSER']->id;
$user->loginascontext = $context;
// Let enrol plugins deal with new enrolments if necessary.
enrol_check_plugins($user);
// Create event before $USER is updated.
$event = \core\event\user_loggedinas::create(
array(
'objectid' => $USER->id,
'context' => $context,
'relateduserid' => $userid,
'other' => array(
'originalusername' => fullname($USER, true),
'loggedinasusername' => fullname($user, true)
)
)
);
// Set up global $USER.
\core\session\manager::set_user($user);
$event->trigger();
}
}

View File

@ -0,0 +1,187 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Memcached based session handler.
*
* @package core
* @copyright 2013 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\session;
defined('MOODLE_INTERNAL') || die();
/**
* Memcached based session handler.
*
* @package core
* @copyright 2013 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class memcached extends handler {
/** @var string $savepath save_path string */
protected $savepath;
/** @var array $servers list of servers parsed from save_path */
protected $servers;
/** @var string $prefix session key prefix */
protected $prefix;
/**
* Create new instance of handler.
*/
public function __construct() {
global $CFG;
if (empty($CFG->session_memcached_save_path)) {
$this->savepath = '';
} else {
$this->savepath = $CFG->session_memcached_save_path;
}
if (empty($this->savepath)) {
$this->servers = array();
} else {
$this->servers = self::connection_string_to_servers($this->savepath);
}
if (empty($CFG->session_memcached_prefix)) {
$this->prefix = ini_get('memcached.sess_prefix');
} else {
$this->prefix = $CFG->session_memcached_prefix;
}
}
/**
* Init session handler.
*/
public function init() {
if (!extension_loaded('memcached')) {
throw new exception('sessionhandlerproblem', 'error', '', null, 'memcached extension is not loaded');
}
$version = phpversion('memcached');
if (!$version or version_compare($version, '2.0') < 0) {
throw new exception('sessionhandlerproblem', 'error', '', null, 'memcached extension version must be at least 2.0');
}
if (empty($this->savepath)) {
throw new exception('sessionhandlerproblem', 'error', '', null, '$CFG->session_memcached_save_path must be specified in config.php');
}
// NOTE: we cannot set any lock acquiring timeout here - bad luck.
ini_set('session.save_handler', 'memcached');
ini_set('session.save_path', $this->savepath);
ini_set('memcached.sess_prefix', $this->prefix);
ini_set('memcached.sess_locking', '1'); // Locking is required!
}
/**
* Check for existing session with id $sid.
*
* Note: this verifies the storage backend only, not the actual session records.
*
* @param string $sid
* @return bool true if session found.
*/
public function session_exists($sid) {
if (!$this->servers) {
return false;
}
$memcached = new \Memcached();
$memcached->addServers($this->servers);
$value = $memcached->get($this->prefix.$sid);
$memcached->quit();
return ($value !== false);
}
/**
* Kill all active sessions, the core sessions table is
* purged afterwards.
*/
public function kill_all_sessions() {
global $DB;
if (!$this->servers) {
return;
}
$memcached = new \Memcached();
$memcached->addServers($this->servers);
// Note: this can be significantly improved by fetching keys from memcached,
// but we need to make sure we are not deleting somebody else's sessions.
$rs = $DB->get_recordset('sessions', array(), 'id DESC', 'id, sid');
foreach ($rs as $record) {
$memcached->delete($this->prefix.$record->sid);
}
$rs->close();
$memcached->quit();
}
/**
* Kill one session, the session record is removed afterwards.
* @param string $sid
*/
public function kill_session($sid) {
if (!$this->servers) {
return;
}
$memcached = new \Memcached();
$memcached->addServers($this->servers);
$memcached->delete($this->prefix.$sid);
$memcached->quit();
}
/**
* Convert a connection string to an array of servers
*
* EG: Converts: "abc:123, xyz:789" to
*
* array(
* array('abc', '123'),
* array('xyz', '789'),
* )
*
* @copyright 2013 Moodlerooms Inc. (http://www.moodlerooms.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Mark Nielsen
*
* @param string $str save_path value containing memcached connection string
* @return array
*/
protected static function connection_string_to_servers($str) {
$servers = array();
$parts = explode(',', $str);
foreach ($parts as $part) {
$part = trim($part);
$pos = strrpos($part, ':');
if ($pos !== false) {
$host = substr($part, 0, $pos);
$port = substr($part, ($pos + 1));
} else {
$host = $part;
$port = 11211;
}
$servers[] = array($host, $port);
}
return $servers;
}
}

View File

@ -190,9 +190,10 @@ function cron_run() {
mtrace(' Created missing context instances');
// Session gc
session_gc();
mtrace("Cleaned up stale user sessions");
// Session gc.
mtrace("Running session gc tasks...");
\core\session\manager::gc();
mtrace("...finished stale session cleanup");
// Run the auth cron, if any before enrolments

View File

@ -1645,7 +1645,7 @@ function add_to_log($courseid, $module, $action, $url='', $info='', $cm=0, $user
if ($user) {
$userid = $user;
} else {
if (session_is_loggedinas()) { // Don't log
if (\core\session\manager::is_loggedinas()) { // Don't log
return;
}
$userid = empty($USER->id) ? '0' : $USER->id;
@ -1727,7 +1727,7 @@ function add_to_log($courseid, $module, $action, $url='', $info='', $cm=0, $user
function user_accesstime_log($courseid=0) {
global $USER, $CFG, $DB;
if (!isloggedin() or session_is_loggedinas()) {
if (!isloggedin() or \core\session\manager::is_loggedinas()) {
// no access tracking
return;
}

View File

@ -30,6 +30,140 @@
defined('MOODLE_INTERNAL') || die();
/**
* Factory method that was returning moodle_session object.
*
* @deprecated since 2.6
* @return \core\session\manager
*/
function session_get_instance() {
// Note: the new session manager includes all methods from the original session class.
static $deprecatedinstance = null;
debugging('session_get_instance() is deprecated, use \core\session\manager instead', DEBUG_DEVELOPER);
if (!$deprecatedinstance) {
$deprecatedinstance = new \core\session\manager();
}
return $deprecatedinstance;
}
/**
* Returns true if legacy session used.
*
* @deprecated since 2.6
* @return bool
*/
function session_is_legacy() {
debugging('session_is_legacy() is deprecated, do not use any more', DEBUG_DEVELOPER);
return false;
}
/**
* Terminates all sessions, auth hooks are not executed.
* Useful in upgrade scripts.
*
* @deprecated since 2.6
*/
function session_kill_all() {
debugging('session_kill_all() is deprecated, use \core\session\manager::kill_all_sessions() instead', DEBUG_DEVELOPER);
\core\session\manager::kill_all_sessions();
}
/**
* Mark session as accessed, prevents timeouts.
*
* @deprecated since 2.6
* @param string $sid
*/
function session_touch($sid) {
debugging('session_touch() is deprecated, use \core\session\manager::touch_session() instead', DEBUG_DEVELOPER);
\core\session\manager::touch_session($sid);
}
/**
* Terminates one sessions, auth hooks are not executed.
*
* @deprecated since 2.6
* @param string $sid session id
*/
function session_kill($sid) {
debugging('session_kill() is deprecated, use \core\session\manager::kill_session() instead', DEBUG_DEVELOPER);
\core\session\manager::kill_session($sid);
}
/**
* Terminates all sessions of one user, auth hooks are not executed.
* NOTE: This can not work for file based sessions!
*
* @deprecated since 2.6
* @param int $userid user id
*/
function session_kill_user($userid) {
debugging('session_kill_user() is deprecated, use \core\session\manager::kill_user_sessions() instead', DEBUG_DEVELOPER);
\core\session\manager::kill_user_sessions($userid);
}
/**
* Session garbage collection
* - verify timeout for all users
* - kill sessions of all deleted users
* - kill sessions of users with disabled plugins or 'nologin' plugin
*
* @deprecated since 2.6
*/
function session_gc() {
debugging('session_gc() is deprecated, use \core\session\manager::gc() instead', DEBUG_DEVELOPER);
\core\session\manager::gc();
}
/**
* Setup $USER object - called during login, loginas, etc.
*
* Call sync_user_enrolments() manually after log-in, or log-in-as.
*
* @deprecated since 2.6
* @param stdClass $user full user record object
* @return void
*/
function session_set_user($user) {
debugging('session_set_user() is deprecated, use \core\session\manager::set_user() instead', DEBUG_DEVELOPER);
\core\session\manager::set_user($user);
}
/**
* Is current $USER logged-in-as somebody else?
* @deprecated since 2.6
* @return bool
*/
function session_is_loggedinas() {
debugging('session_is_loggedinas() is deprecated, use \core\session\manager::is_loggedinas() instead', DEBUG_DEVELOPER);
return \core\session\manager::is_loggedinas();
}
/**
* Returns the $USER object ignoring current login-as session
* @deprecated since 2.6
* @return stdClass user object
*/
function session_get_realuser() {
debugging('session_get_realuser() is deprecated, use \core\session\manager::get_realuser() instead', DEBUG_DEVELOPER);
return \core\session\manager::get_realuser();
}
/**
* Login as another user - no security checks here.
* @deprecated since 2.6
* @param int $userid
* @param stdClass $context
* @return void
*/
function session_loginas($userid, $context) {
debugging('session_loginas() is deprecated, use \core\session\manager::loginas() instead', DEBUG_DEVELOPER);
\core\session\manager::loginas($userid, $context);
}
/**
* Minify JavaScript files.
*

View File

@ -119,6 +119,12 @@ abstract class moodle_database {
/** @var string MD5 of settings used for connection. Used by MUC as an identifier. */
private $settingshash;
/** @var cache_application for column info */
protected $metacache;
/** @var bool flag marking database instance as disposed */
protected $disposed;
/**
* @var int internal temporary variable used to fix params. Its used by {@link _fix_sql_params_dollar_callback()}.
*/
@ -337,6 +343,10 @@ abstract class moodle_database {
* @return void
*/
public function dispose() {
if ($this->disposed) {
return;
}
$this->disposed = true;
if ($this->transactions) {
// this should not happen, it usually indicates wrong catching of exceptions,
// because all transactions should be finished manually or in default exception handler.
@ -354,9 +364,7 @@ abstract class moodle_database {
}
// Always terminate sessions here to make it consistent,
// this is needed because we need to save session to db before closing it.
if (function_exists('session_get_instance')) {
session_get_instance()->write_close();
}
\core\session\manager::write_close();
$this->used_for_db_sessions = false;
if ($this->temptables) {
@ -368,6 +376,10 @@ abstract class moodle_database {
$this->database_manager = null;
}
$this->tables = null;
// We do not need the MUC cache any more,
// if we did not keep it as property it might be already gone before we saved the session.
$this->metacache = null;
}
/**
@ -955,12 +967,27 @@ abstract class moodle_database {
* @return void
*/
public function reset_caches() {
$this->tables = null;
$this->tables = null;
$this->metacache = null;
// Purge MUC as well
$identifiers = array('dbfamily' => $this->get_dbfamily(), 'settings' => $this->get_settings_hash());
cache_helper::purge_by_definition('core', 'databasemeta', $identifiers);
}
/**
* Call before using $this->metacache.
*
* Note: this is necessary because we want to write to database in shutdown handler
* and it needs to use the caches, but MUC would be already disposed.
*/
protected function init_caches() {
if ($this->metacache) {
return;
}
$properties = array('dbfamily' => $this->get_dbfamily(), 'settings' => $this->get_settings_hash());
$this->metacache = cache::make('core', 'databasemeta', $properties);
}
/**
* Returns the sql generator used for db manipulation.
* Used mostly in upgrade.php scripts.

View File

@ -398,9 +398,8 @@ class mssql_native_moodle_database extends moodle_database {
public function get_columns($table, $usecache=true) {
if ($usecache) {
$properties = array('dbfamily' => $this->get_dbfamily(), 'settings' => $this->get_settings_hash());
$cache = cache::make('core', 'databasemeta', $properties);
if ($data = $cache->get($table)) {
$this->init_caches();
if ($data = $this->metacache->get($table)) {
return $data;
}
}
@ -496,7 +495,7 @@ class mssql_native_moodle_database extends moodle_database {
$this->free_result($result);
if ($usecache) {
$result = $cache->set($table, $structure);
$result = $this->metacache->set($table, $structure);
}
return $structure;

View File

@ -511,9 +511,8 @@ class mysqli_native_moodle_database extends moodle_database {
public function get_columns($table, $usecache=true) {
if ($usecache) {
$properties = array('dbfamily' => $this->get_dbfamily(), 'settings' => $this->get_settings_hash());
$cache = cache::make('core', 'databasemeta', $properties);
if ($data = $cache->get($table)) {
$this->init_caches();
if ($data = $this->metacache->get($table)) {
return $data;
}
}
@ -619,7 +618,7 @@ class mysqli_native_moodle_database extends moodle_database {
}
if ($usecache) {
$result = $cache->set($table, $structure);
$result = $this->metacache->set($table, $structure);
}
return $structure;

View File

@ -470,9 +470,8 @@ class oci_native_moodle_database extends moodle_database {
public function get_columns($table, $usecache=true) {
if ($usecache) {
$properties = array('dbfamily' => $this->get_dbfamily(), 'settings' => $this->get_settings_hash());
$cache = cache::make('core', 'databasemeta', $properties);
if ($data = $cache->get($table)) {
$this->init_caches();
if ($data = $this->metacache->get($table)) {
return $data;
}
}
@ -664,7 +663,7 @@ class oci_native_moodle_database extends moodle_database {
}
if ($usecache) {
$result = $cache->set($table, $structure);
$result = $this->metacache->set($table, $structure);
}
return $structure;
@ -1722,6 +1721,10 @@ class oci_native_moodle_database extends moodle_database {
}
}
public function session_lock_supported() {
return true;
}
/**
* Obtain session lock
* @param int $rowid id of the row with session record

View File

@ -385,9 +385,8 @@ class pgsql_native_moodle_database extends moodle_database {
*/
public function get_columns($table, $usecache=true) {
if ($usecache) {
$properties = array('dbfamily' => $this->get_dbfamily(), 'settings' => $this->get_settings_hash());
$cache = cache::make('core', 'databasemeta', $properties);
if ($data = $cache->get($table)) {
$this->init_caches();
if ($data = $this->metacache->get($table)) {
return $data;
}
}
@ -570,7 +569,7 @@ class pgsql_native_moodle_database extends moodle_database {
pg_free_result($result);
if ($usecache) {
$result = $cache->set($table, $structure);
$result = $this->metacache->set($table, $structure);
}
return $structure;

View File

@ -201,9 +201,8 @@ class sqlite3_pdo_moodle_database extends pdo_moodle_database {
public function get_columns($table, $usecache=true) {
if ($usecache) {
$properties = array('dbfamily' => $this->get_dbfamily(), 'settings' => $this->get_settings_hash());
$cache = cache::make('core', 'databasemeta', $properties);
if ($data = $cache->get($table)) {
$this->init_caches();
if ($data = $this->metacache->get($table)) {
return $data;
}
}
@ -300,7 +299,7 @@ class sqlite3_pdo_moodle_database extends pdo_moodle_database {
}
if ($usecache) {
$result = $cache->set($table, $structure);
$result = $this->metacache->set($table, $structure);
}
return $structure;

View File

@ -462,9 +462,8 @@ class sqlsrv_native_moodle_database extends moodle_database {
*/
public function get_columns($table, $usecache = true) {
if ($usecache) {
$properties = array('dbfamily' => $this->get_dbfamily(), 'settings' => $this->get_settings_hash());
$cache = cache::make('core', 'databasemeta', $properties);
if ($data = $cache->get($table)) {
$this->init_caches();
if ($data = $this->metacache->get($table)) {
return $data;
}
}
@ -560,7 +559,7 @@ class sqlsrv_native_moodle_database extends moodle_database {
$this->free_result($result);
if ($usecache) {
$result = $cache->set($table, $structure);
$result = $this->metacache->set($table, $structure);
}
return $structure;

View File

@ -2171,7 +2171,7 @@ function send_temp_file($path, $filename, $pathisstring=false) {
}
// close session - not needed anymore
session_get_instance()->write_close();
\core\session\manager::write_close();
if (!$pathisstring) {
if (!file_exists($path)) {
@ -2254,7 +2254,7 @@ function send_file($path, $filename, $lifetime = 'default' , $filter=0, $pathiss
}
}
session_get_instance()->write_close(); // unlock session during fileserving
\core\session\manager::write_close(); // Unlock session during file serving.
// Use given MIME type if specified, otherwise guess it using mimeinfo.
// IE, Konqueror and Opera open html file directly in browser from web even when directed to save it to disk :-O
@ -2417,7 +2417,7 @@ function send_stored_file($stored_file, $lifetime=86400 , $filter=0, $forcedownl
ignore_user_abort(true);
}
session_get_instance()->write_close(); // unlock session during fileserving
\core\session\manager::write_close(); // Unlock session during file serving.
// Use given MIME type if specified, otherwise guess it using mimeinfo.
// IE, Konqueror and Opera open html file directly in browser from web even when directed to save it to disk :-O
@ -3918,7 +3918,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) {
send_file_not_found();
}
session_get_instance()->write_close(); // unlock session during fileserving
\core\session\manager::write_close(); // Unlock session during file serving.
send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
} else if ($filearea === 'feedback' and $context->contextlevel == CONTEXT_COURSE) {
@ -3935,7 +3935,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) {
send_file_not_found();
}
session_get_instance()->write_close(); // unlock session during fileserving
\core\session\manager::write_close(); // Unlock session during file serving.
send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
} else {
send_file_not_found();
@ -3956,7 +3956,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) {
send_file_not_found();
}
session_get_instance()->write_close(); // unlock session during fileserving
\core\session\manager::write_close(); // Unlock session during file serving.
send_stored_file($file, 60*60, 0, true, array('preview' => $preview));
} else {
@ -3978,14 +3978,14 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) {
send_file_not_found();
}
session_get_instance()->write_close();
\core\session\manager::write_close();
send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
} else if ($filearea === 'userbadge' and $context->contextlevel == CONTEXT_USER) {
if (!$file = $fs->get_file($context->id, 'badges', 'userbadge', $badge->id, '/', $filename.'.png')) {
send_file_not_found();
}
session_get_instance()->write_close();
\core\session\manager::write_close();
send_stored_file($file, 60*60, 0, true, array('preview' => $preview));
}
// ========================================================================================================================
@ -4012,7 +4012,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) {
send_file_not_found();
}
session_get_instance()->write_close(); // unlock session during fileserving
\core\session\manager::write_close(); // Unlock session during file serving.
send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
} else if ($filearea === 'event_description' and $context->contextlevel == CONTEXT_USER) {
@ -4040,7 +4040,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) {
send_file_not_found();
}
session_get_instance()->write_close(); // unlock session during fileserving
\core\session\manager::write_close(); // Unlock session during file serving.
send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
} else if ($filearea === 'event_description' and $context->contextlevel == CONTEXT_COURSE) {
@ -4087,7 +4087,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) {
send_file_not_found();
}
session_get_instance()->write_close(); // unlock session during fileserving
\core\session\manager::write_close(); // Unlock session during file serving.
send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
} else {
@ -4161,7 +4161,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) {
send_file_not_found();
}
session_get_instance()->write_close(); // unlock session during fileserving
\core\session\manager::write_close(); // Unlock session during file serving.
send_stored_file($file, 0, 0, true, array('preview' => $preview)); // must force download - security!
} else if ($filearea === 'profile' and $context->contextlevel == CONTEXT_USER) {
@ -4208,7 +4208,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) {
send_file_not_found();
}
session_get_instance()->write_close(); // unlock session during fileserving
\core\session\manager::write_close(); // Unlock session during file serving.
send_stored_file($file, 0, 0, true, array('preview' => $preview)); // must force download - security!
} else if ($filearea === 'profile' and $context->contextlevel == CONTEXT_COURSE) {
@ -4246,7 +4246,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) {
send_file_not_found();
}
session_get_instance()->write_close(); // unlock session during fileserving
\core\session\manager::write_close(); // Unlock session during file serving.
send_stored_file($file, 0, 0, true, array('preview' => $preview)); // must force download - security!
} else if ($filearea === 'backup' and $context->contextlevel == CONTEXT_USER) {
@ -4267,7 +4267,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) {
send_file_not_found();
}
session_get_instance()->write_close(); // unlock session during fileserving
\core\session\manager::write_close(); // Unlock session during file serving.
send_stored_file($file, 0, 0, true, array('preview' => $preview)); // must force download - security!
} else {
@ -4292,7 +4292,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) {
send_file_not_found();
}
session_get_instance()->write_close(); // unlock session during fileserving
\core\session\manager::write_close(); // Unlock session during file serving.
send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
} else {
send_file_not_found();
@ -4315,7 +4315,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) {
send_file_not_found();
}
session_get_instance()->write_close(); // unlock session during fileserving
\core\session\manager::write_close(); // Unlock session during file serving.
send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
} else if ($filearea === 'section') {
@ -4337,7 +4337,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) {
send_file_not_found();
}
session_get_instance()->write_close(); // unlock session during fileserving
\core\session\manager::write_close(); // Unlock session during file serving.
send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
} else {
@ -4369,7 +4369,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) {
send_file_not_found();
}
session_get_instance()->write_close(); // unlock session during fileserving
\core\session\manager::write_close(); // Unlock session during file serving.
send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
} else if ($filearea === 'icon') {
@ -4384,7 +4384,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) {
}
}
session_get_instance()->write_close(); // unlock session during fileserving
\core\session\manager::write_close(); // Unlock session during file serving.
send_stored_file($file, 60*60, 0, false, array('preview' => $preview));
} else {
@ -4409,7 +4409,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) {
send_file_not_found();
}
session_get_instance()->write_close(); // unlock session during fileserving
\core\session\manager::write_close(); // Unlock session during file serving.
send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
} else {
@ -4428,7 +4428,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) {
send_file_not_found();
}
session_get_instance()->write_close(); // unlock session during fileserving
\core\session\manager::write_close(); // Unlock session during file serving.
send_stored_file($file, 0, 0, $forcedownload, array('preview' => $preview));
} else if ($filearea === 'section' and $context->contextlevel == CONTEXT_COURSE) {
@ -4443,7 +4443,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) {
send_file_not_found();
}
session_get_instance()->write_close();
\core\session\manager::write_close();
send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
} else if ($filearea === 'activity' and $context->contextlevel == CONTEXT_MODULE) {
@ -4456,7 +4456,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) {
send_file_not_found();
}
session_get_instance()->write_close();
\core\session\manager::write_close();
send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
} else if ($filearea === 'automated' and $context->contextlevel == CONTEXT_COURSE) {
@ -4471,7 +4471,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) {
send_file_not_found();
}
session_get_instance()->write_close(); // unlock session during fileserving
\core\session\manager::write_close(); // Unlock session during file serving.
send_stored_file($file, 0, 0, $forcedownload, array('preview' => $preview));
} else {
@ -4517,7 +4517,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) {
send_file_not_found();
}
session_get_instance()->write_close(); // unlock session during fileserving
\core\session\manager::write_close(); // Unlock session during file serving.
send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
}

View File

@ -495,7 +495,7 @@ function install_cli_database(array $options, $interactive) {
upgrade_finished();
// log in as admin - we need do anything when applying defaults
session_set_user(get_admin());
\core\session\manager::set_user(get_admin());
// apply all default settings, do it twice to fill all defaults - some settings depend on other setting
admin_apply_default_settings(NULL, true);

View File

@ -1582,7 +1582,7 @@ function get_users_from_config($value, $capability, $includeadmins = true) {
* @return void
*/
function purge_all_caches() {
global $CFG;
global $CFG, $DB;
reset_text_filters_cache();
js_reset_all_caches();
@ -1597,6 +1597,7 @@ function purge_all_caches() {
// Ignore exception since this function is also called before upgrade script when field course.cacherev does not exist yet.
}
$DB->reset_caches();
cache_helper::purge_all();
// Purge all other caches: rss, simplepie, etc.
@ -2896,7 +2897,7 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $
}
// Loginas as redirection if needed.
if ($course->id != SITEID and session_is_loggedinas()) {
if ($course->id != SITEID and \core\session\manager::is_loggedinas()) {
if ($USER->loginascontext->contextlevel == CONTEXT_COURSE) {
if ($USER->loginascontext->instanceid != $course->id) {
print_error('loginasonecourse', '', $CFG->wwwroot.'/course/view.php?id='.$USER->loginascontext->instanceid);
@ -2905,7 +2906,7 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $
}
// Check whether the user should be changing password (but only if it is REALLY them).
if (get_user_preferences('auth_forcepasswordchange') && !session_is_loggedinas()) {
if (get_user_preferences('auth_forcepasswordchange') && !\core\session\manager::is_loggedinas()) {
$userauth = get_auth_plugin($USER->auth);
if ($userauth->can_change_password() and !$preventredirect) {
if ($setwantsurltome) {
@ -3013,9 +3014,9 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $
if ($course->id == SITEID) {
// Everybody is enrolled on the frontpage.
} else {
if (session_is_loggedinas()) {
if (\core\session\manager::is_loggedinas()) {
// Make sure the REAL person can access this course first.
$realuser = session_get_realuser();
$realuser = \core\session\manager::get_realuser();
if (!is_enrolled($coursecontext, $realuser->id, '', true) and
!is_viewing($coursecontext, $realuser->id) and !is_siteadmin($realuser->id)) {
if ($preventredirect) {
@ -3147,26 +3148,39 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $
* @category access
*/
function require_logout() {
global $USER;
global $USER, $DB;
if (isloggedin()) {
$authsequence = get_enabled_auth_plugins(); // Auths, in sequence.
foreach ($authsequence as $authname) {
$authplugin = get_auth_plugin($authname);
$authplugin->prelogout_hook();
}
if (!isloggedin()) {
// This should not happen often, no need for hooks or events here.
\core\session\manager::terminate_current();
return;
}
$event = \core\event\user_loggedout::create(
array(
'objectid' => $USER->id,
'context' => context_user::instance($USER->id)
)
);
$event->trigger();
// Execute hooks before action.
$authsequence = get_enabled_auth_plugins();
foreach ($authsequence as $authname) {
$authplugin = get_auth_plugin($authname);
$authplugin->prelogout_hook();
}
session_get_instance()->terminate_current();
unset($GLOBALS['USER']);
// Store info that gets removed during logout.
$sid = session_id();
$event = \core\event\user_loggedout::create(
array(
'userid' => $USER->id,
'objectid' => $USER->id,
'other' => array('sessionid' => $sid),
)
);
if ($session = $DB->get_record('sessions', array('sid'=>$sid))) {
$event->add_record_snapshot('sessions', $session);
}
// Delete session record and drop $_SESSION content.
\core\session\manager::terminate_current();
// Trigger event AFTER action.
$event->trigger();
}
/**
@ -3271,7 +3285,7 @@ function require_user_key_login($script, $instance=null) {
}
// Extra safety.
@session_write_close();
\core\session\manager::write_close();
$keyvalue = required_param('key', PARAM_ALPHANUM);
@ -3296,7 +3310,7 @@ function require_user_key_login($script, $instance=null) {
// Emulate normal session.
enrol_check_plugins($user);
session_set_user($user);
\core\session\manager::set_user($user);
// Note we are not using normal login.
if (!defined('USER_KEY_LOGIN')) {
@ -4154,7 +4168,7 @@ function delete_user(stdClass $user) {
$DB->delete_records('user_private_key', array('userid' => $user->id));
// Force logout - may fail if file based sessions used, sorry.
session_kill_user($user->id);
\core\session\manager::kill_user_sessions($user->id);
// Workaround for bulk deletes of users with the same email address.
$delname = "$user->email.".time();
@ -4386,15 +4400,7 @@ function authenticate_user_login($username, $password, $ignorelockout=false, &$f
function complete_user_login($user) {
global $CFG, $USER;
// Regenerate session id and delete old session,
// this helps prevent session fixation attacks from the same domain.
session_regenerate_id(true);
// Let enrol plugins deal with new enrolments if necessary.
enrol_check_plugins($user);
// Check enrolments, load caps and setup $USER object.
session_set_user($user);
\core\session\manager::login_user($user);
// Reload preferences from DB.
unset($USER->preference);
@ -4406,11 +4412,27 @@ function complete_user_login($user) {
// Extra session prefs init.
set_login_session_preferences();
// Trigger login event.
$event = \core\event\user_loggedin::create(
array(
'userid' => $USER->id,
'objectid' => $USER->id,
'other' => array('username' => $USER->username),
)
);
$event->add_record_snapshot('user', $user);
$event->trigger();
if (isguestuser()) {
// No need to continue when user is THE guest.
return $USER;
}
if (CLI_SCRIPT) {
// We can redirect to password change URL only in browser.
return $USER;
}
// Select password change url.
$userauth = get_auth_plugin($USER->auth);
@ -8834,10 +8856,10 @@ function get_performance_info() {
}
// Display size of session if session started.
if (session_id()) {
$info['sessionsize'] = display_size(strlen(session_encode()));
$info['html'] .= '<span class="sessionsize">Session: ' . $info['sessionsize'] . '</span> ';
$info['txt'] .= "Session: {$info['sessionsize']} ";
if ($si = \core\session\manager::get_performance_info()) {
$info['sessionsize'] = $si['size'];
$info['html'] .= $si['html'];
$info['txt'] .= $si['txt'];
}
if ($stats = cache_helper::get_stats()) {

View File

@ -3347,9 +3347,9 @@ class settings_navigation extends navigation_node {
}
// Check if the user is currently logged in as another user
if (session_is_loggedinas()) {
if (\core\session\manager::is_loggedinas()) {
// Get the actual user, we need this so we can display an informative return link
$realuser = session_get_realuser();
$realuser = \core\session\manager::get_realuser();
// Add the informative return to original user link
$url = new moodle_url('/course/loginas.php',array('id'=>$this->page->course->id, 'return'=>1,'sesskey'=>sesskey()));
$this->add(get_string('returntooriginaluser', 'moodle', fullname($realuser, true)), $url, self::TYPE_SETTING, null, null, new pix_icon('t/left', ''));
@ -4075,7 +4075,7 @@ class settings_navigation extends navigation_node {
}
// Change password link
if ($userauthplugin && $currentuser && !session_is_loggedinas() && !isguestuser() && has_capability('moodle/user:changeownpassword', $systemcontext) && $userauthplugin->can_change_password()) {
if ($userauthplugin && $currentuser && !\core\session\manager::is_loggedinas() && !isguestuser() && has_capability('moodle/user:changeownpassword', $systemcontext) && $userauthplugin->can_change_password()) {
$passwordchangeurl = $userauthplugin->change_password_url();
if (empty($passwordchangeurl)) {
$passwordchangeurl = new moodle_url('/login/change_password.php', array('id'=>$course->id));
@ -4193,7 +4193,7 @@ class settings_navigation extends navigation_node {
$reporttab->trim_if_empty();
// Login as ...
if (!$user->deleted and !$currentuser && !session_is_loggedinas() && has_capability('moodle/user:loginas', $coursecontext) && !is_siteadmin($user->id)) {
if (!$user->deleted and !$currentuser && !\core\session\manager::is_loggedinas() && has_capability('moodle/user:loginas', $coursecontext) && !is_siteadmin($user->id)) {
$url = new moodle_url('/course/loginas.php', array('id'=>$course->id, 'user'=>$user->id, 'sesskey'=>sesskey()));
$usersetting->add(get_string('loginas'), $url, self::TYPE_SETTING);
}

View File

@ -578,8 +578,8 @@ class core_renderer extends renderer_base {
$loginpage = ((string)$this->page->url === get_login_url());
$course = $this->page->course;
if (session_is_loggedinas()) {
$realuser = session_get_realuser();
if (\core\session\manager::is_loggedinas()) {
$realuser = \core\session\manager::get_realuser();
$fullname = fullname($realuser, true);
if ($withlinks) {
$loginastitle = get_string('loginas');
@ -775,7 +775,7 @@ class core_renderer extends renderer_base {
public function header() {
global $USER, $CFG;
if (session_is_loggedinas()) {
if (\core\session\manager::is_loggedinas()) {
$this->page->add_body_class('userloggedinas');
}

View File

@ -422,7 +422,7 @@ abstract class advanced_testcase extends PHPUnit_Framework_TestCase {
unset($user->access);
unset($user->preference);
session_set_user($user);
\core\session\manager::set_user($user);
}
/**

View File

@ -191,7 +191,7 @@ class phpunit_util extends testing_util {
$user = new stdClass();
$user->id = 0;
$user->mnethostid = $CFG->mnet_localhost_id;
session_set_user($user);
\core\session\manager::set_user($user);
// reset all static caches
\core\event\manager::phpunit_reset();

View File

@ -89,7 +89,7 @@ class core_phpunit_advanced_testcase extends advanced_testcase {
$this->assertEquals(3, $_SESSION['USER']->id);
$this->assertSame($_SESSION['USER'], $USER);
session_set_user($user);
\core\session\manager::set_user($user);
$this->assertEquals(2, $USER->id);
$this->assertEquals(2, $_SESSION['USER']->id);
$this->assertSame($_SESSION['USER'], $USER);

File diff suppressed because it is too large Load Diff

View File

@ -728,7 +728,7 @@ if (!defined('SYSCONTEXTID')) {
// Defining the site - aka frontpage course
try {
$SITE = get_site();
} catch (dml_exception $e) {
} catch (moodle_exception $e) {
$SITE = null;
if (empty($CFG->version)) {
$SITE = new stdClass();
@ -760,8 +760,11 @@ if (CLI_SCRIPT) {
}
}
// start session and prepare global $SESSION, $USER
session_get_instance();
// Start session and prepare global $SESSION, $USER.
if (empty($CFG->sessiontimeout)) {
$CFG->sessiontimeout = 7200;
}
\core\session\manager::start();
$SESSION = &$_SESSION['SESSION'];
$USER = &$_SESSION['USER'];
@ -848,7 +851,7 @@ if (!empty($CFG->debugvalidators) and !empty($CFG->guestloginbutton)) {
} else {
$user = guest_user();
}
session_set_user($user);
\core\session\manager::set_user($user);
}
}
}
@ -867,8 +870,8 @@ if ($USER && function_exists('apache_note')
$apachelog_name = clean_filename($USER->firstname . " " .
$USER->lastname);
}
if (session_is_loggedinas()) {
$realuser = session_get_realuser();
if (\core\session\manager::is_loggedinas()) {
$realuser = \core\session\manager::get_realuser();
$apachelog_username = clean_filename($realuser->username." as ".$apachelog_username);
$apachelog_name = clean_filename($realuser->firstname." ".$realuser->lastname ." as ".$apachelog_name);
$apachelog_userid = clean_filename($realuser->id." as ".$apachelog_userid);

View File

@ -1165,7 +1165,7 @@ function redirect_if_major_upgrade_required() {
if (empty($CFG->version) or (float)$CFG->version < $lastmajordbchanges or
during_initial_install() or !empty($CFG->adminsetuppending)) {
try {
@session_get_instance()->terminate_current();
@\core\session\manager::terminate_current();
} catch (Exception $e) {
// Ignore any errors, redirect to upgrade anyway.
}

View File

@ -185,26 +185,6 @@ class core_authlib_testcase extends advanced_testcase {
ini_set('error_log', $oldlog);
}
public function test_user_loggedin_event() {
global $USER;
$this->resetAfterTest(true);
$this->setAdminUser();
$sink = $this->redirectEvents();
$user = clone($USER);
login_attempt_valid($user);
$events = $sink->get_events();
$sink->close();
$this->assertCount(1, $events);
$event = reset($events);
$this->assertInstanceOf('\core\event\user_loggedin', $event);
$this->assertEquals('user', $event->objecttable);
$this->assertEquals('2', $event->objectid);
$this->assertEquals(context_system::instance()->id, $event->contextid);
$this->assertEquals($user, $event->get_record_snapshot('user', 2));
}
public function test_user_loggedin_event_exceptions() {
try {
$event = \core\event\user_loggedin::create(array('objectid' => 1));

View File

@ -176,7 +176,7 @@ class behat_hooks extends behat_base {
// Assing valid data to admin user (some generator-related code needs a valid user).
$user = $DB->get_record('user', array('username' => 'admin'));
session_set_user($user);
\core\session\manager::set_user($user);
// Reset the browser if specified in config.php.
if (!empty($CFG->behat_restart_browser_after) && $this->running_javascript()) {

View File

@ -2422,6 +2422,30 @@ class core_moodlelib_testcase extends advanced_testcase {
$this->assertEquals($expectedarray, order_in_string($valuearray, $formatstring));
}
public function test_complete_user_login() {
global $USER;
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->setUser(0);
$sink = $this->redirectEvents();
@complete_user_login($user); // Hide session header errors.
$this->assertEquals($user->id, $USER->id);
$events = $sink->get_events();
$sink->close();
$this->assertCount(2, $events);
$event = $events[0];
$this->assertInstanceOf('\core\event\user_updated', $event);
$event = $events[1];
$this->assertInstanceOf('\core\event\user_loggedin', $event);
$this->assertEquals('user', $event->objecttable);
$this->assertEquals($user->id, $event->objectid);
$this->assertEquals(context_system::instance()->id, $event->contextid);
$this->assertEquals($user, $event->get_record_snapshot('user', $user->id));
}
/**
* Test require_logout.
*/
@ -2429,7 +2453,6 @@ class core_moodlelib_testcase extends advanced_testcase {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$course = $this->getDataGenerator()->create_course();
$this->assertTrue(isloggedin());

View File

@ -0,0 +1,374 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Unit tests for session manager class.
*
* @package core
* @category phpunit
* @copyright 2013 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Unit tests for session manager class.
*
* @package core
* @category phpunit
* @copyright 2013 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_session_manager_testcase extends advanced_testcase {
public function test_start() {
$this->resetAfterTest();
// Session must be started only once...
\core\session\manager::start();
$this->assertDebuggingCalled('Session was already started!', DEBUG_DEVELOPER);
}
public function test_set_user() {
global $USER;
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->setUser(0);
$this->assertEquals(0, $USER->id);
\core\session\manager::set_user($user);
$this->assertEquals($user->id, $USER->id);
}
public function test_login_user() {
global $USER;
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->setUser(0);
$this->assertEquals(0, $USER->id);
@\core\session\manager::login_user($user); // Ignore header error messages.
$this->assertEquals($user->id, $USER->id);
}
public function test_terminate_current() {
global $USER;
$this->resetAfterTest();
// This can not be tested much without real session...
$this->setAdminUser();
\core\session\manager::terminate_current();
$this->assertEquals(0, $USER->id);
}
public function test_write_close() {
global $USER;
$this->resetAfterTest();
// Just make sure no errors and $USER->id is kept
$this->setAdminUser();
$userid = $USER->id;
\core\session\manager::write_close();
$this->assertSame($userid, $USER->id);
}
public function test_session_exists() {
global $CFG;
$this->resetAfterTest();
// The file handler is used by default, so let's fake the data somehow.
$sid = md5('hokus');
mkdir("$CFG->dataroot/sessions/", $CFG->directorypermissions, true);
touch("$CFG->dataroot/sessions/sess_$sid");
$this->assertTrue(\core\session\manager::session_exists($sid));
}
public function test_touch_session() {
global $DB;
$this->resetAfterTest();
$sid = md5('hokus');
$record = new \stdClass();
$record->state = 0;
$record->sid = $sid;
$record->sessdata = null;
$record->userid = 2;
$record->timecreated = time() - 60*60;
$record->timemodified = time() - 30;
$record->firstip = $record->lastip = '10.0.0.1';
$record->id = $DB->insert_record('sessions', $record);
$now = time();
\core\session\manager::touch_session($sid);
$updated = $DB->get_field('sessions', 'timemodified', array('id'=>$record->id));
$this->assertGreaterThanOrEqual($now, $updated);
$this->assertLessThanOrEqual(time(), $updated);
}
public function test_kill_session() {
global $DB, $USER;
$this->resetAfterTest();
$this->setAdminUser();
$userid = $USER->id;
$sid = md5('hokus');
$record = new \stdClass();
$record->state = 0;
$record->sid = $sid;
$record->sessdata = null;
$record->userid = $userid;
$record->timecreated = time() - 60*60;
$record->timemodified = time() - 30;
$record->firstip = $record->lastip = '10.0.0.1';
$DB->insert_record('sessions', $record);
$record->userid = 0;
$record->sid = md5('pokus');
$DB->insert_record('sessions', $record);
$this->assertEquals(2, $DB->count_records('sessions'));
\core\session\manager::kill_session($sid);
$this->assertEquals(1, $DB->count_records('sessions'));
$this->assertFalse($DB->record_exists('sessions', array('sid'=>$sid)));
$this->assertSame($userid, $USER->id);
}
public function test_kill_user_sessions() {
global $DB, $USER;
$this->resetAfterTest();
$this->setAdminUser();
$userid = $USER->id;
$sid = md5('hokus');
$record = new \stdClass();
$record->state = 0;
$record->sid = $sid;
$record->sessdata = null;
$record->userid = $userid;
$record->timecreated = time() - 60*60;
$record->timemodified = time() - 30;
$record->firstip = $record->lastip = '10.0.0.1';
$DB->insert_record('sessions', $record);
$record->sid = md5('hokus2');
$DB->insert_record('sessions', $record);
$record->userid = 0;
$record->sid = md5('pokus');
$DB->insert_record('sessions', $record);
$this->assertEquals(3, $DB->count_records('sessions'));
\core\session\manager::kill_user_sessions($userid);
$this->assertEquals(1, $DB->count_records('sessions'));
$this->assertFalse($DB->record_exists('sessions', array('userid'=>$userid)));
}
public function test_kill_all_sessions() {
global $DB, $USER;
$this->resetAfterTest();
$this->setAdminUser();
$userid = $USER->id;
$sid = md5('hokus');
$record = new \stdClass();
$record->state = 0;
$record->sid = $sid;
$record->sessdata = null;
$record->userid = $userid;
$record->timecreated = time() - 60*60;
$record->timemodified = time() - 30;
$record->firstip = $record->lastip = '10.0.0.1';
$DB->insert_record('sessions', $record);
$record->sid = md5('hokus2');
$DB->insert_record('sessions', $record);
$record->userid = 0;
$record->sid = md5('pokus');
$DB->insert_record('sessions', $record);
$this->assertEquals(3, $DB->count_records('sessions'));
\core\session\manager::kill_all_sessions();
$this->assertEquals(0, $DB->count_records('sessions'));
$this->assertSame(0, $USER->id);
}
public function test_gc() {
global $CFG, $DB, $USER;
$this->resetAfterTest();
$this->setAdminUser();
$adminid = $USER->id;
$this->setGuestUser();
$guestid = $USER->id;
$this->setUser(0);
$CFG->sessiontimeout = 60*10;
$record = new \stdClass();
$record->state = 0;
$record->sid = md5('hokus1');
$record->sessdata = null;
$record->userid = $adminid;
$record->timecreated = time() - 60*60;
$record->timemodified = time() - 30;
$record->firstip = $record->lastip = '10.0.0.1';
$r1 = $DB->insert_record('sessions', $record);
$record->sid = md5('hokus2');
$record->userid = $adminid;
$record->timecreated = time() - 60*60;
$record->timemodified = time() - 60*20;
$r2 = $DB->insert_record('sessions', $record);
$record->sid = md5('hokus3');
$record->userid = $guestid;
$record->timecreated = time() - 60*60*60;
$record->timemodified = time() - 60*20;
$r3 = $DB->insert_record('sessions', $record);
$record->sid = md5('hokus4');
$record->userid = $guestid;
$record->timecreated = time() - 60*60*60;
$record->timemodified = time() - 60*10*5 - 60;
$r4 = $DB->insert_record('sessions', $record);
$record->sid = md5('hokus5');
$record->userid = 0;
$record->timecreated = time() - 60*5;
$record->timemodified = time() - 60*5;
$r5 = $DB->insert_record('sessions', $record);
$record->sid = md5('hokus6');
$record->userid = 0;
$record->timecreated = time() - 60*60;
$record->timemodified = time() - 60*10 -10;
$r6 = $DB->insert_record('sessions', $record);
$record->sid = md5('hokus7');
$record->userid = 0;
$record->timecreated = time() - 60*60;
$record->timemodified = time() - 60*9;
$r7 = $DB->insert_record('sessions', $record);
\core\session\manager::gc();
$this->assertTrue($DB->record_exists('sessions', array('id'=>$r1)));
$this->assertFalse($DB->record_exists('sessions', array('id'=>$r2)));
$this->assertTrue($DB->record_exists('sessions', array('id'=>$r3)));
$this->assertFalse($DB->record_exists('sessions', array('id'=>$r4)));
$this->assertFalse($DB->record_exists('sessions', array('id'=>$r5)));
$this->assertFalse($DB->record_exists('sessions', array('id'=>$r6)));
$this->assertTrue($DB->record_exists('sessions', array('id'=>$r7)));
}
/**
* Test loginas.
* @copyright 2103 Rajesh Taneja <rajesh@moodle.com>
*/
public function test_loginas() {
global $USER;
$this->resetAfterTest();
// Set current user as Admin user and save it for later use.
$this->setAdminUser();
$adminuser = $USER;
// Create a new user and try admin loginas this user.
$user = $this->getDataGenerator()->create_user();
\core\session\manager::loginas($user->id, context_system::instance());
$this->assertSame($user->id, $USER->id);
$this->assertSame(context_system::instance(), $USER->loginascontext);
$this->assertSame($adminuser->id, $USER->realuser);
// Set user as current user and login as admin user in course context.
$this->setUser($user);
$this->assertNotEquals($adminuser->id, $USER->id);
$course = $this->getDataGenerator()->create_course();
$coursecontext = context_course::instance($course->id);
// Catch event triggered.
$sink = $this->redirectEvents();
\core\session\manager::loginas($adminuser->id, $coursecontext);
$events = $sink->get_events();
$sink->close();
$event = array_pop($events);
$this->assertSame($adminuser->id, $USER->id);
$this->assertSame($coursecontext, $USER->loginascontext);
$this->assertSame($user->id, $USER->realuser);
// Test event captured has proper information.
$this->assertInstanceOf('\core\event\user_loggedinas', $event);
$this->assertSame($user->id, $event->objectid);
$this->assertSame($adminuser->id, $event->relateduserid);
$this->assertSame($course->id, $event->courseid);
$this->assertEquals($coursecontext, $event->get_context());
$oldfullname = fullname($user, true);
$newfullname = fullname($adminuser, true);
$expectedlogdata = array($course->id, "course", "loginas", "../user/view.php?id=$course->id&amp;user=$user->id", "$oldfullname -> $newfullname");
$this->assertEventLegacyLogData($expectedlogdata, $event);
}
public function test_is_loggedinas() {
$this->resetAfterTest();
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$this->assertFalse(\core\session\manager::is_loggedinas());
$this->setUser($user1);
\core\session\manager::loginas($user2->id, context_system::instance());
$this->assertTrue(\core\session\manager::is_loggedinas());
}
public function test_get_realuser() {
$this->resetAfterTest();
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$this->setUser($user1);
\core\session\manager::loginas($user2->id, context_system::instance());
$real = \core\session\manager::get_realuser();
unset($real->password);
unset($real->description);
unset($real->sesskey);
unset($user1->password);
unset($user1->description);
unset($user1->sesskey);
$this->assertEquals($real, $user1);
}
}

View File

@ -1,88 +0,0 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Unit tests for (some of) ../sessionlib.php.
*
* @package core_session
* @category phpunit
* @copyright 2103 Rajesh Taneja <rajesh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir . '/sessionlib.php');
/**
* Unit tests for (some of) ../sessionlib.php.
*
* @package core_session
* @category phpunit
* @copyright 2103 Rajesh Taneja <rajesh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_sessionlib_testcase extends advanced_testcase {
/**
* Test session_loginas.
*/
public function test_session_loginas() {
global $USER;
$this->resetAfterTest();
// Set current user as Admin user and save it for later use.
$this->setAdminUser();
$adminuser = $USER;
// Create a new user and try admin loginas this user.
$user = $this->getDataGenerator()->create_user();
session_loginas($user->id, context_system::instance());
$this->assertSame($user->id, $USER->id);
$this->assertSame(context_system::instance(), $USER->loginascontext);
$this->assertSame($adminuser->id, $USER->realuser);
// Set user as current user and login as admin user in course context.
$this->setUser($user);
$this->assertNotEquals($adminuser->id, $USER->id);
$course = $this->getDataGenerator()->create_course();
$coursecontext = context_course::instance($course->id);
// Catch event triggred.
$sink = $this->redirectEvents();
session_loginas($adminuser->id, $coursecontext);
$events = $sink->get_events();
$sink->close();
$event = array_pop($events);
$this->assertSame($adminuser->id, $USER->id);
$this->assertSame($coursecontext, $USER->loginascontext);
$this->assertSame($user->id, $USER->realuser);
// Test event captured has proper information.
$this->assertInstanceOf('\core\event\user_loggedinas', $event);
$this->assertSame($user->id, $event->objectid);
$this->assertSame($adminuser->id, $event->relateduserid);
$this->assertSame($course->id, $event->courseid);
$this->assertEquals($coursecontext, $event->get_context());
$oldfullname = fullname($user, true);
$newfullname = fullname($adminuser, true);
$expectedlogdata = array($course->id, "course", "loginas", "../user/view.php?id=$course->id&amp;user=$user->id", "$oldfullname -> $newfullname");
$this->assertEventLegacyLogData($expectedlogdata, $event);
}
}

View File

@ -110,6 +110,18 @@ Misc:
* course_modinfo::build_section_cache() -> (no replacement)
* generate_email_supportuser() -> core_user::get_support_user()
Sessions:
* session_get_instance()->xxx() -> \core\session\manager::xxx()
* session_kill_all() -> \core\session\manager::kill_all_sessions()
* session_touch() -> \core\session\manager::touch_session()
* session_kill() -> \core\session\manager::kill_session()
* session_kill_user() -> \core\session\manager::kill_user_sessions()
* session_gc() -> \core\session\manager::gc()
* session_set_user() -> \core\session\manager::set_user()
* session_is_loggedinas() -> \core\session\manager::is_loggedinas()
* session_get_realuser() -> \core\session\manager::get_realuser()
* session_loginas() -> \core\session\manager::loginas()
User-agent related functions:
* check_browser_operating_system() -> core_useragent::check_browser_operating_system()
* check_browser_version() -> core_useragent::check_browser_version()

View File

@ -2588,12 +2588,11 @@ function redirect($url, $message='', $delay=-1) {
}
}
if ($delay == 0 && !$debugdisableredirect && !headers_sent()) {
// Workaround for IIS bug http://support.microsoft.com/kb/q176113/.
if (session_id()) {
session_get_instance()->write_close();
}
// Make sure the session is closed properly, this prevents problems in IIS
// and also some potential PHP shutdown issues.
\core\session\manager::write_close();
if ($delay == 0 && !$debugdisableredirect && !headers_sent()) {
// 302 might not work for POST requests, 303 is ignored by obsolete clients.
@header($_SERVER['SERVER_PROTOCOL'] . ' 303 See Other');
@header('Location: '.$url);

View File

@ -73,7 +73,7 @@ if (!get_user_preferences('auth_forcepasswordchange', false)) {
}
// do not allow "Logged in as" users to change any passwords
if (session_is_loggedinas()) {
if (\core\session\manager::is_loggedinas()) {
print_error('cannotcallscript');
}

View File

@ -68,7 +68,7 @@ if (!empty($user)) {
enrol_check_plugins($user);
// setup user session to check capability
session_set_user($user);
\core\session\manager::set_user($user);
//check if the service exists and is enabled
$service = $DB->get_record('external_services', array('shortname' => $serviceshortname, 'enabled' => 1));
@ -116,8 +116,7 @@ if (!empty($user)) {
$unsettoken = false;
//if sid is set then there must be a valid associated session no matter the token type
if (!empty($token->sid)) {
$session = session_get_instance();
if (!$session->session_exists($token->sid)){
if (!\core\session\manager::session_exists($token->sid)){
//this token will never be valid anymore, delete it
$DB->delete_records('external_tokens', array('sid'=>$token->sid));
$unsettoken = true;

View File

@ -420,7 +420,7 @@ class assignment_online extends assignment_base {
send_file_not_found();
}
session_get_instance()->write_close(); // unlock session during fileserving
\core\session\manager::write_close(); // Unlock session during file serving.
send_stored_file($file, 60*60, 0, true, $options);
}

View File

@ -68,7 +68,7 @@ case 'init':
break;
case 'chat':
session_get_instance()->write_close();
\core\session\manager::write_close();
chat_delete_old_users();
$chat_message = clean_text($chat_message, FORMAT_MOODLE);

View File

@ -30,7 +30,7 @@ if (isguestuser()) {
print_error('noguests');
}
session_get_instance()->write_close();
\core\session\manager::write_close();
/// Delete old users now

View File

@ -320,7 +320,7 @@ class quiz_overview_report extends quiz_attempts_report {
* Unlock the session and allow the regrading process to run in the background.
*/
protected function unlock_session() {
session_get_instance()->write_close();
\core\session\manager::write_close();
ignore_user_abort(true);
}

View File

@ -135,7 +135,7 @@ $adminediting = optional_param('adminedit', -1, PARAM_BOOL);
if ($PAGE->user_allowed_editing() && $adminediting != -1) {
$USER->editing = $adminediting;
}
session_get_instance()->write_close();
\core\session\manager::write_close();
if (!empty($chooselog)) {
$userinfo = get_string('allparticipants');

View File

@ -46,7 +46,7 @@ require_capability('report/loglive:view', $context);
$strlivelogs = get_string('livelogs', 'report_loglive');
if ($inpopup) {
session_get_instance()->write_close();
\core\session\manager::write_close();
$date = time() - 3600;

View File

@ -743,7 +743,7 @@ abstract class repository implements cacheable_object {
$repocontext = context::instance_by_id($this->instance->contextid);
// Prevent access to private repositories when logged in as.
if ($can && session_is_loggedinas()) {
if ($can && \core\session\manager::is_loggedinas()) {
if ($this->contains_private_data() || $repocontext->contextlevel == CONTEXT_USER) {
$can = false;
}

View File

@ -436,7 +436,7 @@ class core_repositorylib_testcase extends advanced_testcase {
$userrepo = repository::get_repository_by_id($user1repoid, $syscontext);
$this->setAdminUser();
session_loginas($user1->id, $syscontext);
\core\session\manager::loginas($user1->id, $syscontext);
// Logged in as, I cannot view a user instance.
$caughtexception = false;

View File

@ -128,7 +128,7 @@ $user = get_complete_user_data('id', $userid);
// let enrol plugins deal with new enrolments if necessary
enrol_check_plugins($user);
session_set_user($user); //for login and capability checks
\core\session\manager::set_user($user); //for login and capability checks
try {
$autologinguest = true;

View File

@ -307,8 +307,8 @@ class theme_mymobile_core_renderer extends core_renderer {
$course = $this->page->course;
if (session_is_loggedinas()) {
$realuser = session_get_realuser();
if (\core\session\manager::is_loggedinas()) {
$realuser = \core\session\manager::get_realuser();
$fullname = fullname($realuser, true);
$realuserinfo = " [<a href=\"$CFG->wwwroot/course/loginas.php?id=$course->id&amp;sesskey=".sesskey()."\">$fullname</a>] ";
} else {
@ -386,8 +386,8 @@ class theme_mymobile_core_renderer extends core_renderer {
$loginpage = ((string)$this->page->url === get_login_url());
$course = $this->page->course;
if (session_is_loggedinas()) {
$realuser = session_get_realuser();
if (\core\session\manager::is_loggedinas()) {
$realuser = \core\session\manager::get_realuser();
$fullname = fullname($realuser, true);
$realuserinfo = ' [<a href="'.$CFG->wwwroot.'/course/loginas.php?id=$course->id&amp;sesskey='.sesskey().'">$fullname</a>] ';
} else {
@ -628,7 +628,7 @@ class theme_mymobile_core_renderer extends core_renderer {
public function header() {
global $USER, $CFG;
if (session_is_loggedinas()) {
if (\core\session\manager::is_loggedinas()) {
$this->page->add_body_class('userloggedinas');
}

View File

@ -200,7 +200,7 @@ if ($usernew = $userform->get_data()) {
// force logout if user just suspended
if (isset($usernew->suspended) and $usernew->suspended and !$user->suspended) {
session_kill_user($user->id);
\core\session\manager::kill_user_sessions($user->id);
}
}
@ -255,7 +255,7 @@ if ($usernew = $userform->get_data()) {
redirect("$CFG->wwwroot/user/view.php?id=$USER->id&course=$course->id");
}
} else {
session_gc(); // remove stale sessions
\core\session\manager::gc(); // Remove stale sessions.
redirect("$CFG->wwwroot/$CFG->admin/user.php");
}
//never reached

View File

@ -673,7 +673,7 @@
$links[] = html_writer::link(new moodle_url('/course/user.php?id='. $course->id .'&user='. $user->id), get_string('activity'));
}
if ($USER->id != $user->id && !session_is_loggedinas() && has_capability('moodle/user:loginas', $context) && !is_siteadmin($user->id)) {
if ($USER->id != $user->id && !\core\session\manager::is_loggedinas() && has_capability('moodle/user:loginas', $context) && !is_siteadmin($user->id)) {
$links[] = html_writer::link(new moodle_url('/course/loginas.php?id='. $course->id .'&user='. $user->id .'&sesskey='. sesskey()), get_string('loginas'));
}

View File

@ -90,12 +90,11 @@ class webservice {
enrol_check_plugins($user);
// setup user session to check capability
session_set_user($user);
\core\session\manager::set_user($user);
//assumes that if sid is set then there must be a valid associated session no matter the token type
if ($token->sid) {
$session = session_get_instance();
if (!$session->session_exists($token->sid)) {
if (!\core\session\manager::session_exists($token->sid)) {
$DB->delete_records('external_tokens', array('sid' => $token->sid));
throw new webservice_access_exception('Invalid session based token - session not found or expired');
}
@ -905,7 +904,7 @@ abstract class webservice_server implements webservice_server_interface {
// now fake user login, the session is completely empty too
enrol_check_plugins($user);
session_set_user($user);
\core\session\manager::set_user($user);
$this->userid = $user->id;
if ($this->authmethod != WEBSERVICE_AUTHMETHOD_SESSION_TOKEN && !has_capability("webservice/$this->wsname:use", $this->restricted_context)) {
@ -936,8 +935,7 @@ abstract class webservice_server implements webservice_server_interface {
}
if ($token->sid){//assumes that if sid is set then there must be a valid associated session no matter the token type
$session = session_get_instance();
if (!$session->session_exists($token->sid)){
if (!\core\session\manager::session_exists($token->sid)){
$DB->delete_records('external_tokens', array('sid'=>$token->sid));
throw new webservice_access_exception('Invalid session based token - session not found or expired');
}