Skip to content
Snippets Groups Projects
Commit e65dec07 authored by Markus Kalkbrenner's avatar Markus Kalkbrenner Committed by Andrey Postnikov
Browse files

Issue #3042321 by andypost, jedihe, mkalkbrenner, xSDx, shubham.prakash: Last...

Issue #3042321 by andypost, jedihe, mkalkbrenner, xSDx, shubham.prakash: Last access by a user should not change after masquerade as that user (D8)
parent c6b0cde5
No related branches found
No related tags found
No related merge requests found
update_user_last_access: false
......@@ -5,3 +5,11 @@ block.settings.masquerade:
show_unmasquerade_link:
type: boolean
label: 'Show unmasquerade link in block'
masquerade.settings:
type: config_object
label: Masquerade settings
mapping:
update_user_last_access:
type: boolean
label: Update user's last access time when masquerading
......@@ -23,3 +23,13 @@ function masquerade_post_update_add_block_setting_link(&$sandbox) {
return FALSE;
});
}
/**
* Add configuration to force update last user's access time.
*/
function masquerade_post_update_add_settings() {
\Drupal::configFactory()
->getEditable('masquerade.settings')
->set('update_user_last_access', TRUE)
->save(TRUE);
}
......@@ -23,3 +23,15 @@ services:
masquerade.callbacks:
class: Drupal\masquerade\MasqueradeCallbacks
arguments: ['@entity_type.manager', '@masquerade']
masquerade.user_last_access_subscriber:
class: Drupal\masquerade\EventSubscriber\MasqueradeUserRequestSubscriber
decorates: user_last_access_subscriber
parent: user_last_access_subscriber
public: false
calls:
- [setMasquerade, ['@masquerade', '@config.factory']]
masquerade.session_manager.metadata_bag:
class: Drupal\masquerade\Session\MetadataBag
decorates: session_manager.metadata_bag
parent: session_manager.metadata_bag
public: false
......@@ -26,8 +26,9 @@ class MasqueradeCacheContext extends RequestStackCacheContextBase implements Cac
*/
public function getContext() {
if ($request = $this->requestStack->getCurrentRequest()) {
if ($request->hasSession() && ($session = $request->getSession())) {
if ($session->has('masquerading')) {
if ($request->hasSession() && ($bag = $request->getSession()->getMetadataBag())) {
/** @var \Drupal\masquerade\Session\MetadataBag $bag */
if ($bag->getMasquerade()) {
// Previous account supposed to be Authenticated.
return '1';
}
......
<?php
namespace Drupal\masquerade\EventSubscriber;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\masquerade\Masquerade;
use Drupal\user\EventSubscriber\UserRequestSubscriber;
use Symfony\Component\HttpKernel\Event\TerminateEvent;
/**
* Decorates service user_last_access_subscriber to prevent user's data changes.
*
* @internal
* Implementation could change later.
*/
class MasqueradeUserRequestSubscriber extends UserRequestSubscriber {
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The masquerade service.
*
* @var \Drupal\masquerade\Masquerade
*/
protected $masquerade;
/**
* {@inheritdoc}
*/
public function onKernelTerminate(TerminateEvent $event) {
$force = (bool) $this->configFactory
->get('masquerade.settings')
->get('update_user_last_access');
if (!$this->masquerade->isMasquerading() || $force) {
parent::onKernelTerminate($event);
}
}
/**
* Initialises masquerade required services.
*
* @param \Drupal\masquerade\Masquerade $masquerade
* The masquerade service.
* @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
* The config factory.
*/
public function setMasquerade(Masquerade $masquerade, ConfigFactoryInterface $configFactory) {
$this->masquerade = $masquerade;
$this->configFactory = $configFactory;
}
}
......@@ -126,6 +126,19 @@ class Masquerade {
return $previous;
}
/**
* Returns the masquerading identifier.
*
* @return string|null
* The per-session masquerade identifier or null when no value is set.
*
* @see \Drupal\masquerade\Session\MetadataBag::getMasquerade()
*/
protected function getMasquerade() {
// Accessing metadata does not try to start session.
return $this->session->getMetadataBag()->getMasquerade();
}
/**
* Returns whether the current user is masquerading.
*
......@@ -133,8 +146,7 @@ class Masquerade {
* TRUE when already masquerading, FALSE otherwise.
*/
public function isMasquerading() {
// Do not start new session trying to access its attributes.
return $this->session->isStarted() && $this->session->has('masquerading');
return (bool) $this->getMasquerade();
}
/**
......@@ -145,13 +157,15 @@ class Masquerade {
*
* @return bool
* TRUE when masqueraded, FALSE otherwise.
*
* @see \Drupal\masquerade\Session\MetadataBag::setMasquerade()
*/
public function switchTo(UserInterface $target_account) {
// Save previous account ID to session storage, set this before
// switching so that other modules can react to it, e.g. during
// hook_user_logout().
$this->session->set('masquerading', $this->currentUser->id());
$this->session->getMetadataBag()->setMasquerade($this->currentUser->id());
$account = $this->switchUser($target_account);
......@@ -168,13 +182,15 @@ class Masquerade {
*
* @return bool
* TRUE when switched back, FALSE otherwise.
*
* @see \Drupal\masquerade\Session\MetadataBag::clearMasquerade()
*/
public function switchBack() {
if (!$this->session->isStarted() && !$this->session->has('masquerading')) {
if (!$this->isMasquerading()) {
return FALSE;
}
// Load previous user account.
$user = $this->userStorage->load($this->session->get('masquerading'));
$user = $this->userStorage->load($this->getMasquerade());
if (!$user) {
// Ensure the flag is cleared.
$this->session->remove('masquerading');
......@@ -186,7 +202,7 @@ class Masquerade {
// Clear the masquerading flag after switching the user so that hook
// implementations can differentiate this from a real logout/login.
$this->session->remove('masquerading');
$this->session->getMetadataBag()->clearMasquerade();
$this->logger->info('User %username stopped masquerading as %old_username.', [
'%username' => $user->getDisplayName(),
......
<?php
namespace Drupal\masquerade\Session;
use Drupal\Core\Session\MetadataBag as CoreMetadataBag;
/**
* Decorates service session_manager.metadata_bag to store required flag.
*
* @internal
* Implementation could change later.
*/
class MetadataBag extends CoreMetadataBag {
/**
* The key used to store the masquerading user ID in the session.
*/
const MASQUERADE = 'masquerading';
/**
* Sets the masquerading identifier.
*
* @param string $uid
* The per-session identifier of masquerading user.
*/
public function setMasquerade($uid) {
$this->meta[static::MASQUERADE] = $uid;
}
/**
* Get the masquerading identifier.
*
* @return string|null
* The per-session masquerade identifier or null when no value is set.
*/
public function getMasquerade() {
if (isset($this->meta[static::MASQUERADE])) {
return $this->meta[static::MASQUERADE];
}
}
/**
* Clear the masquerade identifier.
*/
public function clearMasquerade() {
unset($this->meta[static::MASQUERADE]);
}
}
......@@ -18,6 +18,8 @@ class MasqueradeTest extends MasqueradeWebTestBase {
* Tests masquerade user links.
*/
public function testMasquerade() {
$original_last_access = $this->authUser->getLastAccessedTime();
$this->drupalLogin($this->adminUser);
// Verify that a token is required.
......@@ -48,6 +50,13 @@ class MasqueradeTest extends MasqueradeWebTestBase {
$this->unmasquerade($this->authUser);
$this->assertNoSessionByUid($this->authUser->id());
$this->assertSessionByUid($this->adminUser->id());
// Verify that masquerading as $authUser did not change the last login
// time.
$authUser = \Drupal::entityTypeManager()
->getStorage('user')
->loadUnchanged($this->authUser->id());
$this->assertEquals($original_last_access, $authUser->getLastAccessedTime(), 'Last access timestamp for impersonated user was not changed.');
}
/**
......
<?php
namespace Drupal\Tests\masquerade\Kernel;
use Drupal\KernelTests\KernelTestBase;
/**
* Class ServiceDecoratorsTest.
*
* @group masquerade
*/
class ServiceDecoratorsTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = [
'masquerade',
'user',
];
/**
* Tests the MetadataBag methods.
*
* @covers \Drupal\masquerade\Session\MetadataBag::setMasquerade
* @covers \Drupal\masquerade\Session\MetadataBag::getMasquerade
* @covers \Drupal\masquerade\Session\MetadataBag::clearMasquerade
*/
public function testSetMasquerade() {
/** @var \Drupal\masquerade\Session\MetadataBag $bag */
$bag = \Drupal::service('session')->getMetadataBag();
$this->assertTrue(method_exists($bag, 'setMasquerade'));
$this->assertTrue(method_exists($bag, 'getMasquerade'));
$this->assertTrue(method_exists($bag, 'clearMasquerade'));
$this->assertNull($bag->getMasquerade());
$uid = '1000';
$bag->setMasquerade($uid);
$this->assertSame($bag->getMasquerade(), $uid);
$uid = '1';
$bag->setMasquerade($uid);
$this->assertSame($bag->getMasquerade(), $uid);
$bag->clearMasquerade();
$this->assertNull($bag->getMasquerade());
}
/**
* Tests the MasqueradeUserRequestSubscriber methods.
*
* @covers \Drupal\masquerade\EventSubscriber\MasqueradeUserRequestSubscriber::setMasquerade
*/
public function testServiceSetMasquerade() {
/** @var \Drupal\masquerade\EventSubscriber\MasqueradeUserRequestSubscriber $service */
$service = \Drupal::service('user_last_access_subscriber');
$this->assertTrue(method_exists($service, 'setMasquerade'));
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment