Issue #2180109 by damiankloip, dawehner, ParisLiakos, joelpittet, MartijnBraam, Xano: Change the current_user service to a proxy.

8.0.x
catch 2014-03-27 16:25:24 +01:00
parent 74027cd328
commit d026a1c363
21 changed files with 307 additions and 68 deletions

View File

@ -102,7 +102,7 @@ services:
arguments: ['@config.storage', '@config.storage.schema', '@cache.config']
cron:
class: Drupal\Core\Cron
arguments: ['@module_handler', '@lock', '@queue', '@state']
arguments: ['@module_handler', '@lock', '@queue', '@state', '@current_user']
database:
class: Drupal\Core\Database\Connection
factory_class: Drupal\Core\Database\Database
@ -408,11 +408,9 @@ services:
- { name: event_subscriber }
route_enhancer.authentication:
class: Drupal\Core\Routing\Enhancer\AuthenticationEnhancer
calls:
- [setContainer, ['@service_container']]
tags:
- { name: route_enhancer, priority: 1000 }
arguments: ['@authentication']
arguments: ['@authentication', '@current_user']
route_enhancer.entity:
class: Drupal\Core\Entity\Enhancer\EntityRouteEnhancer
arguments: ['@controller_resolver', '@entity.manager', '@form_builder']
@ -716,11 +714,10 @@ services:
- { name: event_subscriber }
arguments: ['@authentication']
current_user:
class: Drupal\Core\Session\AccountInterface
factory_method: authenticate
factory_service: authentication
arguments: ['@request']
synchronized: true
class: Drupal\Core\Session\AccountProxy
arguments: ['@authentication']
calls:
- [setRequest, ['@?request=']]
asset.css.collection_renderer:
class: Drupal\Core\Asset\CssCollectionRenderer
arguments: [ '@state' ]

View File

