Issue #287292 by almaudoh, mr.baileys, drewish, Berdir, znerol, boombatower, dawehner, jpetso, floretan: Add functionality to impersonate a user
parent
fa140d27b3
commit
ef5b0e3401
|
@ -163,7 +163,7 @@ services:
|
|||
arguments: ['@typed_data_manager']
|
||||
cron:
|
||||
class: Drupal\Core\Cron
|
||||
arguments: ['@module_handler', '@lock', '@queue', '@state', '@current_user', '@session_manager', '@logger.channel.cron', '@plugin.manager.queue_worker']
|
||||
arguments: ['@module_handler', '@lock', '@queue', '@state', '@account_switcher', '@logger.channel.cron', '@plugin.manager.queue_worker']
|
||||
diff.formatter:
|
||||
class: Drupal\Core\Diff\DiffFormatter
|
||||
arguments: ['@config.factory']
|
||||
|
@ -1012,6 +1012,9 @@ services:
|
|||
tags:
|
||||
- { name: event_subscriber }
|
||||
arguments: ['@authentication']
|
||||
account_switcher:
|
||||
class: Drupal\Core\Session\AccountSwitcher
|
||||
arguments: ['@current_user', '@session_manager']
|
||||
current_user:
|
||||
class: Drupal\Core\Session\AccountProxy
|
||||
arguments: ['@authentication', '@request_stack']
|
||||
|
|
|
@ -12,9 +12,8 @@ use Drupal\Core\Queue\QueueWorkerManagerInterface;
|
|||
use Drupal\Core\State\StateInterface;
|
||||
use Drupal\Core\Lock\LockBackendInterface;
|
||||
use Drupal\Core\Queue\QueueFactory;
|
||||
use Drupal\Core\Session\AccountProxyInterface;
|
||||
use Drupal\Core\Session\AnonymousUserSession;
|
||||
use Drupal\Core\Session\SessionManagerInterface;
|
||||
use Drupal\Core\Session\AccountSwitcherInterface;
|
||||
use Drupal\Core\Queue\SuspendQueueException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
|
@ -52,18 +51,11 @@ class Cron implements CronInterface {
|
|||
protected $state;
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
* The account switcher service.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountProxyInterface
|
||||
* @var \Drupal\Core\Session\AccountSwitcherInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* The session manager.
|
||||
*
|
||||
* @var \Drupal\Core\Session\SessionManagerInterface
|
||||
*/
|
||||
protected $sessionManager;
|
||||
protected $accountSwitcher;
|
||||
|
||||
/**
|
||||
* A logger instance.
|
||||
|
@ -90,22 +82,19 @@ class Cron implements CronInterface {
|
|||
* The queue service.
|
||||
* @param \Drupal\Core\State\StateInterface $state
|
||||
* The state service.
|
||||
* @param \Drupal\Core\Session\AccountProxyInterface $current_user
|
||||
* The current user.
|
||||
* @param \Drupal\Core\Session\SessionManagerInterface $session_manager
|
||||
* The session manager.
|
||||
* @param \Drupal\Core\Session\AccountSwitcherInterface $account_switcher
|
||||
* The account switching service.
|
||||
* @param \Psr\Log\LoggerInterface $logger
|
||||
* A logger instance.
|
||||
* @param \Drupal\Core\Queue\QueueWorkerManagerInterface
|
||||
* The queue plugin manager.
|
||||
*/
|
||||
public function __construct(ModuleHandlerInterface $module_handler, LockBackendInterface $lock, QueueFactory $queue_factory, StateInterface $state, AccountProxyInterface $current_user, SessionManagerInterface $session_manager, LoggerInterface $logger, QueueWorkerManagerInterface $queue_manager) {
|
||||
public function __construct(ModuleHandlerInterface $module_handler, LockBackendInterface $lock, QueueFactory $queue_factory, StateInterface $state, AccountSwitcherInterface $account_switcher, LoggerInterface $logger, QueueWorkerManagerInterface $queue_manager) {
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->lock = $lock;
|
||||
$this->queueFactory = $queue_factory;
|
||||
$this->state = $state;
|
||||
$this->currentUser = $current_user;
|
||||
$this->sessionManager = $session_manager;
|
||||
$this->accountSwitcher = $account_switcher;
|
||||
$this->logger = $logger;
|
||||
$this->queueManager = $queue_manager;
|
||||
}
|
||||
|
@ -117,14 +106,9 @@ class Cron implements CronInterface {
|
|||
// Allow execution to continue even if the request gets cancelled.
|
||||
@ignore_user_abort(TRUE);
|
||||
|
||||
// Prevent session information from being saved while cron is running.
|
||||
$original_session_saving = $this->sessionManager->isEnabled();
|
||||
$this->sessionManager->disable();
|
||||
|
||||
// Force the current user to anonymous to ensure consistent permissions on
|
||||
// cron runs.
|
||||
$original_user = $this->currentUser->getAccount();
|
||||
$this->currentUser->setAccount(new AnonymousUserSession());
|
||||
$this->accountSwitcher->switchTo(new AnonymousUserSession());
|
||||
|
||||
// Try to allocate enough time to run all the hook_cron implementations.
|
||||
drupal_set_time_limit(240);
|
||||
|
@ -151,10 +135,7 @@ class Cron implements CronInterface {
|
|||
$this->processQueues();
|
||||
|
||||
// Restore the user.
|
||||
$this->currentUser->setAccount($original_user);
|
||||
if ($original_session_saving) {
|
||||
$this->sessionManager->enable();
|
||||
}
|
||||
$this->accountSwitcher->switchBack();
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Session\AccountSwitcher.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Session;
|
||||
|
||||
/**
|
||||
* An implementation of AccountSwitcherInterface.
|
||||
*
|
||||
* This allows for safe switching of user accounts by ensuring that session
|
||||
* data for one user is not leaked in to others. It also provides a stack that
|
||||
* allows reverting to a previous user after switching.
|
||||
*/
|
||||
class AccountSwitcher implements AccountSwitcherInterface {
|
||||
|
||||
/**
|
||||
* A stack of previous overridden accounts.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface[]
|
||||
*/
|
||||
protected $accountStack = array();
|
||||
|
||||
/**
|
||||
* The current user service.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountProxyInterface
|
||||
*/
|
||||
protected $currentUser = array();
|
||||
|
||||
/**
|
||||
* The session manager.
|
||||
*
|
||||
* @var \Drupal\Core\Session\SessionManagerInterface
|
||||
*/
|
||||
protected $sessionManager;
|
||||
|
||||
/**
|
||||
* The original state of session saving prior to account switching.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $originalSessionSaving;
|
||||
|
||||
/**
|
||||
* Constructs a new AccountSwitcher.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountProxyInterface $current_user
|
||||
* The current user service.
|
||||
* @param \Drupal\Core\Session\SessionManagerInterface $session_manager
|
||||
* The session manager.
|
||||
*/
|
||||
public function __construct(AccountProxyInterface $current_user, SessionManagerInterface $session_manager) {
|
||||
$this->currentUser = $current_user;
|
||||
$this->sessionManager = $session_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function switchTo(AccountInterface $account) {
|
||||
// Prevent session information from being saved and push previous account.
|
||||
if (!isset($this->originalSessionSaving)) {
|
||||
// Ensure that only the first session saving status is saved.
|
||||
$this->originalSessionSaving = $this->sessionManager->isEnabled();
|
||||
}
|
||||
$this->sessionManager->disable();
|
||||
array_push($this->accountStack, $this->currentUser->getAccount());
|
||||
$this->currentUser->setAccount($account);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function switchBack() {
|
||||
// Restore the previous account from the stack.
|
||||
if (!empty($this->accountStack)) {
|
||||
$this->currentUser->setAccount(array_pop($this->accountStack));
|
||||
}
|
||||
else {
|
||||
throw new \RuntimeException('No more accounts to revert to.');
|
||||
}
|
||||
// Restore original session saving status if all account switches are
|
||||
// reverted.
|
||||
if (empty($this->accountStack)) {
|
||||
if ($this->originalSessionSaving) {
|
||||
$this->sessionManager->enable();
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Session\AccountSwitcherInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Session;
|
||||
|
||||
/**
|
||||
* Defines an interface for a service for safe account switching.
|
||||
*
|
||||
* @ingroup user_api
|
||||
*/
|
||||
interface AccountSwitcherInterface {
|
||||
|
||||
/**
|
||||
* Safely switches to another account.
|
||||
*
|
||||
* Each invocation of AccountSwitcherInterface::switchTo() must be
|
||||
* matched by a corresponding invocation of
|
||||
* AccountSwitcherInterface::switchBack() in the same function.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The account to switch to.
|
||||
*
|
||||
* @return \Drupal\Core\Session\AccountSwitcherInterface
|
||||
* $this.
|
||||
*/
|
||||
public function switchTo(AccountInterface $account);
|
||||
|
||||
/**
|
||||
* Reverts back to a previous account after switching.
|
||||
*
|
||||
* @return \Drupal\Core\Session\AccountSwitcherInterface
|
||||
* $this.
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
* When there are no more account switches to revert.
|
||||
*/
|
||||
public function switchBack();
|
||||
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains Drupal\system\Tests\Session\AccountSwitcherTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Session;
|
||||
|
||||
use Drupal\Core\Session\UserSession;
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Test case for account switching.
|
||||
*
|
||||
* @group Session
|
||||
*/
|
||||
class AccountSwitcherTest extends KernelTestBase {
|
||||
|
||||
public function testAccountSwitching() {
|
||||
$session_manager = $this->container->get('session_manager');
|
||||
$user = $this->container->get('current_user');
|
||||
$switcher = $this->container->get('account_switcher');
|
||||
$original_user = $user->getAccount();
|
||||
$original_session_saving = $session_manager->isEnabled();
|
||||
|
||||
// Switch to user with uid 2.
|
||||
$switcher->switchTo(new UserSession(array('uid' => 2)));
|
||||
|
||||
// Verify that the active user has changed, and that session saving is
|
||||
// disabled.
|
||||
$this->assertEqual($user->id(), 2, 'Switched to user 2.');
|
||||
$this->assertFalse($session_manager->isEnabled(), 'Session saving is disabled.');
|
||||
|
||||
// Perform a second (nested) user account switch.
|
||||
$switcher->switchTo(new UserSession(array('uid' => 3)));
|
||||
$this->assertEqual($user->id(), 3, 'Switched to user 3.');
|
||||
|
||||
// Revert to the user session that was active between the first and second
|
||||
// switch.
|
||||
$switcher->switchBack();
|
||||
|
||||
// Since we are still in the account from the first switch, session handling
|
||||
// still needs to be disabled.
|
||||
$this->assertEqual($user->id(), 2, 'Reverted back to user 2.');
|
||||
$this->assertFalse($session_manager->isEnabled(), 'Session saving still disabled.');
|
||||
|
||||
// Revert to the original account which was active before the first switch.
|
||||
$switcher->switchBack();
|
||||
|
||||
// Assert that the original account is active again, and that session saving
|
||||
// has been re-enabled.
|
||||
$this->assertEqual($user->id(), $original_user->id(), 'Original user correctly restored.');
|
||||
$this->assertEqual($session_manager->isEnabled(), $original_session_saving, 'Original session saving correctly restored.');
|
||||
|
||||
// Verify that AccountSwitcherInterface::switchBack() will throw
|
||||
// an exception if there are no accounts left in the stack.
|
||||
try {
|
||||
$switcher->switchBack();
|
||||
$this->fail('::switchBack() throws exception if called without previous switch.');
|
||||
}
|
||||
catch (\RuntimeException $e) {
|
||||
if ($e->getMessage() == 'No more accounts to revert to.') {
|
||||
$this->pass('::switchBack() throws exception if called without previous switch.');
|
||||
}
|
||||
else {
|
||||
$this->fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue