Skip to content

Commit

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

Limitations:
 * Session access time is now updated right after session start.
 * Support for $CFG->sessionlockloggedinonly was removed.
 * First request does not update userid in sessions table.
 * The timeouts may break badly if server hosting forces PHP.ini session settings.
 * The session GC is a lot slower, we do not rely on external session timeouts.
 * There cannot be any hooks triggered at the session write time.
 * File and memcached handlers do not support session lock acquire timeouts.
 * Some low level PHP session functions can not be used directly in Moodle code.
  • Loading branch information
skodak committed Sep 21, 2013
1 parent 81881cb commit d79d5ac
Show file tree
Hide file tree
Showing 77 changed files with 2,266 additions and 1,349 deletions.
4 changes: 2 additions & 2 deletions admin/auth.php
Expand Up @@ -51,7 +51,7 @@
if ($auth == $CFG->registerauth) {
set_config('registerauth', '');
}
session_gc(); // remove stale sessions
\core\session\manager::gc(); // Remove stale sessions.
break;

case 'enable':
Expand All @@ -61,7 +61,7 @@
$authsenabled = array_unique($authsenabled);
set_config('auth', implode(',', $authsenabled));
}
session_gc(); // remove stale sessions
\core\session\manager::gc(); // Remove stale sessions.
break;

case 'down':
Expand Down
2 changes: 1 addition & 1 deletion admin/cli/upgrade.php
Expand Up @@ -172,7 +172,7 @@
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);
Expand Down
2 changes: 1 addition & 1 deletion admin/cron.php
Expand Up @@ -53,7 +53,7 @@
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)) {
Expand Down
2 changes: 1 addition & 1 deletion admin/index.php
Expand Up @@ -163,7 +163,7 @@
$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');
Expand Down
4 changes: 3 additions & 1 deletion admin/settings/server.php
Expand Up @@ -35,7 +35,9 @@

// "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),
Expand Down
2 changes: 1 addition & 1 deletion admin/tool/assignmentupgrade/batchupgrade.php
Expand Up @@ -47,7 +47,7 @@
}
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'));
Expand Down
4 changes: 2 additions & 2 deletions admin/tool/dbtransfer/locallib.php
Expand Up @@ -52,7 +52,7 @@
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');
Expand All @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion admin/tool/generator/cli/maketestcourse.php
Expand Up @@ -90,7 +90,7 @@
}

// 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']));
Expand Down
2 changes: 1 addition & 1 deletion admin/tool/generator/cli/maketestsite.php
Expand Up @@ -88,7 +88,7 @@
}

// 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']));
Expand Down
2 changes: 1 addition & 1 deletion admin/tool/uploaduser/index.php
Expand Up @@ -687,7 +687,7 @@
}

if ($dologout) {
session_kill_user($existinguser->id);
\core\session\manager::kill_user_sessions($existinguser->id);
}

} else {
Expand Down
6 changes: 3 additions & 3 deletions admin/user.php
Expand Up @@ -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)));
}
Expand Down Expand Up @@ -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);
}
}
Expand Down
2 changes: 1 addition & 1 deletion admin/user/user_bulk_delete.php
Expand Up @@ -34,7 +34,7 @@
}
}
$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;
Expand Down
2 changes: 1 addition & 1 deletion auth/ldap/auth.php
Expand Up @@ -808,7 +808,7 @@ function sync_users($do_updates=true) {
$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');
Expand Down
10 changes: 5 additions & 5 deletions auth/mnet/auth.php
Expand Up @@ -141,7 +141,7 @@ function start_jump_session($mnethostid, $wantsurl, $wantsurlbackhere=false) {
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');
}

Expand Down Expand Up @@ -919,7 +919,7 @@ function keepalive_server($array) {
$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);
}
}
}
Expand Down Expand Up @@ -1076,7 +1076,7 @@ function kill_children($username, $useragent) {
array('useragent'=>$useragent, 'userid'=>$userid));

if (isset($remoteclient) && isset($remoteclient->id)) {
session_kill_user($userid);
\core\session\manager::kill_user_sessions($userid);
}
return $returnstring;
}
Expand All @@ -1096,7 +1096,7 @@ function kill_child($username, $useragent) {
$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;
Expand All @@ -1113,7 +1113,7 @@ function end_local_sessions(&$sessionArray) {
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;
}
Expand Down
2 changes: 1 addition & 1 deletion auth/shibboleth/index.php
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion badges/ajax.php
Expand Up @@ -34,7 +34,7 @@
$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();

Expand Down
2 changes: 1 addition & 1 deletion blocks/html/lib.php
Expand Up @@ -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);
}

Expand Down
2 changes: 1 addition & 1 deletion blocks/mnet_hosts/block_mnet_hosts.php
Expand Up @@ -25,7 +25,7 @@ function get_content() {
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'));
Expand Down
2 changes: 1 addition & 1 deletion calendar/tests/calendartype_test.php
Expand Up @@ -277,6 +277,6 @@ private function datetime_field_submission_test($type, $date) {
*/
private function set_calendar_type($type) {
$this->user->calendartype = $type;
session_set_user($this->user);
\core\session\manager::set_user($this->user);
}
}
20 changes: 16 additions & 4 deletions config-dist.php
Expand Up @@ -224,10 +224,22 @@
// 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
Expand Down
4 changes: 2 additions & 2 deletions course/loginas.php
Expand Up @@ -11,7 +11,7 @@
$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();

Expand Down Expand Up @@ -61,7 +61,7 @@
}

// 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');
Expand Down
2 changes: 1 addition & 1 deletion draftfile.php
Expand Up @@ -84,5 +84,5 @@
// ========================================
// 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!
2 changes: 1 addition & 1 deletion enrol/index.php
Expand Up @@ -46,7 +46,7 @@
$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);
}

Expand Down
2 changes: 1 addition & 1 deletion file.php
Expand Up @@ -111,7 +111,7 @@
// ========================================
// 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);


1 change: 1 addition & 0 deletions lang/en/error.php
Expand Up @@ -466,6 +466,7 @@
$string['sessionwaiterr'] = 'Timed out while waiting for session lock.<br />Wait for your current requests to finish and try again later.';
$string['sessioncookiesdisable'] = 'Incorrect use of require_key_login() - session cookies must be disabled!';
$string['sessiondiskfull'] = 'The session partition is full. It is not possible to login at this time.<br /><br />Please notify server administrator.';
$string['sessionhandlerproblem'] = 'Session handler is misconfigured';
$string['sessionerroruser'] = 'Your session has timed out. Please login again.';
$string['sessionerroruser2'] = 'A server error that affects your login session was detected. Please login again or restart your browser.';
$string['sessionipnomatch'] = 'Sorry, but your IP number seems to have changed from when you first logged in. This security feature prevents crackers stealing your identity while logged in to this site. Normal users should not be seeing this message - please ask the site administrator for help.';
Expand Down
4 changes: 1 addition & 3 deletions lib/authlib.php
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion lib/classes/event/user_loggedin.php
Expand Up @@ -96,7 +96,7 @@ protected function init() {
/**
* Custom validation.
*
* @throws coding_exception when validation does not pass.
* @throws \coding_exception when validation does not pass.
* @return void
*/
protected function validate_data() {
Expand Down
1 change: 1 addition & 0 deletions lib/classes/event/user_loggedout.php
Expand Up @@ -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;
Expand Down

0 comments on commit d79d5ac

Please sign in to comment.