From d0dd8d33bb5a2dfab8f07660e05b257b04651893 Mon Sep 17 00:00:00 2001 From: Petr Skoda Date: Tue, 24 Jun 2014 10:48:28 +1200 Subject: [PATCH] MDL-46099 session: fix use of references for session globals This reverses the references used for global $USER and $SESSION, the reason is that PHP does not allow references to references. $USER is a reference to $GLOBALS['USER'] which means we cannot put any references to it. Solution is to store the current user and session objects in $GLOBALS['USER'] and $GLOBALS['SESSIOn'] are reference them in $_SESSION. This patch makes the session code behave the same way in CLI, phpunit and normal web requests - this allows use to finally unit test most aspects of the session code in Moodle. --- admin/cli/install.php | 10 +- install.php | 10 +- lib/classes/session/manager.php | 87 ++++++----- lib/phpunit/classes/util.php | 9 +- lib/phpunit/tests/advanced_test.php | 6 + lib/sessionlib.php | 32 +++- lib/setup.php | 4 - lib/tests/behat/behat_hooks.php | 4 +- lib/tests/session_manager_test.php | 124 +++++++++++++-- lib/tests/sessionlib_test.php | 229 ++++++++++++++++++++++++++++ 10 files changed, 428 insertions(+), 87 deletions(-) create mode 100644 lib/tests/sessionlib_test.php diff --git a/admin/cli/install.php b/admin/cli/install.php index a6c3873499a..ab18555ad09 100644 --- a/admin/cli/install.php +++ b/admin/cli/install.php @@ -201,17 +201,9 @@ if (defined('COMPONENT_CLASSLOADER')) { require($CFG->dirroot.'/version.php'); $CFG->target_release = $release; -$_SESSION = array(); -$_SESSION['SESSION'] = new stdClass(); -$_SESSION['SESSION']->lang = $CFG->lang; -$_SESSION['USER'] = new stdClass(); -$_SESSION['USER']->id = 0; -$_SESSION['USER']->mnethostid = 1; - +\core\session\manager::init_empty_session(); global $SESSION; global $USER; -$SESSION = &$_SESSION['SESSION']; -$USER = &$_SESSION['USER']; global $COURSE; $COURSE = new stdClass(); diff --git a/install.php b/install.php index 0c250a05f30..74eb6cedd75 100644 --- a/install.php +++ b/install.php @@ -233,17 +233,9 @@ if (defined('COMPONENT_CLASSLOADER')) { require('version.php'); $CFG->target_release = $release; -$_SESSION = array(); -$_SESSION['SESSION'] = new stdClass(); -$_SESSION['SESSION']->lang = $CFG->lang; -$_SESSION['USER'] = new stdClass(); -$_SESSION['USER']->id = 0; -$_SESSION['USER']->mnethostid = 1; - +\core\session\manager::init_empty_session(); global $SESSION; global $USER; -$SESSION = &$_SESSION['SESSION']; -$USER = &$_SESSION['USER']; global $COURSE; $COURSE = new stdClass(); diff --git a/lib/classes/session/manager.php b/lib/classes/session/manager.php index a6715e86bec..8ddca909959 100644 --- a/lib/classes/session/manager.php +++ b/lib/classes/session/manager.php @@ -79,6 +79,16 @@ class manager { self::initialise_user_session($newsid); self::check_security(); + // Link global $USER and $SESSION, + // this is tricky because PHP does not allow references to references + // and global keyword uses internally once reference to the $GLOBALS array. + // The solution is to use the $GLOBALS['USER'] and $GLOBALS['$SESSION'] + // as the main storage of data and put references to $_SESSION. + $GLOBALS['USER'] = $_SESSION['USER']; + $_SESSION['USER'] =& $GLOBALS['USER']; + $GLOBALS['SESSION'] = $_SESSION['SESSION']; + $_SESSION['SESSION'] =& $GLOBALS['SESSION']; + } catch (\Exception $ex) { @session_write_close(); self::init_empty_session(); @@ -139,31 +149,29 @@ class manager { /** * Empty current session, fill it with not-logged-in user info. + * + * This is intended for installation scripts, unit tests and other + * special areas. Do NOT use for logout and session termination + * in normal requests! */ - protected static function init_empty_session() { + public static function init_empty_session() { global $CFG; - // Session not used at all. - $_SESSION = array(); - $_SESSION['SESSION'] = new \stdClass(); - $_SESSION['USER'] = new \stdClass(); - $_SESSION['USER']->id = 0; + $GLOBALS['SESSION'] = new \stdClass(); + + $GLOBALS['USER'] = new \stdClass(); + $GLOBALS['USER']->id = 0; if (isset($CFG->mnet_localhost_id)) { - $_SESSION['USER']->mnethostid = $CFG->mnet_localhost_id; + $GLOBALS['USER']->mnethostid = $CFG->mnet_localhost_id; } else { // Not installed yet, the future host id will be most probably 1. - $_SESSION['USER']->mnethostid = 1; + $GLOBALS['USER']->mnethostid = 1; } - if (PHPUNIT_TEST or defined('BEHAT_TEST')) { - // Phpunit tests and behat init use reversed reference, - // the reason is we can not point global to $_SESSION outside of global scope. - global $USER, $SESSION; - $USER = $_SESSION['USER']; - $SESSION = $_SESSION['SESSION']; - $_SESSION['USER'] =& $USER; - $_SESSION['SESSION'] =& $SESSION; - } + // Link global $USER and $SESSION. + $_SESSION = array(); + $_SESSION['USER'] =& $GLOBALS['USER']; + $_SESSION['SESSION'] =& $GLOBALS['SESSION']; } /** @@ -249,9 +257,11 @@ class manager { } /** - * Initialise $USER and $SESSION objects, handles google access + * Initialise $_SESSION, handles google access * and sets up not-logged-in user properly. * + * WARNING: $USER and $SESSION are set up later, do not use them yet! + * * @param bool $newsid is this a new session in first http request? */ protected static function initialise_user_session($newsid) { @@ -416,6 +426,8 @@ class manager { /** * Do various session security checks. + * + * WARNING: $USER and $SESSION are set up later, do not use them yet! */ protected static function check_security() { global $CFG; @@ -489,7 +501,7 @@ class manager { session_regenerate_id(true); $DB->delete_records('sessions', array('sid'=>$sid)); self::init_empty_session(); - self::add_session_record($_SESSION['USER']->id); + self::add_session_record($_SESSION['USER']->id); // Do not use $USER here because it may not be set up yet. session_write_close(); self::$sessionactive = false; } @@ -590,22 +602,19 @@ class manager { * @param \stdClass $user record */ public static function set_user(\stdClass $user) { - $_SESSION['USER'] = $user; - unset($_SESSION['USER']->description); // Conserve memory. - unset($_SESSION['USER']->password); // Improve security. - if (isset($_SESSION['USER']->lang)) { + $GLOBALS['USER'] = $user; + unset($GLOBALS['USER']->description); // Conserve memory. + unset($GLOBALS['USER']->password); // Improve security. + if (isset($GLOBALS['USER']->lang)) { // Make sure it is a valid lang pack name. - $_SESSION['USER']->lang = clean_param($_SESSION['USER']->lang, PARAM_LANG); + $GLOBALS['USER']->lang = clean_param($GLOBALS['USER']->lang, PARAM_LANG); } - sesskey(); // Init session key. - if (PHPUNIT_TEST or defined('BEHAT_TEST')) { - // Phpunit tests and behat init use reversed reference, - // the reason is we can not point global to $_SESSION outside of global scope. - global $USER; - $USER = $_SESSION['USER']; - $_SESSION['USER'] =& $USER; - } + // Relink session with global $USER just in case it got unlinked somehow. + $_SESSION['USER'] =& $GLOBALS['USER']; + + // Init session key. + sesskey(); } /** @@ -697,7 +706,7 @@ class manager { * @return bool */ public static function is_loggedinas() { - return !empty($_SESSION['USER']->realuser); + return !empty($GLOBALS['USER']->realuser); } /** @@ -708,7 +717,7 @@ class manager { if (self::is_loggedinas()) { return $_SESSION['REALUSER']; } else { - return $_SESSION['USER']; + return $GLOBALS['USER']; } } @@ -725,12 +734,14 @@ class manager { return; } - // Switch to fresh new $SESSION. - $_SESSION['REALSESSION'] = $_SESSION['SESSION']; - $_SESSION['SESSION'] = new \stdClass(); + // Switch to fresh new $_SESSION. + $_SESSION = array(); + $_SESSION['REALSESSION'] = clone($GLOBALS['SESSION']); + $GLOBALS['SESSION'] = new \stdClass(); + $_SESSION['SESSION'] =& $GLOBALS['SESSION']; // Create the new $USER object with all details and reload needed capabilities. - $_SESSION['REALUSER'] = $_SESSION['USER']; + $_SESSION['REALUSER'] = clone($GLOBALS['USER']); $user = get_complete_user_data('id', $userid); $user->realuser = $_SESSION['REALUSER']->id; $user->loginascontext = $context; diff --git a/lib/phpunit/classes/util.php b/lib/phpunit/classes/util.php index 65082bdde3f..c881256bfd5 100644 --- a/lib/phpunit/classes/util.php +++ b/lib/phpunit/classes/util.php @@ -189,14 +189,9 @@ class phpunit_util extends testing_util { $FULLME = null; $ME = null; $SCRIPT = null; - $SESSION = new stdClass(); - $_SESSION['SESSION'] =& $SESSION; - // set fresh new not-logged-in user - $user = new stdClass(); - $user->id = 0; - $user->mnethostid = $CFG->mnet_localhost_id; - \core\session\manager::set_user($user); + // Empty sessison and set fresh new not-logged-in user. + \core\session\manager::init_empty_session(); // reset all static caches \core\event\manager::phpunit_reset(); diff --git a/lib/phpunit/tests/advanced_test.php b/lib/phpunit/tests/advanced_test.php index 749f3ec6cc0..577eaa8c67d 100644 --- a/lib/phpunit/tests/advanced_test.php +++ b/lib/phpunit/tests/advanced_test.php @@ -76,6 +76,7 @@ class core_phpunit_advanced_testcase extends advanced_testcase { $this->assertEquals(0, $USER->id); $this->assertSame($_SESSION['USER'], $USER); + $this->assertSame($GLOBALS['USER'], $USER); $user = $DB->get_record('user', array('id'=>2)); $this->assertNotEmpty($user); @@ -83,26 +84,31 @@ class core_phpunit_advanced_testcase extends advanced_testcase { $this->assertEquals(2, $USER->id); $this->assertEquals(2, $_SESSION['USER']->id); $this->assertSame($_SESSION['USER'], $USER); + $this->assertSame($GLOBALS['USER'], $USER); $USER->id = 3; $this->assertEquals(3, $USER->id); $this->assertEquals(3, $_SESSION['USER']->id); $this->assertSame($_SESSION['USER'], $USER); + $this->assertSame($GLOBALS['USER'], $USER); \core\session\manager::set_user($user); $this->assertEquals(2, $USER->id); $this->assertEquals(2, $_SESSION['USER']->id); $this->assertSame($_SESSION['USER'], $USER); + $this->assertSame($GLOBALS['USER'], $USER); $USER = $DB->get_record('user', array('id'=>1)); $this->assertNotEmpty($USER); $this->assertEquals(1, $USER->id); $this->assertEquals(1, $_SESSION['USER']->id); $this->assertSame($_SESSION['USER'], $USER); + $this->assertSame($GLOBALS['USER'], $USER); $this->setUser(null); $this->assertEquals(0, $USER->id); $this->assertSame($_SESSION['USER'], $USER); + $this->assertSame($GLOBALS['USER'], $USER); } public function test_set_admin_user() { diff --git a/lib/sessionlib.php b/lib/sessionlib.php index 49b542da7bb..e60462f34d3 100644 --- a/lib/sessionlib.php +++ b/lib/sessionlib.php @@ -37,7 +37,10 @@ function sesskey() { // note: do not use $USER because it may not be initialised yet if (empty($_SESSION['USER']->sesskey)) { if (!isset($_SESSION['USER'])) { - $_SESSION['USER'] = new stdClass; + // This should never happen, + // do not mess with session and globals here, + // let any checks fail instead! + return false; } $_SESSION['USER']->sesskey = random_string(10); } @@ -151,16 +154,28 @@ function get_moodle_cookie() { * Sets up current user and course environment (lang, etc.) in cron. * Do not use outside of cron script! * - * @param stdClass $user full user object, null means default cron user (admin) - * @param $course full course record, null means $SITE + * @param stdClass $user full user object, null means default cron user (admin), + * value 'reset' means reset internal static caches. + * @param stdClass $course full course record, null means $SITE * @return void */ function cron_setup_user($user = NULL, $course = NULL) { global $CFG, $SITE, $PAGE; + if (!CLI_SCRIPT) { + throw new coding_exception('Function cron_setup_user() cannot be used in normal requests!'); + } + static $cronuser = NULL; static $cronsession = NULL; + if ($user === 'reset') { + $cronuser = null; + $cronsession = null; + \core\session\manager::init_empty_session(); + return; + } + if (empty($cronuser)) { /// ignore admins timezone, language and locale - use site default instead! $cronuser = get_admin(); @@ -173,15 +188,16 @@ function cron_setup_user($user = NULL, $course = NULL) { } if (!$user) { - // cached default cron user (==modified admin for now) + // Cached default cron user (==modified admin for now). + \core\session\manager::init_empty_session(); \core\session\manager::set_user($cronuser); - $_SESSION['SESSION'] = $cronsession; + $GLOBALS['SESSION'] = $cronsession; } else { - // emulate real user session - needed for caps in cron - if ($_SESSION['USER']->id != $user->id) { + // Emulate real user session - needed for caps in cron. + if ($GLOBALS['USER']->id != $user->id) { + \core\session\manager::init_empty_session(); \core\session\manager::set_user($user); - $_SESSION['SESSION'] = new stdClass(); } } diff --git a/lib/setup.php b/lib/setup.php index 3eff34fb225..5cf79e8d54c 100644 --- a/lib/setup.php +++ b/lib/setup.php @@ -804,10 +804,6 @@ if (empty($CFG->sessiontimeout)) { $CFG->sessiontimeout = 7200; } \core\session\manager::start(); -if (!PHPUNIT_TEST and !defined('BEHAT_TEST')) { - $SESSION =& $_SESSION['SESSION']; - $USER =& $_SESSION['USER']; -} // Initialise some variables that are supposed to be set in config.php only. if (!isset($CFG->filelifetime)) { diff --git a/lib/tests/behat/behat_hooks.php b/lib/tests/behat/behat_hooks.php index 72ed10f25b5..523ac048594 100644 --- a/lib/tests/behat/behat_hooks.php +++ b/lib/tests/behat/behat_hooks.php @@ -189,9 +189,7 @@ class behat_hooks extends behat_base { } // Reset $SESSION. - $_SESSION = array(); - $SESSION = new stdClass(); - $_SESSION['SESSION'] =& $SESSION; + \core\session\manager::init_empty_session(); behat_util::reset_database(); behat_util::reset_dataroot(); diff --git a/lib/tests/session_manager_test.php b/lib/tests/session_manager_test.php index d964b9c459d..5333917b9f3 100644 --- a/lib/tests/session_manager_test.php +++ b/lib/tests/session_manager_test.php @@ -41,38 +41,122 @@ class core_session_manager_testcase extends advanced_testcase { $this->assertDebuggingCalled('Session was already started!', DEBUG_DEVELOPER); } + public function test_init_empty_session() { + global $SESSION, $USER; + $this->resetAfterTest(); + + $user = $this->getDataGenerator()->create_user(); + + $SESSION->test = true; + $this->assertTrue($GLOBALS['SESSION']->test); + $this->assertTrue($_SESSION['SESSION']->test); + + \core\session\manager::set_user($user); + $this->assertSame($user, $USER); + $this->assertSame($user, $GLOBALS['USER']); + $this->assertSame($user, $_SESSION['USER']); + + \core\session\manager::init_empty_session(); + + $this->assertInstanceOf('stdClass', $SESSION); + $this->assertEmpty((array)$SESSION); + $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']); + $this->assertSame($GLOBALS['SESSION'], $SESSION); + + $this->assertInstanceOf('stdClass', $USER); + $this->assertEquals(array('id' => 0, 'mnethostid' => 1), (array)$USER, '', 0, 10, true); + $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); + $this->assertSame($GLOBALS['USER'], $USER); + + // Now test how references work. + + $GLOBALS['SESSION'] = new \stdClass(); + $GLOBALS['SESSION']->test = true; + $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']); + $this->assertSame($GLOBALS['SESSION'], $SESSION); + + $SESSION = new \stdClass(); + $SESSION->test2 = true; + $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']); + $this->assertSame($GLOBALS['SESSION'], $SESSION); + + $_SESSION['SESSION'] = new stdClass(); + $_SESSION['SESSION']->test3 = true; + $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']); + $this->assertSame($GLOBALS['SESSION'], $SESSION); + + $GLOBALS['USER'] = new \stdClass(); + $GLOBALS['USER']->test = true; + $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); + $this->assertSame($GLOBALS['USER'], $USER); + + $USER = new \stdClass(); + $USER->test2 = true; + $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); + $this->assertSame($GLOBALS['USER'], $USER); + + $_SESSION['USER'] = new stdClass(); + $_SESSION['USER']->test3 = true; + $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); + $this->assertSame($GLOBALS['USER'], $USER); + } + public function test_set_user() { global $USER; $this->resetAfterTest(); - $user = $this->getDataGenerator()->create_user(); - $this->setUser(0); $this->assertEquals(0, $USER->id); + $user = $this->getDataGenerator()->create_user(); + $this->assertObjectHasAttribute('description', $user); + $this->assertObjectHasAttribute('password', $user); + \core\session\manager::set_user($user); + $this->assertEquals($user->id, $USER->id); + $this->assertObjectNotHasAttribute('description', $user); + $this->assertObjectNotHasAttribute('password', $user); + $this->assertObjectHasAttribute('sesskey', $user); + $this->assertSame($user, $GLOBALS['USER']); + $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); + $this->assertSame($GLOBALS['USER'], $USER); } public function test_login_user() { global $USER; $this->resetAfterTest(); - $user = $this->getDataGenerator()->create_user(); - $this->setUser(0); $this->assertEquals(0, $USER->id); + $user = $this->getDataGenerator()->create_user(); + @\core\session\manager::login_user($user); // Ignore header error messages. $this->assertEquals($user->id, $USER->id); + + $this->assertObjectNotHasAttribute('description', $user); + $this->assertObjectNotHasAttribute('password', $user); + $this->assertSame($user, $GLOBALS['USER']); + $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); + $this->assertSame($GLOBALS['USER'], $USER); } public function test_terminate_current() { - global $USER; + global $USER, $SESSION; $this->resetAfterTest(); - // This can not be tested much without real session... $this->setAdminUser(); \core\session\manager::terminate_current(); $this->assertEquals(0, $USER->id); + + $this->assertInstanceOf('stdClass', $SESSION); + $this->assertEmpty((array)$SESSION); + $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']); + $this->assertSame($GLOBALS['SESSION'], $SESSION); + + $this->assertInstanceOf('stdClass', $USER); + $this->assertEquals(array('id' => 0, 'mnethostid' => 1), (array)$USER, '', 0, 10, true); + $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); + $this->assertSame($GLOBALS['USER'], $USER); } public function test_write_close() { @@ -84,6 +168,9 @@ class core_session_manager_testcase extends advanced_testcase { $userid = $USER->id; \core\session\manager::write_close(); $this->assertSame($userid, $USER->id); + + $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); + $this->assertSame($GLOBALS['USER'], $USER); } public function test_session_exists() { @@ -293,22 +380,37 @@ class core_session_manager_testcase extends advanced_testcase { * @copyright 2103 Rajesh Taneja */ public function test_loginas() { - global $USER; + global $USER, $SESSION; $this->resetAfterTest(); // Set current user as Admin user and save it for later use. $this->setAdminUser(); $adminuser = $USER; - - // Create a new user and try admin loginas this user. + $adminsession = $SESSION; $user = $this->getDataGenerator()->create_user(); + $_SESSION['extra'] = true; + + // Try admin loginas this user in system context. + $this->assertObjectNotHasAttribute('realuser', $USER); \core\session\manager::loginas($user->id, context_system::instance()); $this->assertSame($user->id, $USER->id); $this->assertSame(context_system::instance(), $USER->loginascontext); $this->assertSame($adminuser->id, $USER->realuser); + $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); + $this->assertSame($GLOBALS['USER'], $USER); + $this->assertNotSame($adminuser, $_SESSION['REALUSER']); + $this->assertEquals($adminuser, $_SESSION['REALUSER']); + + $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']); + $this->assertSame($GLOBALS['SESSION'], $SESSION); + $this->assertNotSame($adminsession, $_SESSION['REALSESSION']); + $this->assertEquals($adminsession, $_SESSION['REALSESSION']); + + $this->assertArrayNotHasKey('extra', $_SESSION); // Set user as current user and login as admin user in course context. + \core\session\manager::init_empty_session(); $this->setUser($user); $this->assertNotEquals($adminuser->id, $USER->id); $course = $this->getDataGenerator()->create_course(); @@ -358,6 +460,9 @@ class core_session_manager_testcase extends advanced_testcase { $user2 = $this->getDataGenerator()->create_user(); $this->setUser($user1); + $normal = \core\session\manager::get_realuser(); + $this->assertSame($GLOBALS['USER'], $normal); + \core\session\manager::loginas($user2->id, context_system::instance()); $real = \core\session\manager::get_realuser(); @@ -370,5 +475,6 @@ class core_session_manager_testcase extends advanced_testcase { unset($user1->sesskey); $this->assertEquals($real, $user1); + $this->assertSame($_SESSION['REALUSER'], $real); } } diff --git a/lib/tests/sessionlib_test.php b/lib/tests/sessionlib_test.php new file mode 100644 index 00000000000..6879ae58aaa --- /dev/null +++ b/lib/tests/sessionlib_test.php @@ -0,0 +1,229 @@ +. + +/** + * Unit tests for sessionlib.php file. + * + * @package core + * @category phpunit + * @author Petr Skoda + * @copyright 2014 Totara Learning Solutions Ltd {@link http://www.totaralms.com/} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * Unit tests for sessionlib.php file. + * + * @package core + * @category phpunit + * @author Petr Skoda + * @copyright 2014 Totara Learning Solutions Ltd {@link http://www.totaralms.com/} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class core_sessionlib_testcase extends advanced_testcase { + public function test_cron_setup_user() { + global $PAGE, $USER, $SESSION, $SITE, $CFG; + $this->resetAfterTest(); + + // NOTE: this function contains some static caches, let's reset first. + cron_setup_user('reset'); + + $admin = get_admin(); + $user1 = $this->getDataGenerator()->create_user(); + $user2 = $this->getDataGenerator()->create_user(); + $course = $this->getDataGenerator()->create_course(); + + cron_setup_user(); + $this->assertSame($admin->id, $USER->id); + $this->assertSame($PAGE->context, context_course::instance($SITE->id)); + $this->assertSame($CFG->timezone, $USER->timezone); + $this->assertSame('', $USER->lang); + $this->assertSame('', $USER->theme); + $SESSION->test1 = true; + $adminsession = $SESSION; + $adminuser = $USER; + $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']); + $this->assertSame($GLOBALS['SESSION'], $SESSION); + $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); + $this->assertSame($GLOBALS['USER'], $USER); + + cron_setup_user(null, $course); + $this->assertSame($admin->id, $USER->id); + $this->assertSame($PAGE->context, context_course::instance($course->id)); + $this->assertSame($adminsession, $SESSION); + $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']); + $this->assertSame($GLOBALS['SESSION'], $SESSION); + $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); + $this->assertSame($GLOBALS['USER'], $USER); + + cron_setup_user($user1); + $this->assertSame($user1->id, $USER->id); + $this->assertSame($PAGE->context, context_course::instance($SITE->id)); + $this->assertNotSame($adminsession, $SESSION); + $this->assertObjectNotHasAttribute('test1', $SESSION); + $this->assertEmpty((array)$SESSION); + $usersession1 = $SESSION; + $SESSION->test2 = true; + $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']); + $this->assertSame($GLOBALS['SESSION'], $SESSION); + $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); + $this->assertSame($GLOBALS['USER'], $USER); + + cron_setup_user($user1); + $this->assertSame($user1->id, $USER->id); + $this->assertSame($PAGE->context, context_course::instance($SITE->id)); + $this->assertNotSame($adminsession, $SESSION); + $this->assertSame($usersession1, $SESSION); + $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']); + $this->assertSame($GLOBALS['SESSION'], $SESSION); + $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); + $this->assertSame($GLOBALS['USER'], $USER); + + cron_setup_user($user2); + $this->assertSame($user2->id, $USER->id); + $this->assertSame($PAGE->context, context_course::instance($SITE->id)); + $this->assertNotSame($adminsession, $SESSION); + $this->assertNotSame($usersession1, $SESSION); + $this->assertEmpty((array)$SESSION); + $usersession2 = $SESSION; + $usersession2->test3 = true; + $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']); + $this->assertSame($GLOBALS['SESSION'], $SESSION); + $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); + $this->assertSame($GLOBALS['USER'], $USER); + + cron_setup_user($user2, $course); + $this->assertSame($user2->id, $USER->id); + $this->assertSame($PAGE->context, context_course::instance($course->id)); + $this->assertNotSame($adminsession, $SESSION); + $this->assertNotSame($usersession1, $SESSION); + $this->assertSame($usersession2, $SESSION); + $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']); + $this->assertSame($GLOBALS['SESSION'], $SESSION); + $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); + $this->assertSame($GLOBALS['USER'], $USER); + + cron_setup_user($user1); + $this->assertSame($user1->id, $USER->id); + $this->assertSame($PAGE->context, context_course::instance($SITE->id)); + $this->assertNotSame($adminsession, $SESSION); + $this->assertNotSame($usersession1, $SESSION); + $this->assertEmpty((array)$SESSION); + $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']); + $this->assertSame($GLOBALS['SESSION'], $SESSION); + $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); + $this->assertSame($GLOBALS['USER'], $USER); + + cron_setup_user(); + $this->assertSame($admin->id, $USER->id); + $this->assertSame($PAGE->context, context_course::instance($SITE->id)); + $this->assertSame($adminsession, $SESSION); + $this->assertSame($adminuser, $USER); + $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']); + $this->assertSame($GLOBALS['SESSION'], $SESSION); + $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); + $this->assertSame($GLOBALS['USER'], $USER); + + cron_setup_user('reset'); + $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']); + $this->assertSame($GLOBALS['SESSION'], $SESSION); + $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); + $this->assertSame($GLOBALS['USER'], $USER); + + cron_setup_user(); + $this->assertNotSame($adminsession, $SESSION); + $this->assertNotSame($adminuser, $USER); + $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']); + $this->assertSame($GLOBALS['SESSION'], $SESSION); + $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); + $this->assertSame($GLOBALS['USER'], $USER); + } + + public function test_sesskey() { + global $USER; + $this->resetAfterTest(); + + $user = $this->getDataGenerator()->create_user(); + + \core\session\manager::init_empty_session(); + $this->assertObjectNotHasAttribute('sesskey', $USER); + + $sesskey = sesskey(); + $this->assertNotEmpty($sesskey); + $this->assertSame($sesskey, $USER->sesskey); + $this->assertSame($GLOBALS['USER'], $_SESSION['USER']); + $this->assertSame($GLOBALS['USER'], $USER); + + $this->assertSame($sesskey, sesskey()); + + // Test incomplete session init - the sesskeys should return random values. + $_SESSION = array(); + unset($GLOBALS['USER']); + unset($GLOBALS['SESSION']); + + $this->assertFalse(sesskey()); + $this->assertArrayNotHasKey('USER', $GLOBALS); + $this->assertFalse(sesskey()); + } + + public function test_confirm_sesskey() { + $this->resetAfterTest(); + + $sesskey = sesskey(); + + try { + confirm_sesskey(); + $this->fail('Exception expected when sesskey not present'); + } catch (moodle_exception $e) { + $this->assertSame('missingparam', $e->errorcode); + } + + $this->assertTrue(confirm_sesskey($sesskey)); + $this->assertFalse(confirm_sesskey('blahblah')); + + $_GET['sesskey'] = $sesskey; + $this->assertTrue(confirm_sesskey()); + + $_GET['sesskey'] = 'blah'; + $this->assertFalse(confirm_sesskey()); + } + + public function test_require_sesskey() { + $this->resetAfterTest(); + + $sesskey = sesskey(); + + try { + require_sesskey(); + $this->fail('Exception expected when sesskey not present'); + } catch (moodle_exception $e) { + $this->assertSame('missingparam', $e->errorcode); + } + + $_GET['sesskey'] = $sesskey; + require_sesskey(); + + $_GET['sesskey'] = 'blah'; + try { + require_sesskey(); + $this->fail('Exception expected when sesskey not incorrect'); + } catch (moodle_exception $e) { + $this->assertSame('invalidsesskey', $e->errorcode); + } + } +}