diff --git a/core/includes/common.inc b/core/includes/common.inc index 9d498c2..63a8aec 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -5201,13 +5201,9 @@ function drupal_cron_run() { // Allow execution to continue even if the request gets canceled. @ignore_user_abort(TRUE); - // Prevent session information from being saved while cron is running. - drupal_save_session(FALSE); - // Force the current user to anonymous to ensure consistent permissions on // cron runs. - $original_user = $GLOBALS['user']; - $GLOBALS['user'] = drupal_anonymous_user(); + user_impersonate_user(drupal_anonymous_user()); // Try to allocate enough time to run all the hook_cron implementations. drupal_set_time_limit(240); @@ -5263,8 +5259,7 @@ function drupal_cron_run() { } } // Restore the user. - $GLOBALS['user'] = $original_user; - drupal_save_session(TRUE); + user_revert_user(); return $return; } diff --git a/core/modules/simpletest/drupal_web_test_case.php b/core/modules/simpletest/drupal_web_test_case.php index 3069268..dfbef9d 100644 --- a/core/modules/simpletest/drupal_web_test_case.php +++ b/core/modules/simpletest/drupal_web_test_case.php @@ -1414,9 +1414,7 @@ class DrupalWebTestCase extends DrupalTestCase { drupal_cron_run(); // Log in with a clean $user. - $this->originalUser = $user; - drupal_save_session(FALSE); - $user = user_load(1); + user_impersonate_user(user_load(1)); // Restore necessary variables. variable_set('install_task', 'done'); @@ -1550,8 +1548,7 @@ class DrupalWebTestCase extends DrupalTestCase { $callbacks = $this->originalShutdownCallbacks; // Return the user to the original one. - $user = $this->originalUser; - drupal_save_session(TRUE); + user_revert_user(); // Ensure that internal logged in variable and cURL options are reset. $this->loggedInUser = FALSE; diff --git a/core/modules/simpletest/tests/common.test b/core/modules/simpletest/tests/common.test index fee3831..07b0f5b 100644 --- a/core/modules/simpletest/tests/common.test +++ b/core/modules/simpletest/tests/common.test @@ -2397,11 +2397,9 @@ class CommonFormatDateTestCase extends DrupalWebTestCase { $edit = array('language' => self::LANGCODE, 'mail' => $test_user->mail, 'timezone' => 'America/Los_Angeles'); $this->drupalPost('user/' . $test_user->uid . '/edit', $edit, t('Save')); - // Disable session saving as we are about to modify the global $user. - drupal_save_session(FALSE); - // Save the original user and language and then replace it with the test user and language. - $real_user = $user; - $user = user_load($test_user->uid, TRUE); + // Switch to test user. + user_impersonate_user(user_load($test_user->uid)); + $real_language = $language_interface->langcode; $language_interface->langcode = $user->language; // Simulate a Drupal bootstrap with the logged-in user. @@ -2424,11 +2422,10 @@ class CommonFormatDateTestCase extends DrupalWebTestCase { $this->assertIdentical(format_date($timestamp, 'html_year'), '2007', t('Test html_year date format.')); // Restore the original user and language, and enable session saving. - $user = $real_user; + user_revert_user(); $language_interface->langcode = $real_language; // Restore default time zone. date_default_timezone_set(drupal_get_user_timezone()); - drupal_save_session(TRUE); } } diff --git a/core/modules/simpletest/tests/file.test b/core/modules/simpletest/tests/file.test index a369a44..8b09dbe 100644 --- a/core/modules/simpletest/tests/file.test +++ b/core/modules/simpletest/tests/file.test @@ -480,20 +480,18 @@ class FileValidatorTest extends DrupalWebTestCase { * Test file_validate_size(). */ function testFileValidateSize() { - global $user; - $original_user = $user; - drupal_save_session(FALSE); - // Run these test as uid = 1. - $user = user_load(1); + user_impersonate_user(user_load(1)); $file = new stdClass(); $file->filesize = 999999; $errors = file_validate_size($file, 1, 1); $this->assertEqual(count($errors), 0, t('No size limits enforced on uid=1.'), 'File'); + user_revert_user(); // Run these tests as a regular user. $user = $this->drupalCreateUser(); + user_impersonate_user($user); // Create a file with a size of 1000 bytes, and quotas of only 1 byte. $file = new stdClass(); @@ -507,8 +505,7 @@ class FileValidatorTest extends DrupalWebTestCase { $errors = file_validate_size($file, 1, 1); $this->assertEqual(count($errors), 2, t('Errors for both the file and their limit.'), 'File'); - $user = $original_user; - drupal_save_session(TRUE); + user_revert_user(); } } diff --git a/core/modules/simpletest/tests/upgrade/upgrade.test b/core/modules/simpletest/tests/upgrade/upgrade.test index 381816c..1533478 100644 --- a/core/modules/simpletest/tests/upgrade/upgrade.test +++ b/core/modules/simpletest/tests/upgrade/upgrade.test @@ -119,10 +119,9 @@ abstract class UpgradePathTestCase extends DrupalWebTestCase { $this->pass('Finished loading the dump.'); - // Load user 1. - $this->originalUser = $user; - drupal_save_session(FALSE); - $user = db_query('SELECT * FROM {users} WHERE uid = :uid', array(':uid' => 1))->fetchObject(); + // A direct query is used instead of user_load() because the latter would + // trigger hook_entity_info(), which is not available yet. + user_impersonate_user(db_query('SELECT * FROM {users} WHERE uid = :uid', array(':uid' => 1))->fetchObject()); // Generate and set a D6-compatible session cookie. $this->curlInitialize(); @@ -167,8 +166,7 @@ abstract class UpgradePathTestCase extends DrupalWebTestCase { } // Return the user to the original one. - $user = $this->originalUser; - drupal_save_session(TRUE); + user_revert_user(); // Ensure that internal logged in variable and cURL options are reset. $this->loggedInUser = FALSE; diff --git a/core/modules/user/user.module b/core/modules/user/user.module index f4479ec..15a4bba 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -3922,3 +3922,62 @@ function user_file_download_access($field, $entity_type, $entity) { return user_view_access($entity); } } + +/** + * Impersonates another user. + * + * Each time this function is called, the active user is saved and $new_user + * becomes the active user. Multiple calls to this function can be nested, + * and session saving will be disabled until all impersonation attempts have + * been reverted using user_revert_user(). + * + * @param $new_user + * User to impersonate, either a UID or a user object. + * + * @return + * Current user object. + * + * @see user_revert_user() + */ +function user_impersonate_user($new_user = NULL) { + global $user; + $user_original = &drupal_static(__FUNCTION__); + + if (!isset($new_user)) { + if (isset($user_original) && !empty($user_original)) { + // Restore the previous user from the stack. + $user = array_pop($user_original); + + // Re-enable session saving if we are no longer impersonating a user. + if (empty($user_original)) { + drupal_save_session(TRUE); + } + } + } + else { + // Push the original user onto the stack and prevent session saving. + $user_original[] = $user; + drupal_save_session(FALSE); + + if (is_numeric($new_user)) { + $user = user_load($new_user); + } + else { + $user = is_object($new_user) ? $new_user : (object) $new_user; + } + } + + return $user; +} + +/** + * Reverts to the previous user after impersonating. + * + * @return + * Current user. + * + * @see user_impersonate_user() + */ +function user_revert_user() { + return user_impersonate_user(); +} diff --git a/core/modules/user/user.test b/core/modules/user/user.test index 0c5f90f..75dccf4 100644 --- a/core/modules/user/user.test +++ b/core/modules/user/user.test @@ -1880,6 +1880,70 @@ class UserRoleAdminTestCase extends DrupalWebTestCase { } /** + * Test case for impersonating users. + */ +class UserImpersonatingUserTestCase extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Impersonate users', + 'description' => 'Temporarily impersonate another user, and then restore the original user.', + 'group' => 'User', + ); + } + + function setUp() { + parent::setUp(); + } + + function testUserImpersonateUser() { + global $user; + $original_user = $user; + + // If not currently logged in, use user_user_impersonate_user() to switch to + // user 1. If logged in, switch to the anonymous user instead. + if (user_is_anonymous()) { + user_impersonate_user(1); + } + else { + user_impersonate_user(0); + } + + // Verify that the active user has changed, and that session saving is + // disabled. + $this->assertEqual($user->uid, ($original_user->uid == 0 ? 1 : 0), t('User switched')); + $this->assertFalse(drupal_save_session(), t('Session saving is disabled.')); + + // Perform a second (nested) impersonation. + user_impersonate_user(1); + $this->assertEqual($user->uid, 1, t('User switched.')); + + // Revert to the user which was active between the first and second + // impersonation attempt. + user_revert_user(); + + // Since we are still impersonating the user from the first attempt, + // session handling still needs to be disabled. + $this->assertEqual($user->uid, ($original_user->uid == 0 ? 1 : 0), t('User switched.')); + $this->assertFalse(drupal_save_session(), t('Session saving is disabled.')); + + // Revert to the original user which was active before the first + // impersonation attempt. + user_revert_user(); + + // Assert that the original user is the active user again, and that session + // saving has been re-enabled. + $this->assertEqual($user->uid, $original_user->uid, t('Original user successfully restored.')); + + // Simpletest uses user_impersonate_user() too, revert the impersonation by + // Simpletest to enable session saving again. This is safe because calling + // user_revert_user() too often simply results in returning the active user. + user_revert_user(); + $this->assertTrue(drupal_save_session(), t('Session saving is enabled.')); + } +} + +/** * Test user token replacement in strings. */ class UserTokenReplaceTestCase extends DrupalWebTestCase {