diff --git a/cache/classes/loaders.php b/cache/classes/loaders.php index 6402e2c823b..fd1a3403c23 100644 --- a/cache/classes/loaders.php +++ b/cache/classes/loaders.php @@ -1410,6 +1410,17 @@ class cache_application extends cache implements cache_loader_with_locking { * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class cache_session extends cache { + /** + * The user the session has been established for. + * @var int + */ + protected static $loadeduserid = null; + + /** + * The userid this cache is currently using. + * @var int + */ + protected $currentuserid = null; /** * Override the cache::construct method. * @@ -1426,6 +1437,8 @@ class cache_session extends cache { * @return void */ public function __construct(cache_definition $definition, cache_store $store, $loader = null) { + // First up copy the loadeduserid to the current user id. + $this->currentuserid = self::$loadeduserid; parent::__construct($definition, $store, $loader); if ($definition->has_invalidation_events()) { $lastinvalidation = $this->get('lastsessioninvalidation'); @@ -1474,6 +1487,47 @@ class cache_session extends cache { $this->set('lastsessioninvalidation', cache::now()); } } + + /** + * Parses the key turning it into a string (or array is required) suitable to be passed to the cache store. + * + * This function is called for every operation that uses keys. For this reason we use this function to also check + * that the current user is the same as the user who last used this cache. + * + * @param string|int $key As passed to get|set|delete etc. + * @return string|array String unless the store supports multi-identifiers in which case an array if returned. + */ + protected function parse_key($key) { + $this->check_tracked_user(); + return parent::parse_key($key); + } + + /** + * Check that this cache instance is tracking the current user. + */ + protected function check_tracked_user() { + if (isset($_SESSION['USER']->id)) { + // Get the id of the current user. + $new = $_SESSION['USER']->id; + } else { + // No user set up yet. + $new = 0; + } + if ($new !== self::$loadeduserid) { + // The current user doesn't match the tracker userid for this request. + if (!is_null(self::$loadeduserid)) { + // Purge the data we have for the old user. + // This way we don't bloat the session. + $this->purge(); + } + self::$loadeduserid = $new; + $this->currentuserid = $new; + } else if ($new !== $this->currentuserid) { + // The current user matches the loaded user but not the user last used by this cache. + $this->purge(); + $this->currentuserid = $new; + } + } } /** diff --git a/cache/tests/cache_test.php b/cache/tests/cache_test.php index e7fe8369204..ba8115f8459 100644 --- a/cache/tests/cache_test.php +++ b/cache/tests/cache_test.php @@ -866,4 +866,63 @@ class cache_phpunit_tests extends advanced_testcase { $this->assertTrue($cache->set('test', 'test')); $this->assertEquals('test', $cache->get('test')); } + + /** + * Test switching users with session caches. + */ + public function test_session_cache_switch_user() { + $this->resetAfterTest(true); + $cache = cache::make_from_params(cache_store::MODE_SESSION, 'phpunit', 'sessioncache'); + $user1 = $this->getDataGenerator()->create_user(); + $user2 = $this->getDataGenerator()->create_user(); + + // Log in as the first user. + $this->setUser($user1); + $sesskey1 = sesskey(); + + // Set a basic value in the cache. + $cache->set('var', 1); + $this->assertTrue($cache->has('var')); + $this->assertEquals(1, $cache->get('var')); + + // Change to the second user. + $this->setUser($user2); + $sesskey2 = sesskey(); + + // Make sure the cache doesn't give us the data for the last user. + $this->assertNotEquals($sesskey1, $sesskey2); + $this->assertFalse($cache->has('var')); + $this->assertEquals(false, $cache->get('var')); + } + + /** + * Test multiple session caches when switching user. + */ + public function test_session_cache_switch_user_multiple() { + $this->resetAfterTest(true); + $cache1 = cache::make_from_params(cache_store::MODE_SESSION, 'phpunit', 'sessioncache1'); + $cache2 = cache::make_from_params(cache_store::MODE_SESSION, 'phpunit', 'sessioncache2'); + $user1 = $this->getDataGenerator()->create_user(); + $user2 = $this->getDataGenerator()->create_user(); + + // Log in as the first user. + $this->setUser($user1); + $sesskey1 = sesskey(); + + // Set a basic value in the caches. + $cache1->set('var', 1); + $cache2->set('var', 2); + $this->assertEquals(1, $cache1->get('var')); + $this->assertEquals(2, $cache2->get('var')); + + // Change to the second user. + $this->setUser($user2); + $sesskey2 = sesskey(); + + // Make sure the cache doesn't give us the data for the last user. + // Also make sure that switching the user has lead to both caches being purged. + $this->assertNotEquals($sesskey1, $sesskey2); + $this->assertEquals(false, $cache1->get('var')); + $this->assertEquals(false, $cache2->get('var')); + } }