Commit ef5b0e34 authored by alexpott's avatar alexpott

Issue #287292 by almaudoh, mr.baileys, drewish, Berdir, znerol, boombatower,...

Issue #287292 by almaudoh, mr.baileys, drewish, Berdir, znerol, boombatower, dawehner, jpetso, floretan: Add functionality to impersonate a user
parent fa140d27
......@@ -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\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 @@ public function run() {
// 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 @@ public function run() {
$this->processQueues();
// Restore the user.
$this->currentUser->setAccount($original_user);
if ($original_session_saving) {
$this->sessionManager->enable();
}
$this->accountSwitcher->switchBack();
return $return;
}
......
<?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;
}
}
<?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();
}
<?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());
}
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment