Permalink
Browse files

MDL-31501 rework user session architecture

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

Limitations:
 * Session access time is now updated right after session start.
 * Support for $CFG->sessionlockloggedinonly was removed.
 * First request does not update userid in sessions table.
 * The timeouts may break badly if server hosting forces PHP.ini session settings.
 * The session GC is a lot slower, we do not rely on external session timeouts.
 * There cannot be any hooks triggered at the session write time.
 * File and memcached handlers do not support session lock acquire timeouts.
 * Some low level PHP session functions can not be used directly in Moodle code.
  • Loading branch information...
1 parent 81881cb commit d79d5ac2760b7c5508aca8a7b319543ce08e1b17 @skodak skodak committed Sep 8, 2013
Showing with 2,266 additions and 1,349 deletions.
  1. +2 −2 admin/auth.php
  2. +1 −1 admin/cli/upgrade.php
  3. +1 −1 admin/cron.php
  4. +1 −1 admin/index.php
  5. +3 −1 admin/settings/server.php
  6. +1 −1 admin/tool/assignmentupgrade/batchupgrade.php
  7. +2 −2 admin/tool/dbtransfer/locallib.php
  8. +1 −1 admin/tool/generator/cli/maketestcourse.php
  9. +1 −1 admin/tool/generator/cli/maketestsite.php
  10. +1 −1 admin/tool/uploaduser/index.php
  11. +3 −3 admin/user.php
  12. +1 −1 admin/user/user_bulk_delete.php
  13. +1 −1 auth/ldap/auth.php
  14. +5 −5 auth/mnet/auth.php
  15. +1 −1 auth/shibboleth/index.php
  16. +1 −1 badges/ajax.php
  17. +1 −1 blocks/html/lib.php
  18. +1 −1 blocks/mnet_hosts/block_mnet_hosts.php
  19. +1 −1 calendar/tests/calendartype_test.php
  20. +16 −4 config-dist.php
  21. +2 −2 course/loginas.php
  22. +1 −1 draftfile.php
  23. +1 −1 enrol/index.php
  24. +1 −1 file.php
  25. +1 −0 lang/en/error.php
  26. +1 −3 lib/authlib.php
  27. +1 −1 lib/classes/event/user_loggedin.php
  28. +1 −0 lib/classes/event/user_loggedout.php
  29. +315 −0 lib/classes/session/database.php
  30. +34 −0 lib/classes/session/exception.php
  31. +120 −0 lib/classes/session/file.php
  32. +63 −0 lib/classes/session/handler.php
  33. +754 −0 lib/classes/session/manager.php
  34. +187 −0 lib/classes/session/memcached.php
  35. +4 −3 lib/cronlib.php
  36. +2 −2 lib/datalib.php
  37. +134 −0 lib/deprecatedlib.php
  38. +31 −4 lib/dml/moodle_database.php
  39. +3 −4 lib/dml/mssql_native_moodle_database.php
  40. +3 −4 lib/dml/mysqli_native_moodle_database.php
  41. +7 −4 lib/dml/oci_native_moodle_database.php
  42. +3 −4 lib/dml/pgsql_native_moodle_database.php
  43. +3 −4 lib/dml/sqlite3_pdo_moodle_database.php
  44. +3 −4 lib/dml/sqlsrv_native_moodle_database.php
  45. +26 −26 lib/filelib.php
  46. +1 −1 lib/installlib.php
  47. +58 −36 lib/moodlelib.php
  48. +4 −4 lib/navigationlib.php
  49. +3 −3 lib/outputrenderers.php
  50. +1 −1 lib/phpunit/classes/advanced_testcase.php
  51. +1 −1 lib/phpunit/classes/util.php
  52. +1 −1 lib/phpunit/tests/advanced_test.php
  53. +2 −1,054 lib/sessionlib.php
  54. +9 −6 lib/setup.php
  55. +1 −1 lib/setuplib.php
  56. +0 −20 lib/tests/authlib_test.php
  57. +1 −1 lib/tests/behat/behat_hooks.php
  58. +24 −1 lib/tests/moodlelib_test.php
  59. +374 −0 lib/tests/session_manager_test.php
  60. +0 −88 lib/tests/sessionlib_test.php
  61. +12 −0 lib/upgrade.txt
  62. +4 −5 lib/weblib.php
  63. +1 −1 login/change_password.php
  64. +2 −3 login/token.php
  65. +1 −1 mod/assignment/type/online/assignment.class.php
  66. +1 −1 mod/chat/chat_ajax.php
  67. +1 −1 mod/chat/gui_header_js/insert.php
  68. +1 −1 mod/quiz/report/overview/report.php
  69. +1 −1 report/log/index.php
  70. +1 −1 report/loglive/index.php
  71. +1 −1 repository/lib.php
  72. +1 −1 repository/tests/repositorylib_test.php
  73. +1 −1 rss/file.php
  74. +5 −5 theme/mymobile/renderers.php
  75. +2 −2 user/editadvanced.php
  76. +1 −1 user/index.php
  77. +4 −6 webservice/lib.php
View
@@ -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':
@@ -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':
@@ -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);
View
@@ -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)) {
View
@@ -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');
@@ -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),
@@ -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'));
@@ -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');
@@ -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);
@@ -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']));
@@ -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']));
@@ -687,7 +687,7 @@
}
if ($dologout) {
- session_kill_user($existinguser->id);
+ \core\session\manager::kill_user_sessions($existinguser->id);
}
} else {
View
@@ -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);
}
}
@@ -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;
View
@@ -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');
View
@@ -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');
}
@@ -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);
}
}
}
@@ -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;
}
@@ -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;
@@ -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;
}
@@ -48,7 +48,7 @@
&& $user = authenticate_user_login($frm->username, $frm->password)) {
enrol_check_plugins($user);
- session_set_user($user);
+ \core\session\manager::set_user($user);
$USER->loggedin = true;
$USER->site = $CFG->wwwroot; // for added security, store the site in the
View
@@ -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();
View
@@ -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);
}
@@ -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'));
@@ -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);
}
}
View
@@ -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
View
@@ -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();
@@ -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');
View
@@ -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!
View
@@ -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);
}
View
@@ -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);
View
@@ -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.';
View
@@ -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;
@@ -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() {
@@ -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;
Oops, something went wrong.

0 comments on commit d79d5ac

Please sign in to comment.