diff --git a/admin/auth.php b/admin/auth.php
index 934acd371ef..300dead1a54 100644
--- a/admin/auth.php
+++ b/admin/auth.php
@@ -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':
diff --git a/admin/cli/upgrade.php b/admin/cli/upgrade.php
index 98e87264b0b..28d7c8d2342 100644
--- a/admin/cli/upgrade.php
+++ b/admin/cli/upgrade.php
@@ -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);
diff --git a/admin/cron.php b/admin/cron.php
index e4115458c76..ed19d46360e 100644
--- a/admin/cron.php
+++ b/admin/cron.php
@@ -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)) {
diff --git a/admin/index.php b/admin/index.php
index 1f186b6417a..3c0cb6c1a95 100644
--- a/admin/index.php
+++ b/admin/index.php
@@ -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');
diff --git a/admin/settings/server.php b/admin/settings/server.php
index 3a58d34479e..ea2491a14bc 100644
--- a/admin/settings/server.php
+++ b/admin/settings/server.php
@@ -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),
diff --git a/admin/tool/assignmentupgrade/batchupgrade.php b/admin/tool/assignmentupgrade/batchupgrade.php
index bab7a943cbb..0d92dc0cb56 100644
--- a/admin/tool/assignmentupgrade/batchupgrade.php
+++ b/admin/tool/assignmentupgrade/batchupgrade.php
@@ -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'));
diff --git a/admin/tool/dbtransfer/locallib.php b/admin/tool/dbtransfer/locallib.php
index 190d86fc84a..3357365165d 100644
--- a/admin/tool/dbtransfer/locallib.php
+++ b/admin/tool/dbtransfer/locallib.php
@@ -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);
diff --git a/admin/tool/generator/cli/maketestcourse.php b/admin/tool/generator/cli/maketestcourse.php
index 407a58f12db..940e753b92f 100644
--- a/admin/tool/generator/cli/maketestcourse.php
+++ b/admin/tool/generator/cli/maketestcourse.php
@@ -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']));
diff --git a/admin/tool/generator/cli/maketestsite.php b/admin/tool/generator/cli/maketestsite.php
index bc91d7d8a9f..1e2a7d40141 100644
--- a/admin/tool/generator/cli/maketestsite.php
+++ b/admin/tool/generator/cli/maketestsite.php
@@ -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']));
diff --git a/admin/tool/uploaduser/index.php b/admin/tool/uploaduser/index.php
index 7949d94eada..509374cf6a3 100644
--- a/admin/tool/uploaduser/index.php
+++ b/admin/tool/uploaduser/index.php
@@ -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 {
diff --git a/admin/user.php b/admin/user.php
index 699babbeea8..45ddc21a33b 100644
--- a/admin/user.php
+++ b/admin/user.php
@@ -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);
}
}
diff --git a/admin/user/user_bulk_delete.php b/admin/user/user_bulk_delete.php
index 4dbb75df057..474903a7055 100644
--- a/admin/user/user_bulk_delete.php
+++ b/admin/user/user_bulk_delete.php
@@ -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;
diff --git a/auth/ldap/auth.php b/auth/ldap/auth.php
index a435caaa1b1..0f80f7d30bb 100644
--- a/auth/ldap/auth.php
+++ b/auth/ldap/auth.php
@@ -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');
diff --git a/auth/mnet/auth.php b/auth/mnet/auth.php
index e21d77a687c..e9950215705 100644
--- a/auth/mnet/auth.php
+++ b/auth/mnet/auth.php
@@ -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;
}
diff --git a/auth/shibboleth/index.php b/auth/shibboleth/index.php
index 61f6ba877f8..3eaf702d651 100644
--- a/auth/shibboleth/index.php
+++ b/auth/shibboleth/index.php
@@ -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
diff --git a/badges/ajax.php b/badges/ajax.php
index 76e8c26581b..29c1f1d6b18 100644
--- a/badges/ajax.php
+++ b/badges/ajax.php
@@ -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();
diff --git a/blocks/html/lib.php b/blocks/html/lib.php
index 58de649618a..98906612d45 100644
--- a/blocks/html/lib.php
+++ b/blocks/html/lib.php
@@ -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);
}
diff --git a/blocks/mnet_hosts/block_mnet_hosts.php b/blocks/mnet_hosts/block_mnet_hosts.php
index 2021fefe151..e776a4565d8 100644
--- a/blocks/mnet_hosts/block_mnet_hosts.php
+++ b/blocks/mnet_hosts/block_mnet_hosts.php
@@ -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'));
diff --git a/calendar/tests/calendartype_test.php b/calendar/tests/calendartype_test.php
index 10e72ea254f..9f117992c76 100644
--- a/calendar/tests/calendartype_test.php
+++ b/calendar/tests/calendartype_test.php
@@ -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);
}
}
diff --git a/config-dist.php b/config-dist.php
index 186516637ae..59830d9862b 100644
--- a/config-dist.php
+++ b/config-dist.php
@@ -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
diff --git a/course/loginas.php b/course/loginas.php
index 97f7d2a81ef..bfc3495eb75 100644
--- a/course/loginas.php
+++ b/course/loginas.php
@@ -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');
diff --git a/draftfile.php b/draftfile.php
index 821ae576650..ce61d199e0c 100644
--- a/draftfile.php
+++ b/draftfile.php
@@ -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!
diff --git a/enrol/index.php b/enrol/index.php
index d0e91b53443..3cc533507c0 100644
--- a/enrol/index.php
+++ b/enrol/index.php
@@ -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);
}
diff --git a/file.php b/file.php
index fce3f788ec1..bfbe8701961 100644
--- a/file.php
+++ b/file.php
@@ -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);
diff --git a/lang/en/error.php b/lang/en/error.php
index 2f18aa61dfa..d615046c251 100644
--- a/lang/en/error.php
+++ b/lang/en/error.php
@@ -466,6 +466,7 @@ $string['servicedonotexist'] = 'The service does not exist';
$string['sessionwaiterr'] = 'Timed out while waiting for session lock.
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.
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.';
diff --git a/lib/authlib.php b/lib/authlib.php
index f8fac6ea501..736374214a2 100644
--- a/lib/authlib.php
+++ b/lib/authlib.php
@@ -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;
diff --git a/lib/classes/component.php b/lib/classes/component.php
index 51db6e9f4ce..0e21af390b4 100644
--- a/lib/classes/component.php
+++ b/lib/classes/component.php
@@ -24,6 +24,20 @@
defined('MOODLE_INTERNAL') || die();
+// Constants used in version.php files, these must exist when core_component executes.
+
+/** Software maturity level - internals can be tested using white box techniques. */
+define('MATURITY_ALPHA', 50);
+/** Software maturity level - feature complete, ready for preview and testing. */
+define('MATURITY_BETA', 100);
+/** Software maturity level - tested, will be released unless there are fatal bugs. */
+define('MATURITY_RC', 150);
+/** Software maturity level - ready for production deployment. */
+define('MATURITY_STABLE', 200);
+/** Any version - special value that can be used in $plugin->dependencies in version.php files. */
+define('ANY_VERSION', 'any');
+
+
/**
* Collection of components related methods.
*/
@@ -268,6 +282,7 @@ $cache = '.var_export($cache, true).';
protected static function fetch_core_version() {
global $CFG;
if (self::$version === null) {
+ $version = null; // Prevent IDE complaints.
require($CFG->dirroot . '/version.php');
self::$version = $version;
}
diff --git a/lib/classes/event/user_loggedin.php b/lib/classes/event/user_loggedin.php
index 006485f75f9..9065aa77363 100644
--- a/lib/classes/event/user_loggedin.php
+++ b/lib/classes/event/user_loggedin.php
@@ -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() {
diff --git a/lib/classes/event/user_loggedout.php b/lib/classes/event/user_loggedout.php
index f881b471884..8f2c6aacfdd 100644
--- a/lib/classes/event/user_loggedout.php
+++ b/lib/classes/event/user_loggedout.php
@@ -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;
diff --git a/lib/classes/session/database.php b/lib/classes/session/database.php
new file mode 100644
index 00000000000..4216980dff2
--- /dev/null
+++ b/lib/classes/session/database.php
@@ -0,0 +1,315 @@
+.
+
+/**
+ * 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();
+ }
+}
diff --git a/lib/classes/session/exception.php b/lib/classes/session/exception.php
new file mode 100644
index 00000000000..ea58b0f5e8f
--- /dev/null
+++ b/lib/classes/session/exception.php
@@ -0,0 +1,34 @@
+.
+
+/**
+ * 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 {
+}
diff --git a/lib/classes/session/file.php b/lib/classes/session/file.php
new file mode 100644
index 00000000000..7ecb00c2f33
--- /dev/null
+++ b/lib/classes/session/file.php
@@ -0,0 +1,120 @@
+.
+
+/**
+ * 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);
+ }
+ }
+}
diff --git a/lib/classes/session/handler.php b/lib/classes/session/handler.php
new file mode 100644
index 00000000000..a656175f74d
--- /dev/null
+++ b/lib/classes/session/handler.php
@@ -0,0 +1,63 @@
+.
+
+/**
+ * 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);
+}
diff --git a/lib/classes/session/manager.php b/lib/classes/session/manager.php
new file mode 100644
index 00000000000..90153ef46ed
--- /dev/null
+++ b/lib/classes/session/manager.php
@@ -0,0 +1,754 @@
+.
+
+/**
+ * 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'] = "Session ($handler): $size ";
+ $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();
+ }
+}
diff --git a/lib/classes/session/memcached.php b/lib/classes/session/memcached.php
new file mode 100644
index 00000000000..728153705cb
--- /dev/null
+++ b/lib/classes/session/memcached.php
@@ -0,0 +1,187 @@
+.
+
+/**
+ * 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;
+ }
+}
diff --git a/lib/cronlib.php b/lib/cronlib.php
index b15105a4c98..0f95c752bb8 100644
--- a/lib/cronlib.php
+++ b/lib/cronlib.php
@@ -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
diff --git a/lib/datalib.php b/lib/datalib.php
index f2044c34d96..3509b444495 100644
--- a/lib/datalib.php
+++ b/lib/datalib.php
@@ -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;
}
diff --git a/lib/deprecatedlib.php b/lib/deprecatedlib.php
index 3cc26edb3e2..6ef7851d6eb 100644
--- a/lib/deprecatedlib.php
+++ b/lib/deprecatedlib.php
@@ -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.
*
diff --git a/lib/dml/moodle_database.php b/lib/dml/moodle_database.php
index c9ab87c60e5..815248758b1 100644
--- a/lib/dml/moodle_database.php
+++ b/lib/dml/moodle_database.php
@@ -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.
diff --git a/lib/dml/mssql_native_moodle_database.php b/lib/dml/mssql_native_moodle_database.php
index b4def7a0260..88c35755822 100644
--- a/lib/dml/mssql_native_moodle_database.php
+++ b/lib/dml/mssql_native_moodle_database.php
@@ -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;
diff --git a/lib/dml/mysqli_native_moodle_database.php b/lib/dml/mysqli_native_moodle_database.php
index 7a2d31f1707..f80c479259a 100644
--- a/lib/dml/mysqli_native_moodle_database.php
+++ b/lib/dml/mysqli_native_moodle_database.php
@@ -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;
diff --git a/lib/dml/oci_native_moodle_database.php b/lib/dml/oci_native_moodle_database.php
index 10906b88f95..5f412ef5c3f 100644
--- a/lib/dml/oci_native_moodle_database.php
+++ b/lib/dml/oci_native_moodle_database.php
@@ -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
diff --git a/lib/dml/pgsql_native_moodle_database.php b/lib/dml/pgsql_native_moodle_database.php
index 7c9792ecf0c..3ae868d4eac 100644
--- a/lib/dml/pgsql_native_moodle_database.php
+++ b/lib/dml/pgsql_native_moodle_database.php
@@ -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;
diff --git a/lib/dml/sqlite3_pdo_moodle_database.php b/lib/dml/sqlite3_pdo_moodle_database.php
index d5a0080bc05..c71119700ea 100644
--- a/lib/dml/sqlite3_pdo_moodle_database.php
+++ b/lib/dml/sqlite3_pdo_moodle_database.php
@@ -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;
diff --git a/lib/dml/sqlsrv_native_moodle_database.php b/lib/dml/sqlsrv_native_moodle_database.php
index b939e9f9307..3d456970be4 100644
--- a/lib/dml/sqlsrv_native_moodle_database.php
+++ b/lib/dml/sqlsrv_native_moodle_database.php
@@ -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;
diff --git a/lib/filelib.php b/lib/filelib.php
index cf825852b83..8127f408497 100644
--- a/lib/filelib.php
+++ b/lib/filelib.php
@@ -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));
}
diff --git a/lib/installlib.php b/lib/installlib.php
index f60605b1eef..40705f1006f 100644
--- a/lib/installlib.php
+++ b/lib/installlib.php
@@ -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);
diff --git a/lib/moodlelib.php b/lib/moodlelib.php
index 92091ea16df..a7708cfabee 100644
--- a/lib/moodlelib.php
+++ b/lib/moodlelib.php
@@ -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'] .= 'Session: ' . $info['sessionsize'] . ' ';
- $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()) {
diff --git a/lib/navigationlib.php b/lib/navigationlib.php
index bf958a61dcc..506d2d7cc32 100644
--- a/lib/navigationlib.php
+++ b/lib/navigationlib.php
@@ -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);
}
diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php
index 35173a8f641..15de22ba808 100644
--- a/lib/outputrenderers.php
+++ b/lib/outputrenderers.php
@@ -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');
}
diff --git a/lib/phpunit/classes/advanced_testcase.php b/lib/phpunit/classes/advanced_testcase.php
index 4b60ec3a7c4..42d4dd3f577 100644
--- a/lib/phpunit/classes/advanced_testcase.php
+++ b/lib/phpunit/classes/advanced_testcase.php
@@ -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);
}
/**
diff --git a/lib/phpunit/classes/util.php b/lib/phpunit/classes/util.php
index a48f71daeef..b0c5bed6df4 100644
--- a/lib/phpunit/classes/util.php
+++ b/lib/phpunit/classes/util.php
@@ -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();
diff --git a/lib/phpunit/tests/advanced_test.php b/lib/phpunit/tests/advanced_test.php
index fba7d04ca2f..52328afe831 100644
--- a/lib/phpunit/tests/advanced_test.php
+++ b/lib/phpunit/tests/advanced_test.php
@@ -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);
diff --git a/lib/sessionlib.php b/lib/sessionlib.php
index 8063106df65..49b542da7bb 100644
--- a/lib/sessionlib.php
+++ b/lib/sessionlib.php
@@ -24,969 +24,6 @@
defined('MOODLE_INTERNAL') || die();
-if (!defined('SESSION_ACQUIRE_LOCK_TIMEOUT')) {
- /**
- * How much time to wait for session lock before displaying error (in seconds),
- * 2 minutes by default should be a reasonable time before telling users to wait and refresh browser.
- */
- define('SESSION_ACQUIRE_LOCK_TIMEOUT', 60*2);
-}
-
-/**
- * Factory method returning moodle_session object.
- * @return moodle_session
- */
-function session_get_instance() {
- global $CFG, $DB;
-
- static $session = null;
-
- if (is_null($session)) {
- if (!defined('NO_MOODLE_COOKIES') or empty($DB)) {
- // Moodle was not initialised properly in lib/setup.php.
- $session = new emergency_session();
- return $session;
- }
-
- if (empty($CFG->sessiontimeout)) {
- $CFG->sessiontimeout = 7200;
- }
-
- try {
- if (defined('SESSION_CUSTOM_CLASS')) {
- // this is a hook for webservices, key based login, etc.
- if (defined('SESSION_CUSTOM_FILE')) {
- require_once($CFG->dirroot.SESSION_CUSTOM_FILE);
- }
- $session_class = SESSION_CUSTOM_CLASS;
- $session = new $session_class();
-
- } else if ((!isset($CFG->dbsessions) or $CFG->dbsessions) and $DB->session_lock_supported()) {
- // default recommended session type
- $session = new database_session();
-
- } else {
- // legacy limited file based storage - some features and auth plugins will not work, sorry
- $session = new legacy_file_session();
- }
- } catch (Exception $ex) {
- // prevent repeated inits
- $session = new emergency_session();
- throw $ex;
- }
- }
-
- return $session;
-}
-
-
-/**
- * Moodle session abstraction
- *
- * @package core
- * @subpackage session
- * @copyright 2008 Petr Skoda {@link http://skodak.org}
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-interface moodle_session {
- /**
- * Terminate current session
- * @return void
- */
- public function terminate_current();
-
- /**
- * No more changes in session expected.
- * Unblocks the sessions, other scripts may start executing in parallel.
- * @return void
- */
- public function write_close();
-
- /**
- * Check for existing session with id $sid
- * @param unknown_type $sid
- * @return boolean true if session found.
- */
- public function session_exists($sid);
-}
-
-
-/**
- * Fallback session handler when standard session init fails.
- * This prevents repeated attempts to init faulty handler.
- *
- * @package core
- * @subpackage session
- * @copyright 2011 Petr Skoda {@link http://skodak.org}
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class emergency_session implements moodle_session {
-
- public function __construct() {
- // session not used at all
- $_SESSION = array();
- $_SESSION['SESSION'] = new stdClass();
- $_SESSION['USER'] = new stdClass();
- }
-
- /**
- * Terminate current session
- * @return void
- */
- public function terminate_current() {
- return;
- }
-
- /**
- * No more changes in session expected.
- * Unblocks the sessions, other scripts may start executing in parallel.
- * @return void
- */
- public function write_close() {
- return;
- }
-
- /**
- * Check for existing session with id $sid
- * @param unknown_type $sid
- * @return boolean true if session found.
- */
- public function session_exists($sid) {
- return false;
- }
-}
-
-
-/**
- * Class handling all session and cookies related stuff.
- *
- * @package core
- * @subpackage session
- * @copyright 2009 Petr Skoda {@link http://skodak.org}
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-abstract class session_stub implements moodle_session {
- protected $justloggedout;
-
- public function __construct() {
- global $CFG;
-
- if (NO_MOODLE_COOKIES) {
- // session not used at all
- $_SESSION = array();
- $_SESSION['SESSION'] = new stdClass();
- $_SESSION['USER'] = new stdClass();
-
- } else {
- $this->prepare_cookies();
- $this->init_session_storage();
-
- $newsession = empty($_COOKIE['MoodleSession'.$CFG->sessioncookie]);
-
- ini_set('session.use_trans_sid', '0');
-
- session_name('MoodleSession'.$CFG->sessioncookie);
- session_set_cookie_params(0, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
- session_start();
- if (!isset($_SESSION['SESSION'])) {
- $_SESSION['SESSION'] = new stdClass();
- if (!$newsession and !$this->justloggedout) {
- $_SESSION['SESSION']->has_timed_out = true;
- }
- }
- if (!isset($_SESSION['USER'])) {
- $_SESSION['USER'] = new stdClass();
- }
- }
-
- $this->check_user_initialised();
-
- $this->check_security();
- }
-
- /**
- * Terminate current session
- * @return void
- */
- public function terminate_current() {
- global $CFG, $SESSION, $USER, $DB;
-
- try {
- $DB->delete_records('external_tokens', array('sid'=>session_id(), 'tokentype'=>EXTERNAL_TOKEN_EMBEDDED));
- } catch (Exception $ignored) {
- // probably install/upgrade - ignore this problem
- }
-
- if (NO_MOODLE_COOKIES) {
- return;
- }
-
- // Initialize variable to pass-by-reference to headers_sent(&$file, &$line)
- $_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;
- }
- $SESSION = $_SESSION['SESSION']; // this may not work properly
- $USER = $_SESSION['USER']; // this may not work properly
-
- $file = null;
- $line = null;
- if (headers_sent($file, $line)) {
- error_log('Can not terminate session properly - headers were already sent in file: '.$file.' on line '.$line);
- }
-
- // now let's try to get a new session id and delete the old one
- $this->justloggedout = true;
- session_regenerate_id(true);
- $this->justloggedout = false;
-
- // write the new session
- session_write_close();
- }
-
- /**
- * No more changes in session expected.
- * Unblocks the sessions, other scripts may start executing in parallel.
- * @return void
- */
- public function write_close() {
- if (NO_MOODLE_COOKIES) {
- return;
- }
-
- session_write_close();
- }
-
- /**
- * Initialise $USER object, handles google access
- * and sets up not logged in user properly.
- *
- * @return void
- */
- protected function check_user_initialised() {
- global $CFG;
-
- if (isset($_SESSION['USER']->id)) {
- // already set up $USER
- return;
- }
-
- $user = null;
-
- if (!empty($CFG->opentogoogle) and !NO_MOODLE_COOKIES) {
- if (is_web_crawler()) {
- $user = guest_user();
- }
- if (!empty($CFG->guestloginbutton) and !$user and !empty($_SERVER['HTTP_REFERER'])) {
- // automaticaly 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();
- }
- }
- }
-
- if (!$user) {
- $user = new stdClass();
- $user->id = 0; // to enable proper function of $CFG->notloggedinroleid hack
- if (isset($CFG->mnet_localhost_id)) {
- $user->mnethostid = $CFG->mnet_localhost_id;
- } else {
- $user->mnethostid = 1;
- }
- }
- session_set_user($user);
- }
-
- /**
- * Does various session security checks
- * @global void
- */
- protected function check_security() {
- global $CFG;
-
- if (NO_MOODLE_COOKIES) {
- return;
- }
-
- 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
- $this->terminate_current();
- print_error('sessionipnomatch2', 'error');
- }
- }
- }
-
- /**
- * Prepare cookies and various system settings
- */
- protected 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 and sessioncookiepath variable if it isn't already
- if (!isset($CFG->sessioncookie)) {
- $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(${'MoodleSession'.$CFG->sessioncookie});
- unset($_GET['MoodleSession'.$CFG->sessioncookie]);
- unset($_POST['MoodleSession'.$CFG->sessioncookie]);
- unset($_REQUEST['MoodleSession'.$CFG->sessioncookie]);
-
- //compatibility hack for Moodle Cron, cookies not deleted, but set to "deleted" - should not be needed with NO_MOODLE_COOKIES in cron.php now
- if (!empty($_COOKIE['MoodleSession'.$CFG->sessioncookie]) && $_COOKIE['MoodleSession'.$CFG->sessioncookie] == "deleted") {
- unset($_COOKIE['MoodleSession'.$CFG->sessioncookie]);
- }
- }
-
- /**
- * Init session storage.
- */
- protected abstract function init_session_storage();
-}
-
-
-/**
- * Legacy moodle sessions stored in files, not recommended any more.
- *
- * @package core
- * @subpackage session
- * @copyright 2009 Petr Skoda {@link http://skodak.org}
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class legacy_file_session extends session_stub {
- /**
- * Init session storage.
- */
- protected function init_session_storage() {
- global $CFG;
-
- ini_set('session.save_handler', 'files');
-
- // Some distros disable GC by setting probability to 0
- // overriding the PHP default of 1
- // (gc_probability is divided by gc_divisor, which defaults to 1000)
- if (ini_get('session.gc_probability') == 0) {
- ini_set('session.gc_probability', 1);
- }
-
- ini_set('session.gc_maxlifetime', $CFG->sessiontimeout);
-
- // make sure sessions dir exists and is writable, throws exception if not
- make_upload_directory('sessions');
-
- // Need to disable debugging since disk_free_space()
- // will fail on very large partitions (see MDL-19222)
- $freespace = @disk_free_space($CFG->dataroot.'/sessions');
- if (!($freespace > 2048) and $freespace !== false) {
- print_error('sessiondiskfull', 'error');
- }
- ini_set('session.save_path', $CFG->dataroot .'/sessions');
- }
- /**
- * Check for existing session with id $sid
- * @param unknown_type $sid
- * @return boolean true if session found.
- */
- public function session_exists($sid){
- global $CFG;
-
- $sid = clean_param($sid, PARAM_FILE);
- $sessionfile = "$CFG->dataroot/sessions/sess_$sid";
- return file_exists($sessionfile);
- }
-}
-
-
-/**
- * Recommended moodle session storage.
- *
- * @package core
- * @subpackage session
- * @copyright 2009 Petr Skoda {@link http://skodak.org}
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class database_session extends session_stub {
- /** @var stdClass $record session record */
- protected $record = 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 hash of the session data content */
- protected $lasthash = null;
-
- public function __construct() {
- global $DB;
- $this->database = $DB;
- parent::__construct();
-
- if (!empty($this->record->state)) {
- // something is very wrong
- session_kill($this->record->sid);
-
- if ($this->record->state == 9) {
- print_error('dbsessionmysqlpacketsize', 'error');
- }
- }
- }
-
- /**
- * Check for existing session with id $sid
- * @param string $sid
- * @return boolean true if session found.
- */
- public function session_exists($sid){
- global $CFG;
- try {
- $sql = "SELECT * FROM {sessions} WHERE timemodified < ? AND sid=? AND state=?";
- $params = array(time() + $CFG->sessiontimeout, $sid, 0);
-
- return $this->database->record_exists_sql($sql, $params);
- } catch (dml_exception $ex) {
- error_log('Error checking existance of database session');
- return false;
- }
- }
-
- /**
- * Init session storage.
- */
- protected function init_session_storage() {
- global $CFG;
-
- // gc only from CRON - individual user timeouts now checked during each access
- ini_set('session.gc_probability', 0);
-
- ini_set('session.gc_maxlifetime', $CFG->sessiontimeout);
-
- $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) {
- print_error('dbsessionhandlerproblem', 'error');
- }
- }
-
- /**
- * 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) {
- 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 (isset($this->record->id)) {
- try {
- $this->database->release_session_lock($this->record->id);
- } catch (Exception $ex) {
- // ignore any problems
- }
- }
- $this->record = 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) {
- global $CFG;
-
- if ($this->record and $this->record->sid != $sid) {
- error_log('Weird error reading database session - mismatched sid');
- $this->failed = true;
- return '';
- }
-
- try {
- // Do not fetch full record yet, wait until it is locked.
- if (!$record = $this->database->get_record('sessions', array('sid'=>$sid), 'id, userid')) {
- $record = new stdClass();
- $record->state = 0;
- $record->sid = $sid;
- $record->sessdata = null;
- $record->userid = 0;
- $record->timecreated = $record->timemodified = time();
- $record->firstip = $record->lastip = getremoteaddr();
- $record->id = $this->database->insert_record_raw('sessions', $record);
- }
- } catch (Exception $ex) {
- // do not rethrow exceptions here, we need this to work somehow before 1.9.x upgrade and during install
- error_log('Can not read or insert database sessions');
- $this->failed = true;
- return '';
- }
-
- try {
- if (!empty($CFG->sessionlockloggedinonly) and (isguestuser($record->userid) or empty($record->userid))) {
- // No session locking for guests and not-logged-in users,
- // these users mostly read stuff, there should not be any major
- // session race conditions. Hopefully they do not access other
- // pages while being logged-in.
- } else {
- $this->database->get_session_lock($record->id, SESSION_ACQUIRE_LOCK_TIMEOUT);
- }
- } catch (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 soon after access control checks
- error_log('Can not obtain session lock');
- $this->failed = true;
- throw $ex;
- }
-
- // 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))) {
- error_log('Cannot read session record');
- $this->failed = true;
- return '';
- }
-
- // verify timeout
- if ($record->timemodified + $CFG->sessiontimeout < time()) {
- $ignoretimeout = false;
- if (!empty($record->userid)) { // skips not logged in
- if ($user = $this->database->get_record('user', array('id'=>$record->userid))) {
-
- // Refresh session if logged as a guest
- if (isguestuser($user)) {
- $ignoretimeout = true;
- } else {
- $authsequence = get_enabled_auth_plugins(); // auths, in sequence
- foreach($authsequence as $authname) {
- $authplugin = get_auth_plugin($authname);
- if ($authplugin->ignore_timeout_hook($user, $record->sid, $record->timecreated, $record->timemodified)) {
- $ignoretimeout = true;
- break;
- }
- }
- }
- }
- }
- if ($ignoretimeout) {
- //refresh session
- $record->timemodified = time();
- try {
- $this->database->update_record('sessions', $record);
- } catch (Exception $ex) {
- // very unlikely error
- error_log('Can not refresh database session');
- $this->failed = true;
- throw $ex;
- }
- } else {
- //time out session
- $record->state = 0;
- $record->sessdata = null;
- $record->userid = 0;
- $record->timecreated = $record->timemodified = time();
- $record->firstip = $record->lastip = getremoteaddr();
- try {
- $this->database->update_record('sessions', $record);
- } catch (Exception $ex) {
- // very unlikely error
- error_log('Can not time out database session');
- $this->failed = true;
- throw $ex;
- }
- }
- }
-
- if (is_null($record->sessdata)) {
- $data = '';
- $this->lasthash = sha1('');
- } else {
- $data = base64_decode($record->sessdata);
- $this->lasthash = sha1($record->sessdata);
- }
-
- unset($record->sessdata); // conserve memory
- $this->record = $record;
-
- 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) {
- global $USER;
-
- // TODO: MDL-20625 we need to rollback all active transactions and log error if any open needed
-
- if ($this->failed) {
- // do not write anything back - we failed to start the session properly
- return false;
- }
-
- $userid = 0;
- if (!empty($USER->realuser)) {
- $userid = $USER->realuser;
- } else if (!empty($USER->id)) {
- $userid = $USER->id;
- }
-
- if (isset($this->record->id)) {
- $data = base64_encode($session_data); // There might be some binary mess :-(
-
- // Skip db update if nothing changed,
- // do not update the timemodified each second.
- $hash = sha1($data);
- if ($this->lasthash === $hash
- and $this->record->userid == $userid
- and (time() - $this->record->timemodified < 20)
- and $this->record->lastip == getremoteaddr()
- ) {
- // No need to update anything!
- return true;
- }
-
- $this->record->sessdata = $data;
- $this->record->userid = $userid;
- $this->record->timemodified = time();
- $this->record->lastip = getremoteaddr();
-
- try {
- $this->database->update_record_raw('sessions', $this->record);
- $this->lasthash = $hash;
- } catch (dml_exception $ex) {
- if ($this->database->get_dbfamily() === 'mysql') {
- try {
- $this->database->set_field('sessions', 'state', 9, array('id'=>$this->record->id));
- } catch (Exception $ignored) {
- }
- error_log('Can not write database session - please verify max_allowed_packet is at least 4M!');
- } else {
- error_log('Can not write database session');
- }
- return false;
- } catch (Exception $ex) {
- error_log('Can not write database session');
- return false;
- }
-
- } else {
- // fresh new session
- try {
- $record = new stdClass();
- $record->state = 0;
- $record->sid = $sid;
- $record->sessdata = base64_encode($session_data); // there might be some binary mess :-(
- $record->userid = $userid;
- $record->timecreated = $record->timemodified = time();
- $record->firstip = $record->lastip = getremoteaddr();
- $record->id = $this->database->insert_record_raw('sessions', $record);
-
- $this->record = $this->database->get_record('sessions', array('id'=>$record->id));
- $this->lasthash = sha1($record->sessdata);
-
- $this->database->get_session_lock($this->record->id, SESSION_ACQUIRE_LOCK_TIMEOUT);
- } catch (Exception $ex) {
- // this should not happen
- error_log('Can not write new database session or acquire session lock');
- $this->failed = true;
- return false;
- }
- }
-
- 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) {
- session_kill($sid);
-
- if (isset($this->record->id) and $this->record->sid === $sid) {
- try {
- $this->database->release_session_lock($this->record->id);
- } catch (Exception $ex) {
- // ignore problems
- }
- $this->record = null;
- }
-
- $this->lasthash = null;
-
- 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) {
- session_gc();
- return true;
- }
-}
-
-
-/**
- * returns true if legacy session used.
- * @return bool true if legacy(==file) based session used
- */
-function session_is_legacy() {
- global $CFG, $DB;
- return ((isset($CFG->dbsessions) and !$CFG->dbsessions) or !$DB->session_lock_supported());
-}
-
-/**
- * Terminates all sessions, auth hooks are not executed.
- * Useful in upgrade scripts.
- */
-function session_kill_all() {
- global $CFG, $DB;
-
- // always check db table - custom session classes use sessions table
- try {
- $DB->delete_records('sessions');
- } catch (dml_exception $ignored) {
- // do not show any warnings - might be during upgrade/installation
- }
-
- if (session_is_legacy()) {
- $sessiondir = "$CFG->dataroot/sessions";
- if (is_dir($sessiondir)) {
- foreach (glob("$sessiondir/sess_*") as $filename) {
- @unlink($filename);
- }
- }
- }
-}
-
-/**
- * Mark session as accessed, prevents timeouts.
- * @param string $sid
- */
-function session_touch($sid) {
- global $CFG, $DB;
-
- // always check db table - custom session classes use sessions table
- try {
- $sql = "UPDATE {sessions} SET timemodified=? WHERE sid=?";
- $params = array(time(), $sid);
- $DB->execute($sql, $params);
- } catch (dml_exception $ignored) {
- // do not show any warnings - might be during upgrade/installation
- }
-
- if (session_is_legacy()) {
- $sid = clean_param($sid, PARAM_FILE);
- $sessionfile = clean_param("$CFG->dataroot/sessions/sess_$sid", PARAM_FILE);
- if (file_exists($sessionfile)) {
- // if the file is locked it means that it will be updated anyway
- @touch($sessionfile);
- }
- }
-}
-
-/**
- * Terminates one sessions, auth hooks are not executed.
- *
- * @param string $sid session id
- */
-function session_kill($sid) {
- global $CFG, $DB;
-
- // always check db table - custom session classes use sessions table
- try {
- $DB->delete_records('sessions', array('sid'=>$sid));
- } catch (dml_exception $ignored) {
- // do not show any warnings - might be during upgrade/installation
- }
-
- if (session_is_legacy()) {
- $sid = clean_param($sid, PARAM_FILE);
- $sessionfile = "$CFG->dataroot/sessions/sess_$sid";
- if (file_exists($sessionfile)) {
- @unlink($sessionfile);
- }
- }
-}
-
-/**
- * Terminates all sessions of one user, auth hooks are not executed.
- * NOTE: This can not work for file based sessions!
- *
- * @param int $userid user id
- */
-function session_kill_user($userid) {
- global $CFG, $DB;
-
- // always check db table - custom session classes use sessions table
- try {
- $DB->delete_records('sessions', array('userid'=>$userid));
- } catch (dml_exception $ignored) {
- // do not show any warnings - might be during upgrade/installation
- }
-
- if (session_is_legacy()) {
- // log error?
- }
-}
-
-/**
- * Session garbage collection
- * - verify timeout for all users
- * - kill sessions of all deleted users
- * - kill sessions of users with disabled plugins or 'nologin' plugin
- *
- * NOTE: this can not work when legacy file sessions used!
- */
-function session_gc() {
- global $CFG, $DB;
-
- $maxlifetime = $CFG->sessiontimeout;
-
- try {
- /// kill all sessions of deleted users
- $DB->delete_records_select('sessions', "userid IN (SELECT id FROM {user} WHERE deleted <> 0)");
-
- /// 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 allowed
- $auth_sequence = array_flip($auth_sequence);
- $notplugins = null;
- list($notplugins, $params) = $DB->get_in_or_equal($auth_sequence, SQL_PARAMS_QM, '', false);
- $DB->delete_records_select('sessions', "userid IN (SELECT id FROM {user} WHERE auth $notplugins)", $params);
-
- /// now get a list of time-out candidates
- $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 + ? < ? AND u.id <> ?";
- $params = array($maxlifetime, time(), $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) {
- if ($authplugin->ignore_timeout_hook($user, $user->sid, $user->s_timecreated, $user->s_timemodified)) {
- continue;
- }
- }
- $DB->delete_records('sessions', array('sid'=>$user->sid));
- }
- $rs->close();
-
- // Extending the timeout period for guest sessions as they are renewed.
- $purgebefore = time() - $maxlifetime;
- $purgebeforeguests = time() - ($maxlifetime * 5);
-
- // delete expired sessions for guest user account
- $DB->delete_records_select('sessions', 'userid = ? AND timemodified < ?', array($CFG->siteguest, $purgebeforeguests));
- // delete expired sessions for userid = 0 (not logged in)
- $DB->delete_records_select('sessions', 'userid = 0 AND timemodified < ?', array($purgebefore));
- } catch (dml_exception $ex) {
- error_log('Error gc-ing sessions');
- }
-}
/**
* Makes sure that $USER->sesskey exists, if $USER itself exists. It sets a new sesskey
@@ -1110,95 +147,6 @@ function get_moodle_cookie() {
}
}
-
-/**
- * Setup $USER object - called during login, loginas, etc.
- *
- * Call sync_user_enrolments() manually after log-in, or log-in-as.
- *
- * @param stdClass $user full user record object
- * @return void
- */
-function session_set_user($user) {
- $_SESSION['USER'] = $user;
- unset($_SESSION['USER']->description); // conserve memory
- 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 || defined('BEHAT_TEST')) {
- // phpunit tests use reversed reference
- global $USER;
- $USER = $_SESSION['USER'];
- $_SESSION['USER'] =& $USER;
- }
-}
-
-/**
- * Is current $USER logged-in-as somebody else?
- * @return bool
- */
-function session_is_loggedinas() {
- return !empty($_SESSION['USER']->realuser);
-}
-
-/**
- * Returns the $USER object ignoring current login-as session
- * @return stdClass user object
- */
-function session_get_realuser() {
- if (session_is_loggedinas()) {
- return $_SESSION['REALUSER'];
- } else {
- return $_SESSION['USER'];
- }
-}
-
-/**
- * Login as another user - no security checks here.
- * @param int $userid
- * @param stdClass $context
- * @return void
- */
-function session_loginas($userid, $context) {
- global $USER;
-
- if (session_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.
- session_set_user($user);
- $event->trigger();
-}
-
/**
* Sets up current user and course environment (lang, etc.) in cron.
* Do not use outside of cron script!
@@ -1226,13 +174,13 @@ function cron_setup_user($user = NULL, $course = NULL) {
if (!$user) {
// cached default cron user (==modified admin for now)
- session_set_user($cronuser);
+ \core\session\manager::set_user($cronuser);
$_SESSION['SESSION'] = $cronsession;
} else {
// emulate real user session - needed for caps in cron
if ($_SESSION['USER']->id != $user->id) {
- session_set_user($user);
+ \core\session\manager::set_user($user);
$_SESSION['SESSION'] = new stdClass();
}
}
diff --git a/lib/setup.php b/lib/setup.php
index 40cab06f4da..f3c51dd1d7f 100644
--- a/lib/setup.php
+++ b/lib/setup.php
@@ -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);
diff --git a/lib/setuplib.php b/lib/setuplib.php
index 492eb960f54..fcbad372157 100644
--- a/lib/setuplib.php
+++ b/lib/setuplib.php
@@ -51,19 +51,6 @@ define('MEMORY_EXTRA', -3);
/** Extremely large memory limit - not recommended for standard scripts */
define('MEMORY_HUGE', -4);
-/**
- * Software maturity levels used by the core and plugins
- */
-define('MATURITY_ALPHA', 50); // internals can be tested using white box techniques
-define('MATURITY_BETA', 100); // feature complete, ready for preview and testing
-define('MATURITY_RC', 150); // tested, will be released unless there are fatal bugs
-define('MATURITY_STABLE', 200); // ready for production deployment
-
-/**
- * Special value that can be used in $plugin->dependencies in version.php files.
- */
-define('ANY_VERSION', 'any');
-
/**
* Simple class. It is usually used instead of stdClass because it looks
@@ -1178,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.
}
diff --git a/lib/tests/authlib_test.php b/lib/tests/authlib_test.php
index 851b14106d3..2abcd82d96e 100644
--- a/lib/tests/authlib_test.php
+++ b/lib/tests/authlib_test.php
@@ -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));
diff --git a/lib/tests/behat/behat_hooks.php b/lib/tests/behat/behat_hooks.php
index b0e17e3b7eb..08e64a2df03 100644
--- a/lib/tests/behat/behat_hooks.php
+++ b/lib/tests/behat/behat_hooks.php
@@ -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()) {
diff --git a/lib/tests/moodlelib_test.php b/lib/tests/moodlelib_test.php
index 2a70acc1c17..4ef9da68073 100644
--- a/lib/tests/moodlelib_test.php
+++ b/lib/tests/moodlelib_test.php
@@ -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());
diff --git a/lib/tests/session_manager_test.php b/lib/tests/session_manager_test.php
new file mode 100644
index 00000000000..d964b9c459d
--- /dev/null
+++ b/lib/tests/session_manager_test.php
@@ -0,0 +1,374 @@
+.
+
+/**
+ * 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
+ */
+ 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&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);
+ }
+}
diff --git a/lib/tests/sessionlib_test.php b/lib/tests/sessionlib_test.php
deleted file mode 100644
index 5f3b0329199..00000000000
--- a/lib/tests/sessionlib_test.php
+++ /dev/null
@@ -1,88 +0,0 @@
-.
-
-/**
- * Unit tests for (some of) ../sessionlib.php.
- *
- * @package core_session
- * @category phpunit
- * @copyright 2103 Rajesh Taneja
- * @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
- * @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&user=$user->id", "$oldfullname -> $newfullname");
- $this->assertEventLegacyLogData($expectedlogdata, $event);
- }
-}
diff --git a/lib/upgrade.txt b/lib/upgrade.txt
index ae667267642..62d2037706e 100644
--- a/lib/upgrade.txt
+++ b/lib/upgrade.txt
@@ -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()
diff --git a/lib/weblib.php b/lib/weblib.php
index fdde141ad5f..20e99973f02 100644
--- a/lib/weblib.php
+++ b/lib/weblib.php
@@ -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);
diff --git a/login/change_password.php b/login/change_password.php
index 73f0e8e5b19..88a7cf50fb4 100644
--- a/login/change_password.php
+++ b/login/change_password.php
@@ -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');
}
diff --git a/login/token.php b/login/token.php
index 412b24725e8..9fd88b069ef 100644
--- a/login/token.php
+++ b/login/token.php
@@ -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;
diff --git a/mod/assignment/type/online/assignment.class.php b/mod/assignment/type/online/assignment.class.php
index 63c436728d1..8888caa1c06 100644
--- a/mod/assignment/type/online/assignment.class.php
+++ b/mod/assignment/type/online/assignment.class.php
@@ -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);
}
diff --git a/mod/chat/chat_ajax.php b/mod/chat/chat_ajax.php
index d9e6b374829..8de18fc949c 100644
--- a/mod/chat/chat_ajax.php
+++ b/mod/chat/chat_ajax.php
@@ -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);
diff --git a/mod/chat/gui_header_js/insert.php b/mod/chat/gui_header_js/insert.php
index 6b842d2977b..0573fcf3472 100644
--- a/mod/chat/gui_header_js/insert.php
+++ b/mod/chat/gui_header_js/insert.php
@@ -30,7 +30,7 @@ if (isguestuser()) {
print_error('noguests');
}
-session_get_instance()->write_close();
+\core\session\manager::write_close();
/// Delete old users now
diff --git a/mod/quiz/report/overview/report.php b/mod/quiz/report/overview/report.php
index 38bb483a769..5f62bc12c57 100644
--- a/mod/quiz/report/overview/report.php
+++ b/mod/quiz/report/overview/report.php
@@ -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);
}
diff --git a/report/log/index.php b/report/log/index.php
index 6468d0427e1..ad946395b75 100644
--- a/report/log/index.php
+++ b/report/log/index.php
@@ -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');
diff --git a/report/loglive/index.php b/report/loglive/index.php
index 90e3eb90671..7bb84d82a52 100644
--- a/report/loglive/index.php
+++ b/report/loglive/index.php
@@ -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;
diff --git a/repository/lib.php b/repository/lib.php
index 276c0e9edb0..4231205b0ac 100644
--- a/repository/lib.php
+++ b/repository/lib.php
@@ -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;
}
diff --git a/repository/tests/repositorylib_test.php b/repository/tests/repositorylib_test.php
index 3d41ab0f8a9..fefb6bec4aa 100644
--- a/repository/tests/repositorylib_test.php
+++ b/repository/tests/repositorylib_test.php
@@ -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;
diff --git a/rss/file.php b/rss/file.php
index 6d7dbc03042..cdd6f8971d6 100644
--- a/rss/file.php
+++ b/rss/file.php
@@ -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;
diff --git a/theme/mymobile/renderers.php b/theme/mymobile/renderers.php
index 2969e972d2e..fa812698944 100644
--- a/theme/mymobile/renderers.php
+++ b/theme/mymobile/renderers.php
@@ -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 = " [wwwroot/course/loginas.php?id=$course->id&sesskey=".sesskey()."\">$fullname] ";
} 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 = ' [$fullname] ';
} 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');
}
diff --git a/user/editadvanced.php b/user/editadvanced.php
index 9728bbdcc81..924ebb1a12f 100644
--- a/user/editadvanced.php
+++ b/user/editadvanced.php
@@ -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
diff --git a/user/index.php b/user/index.php
index 8130981ae6f..c6029b1cee1 100644
--- a/user/index.php
+++ b/user/index.php
@@ -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'));
}
diff --git a/webservice/lib.php b/webservice/lib.php
index 8e1898f9327..1723d723740 100644
--- a/webservice/lib.php
+++ b/webservice/lib.php
@@ -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');
}