Commit d026a1c3 authored by catch's avatar catch

Issue #2180109 by damiankloip, dawehner, ParisLiakos, joelpittet,...

Issue #2180109 by damiankloip, dawehner, ParisLiakos, joelpittet, MartijnBraam, Xano: Change the current_user service to a proxy.
parent 74027cd3
......@@ -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' ]
......
......@@ -190,7 +190,7 @@ public static function request() {
/**
* 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');
......
......@@ -10,7 +10,7 @@
/**
* 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();
}
......@@ -11,6 +11,7 @@
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 @@ public function run() {
// 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 @@ public function run() {
}
// 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;
......
......@@ -269,7 +269,7 @@ protected function checkCreateAccess(AccountInterface $account, array $context,
*/
protected function prepareUser(AccountInterface $account = NULL) {
if (!$account) {
$account = $GLOBALS['user'];
$account = \Drupal::currentUser();
}
return $account;
}
......
......@@ -10,7 +10,6 @@
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 @@ public function __construct(AuthenticationProviderInterface $authentication_prov
$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 @@ public function onException(GetResponseForExceptionEvent $event) {
* 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;
......
......@@ -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 @@
* 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 @@ public function enhance(array $defaults, Request $request) {
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.
......
<?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();
}
}
<?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();
}
......@@ -120,7 +120,7 @@ public function testNodeHandler() {
// 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 @@ public function testNodeHandler() {
// 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 @@ public function testUserHandler() {
}
// 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 @@ public function testUserHandler() {
);
$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 @@ public function testCommentHandler() {
// 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 @@ public function testCommentHandler() {
// 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 @@ public function testCommentHandler() {
// 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(
......
......@@ -120,7 +120,7 @@ public function testSort() {
// 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);
......
......@@ -37,7 +37,7 @@ function setUp() {
$user = entity_create('user', array('uid' => 1, 'name' => $this->randomName()));
$user->enforceIsNew();
$user->save();
$this->container->set('current_user', $user);
\Drupal::currentUser()->setAccount($user);
}
/**
......
......@@ -133,7 +133,7 @@ function testFileValidateSize() {
$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));
......
......@@ -207,7 +207,7 @@ function testTypedDataAPI() {
// 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 @@ function testTypedDataAPI() {
$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.");
......
......@@ -18,6 +18,8 @@
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 @@ private function prepareEnvironment() {
// 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 @@ protected function rebuildContainer($environment = 'testing') {
$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());
}
/**
......
......@@ -681,7 +681,7 @@ protected function drupalLogin(AccountInterface $account) {
$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 @@ protected function drupalLogout() {
// @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());
}
}
......
......@@ -49,8 +49,7 @@ function assertEntityAccess($ops, AccessibleInterface $object, AccountInterface
*/
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')));