From: skodak Date: Sun, 18 Jan 2009 11:19:40 +0000 (+0000) Subject: MDL-17942 implemented gc and timeouts for db sessions X-Git-Url: http://git.mjollnir.org/gw?a=commitdiff_plain;h=dd9e22f8711c13f6766508fd10b0ebafc1588c0a;p=moodle.git MDL-17942 implemented gc and timeouts for db sessions --- diff --git a/admin/cron.php b/admin/cron.php index 49d92a6e1e..661cccf9ec 100644 --- a/admin/cron.php +++ b/admin/cron.php @@ -37,7 +37,7 @@ } /// extra safety - @session_get_instance()->write_close(); + session_get_instance()->write_close(); /// check if execution allowed if (isset($_SERVER['REMOTE_ADDR'])) { // if the script is accessed via the web. @@ -83,6 +83,12 @@ mtrace("Server Time: ".date('r',$timenow)."\n\n"); + +/// Session gc + + mtrace("Cleaning up stale sessions"); + session_get_instance()->gc(); + /// Run all cron jobs for each module mtrace("Starting activity modules"); @@ -548,10 +554,6 @@ } - //Unset session variables and destroy it - @session_unset(); - @session_destroy(); - mtrace("Cron script completed correctly"); $difftime = microtime_diff($starttime, microtime()); diff --git a/lib/moodlelib.php b/lib/moodlelib.php index 50c7ee71ee..3abebbee92 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -2170,7 +2170,7 @@ function require_login($courseorid=0, $autologinguest=true, $cm=null, $setwantsu * @uses $USER */ function require_logout() { - global $USER, $CFG, $SESSION; + global $USER; if (isloggedin()) { add_to_log(SITEID, "user", "logout", "view.php?id=$USER->id&course=".SITEID, $USER->id, 0, $USER->id); diff --git a/lib/sessionlib.php b/lib/sessionlib.php index 1435d7ef70..f6d98842ca 100644 --- a/lib/sessionlib.php +++ b/lib/sessionlib.php @@ -39,7 +39,8 @@ interface moodle_session { public function terminate_current(); /** - * Terminates all sessions. + * Terminates all sessions, auth hooks are not executed. + * Useful in ugrade scripts. */ public function terminate_all(); @@ -50,6 +51,13 @@ interface moodle_session { */ public function write_close(); + /** + * Session garbage collection + * - verify timeout for all users + * - kill sessions of all deleted users + * - kill sessions of users with disabled plugins or 'nologin' plugin + */ + public function gc(); } /** @@ -157,7 +165,8 @@ abstract class session_stub implements moodle_session { } /** - * Initialise $USER object, handles google access. + * Initialise $USER object, handles google access + * and sets up not logged in user properly. * * @return void */ @@ -309,9 +318,24 @@ class legacy_file_session extends session_stub { ini_set('session.save_path', $CFG->dataroot .'/sessions'); } + /** + * Terminates all sessions, auth hooks are not executed. + * Useful in ugrade scripts. + */ public function terminate_all() { // TODO } + + /** + * Session garbage collection + * - verify timeout for all users + * - kill sessions of all deleted users + * - kill sessions of users with disabled plugins or 'nologin' plugin + */ + public function gc() { + // difficult/slow + } + } /** @@ -321,11 +345,17 @@ class database_session extends session_stub { protected $record = null; protected $database = null; + public function __construct() { + global $DB; + $this->database = $DB; + parent::__construct(); + } + protected function init_session_storage() { global $CFG; - // ini_get('session.gc_probability') == 0 means we rely on cron cleanup only - // TODO: implement cron db session cleanup + // gc only from CRON - individual user timeouts now checked during each access + ini_set('session.gc_probability', 0); if (empty($CFG->sessiontimeout)) { $CFG->sessiontimeout = 7200; @@ -343,19 +373,71 @@ class database_session extends session_stub { } } + /** + * Terminates all sessions, auth hooks are not executed. + * Useful in ugrade scripts. + */ public function terminate_all() { try { // do not show any warnings - might be during upgrade/installation $this->database->delete_records('sessions'); } catch (dml_exception $ignored) { - } } - public function handler_open($save_path, $session_name) { - global $DB; + /** + * Session garbage collection + * - verify timeout for all users + * - kill sessions of all deleted users + * - kill sessions of users with disabled plugins or 'nologin' plugin + */ + public function gc() { + global $CFG; + $maxlifetime = $CFG->sessiontimeout; - $this->database = $DB; + if (empty($CFG->rolesactive)) { + return; + } + + try { + /// kill all sessions of deleted users + $this->database->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) = $this->database->get_in_or_equal($auth_sequence, SQL_PARAMS_QM, '', false); + $this->database->delete_records_select('sessions', "userid IN (SELECT id FROM {user} WHERE auth $notplugins)", $params); + + /// now get a list of time-out candidates + $sql = "SELECT s.*, u.auth + FROM {sessions} s + JOIN {user} u ON u.id = s.userid + WHERE s.timemodified + ? < ?"; + $params = array($maxlifetime, time()); + + $authplugins = array(); + foreach($auth_sequence as $authname) { + $authplugins[$authname] = get_auth_plugin($authname); + } + $records = $this->database->get_records_sql($sql, $params); + foreach ($records as $record) { + foreach ($authplugins as $authplugin) { + if ($authplugin->ignore_timeout($record->userid, $records->auth, $record->timecreated, $record->timemodified)) { + continue; + } + } + $this->database->delete_records('sessions', array('id'=>$record->id)); + } + } catch (dml_exception $ex) { + error_log('Error gc-ing sessions'); + } + } + + public function handler_open($save_path, $session_name) { return true; } @@ -375,7 +457,7 @@ class database_session extends session_stub { try { if ($record = $this->database->get_record('sessions', array('sid'=>$sid))) { $this->database->get_session_lock($record->id); - + } else { $record = new object(); $record->state = 0; @@ -398,18 +480,38 @@ class database_session extends session_stub { // verify timeout if ($record->timemodified + $CFG->sessiontimeout < time()) { - // TODO: implement auth plugin timeout hook (see gc) - $record->state = 0; - $record->sessdata = null; - $record->sessdatahash = null; - $record->userid = 0; - $record->timecreated = $record->timemodified = time(); - $record->firstip = $record->lastip = getremoteaddr(); - try { - $this->database->update_record('sessions', $record); - } catch (dml_exception $ex) { - error_log('Can not time out database session'); - return ''; + $ignoretimeout = false; + $authsequence = get_enabled_auth_plugins(); // auths, in sequence + foreach($authsequence as $authname) { + $authplugin = get_auth_plugin($authname); + if ($authplugin->ignore_timeout($record->userid, $records->auth, $record->timecreated, $record->timemodified)) { + $ignoretimeout = true; + break; + } + } + if ($ignoretimeout) { + //refresh session + $record->timemodified = time(); + try { + $this->database->update_record('sessions', $record); + } catch (dml_exception $ex) { + error_log('Can not refresh database session'); + return ''; + } + } else { + //time out session + $record->state = 0; + $record->sessdata = null; + $record->sessdatahash = null; + $record->userid = 0; + $record->timecreated = $record->timemodified = time(); + $record->firstip = $record->lastip = getremoteaddr(); + try { + $this->database->update_record('sessions', $record); + } catch (dml_exception $ex) { + error_log('Can not time out database session'); + return ''; + } } } @@ -480,20 +582,7 @@ class database_session extends session_stub { } public function handler_gc($ignored_maxlifetime) { - global $CFG; - $maxlifetime = $CFG->sessiontimeout; - - $select = "timemodified + :maxlifetime < :now"; - $params = array('now'=>time(), 'maxlifetime'=>$maxlifetime); - - // TODO: add auth plugin hook that would allow extending of max lifetime - - try { - $this->database->delete_records_select('sessions', $select, $params); - } catch (dml_exception $ex) { - error_log('Can not garbage collect database sessions.'); - } - + $this->gc(); return true; } @@ -601,8 +690,12 @@ function get_moodle_cookie() { */ function session_set_user($user) { $_SESSION['USER'] = $user; - check_enrolment_plugins($_SESSION['USER']); - load_all_capabilities(); + unset($_SESSION['USER']->description); // conserve memory + if (!isset($_SESSION['USER']->access)) { + // check enrolments and load caps only once + check_enrolment_plugins($_SESSION['USER']); + load_all_capabilities(); + } sesskey(); // init session key } @@ -683,8 +776,9 @@ function cron_setup_user($user=null, $course=null) { /// ignore admins timezone, language and locale - use site deafult instead! $cronuser = get_admin(); $cronuser->timezone = $CFG->timezone; - $cronuser->lang = ''; - $cronuser->theme = ''; + $cronuser->lang = ''; + $cronuser->theme = ''; + unset($cronuser->description); $cronsession = array(); }