From 03ac6089d25e10ee9b63842beea6521f101403bf Sun, 21 Jun 2015 23:53:08 +0200
From: hass <hass@85918.no-reply.drupal.org>
Date: Sun, 21 Jun 2015 23:52:57 +0200
Subject: [PATCH] Issue #287292 by hass: Add functionality to impersonate a user

diff --git a/includes/common.inc b/includes/common.inc
index ceac115..c55fe68 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -5315,14 +5315,9 @@
   // Allow execution to continue even if the request gets canceled.
   @ignore_user_abort(TRUE);
 
-  // Prevent session information from being saved while cron is running.
-  $original_session_saving = drupal_save_session();
-  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);
@@ -5387,8 +5382,7 @@
     }
   }
   // Restore the user.
-  $GLOBALS['user'] = $original_user;
-  drupal_save_session($original_session_saving);
+  user_revert_user();
 
   return $return;
 }
diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php
index fb5c6a6..faf50a6 100644
--- a/modules/simpletest/drupal_web_test_case.php
+++ b/modules/simpletest/drupal_web_test_case.php
@@ -1520,9 +1520,7 @@
 
     // Ensure that the session is not written to the new environment and replace
     // the global $user session with uid 1 from the new test site.
-    drupal_save_session(FALSE);
-    // Login as uid 1.
-    $user = user_load(1);
+    user_impersonate_user(user_load(1));
 
     // Restore necessary variables.
     variable_set('install_task', 'done');
@@ -1668,8 +1666,7 @@
     $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/modules/simpletest/tests/common.test b/modules/simpletest/tests/common.test
index bf85576..6acf4e6 100644
--- a/modules/simpletest/tests/common.test
+++ b/modules/simpletest/tests/common.test
@@ -2719,11 +2719,8 @@
     $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->language;
     $language->language = $user->language;
     // Simulate a Drupal bootstrap with the logged-in user.
@@ -2738,11 +2735,10 @@
     $this->assertIdentical(format_date($timestamp), '25. marzo 2007 - 17:00', 'Test default date format.');
 
     // Restore the original user and language, and enable session saving.
-    $user = $real_user;
+    user_revert_user();
     $language->language = $real_language;
     // Restore default time zone.
     date_default_timezone_set(drupal_get_user_timezone());
-    drupal_save_session(TRUE);
   }
 }
 
diff --git a/modules/simpletest/tests/upgrade/upgrade.test b/modules/simpletest/tests/upgrade/upgrade.test
index 784a091..6226b4c 100644
--- a/modules/simpletest/tests/upgrade/upgrade.test
+++ b/modules/simpletest/tests/upgrade/upgrade.test
@@ -142,13 +142,16 @@
 
     // Ensure that the session is not written to the new environment and replace
     // the global $user session with uid 1 from the new test site.
-    drupal_save_session(FALSE);
-    // Login as uid 1.
-    $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->prepareD7Session();
 
+    // Return the user to the original one.
+    user_revert_user();
+
     // Restore necessary variables.
     $this->variable_set('clean_url', $this->originalCleanUrl);
     $this->variable_set('site_mail', 'simpletest@example.com');
diff --git a/modules/user/user.module b/modules/user/user.module
index 9637a71..5d31fdb 100644
--- a/modules/user/user.module
+++ b/modules/user/user.module
@@ -899,6 +899,65 @@
 }
 
 /**
+ * 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();
+}
+
+/**
  * Implements hook_file_download().
  *
  * Ensure that user pictures (avatars) are always downloadable.
diff --git a/modules/user/user.test b/modules/user/user.test
index 07be4c2..da3e5bb 100644
--- a/modules/user/user.test
+++ b/modules/user/user.test
@@ -2134,6 +2134,70 @@
 }
 
 /**
+ * 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 {