@ -190,7 +190,7 @@ class Drupal {
/**
* Gets the current active user.
*
* @return \Drupal\Core\Session\AccountInterface
* @return \Drupal\Core\Session\AccountProxyInterface
*/
public static function currentUser() {
return static::$container->get('current_user');

View File

@ -10,7 +10,7 @@ namespace Drupal\Core\Authentication;
/**
* Defines an interface for authentication managers.
*/
interface AuthenticationManagerInterface {
interface AuthenticationManagerInterface extends AuthenticationProviderInterface {
/**
* Returns the service id of the default authentication provider.
@ -19,4 +19,5 @@ interface AuthenticationManagerInterface {
* The service id of the default authentication provider.
*/
public function defaultProviderId();
}

View File

@ -11,6 +11,7 @@ use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\KeyValueStore\StateInterface;
use Drupal\Core\Lock\LockBackendInterface;
use Drupal\Core\Queue\QueueFactory;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Session\AnonymousUserSession;
/**
@ -46,6 +47,13 @@ class Cron implements CronInterface {
*/
protected $state;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountProxyInterface
*/
protected $currentUser;
/**
* Constructs a cron object.
*
@ -57,12 +65,15 @@ class Cron implements CronInterface {
* The queue service.
* @param \Drupal\Core\KeyValueStore\StateInterface $state
* The state service.
* @param \Drupal\Core\Session\AccountProxyInterface $current_user
* The current user.
*/
public function __construct(ModuleHandlerInterface $module_handler, LockBackendInterface $lock, QueueFactory $queue_factory, StateInterface $state) {
public function __construct(ModuleHandlerInterface $module_handler, LockBackendInterface $lock, QueueFactory $queue_factory, StateInterface $state, AccountProxyInterface $current_user) {
$this->moduleHandler = $module_handler;
$this->lock = $lock;
$this->queueFactory = $queue_factory;
$this->state = $state;
$this->currentUser = $current_user;
}
/**
@ -78,10 +89,8 @@ class Cron implements CronInterface {
// Force the current user to anonymous to ensure consistent permissions on
// cron runs.
// @todo This currently does not work, as it will not affect the current
// user being injected into services.
$original_user = $GLOBALS['user'];
$GLOBALS['user'] = new AnonymousUserSession();
$original_user = $this->currentUser->getAccount();
$this->currentUser->setAccount(new AnonymousUserSession());
// Try to allocate enough time to run all the hook_cron implementations.
drupal_set_time_limit(240);
@ -147,9 +156,7 @@ class Cron implements CronInterface {
}
// Restore the user.
// @todo This currently does not work, as it will not affect the current
// user being injected into services.
$GLOBALS['user'] = $original_user;
$this->currentUser->setAccount($original_user);
drupal_save_session($original_session_saving);
return $return;

View File

@ -269,7 +269,7 @@ class EntityAccessController extends EntityControllerBase implements EntityAcces
*/
protected function prepareUser(AccountInterface $account = NULL) {
if (!$account) {
$account = $GLOBALS['user'];
$account = \Drupal::currentUser();
}
return $account;
}

View File

@ -10,7 +10,6 @@ namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Authentication\AuthenticationProviderInterface;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@ -39,18 +38,6 @@ class AuthenticationSubscriber implements EventSubscriberInterface {
$this->authenticationProvider = $authentication_provider;
}
/**
* Authenticates user on request.
*
* @see \Drupal\Core\Authentication\AuthenticationProviderInterface::authenticate()
*/
public function onKernelRequestAuthenticate(GetResponseEvent $event) {
if ($event->getRequestType() == HttpKernelInterface::MASTER_REQUEST) {
$request = $event->getRequest();
$this->authenticationProvider->authenticate($request);
}
}
/**
* Triggers authentication clean up on response.
*
@ -83,9 +70,6 @@ class AuthenticationSubscriber implements EventSubscriberInterface {
* Cookie provider to send all relevant session data to the user.
*/
public static function getSubscribedEvents() {
// Priority must be higher than LanguageRequestSubscriber as LanguageManager
// access current user in case language module enabled.
$events[KernelEvents::REQUEST][] = array('onKernelRequestAuthenticate', 300);
$events[KernelEvents::RESPONSE][] = array('onRespond', 0);
$events[KernelEvents::EXCEPTION][] = array('onException', 0);
return $events;

View File

@ -8,9 +8,9 @@
namespace Drupal\Core\Routing\Enhancer;
use Drupal\Core\Authentication\AuthenticationManagerInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Session\AnonymousUserSession;
use Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface;
use Symfony\Component\DependencyInjection\ContainerAware;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
@ -22,7 +22,7 @@ use Symfony\Cmf\Component\Routing\RouteObjectInterface;
* all authentication mechanisms. Instead, we check if the used provider is
* valid for the matched route and if not, force the user to anonymous.
*/
class AuthenticationEnhancer extends ContainerAware implements RouteEnhancerInterface {
class AuthenticationEnhancer implements RouteEnhancerInterface {
/**
* The authentication manager.
@ -31,14 +31,24 @@ class AuthenticationEnhancer extends ContainerAware implements RouteEnhancerInte
*/
protected $manager;
/**
* The current user service.
*
* @var \Drupal\Core\Session\AccountProxyInterface
*/
protected $currentUser;
/**
* Constructs a AuthenticationEnhancer object.
*
* @param AuthenticationManagerInterface $manager
* @param \Drupal\Core\Authentication\AuthenticationManagerInterface $manager
* The authentication manager.
* @param \Drupal\Core\Session\AccountProxyInterface $current_user
* The current user service.
*/
function __construct(AuthenticationManagerInterface $manager) {
function __construct(AuthenticationManagerInterface $manager, AccountProxyInterface $current_user) {
$this->manager = $manager;
$this->currentUser = $current_user;
}
/**
@ -55,7 +65,7 @@ class AuthenticationEnhancer extends ContainerAware implements RouteEnhancerInte
if (!in_array($auth_provider_triggered, $auth_providers)) {
$anonymous_user = new AnonymousUserSession();
$this->container->set('current_user', $anonymous_user, 'request');
$this->currentUser->setAccount($anonymous_user);
// The global $user object is included for backward compatibility only
// and should be considered deprecated.

View File

@ -0,0 +1,198 @@
<?php
/**
* @file
* Contains \Drupal\Core\Session\AccountProxy.
*/
namespace Drupal\Core\Session;
use Drupal\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* A proxied implementation of AccountInterface.
*
* The reason why we need an account proxy is that we don't want to have global
* state directly stored in the container.
*
* This proxy object avoids multiple invocations of the authentication manager
* which can happen if the current user is accessed in constructors. It also
* allows legacy code to change the current user where the user cannot be
* directly injected into dependent code.
*/
class AccountProxy implements AccountProxyInterface {
/**
* The current request.
*
* @var \Symfony\Component\HttpFoundation\Request
*/
protected $request;
/**
* The authentication manager.
*
* @var \Drupal\Core\Authentication\AuthenticationManagerInterface
*/
protected $authenticationManager;
/**
* The instantiated account.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $account;
/**
* Constructs a new AccountProxy.
*
* @param \Drupal\Core\Authentication\AuthenticationManagerInterface $authentication_manager
* The authentication manager.
*/
public function __construct(AuthenticationManagerInterface $authentication_manager) {
$this->authenticationManager = $authentication_manager;
}
/**
* Sets the current request.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request.
*/
public function setRequest(Request $request) {
$this->request = $request;
// Reset the current user to ensure that new calls will return the correct
// user based on the request.
$this->account = NULL;
}
/**
* {@inheritdoc}
*/
public function setAccount(AccountInterface $account) {
// If the passed account is already proxyed, use the actual account instead
// to prevent loops.
if ($account instanceof static) {
$account = $account->getAccount();
}
$this->account = $account;
}
/**
* {@inheritdoc}
*/
public function getAccount() {
if (!isset($this->account)) {
$this->setAccount($this->authenticationManager->authenticate($this->request));
}
return $this->account;
}
/**
* {@inheritdoc}
*/
public function id() {
return $this->getAccount()->id();
}
/**
* {@inheritdoc}
*/
public function getRoles($exclude_locked_roles = FALSE) {
return $this->getAccount()->getRoles($exclude_locked_roles);
}
/**
* {@inheritdoc}
*/
public function getHostname() {
return $this->getAccount()->getHostname();
}
/**
* {@inheritdoc}
*/
public function hasPermission($permission) {
return $this->getAccount()->hasPermission($permission);
}
/**
* {@inheritdoc}
*/
public function getSessionId() {
return $this->getAccount()->getSessionId();
}
/**
* {@inheritdoc}
*/
public function getSecureSessionId() {
return $this->getAccount()->getSecureSessionId();
}
/**
* {@inheritdoc}
*/
public function getSessionData() {
return $this->getAccount()->getSessionData();
}
/**
* {@inheritdoc}
*/
public function isAuthenticated() {
return $this->getAccount()->isAuthenticated();
}
/**
* {@inheritdoc}
*/
public function isAnonymous() {
return $this->getAccount()->isAnonymous();
}
/**
* {@inheritdoc}
*/
public function getPreferredLangcode($default = NULL) {
return $this->getAccount()->getPreferredLangcode($default);
}
/**
* {@inheritdoc}
*/
public function getPreferredAdminLangcode($default = NULL) {
return $this->getAccount()->getPreferredAdminLangcode($default);
}
/**
* {@inheritdoc}
*/
public function getUsername() {
return $this->getAccount()->getUsername();
}
/**
* {@inheritdoc}
*/
public function getEmail() {
return $this->getAccount()->getEmail();
}
/**
* {@inheritdoc}
*/
public function getTimeZone() {
return $this->getAccount()->getTimeZone();
}
/**
* {@inheritdoc}
*/
public function getLastAccessedTime() {
return $this->getAccount()->getLastAccessedTime();
}
}

View File

@ -0,0 +1,38 @@
<?php
/**
* @file
* Contains \Drupal\Core\Session\AccountProxyInterface.
*/
namespace Drupal\Core\Session;
/**
* Defines an interface for a service which has the current account stored.
*/
interface AccountProxyInterface extends AccountInterface {
/**
* Set the current wrapped account.
*
* Setting the current account is highly discouraged! Instead, make sure to
* inject the desired user object into the dependent code directly
*
* @param \Drupal\Core\Session\AccountInterface
* The current account.
*/
public function setAccount(AccountInterface $account);
/**
* Set the current wrapped account.
*
* Setting the current account is highly discouraged! Instead, make sure to
* inject the desired user object into the dependent code directly
*
* @param \Drupal\Core\Session\AccountInterface
* The current account.
*/
public function getAccount();
}

View File

@ -120,7 +120,7 @@ class EntityReferenceSelectionAccessTest extends WebTestBase {
// Test as a non-admin.
$normal_user = $this->drupalCreateUser(array('access content'));
$this->container->set('current_user', $normal_user);
\Drupal::currentUser()->setAccount($normal_user);
$referenceable_tests = array(
array(
'arguments' => array(
@ -172,7 +172,7 @@ class EntityReferenceSelectionAccessTest extends WebTestBase {
// Test as an admin.
$admin_user = $this->drupalCreateUser(array('access content', 'bypass node access'));
$this->container->set('current_user', $admin_user);
\Drupal::currentUser()->setAccount($admin_user);
$referenceable_tests = array(
array(
'arguments' => array(
@ -266,7 +266,7 @@ class EntityReferenceSelectionAccessTest extends WebTestBase {
}
// Test as a non-admin.
$this->container->set('current_user', $users['non_admin']);
\Drupal::currentUser()->setAccount($users['non_admin']);
$referenceable_tests = array(
array(
'arguments' => array(
@ -305,7 +305,7 @@ class EntityReferenceSelectionAccessTest extends WebTestBase {
);
$this->assertReferenceable($instance, $referenceable_tests, 'User handler');
$this->container->set('current_user', $users['admin']);
\Drupal::currentUser()->setAccount($users['admin']);
$referenceable_tests = array(
array(
'arguments' => array(
@ -447,7 +447,7 @@ class EntityReferenceSelectionAccessTest extends WebTestBase {
// Test as a non-admin.
$normal_user = $this->drupalCreateUser(array('access content', 'access comments'));
$this->container->set('current_user', $normal_user);
\Drupal::currentUser()->setAccount($normal_user);
$referenceable_tests = array(
array(
'arguments' => array(
@ -486,7 +486,7 @@ class EntityReferenceSelectionAccessTest extends WebTestBase {
// Test as a comment admin.
$admin_user = $this->drupalCreateUser(array('access content', 'access comments', 'administer comments'));
$this->container->set('current_user', $admin_user);
\Drupal::currentUser()->setAccount($admin_user);
$referenceable_tests = array(
array(
'arguments' => array(
@ -504,7 +504,7 @@ class EntityReferenceSelectionAccessTest extends WebTestBase {
// Test as a node and comment admin.
$admin_user = $this->drupalCreateUser(array('access content', 'access comments', 'administer comments', 'bypass node access'));
$this->container->set('current_user', $admin_user);
\Drupal::currentUser()->setAccount($admin_user);
$referenceable_tests = array(
array(
'arguments' => array(

View File

@ -120,7 +120,7 @@ class EntityReferenceSelectionSortTest extends WebTestBase {
// Test as a non-admin.
$normal_user = $this->drupalCreateUser(array('access content'));
$this->container->set('current_user', $normal_user);
\Drupal::currentUser()->setAccount($normal_user);
$handler = $this->container->get('plugin.manager.entity_reference.selection')->getSelectionHandler($instance);

View File

@ -37,7 +37,7 @@ abstract class FileManagedUnitTestBase extends DrupalUnitTestBase {
$user = entity_create('user', array('uid' => 1, 'name' => $this->randomName()));
$user->enforceIsNew();
$user->save();
$this->container->set('current_user', $user);
\Drupal::currentUser()->setAccount($user);
}
/**

View File

@ -133,7 +133,7 @@ class ValidatorTest extends FileManagedUnitTestBase {
$user = entity_create('user', array('uid' => 2, 'name' => $this->randomName()));
$user->enforceIsNew();
$user->save();
$this->container->set('current_user', $user);
\Drupal::currentUser()->setAccount($user);
// Create a file with a size of 1000 bytes, and quotas of only 1 byte.
$file = entity_create('file', array('filesize' => 1000));

View File

@ -207,7 +207,7 @@ class FilterAPITest extends EntityUnitTestBase {
// Test with anonymous user.
$user = new AnonymousUserSession();
$this->container->set('current_user', $user);
\Drupal::currentUser()->setAccount($user);
$expected_available_options = array(
'filtered_html' => 'Filtered HTML',
@ -246,7 +246,7 @@ class FilterAPITest extends EntityUnitTestBase {
$this->assertFilterFormatViolation($violations, 'filtered_html');
// Set user with access to 'filtered_html' format.
$this->container->set('current_user', $filtered_html_user);
\Drupal::currentUser()->setAccount($filtered_html_user);
$violations = $data->validate();
$this->assertEqual(count($violations), 0, "No validation violation for accessible format 'filtered_html' found.");

View File

@ -18,6 +18,8 @@ use Drupal\Core\Database\ConnectionNotDefinedException;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\DrupalKernel;
use Drupal\Core\Language\Language;
use Drupal\Core\Session\AccountProxy;
use Drupal\Core\Session\AnonymousUserSession;
use Drupal\Core\StreamWrapper\PublicStream;
use Drupal\Core\Utility\Error;
use Symfony\Component\HttpFoundation\Request;
@ -1087,7 +1089,7 @@ abstract class TestBase {
// Run all tests as a anonymous user by default, web tests will replace that
// during the test set up.
$this->container->set('current_user', drupal_anonymous_user());
$this->container->set('current_user', new AnonymousUserSession());
\Drupal::setContainer($this->container);
@ -1146,7 +1148,7 @@ abstract class TestBase {
$this->container = \Drupal::getContainer();
// The current user is set in TestBase::prepareEnvironment().
$this->container->set('request', $request);
$this->container->set('current_user', \Drupal::currentUser());
$this->container->get('current_user')->setAccount(\Drupal::currentUser());
}
/**

View File

@ -681,7 +681,7 @@ abstract class WebTestBase extends TestBase {
$pass = $this->assert($this->drupalUserIsLoggedIn($account), format_string('User %name successfully logged in.', array('%name' => $account->getUsername())), 'User login');
if ($pass) {
$this->loggedInUser = $account;
$this->container->set('current_user', $account);
$this->container->get('current_user')->setAccount($account);
// @todo Temporary workaround for not being able to use synchronized
// services in non dumped container.
$this->container->get('access_subscriber')->setCurrentUser($account);
@ -729,7 +729,7 @@ abstract class WebTestBase extends TestBase {
// @see WebTestBase::drupalUserIsLoggedIn()
unset($this->loggedInUser->session_id);
$this->loggedInUser = FALSE;
$this->container->set('current_user', new AnonymousUserSession());
$this->container->get('current_user')->setAccount(new AnonymousUserSession());
}
}

View File

@ -49,8 +49,7 @@ class EntityAccessTest extends EntityLanguageTestBase {
*/
function testEntityAccess() {
// Set up a non-admin user that is allowed to view test entities.
global $user;
$user = $this->createUser(array('uid' => 2), array('view test entity'));
\Drupal::currentUser()->setAccount($this->createUser(array('uid' => 2), array('view test entity')));
$entity = entity_create('entity_test', array(
'name' => 'test',
));
@ -78,8 +77,7 @@ class EntityAccessTest extends EntityLanguageTestBase {
*/
function testEntityAccessDefaultController() {
// The implementation requires that the global user id can be loaded.
global $user;
$user = $this->createUser(array('uid' => 2));
\Drupal::currentUser()->setAccount($this->createUser(array('uid' => 2)));
// Check that the default access controller is used for entities that don't
// have a specific access controller defined.
@ -101,8 +99,7 @@ class EntityAccessTest extends EntityLanguageTestBase {
function testEntityTranslationAccess() {
// Set up a non-admin user that is allowed to view test entity translations.
global $user;
$user = $this->createUser(array('uid' => 2), array('view test entity translations'));
\Drupal::currentUser()->setAccount($this->createUser(array('uid' => 2), array('view test entity translations')));
// Create two test languages.
foreach (array('foo', 'bar') as $langcode) {

View File

@ -46,7 +46,7 @@ class FormCacheTest extends DrupalUnitTestBase {
* Tests the form cache with a logged-in user.
*/
function testCacheToken() {
$this->container->set('current_user', new UserSession(array('uid' => 1)));
\Drupal::currentUser()->setAccount(new UserSession(array('uid' => 1)));
form_set_cache($this->form_build_id, $this->form, $this->form_state);
$cached_form_state = form_state_defaults();

View File

@ -243,7 +243,7 @@ class FunctionsTest extends WebTestBase {
$this->assertThemeOutput('links', $variables, $expected);
// Verify the data- attributes for setting the "active" class on links.
$this->container->set('current_user', new UserSession(array('uid' => 1)));
\Drupal::currentUser()->setAccount(new UserSession(array('uid' => 1)));
$variables['set_active_class'] = TRUE;
$expected_links = '';
$expected_links .= '<ul id="somelinks">';

View File

@ -58,9 +58,9 @@ class TestContent extends ControllerBase {
}
public function testAccount(UserInterface $user) {
$current_user = $this->currentUser();
\Drupal::getContainer()->set('current_user', $user);
return $current_user->getUsername() . ':' . $user->getUsername();
$current_user_name = $this->currentUser()->getUsername();
$this->currentUser()->setAccount($user);
return $current_user_name . ':' . $user->getUsername();
}
/**

View File

@ -30,6 +30,11 @@ class SessionTestSubscriber implements EventSubscriberInterface {
* The Event to process.
*/
public function onKernelRequestSessionTest(GetResponseEvent $event) {
// Trigger the authentication in the test to ensure that $_SESSION has the
// needed data.
// @todo: On the longrun the session will be lazy initialized, so we no
// longer have to force it here.
\Drupal::currentUser()->getAccount();
$this->emptySession = intval(empty($_SESSION));
}