MDL-72670 session: Correct read only debugging logic

Prior to this patch the debugging mode (when enabled) would trigger
on everywhere, regardless of whether or not READ_ONLY_SESSION is defined.

This patch modifies that behaviour so that the debugging only kicks in
if READ_ONLY_SESSION is defined and set to true.
This commit is contained in:
Cameron Ball 2021-09-24 14:09:34 +08:00
parent 70073fdc74
commit d5eaa5224e
3 changed files with 34 additions and 5 deletions

View file

@ -60,6 +60,15 @@ class manager {
/** @var array Stores the the SESSION before a request is performed, used to check incorrect read-only modes */ /** @var array Stores the the SESSION before a request is performed, used to check incorrect read-only modes */
private static $priorsession = []; private static $priorsession = [];
/**
* @var bool Used to trigger the SESSION mutation warning without actually preventing SESSION mutation.
* This variable is used to "copy" what the $requireslock parameter does in start_session().
* Once requireslock is set in start_session it's later accessible via $handler->requires_write_lock,
* When using $CFG->enable_read_only_sessions_debug mode, this variable serves the same purpose without
* actually setting the handler as requiring a write lock.
*/
private static $requireslockdebug;
/** /**
* If the current session is not writeable, abort it, and re-open it * If the current session is not writeable, abort it, and re-open it
* requesting (and blocking) until a write lock is acquired. * requesting (and blocking) until a write lock is acquired.
@ -69,8 +78,16 @@ class manager {
* if the original session was not opened with the explicit intention of being locked, * if the original session was not opened with the explicit intention of being locked,
* this will still restart your session so that code behaviour matches as closely * this will still restart your session so that code behaviour matches as closely
* as practical across environments. * as practical across environments.
*
* @param bool $readonlysession Used by debugging logic to determine if whatever
* triggered the restart (e.g., a webservice) declared
* itself as read only.
*/ */
public static function restart_with_write_lock() { public static function restart_with_write_lock(bool $readonlysession) {
global $CFG;
self::$requireslockdebug = !$readonlysession;
if (self::$sessionactive && !self::$handler->requires_write_lock()) { if (self::$sessionactive && !self::$handler->requires_write_lock()) {
@self::$handler->abort(); @self::$handler->abort();
self::$sessionactive = false; self::$sessionactive = false;
@ -108,6 +125,17 @@ class manager {
} else { } else {
$requireslock = true; // For backwards compatibility, we default to assuming that a lock is needed. $requireslock = true; // For backwards compatibility, we default to assuming that a lock is needed.
} }
// By default make the two variables the same. This means that when they are
// checked below in start_session and write_close there is no possibility for
// the debug version to "accidentally" execute the debug mode.
self::$requireslockdebug = $requireslock;
if (defined('READ_ONLY_SESSION') && !empty($CFG->enable_read_only_sessions_debug)) {
// Only change the debug variable if READ_ONLY_SESSION is declared,
// as would happen with the real requireslock variable.
self::$requireslockdebug = !READ_ONLY_SESSION;
}
self::start_session($requireslock); self::start_session($requireslock);
} }
@ -138,9 +166,10 @@ class manager {
self::$sessionactive = true; // Set here, so the session can be cleared if the security check fails. self::$sessionactive = true; // Set here, so the session can be cleared if the security check fails.
self::check_security(); self::check_security();
if (!$requireslock || isset($CFG->enable_read_only_sessions_debug)) { if (!$requireslock || !self::$requireslockdebug) {
self::$priorsession = (array) $_SESSION['SESSION']; self::$priorsession = (array) $_SESSION['SESSION'];
} }
if (!empty($CFG->enable_read_only_sessions) && isset($_SESSION['SESSION']->cachestore_session)) { if (!empty($CFG->enable_read_only_sessions) && isset($_SESSION['SESSION']->cachestore_session)) {
$caches = join(', ', array_keys($_SESSION['SESSION']->cachestore_session)); $caches = join(', ', array_keys($_SESSION['SESSION']->cachestore_session));
$caches = str_replace('default_session-', '', $caches); $caches = str_replace('default_session-', '', $caches);
@ -705,7 +734,7 @@ class manager {
self::sessionlock_debugging(); self::sessionlock_debugging();
$requireslock = self::$handler->requires_write_lock(); $requireslock = self::$handler->requires_write_lock();
if (!$requireslock || isset($CFG->enable_read_only_sessions_debug)) { if (!$requireslock || !self::$requireslockdebug) {
// Compare the array of the earlier session data with the array now, if // Compare the array of the earlier session data with the array now, if
// there is a difference then a lock is required. // there is a difference then a lock is required.
$arraydiff = self::array_session_diff( $arraydiff = self::array_session_diff(

View file

@ -427,6 +427,6 @@ class core_dml_read_slave_testcase extends base_testcase {
$this->assertTrue($DB->perf_get_reads() > 0); $this->assertTrue($DB->perf_get_reads() > 0);
}); });
\core\session\manager::restart_with_write_lock(); \core\session\manager::restart_with_write_lock(false);
} }
} }

View file

@ -198,7 +198,7 @@ class external_api {
// Eventually this should shift into the various handlers and not be handled via config. // Eventually this should shift into the various handlers and not be handled via config.
$readonlysession = $externalfunctioninfo->readonlysession ?? false; $readonlysession = $externalfunctioninfo->readonlysession ?? false;
if (!$readonlysession || empty($CFG->enable_read_only_sessions)) { if (!$readonlysession || empty($CFG->enable_read_only_sessions)) {
\core\session\manager::restart_with_write_lock(); \core\session\manager::restart_with_write_lock($readonlysession);
} }
$currentpage = $PAGE; $currentpage = $PAGE;