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']
|
arguments: ['@typed_data_manager']
|
||||||
cron:
|
cron:
|
||||||
class: Drupal\Core\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:
|
diff.formatter:
|
||||||
class: Drupal\Core\Diff\DiffFormatter
|
class: Drupal\Core\Diff\DiffFormatter
|
||||||
arguments: ['@config.factory']
|
arguments: ['@config.factory']
|
||||||
|
@ -1012,6 +1012,9 @@ services:
|
||||||
tags:
|
tags:
|
||||||
- { name: event_subscriber }
|
- { name: event_subscriber }
|
||||||
arguments: ['@authentication']
|
arguments: ['@authentication']
|
||||||
|
account_switcher:
|
||||||
|
class: Drupal\Core\Session\AccountSwitcher
|
||||||
|
arguments: ['@current_user', '@session_manager']
|
||||||
current_user:
|
current_user:
|
||||||
class: Drupal\Core\Session\AccountProxy
|
class: Drupal\Core\Session\AccountProxy
|
||||||
arguments: ['@authentication', '@request_stack']
|
arguments: ['@authentication', '@request_stack']
|
||||||
|
|
|
@ -12,9 +12,8 @@ use Drupal\Core\Queue\QueueWorkerManagerInterface;
|
||||||
use Drupal\Core\State\StateInterface;
|
use Drupal\Core\State\StateInterface;
|
||||||
use Drupal\Core\Lock\LockBackendInterface;
|
use Drupal\Core\Lock\LockBackendInterface;
|
||||||
use Drupal\Core\Queue\QueueFactory;
|
use Drupal\Core\Queue\QueueFactory;
|
||||||
use Drupal\Core\Session\AccountProxyInterface;
|
|
||||||
use Drupal\Core\Session\AnonymousUserSession;
|
use Drupal\Core\Session\AnonymousUserSession;
|
||||||
use Drupal\Core\Session\SessionManagerInterface;
|
use Drupal\Core\Session\AccountSwitcherInterface;
|
||||||
use Drupal\Core\Queue\SuspendQueueException;
|
use Drupal\Core\Queue\SuspendQueueException;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
@ -52,18 +51,11 @@ class Cron implements CronInterface {
|
||||||
protected $state;
|
protected $state;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current user.
|
* The account switcher service.
|
||||||
*
|
*
|
||||||
* @var \Drupal\Core\Session\AccountProxyInterface
|
* @var \Drupal\Core\Session\AccountSwitcherInterface
|
||||||
*/
|
*/
|
||||||
protected $currentUser;
|
protected $accountSwitcher;
|
||||||
|
|
||||||
/**
|
|
||||||
* The session manager.
|
|
||||||
*
|
|
||||||
* @var \Drupal\Core\Session\SessionManagerInterface
|
|
||||||
*/
|
|
||||||
protected $sessionManager;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A logger instance.
|
* A logger instance.
|
||||||
|
@ -90,22 +82,19 @@ class Cron implements CronInterface {
|
||||||
* The queue service.
|
* The queue service.
|
||||||
* @param \Drupal\Core\State\StateInterface $state
|
* @param \Drupal\Core\State\StateInterface $state
|
||||||
* The state service.
|
* The state service.
|
||||||
* @param \Drupal\Core\Session\AccountProxyInterface $current_user
|
* @param \Drupal\Core\Session\AccountSwitcherInterface $account_switcher
|
||||||
* The current user.
|
* The account switching service.
|
||||||
* @param \Drupal\Core\Session\SessionManagerInterface $session_manager
|
|
||||||
* The session manager.
|
|
||||||
* @param \Psr\Log\LoggerInterface $logger
|
* @param \Psr\Log\LoggerInterface $logger
|
||||||
* A logger instance.
|
* A logger instance.
|
||||||
* @param \Drupal\Core\Queue\QueueWorkerManagerInterface
|
* @param \Drupal\Core\Queue\QueueWorkerManagerInterface
|
||||||
* The queue plugin manager.
|
* 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->moduleHandler = $module_handler;
|
||||||
$this->lock = $lock;
|
$this->lock = $lock;
|
||||||
$this->queueFactory = $queue_factory;
|
$this->queueFactory = $queue_factory;
|
||||||
$this->state = $state;
|
$this->state = $state;
|
||||||
$this->currentUser = $current_user;
|
$this->accountSwitcher = $account_switcher;
|
||||||
$this->sessionManager = $session_manager;
|
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
$this->queueManager = $queue_manager;
|
$this->queueManager = $queue_manager;
|
||||||
}
|
}
|
||||||
|
@ -117,14 +106,9 @@ class Cron implements CronInterface {
|
||||||
// Allow execution to continue even if the request gets cancelled.
|
// Allow execution to continue even if the request gets cancelled.
|
||||||
@ignore_user_abort(TRUE);
|
@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
|
// Force the current user to anonymous to ensure consistent permissions on
|
||||||
// cron runs.
|
// cron runs.
|
||||||
$original_user = $this->currentUser->getAccount();
|
$this->accountSwitcher->switchTo(new AnonymousUserSession());
|
||||||
$this->currentUser->setAccount(new AnonymousUserSession());
|
|
||||||
|
|
||||||
// Try to allocate enough time to run all the hook_cron implementations.
|
// Try to allocate enough time to run all the hook_cron implementations.
|
||||||
drupal_set_time_limit(240);
|
drupal_set_time_limit(240);
|
||||||
|
@ -151,10 +135,7 @@ class Cron implements CronInterface {
|
||||||
$this->processQueues();
|
$this->processQueues();
|
||||||
|
|
||||||
// Restore the user.
|
// Restore the user.
|
||||||
$this->currentUser->setAccount($original_user);
|
$this->accountSwitcher->switchBack();
|
||||||
if ($original_session_saving) {
|
|
||||||
$this->sessionManager->enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $return;
|
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