Commit 12abe621 authored by alexpott's avatar alexpott

Issue #2228341 by sun, znerol, skipyT, ParisLiakos: Objectify session...

Issue #2228341 by sun, znerol, skipyT, ParisLiakos: Objectify session management functions + remove session.inc.
parent bfa4a9fd
......@@ -46,8 +46,7 @@
* TRUE if the current user can run authorize.php, and FALSE if not.
*/
function authorize_access_allowed() {
require_once DRUPAL_ROOT . '/' . Settings::get('session_inc', 'core/includes/session.inc');
drupal_session_initialize();
\Drupal::service('session_manager')->initialize();
return Settings::get('allow_authorize_operations', TRUE) && user_access('administer software updates');
}
......
......@@ -102,7 +102,7 @@ services:
arguments: ['@config.storage', '@config.storage.schema', '@cache.config']
cron:
class: Drupal\Core\Cron
arguments: ['@module_handler', '@lock', '@queue', '@state', '@current_user']
arguments: ['@module_handler', '@lock', '@queue', '@state', '@current_user', '@session_manager']
database:
class: Drupal\Core\Database\Connection
factory_class: Drupal\Core\Database\Database
......@@ -712,6 +712,7 @@ services:
class: Drupal\Core\Authentication\AuthenticationManager
authentication.cookie:
class: Drupal\Core\Authentication\Provider\Cookie
arguments: ['@session_manager']
tags:
- { name: authentication_provider, priority: 0 }
authentication_subscriber:
......@@ -722,6 +723,11 @@ services:
current_user:
class: Drupal\Core\Session\AccountProxy
arguments: ['@authentication', '@request']
session_manager:
class: Drupal\Core\Session\SessionManager
arguments: ['@request_stack', '@database']
tags:
- { name: persist }
asset.css.collection_renderer:
class: Drupal\Core\Asset\CssCollectionRenderer
arguments: [ '@state' ]
......
......@@ -1466,7 +1466,9 @@ function drupal_handle_request($test_only = FALSE) {
// Create a request object from the HttpFoundation.
$request = Request::createFromGlobals();
\Drupal::getContainer()->set('request', $request);
$container = \Drupal::getContainer();
$container->set('request', $request);
$container->get('request_stack')->push($request);
drupal_bootstrap(DRUPAL_BOOTSTRAP_CODE);
......@@ -1616,7 +1618,9 @@ function _drupal_bootstrap_kernel() {
$kernel = new DrupalKernel('prod', drupal_classloader());
$kernel->boot();
$request = Request::createFromGlobals();
\Drupal::getContainer()->set('request', $request);
$container = \Drupal::getContainer();
$container->set('request', $request);
$container->get('request_stack')->push($request);
}
}
......@@ -2173,12 +2177,12 @@ function drupal_classloader_register($name, $path) {
*
* Example:
* @code
* function drupal_session_started($set = NULL) {
* static $session_started = FALSE;
* if (isset($set)) {
* $session_started = $set;
* function mymodule_log_stream_handle($new_handle = NULL) {
* static $handle;
* if (isset($new_handle)) {
* $handle = $new_handle;
* }
* return $session_started && session_id();
* return $handle;
* }
* @endcode
*
......
......@@ -2877,7 +2877,7 @@ function drupal_get_private_key() {
*
* @see drupal_get_hash_salt()
* @see \Drupal\Core\Access\CsrfTokenGenerator
* @see drupal_session_start()
* @see \Drupal\Core\Session\SessionManager::start()
*
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* Use \Drupal::csrfToken()->get().
......
......@@ -306,7 +306,6 @@ function install_begin_request(&$install_state) {
// Load module basics (needed for hook invokes).
include_once __DIR__ . '/module.inc';
include_once __DIR__ . '/session.inc';
require_once __DIR__ . '/entity.inc';
// Create a minimal mocked container to support calls to t() in the pre-kernel
......@@ -546,7 +545,7 @@ function install_run_task($task, &$install_state) {
$response = batch_process(install_redirect_url($install_state), install_full_redirect_url($install_state));
if ($response instanceof Response) {
// Save $_SESSION data from batch.
drupal_session_commit();
\Drupal::service('session_manager')->save();
// Send the response.
$response->send();
exit;
......@@ -1780,8 +1779,7 @@ function install_load_profile(&$install_state) {
*/
function install_bootstrap_full() {
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
require_once DRUPAL_ROOT . '/' . Settings::get('session_inc', 'core/includes/session.inc');
drupal_session_initialize();
\Drupal::service('session_manager')->initialize();
}
/**
......
<?php
/**
* @file
* User session handling functions.
*/
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\Settings;
use Drupal\Core\Session\AnonymousUserSession;
use Drupal\Core\Session\SessionHandler;
/**
* Initializes the session handler, starting a session if needed.
*/
function drupal_session_initialize() {
global $user;
// Register the default session handler.
// @todo: Extract session storage from session handler into a service.
$handler = new SessionHandler(\Drupal::request(), \Drupal::database());
session_set_save_handler($handler, TRUE);
$is_https = \Drupal::request()->isSecure();
$cookies = \Drupal::request()->cookies;
if (($cookies->has(session_name()) && ($session_name = $cookies->get(session_name()))) || ($is_https && Settings::get('mixed_mode_sessions', FALSE) && ($cookies->has(substr(session_name(), 1))) && ($session_name = $cookies->get(substr(session_name(), 1))))) {
// If a session cookie exists, initialize the session. Otherwise the
// session is only started on demand in drupal_session_commit(), making
// anonymous users not use a session cookie unless something is stored in
// $_SESSION. This allows HTTP proxies to cache anonymous pageviews.
drupal_session_start();
if ($user->isAuthenticated() || !empty($_SESSION)) {
drupal_page_is_cacheable(FALSE);
}
}
else {
// Set a session identifier for this request. This is necessary because
// we lazily start sessions at the end of this request, and some
// processes (like drupal_get_token()) needs to know the future
// session ID in advance.
$GLOBALS['lazy_session'] = TRUE;
$user = new AnonymousUserSession();
// Less random sessions (which are much faster to generate) are used for
// anonymous users than are generated in drupal_session_regenerate() when
// a user becomes authenticated.
session_id(Crypt::randomBytesBase64());
if ($is_https && Settings::get('mixed_mode_sessions', FALSE)) {
$insecure_session_name = substr(session_name(), 1);
$session_id = Crypt::randomBytesBase64();
$cookies->set($insecure_session_name, $session_id);
}
}
date_default_timezone_set(drupal_get_user_timezone());
}
/**
* Starts a session forcefully, preserving already set session data.
*
* @ingroup php_wrappers
*/
function drupal_session_start() {
// Command line clients do not support cookies nor sessions.
if (!drupal_session_started() && !drupal_is_cli()) {
// Save current session data before starting it, as PHP will destroy it.
$session_data = isset($_SESSION) ? $_SESSION : NULL;
session_start();
drupal_session_started(TRUE);
// Restore session data.
if (!empty($session_data)) {
$_SESSION += $session_data;
}
}
}
/**
* Commits the current session, if necessary.
*
* If an anonymous user already have an empty session, destroy it.
*/
function drupal_session_commit() {
global $user;
if (!drupal_save_session()) {
// We don't have anything to do if we are not allowed to save the session.
return;
}
if ($user->isAnonymous() && empty($_SESSION)) {
// There is no session data to store, destroy the session if it was
// previously started.
if (drupal_session_started()) {
session_destroy();
}
}
else {
// There is session data to store. Start the session if it is not already
// started.
if (!drupal_session_started()) {
drupal_session_start();
if (\Drupal::request()->isSecure() && Settings::get('mixed_mode_sessions', FALSE)) {
$insecure_session_name = substr(session_name(), 1);
$params = session_get_cookie_params();
$expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0;
$cookie_params = \Drupal::request()->cookies;
setcookie($insecure_session_name, $cookie_params->get($insecure_session_name), $expire, $params['path'], $params['domain'], FALSE, $params['httponly']);
}
}
// Write the session data.
session_write_close();
}
}
/**
* Returns whether a session has been started.
*/
function drupal_session_started($set = NULL) {
static $session_started = FALSE;
if (isset($set)) {
$session_started = $set;
}
return $session_started && session_status() === \PHP_SESSION_ACTIVE;
}
/**
* Called when an anonymous user becomes authenticated or vice-versa.
*
* @ingroup php_wrappers
*/
function drupal_session_regenerate() {
global $user;
// Nothing to do if we are not allowed to change the session.
if (!drupal_save_session()) {
return;
}
$is_https = \Drupal::request()->isSecure();
$cookies = \Drupal::request()->cookies;
if ($is_https && Settings::get('mixed_mode_sessions', FALSE)) {
$insecure_session_name = substr(session_name(), 1);
if (!isset($GLOBALS['lazy_session']) && $cookies->has($insecure_session_name)) {
$old_insecure_session_id = $cookies->get($insecure_session_name);
}
$params = session_get_cookie_params();
$session_id = Crypt::randomBytesBase64();
// If a session cookie lifetime is set, the session will expire
// $params['lifetime'] seconds from the current request. If it is not set,
// it will expire when the browser is closed.
$expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0;
setcookie($insecure_session_name, $session_id, $expire, $params['path'], $params['domain'], FALSE, $params['httponly']);
$cookies->set($insecure_session_name, $session_id);
}
if (drupal_session_started()) {
$old_session_id = session_id();
}
session_id(Crypt::randomBytesBase64());
if (isset($old_session_id)) {
$params = session_get_cookie_params();
$expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0;
setcookie(session_name(), session_id(), $expire, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
$fields = array('sid' => Crypt::hashBase64(session_id()));
if ($is_https) {
$fields['ssid'] = Crypt::hashBase64(session_id());
// If the "secure pages" setting is enabled, use the newly-created
// insecure session identifier as the regenerated sid.
if (Settings::get('mixed_mode_sessions', FALSE)) {
$fields['sid'] = Crypt::hashBase64($session_id);
}
}
db_update('sessions')
->fields($fields)
->condition($is_https ? 'ssid' : 'sid', Crypt::hashBase64($old_session_id))
->execute();
}
elseif (isset($old_insecure_session_id)) {
// If logging in to the secure site, and there was no active session on the
// secure site but a session was active on the insecure site, update the
// insecure session with the new session identifiers.
db_update('sessions')
->fields(array('sid' => Crypt::hashBase64($session_id), 'ssid' => Crypt::hashBase64(session_id())))
->condition('sid', Crypt::hashBase64($old_insecure_session_id))
->execute();
}
else {
// Start the session when it doesn't exist yet.
// Preserve the logged in user, as it will be reset to anonymous
// by _drupal_session_read.
$account = $user;
drupal_session_start();
$user = $account;
}
date_default_timezone_set(drupal_get_user_timezone());
}
/**
* Ends a specific user's session(s).
*
* @param $uid
* User ID.
*/
function drupal_session_destroy_uid($uid) {
// Nothing to do if we are not allowed to change the session.
if (!drupal_save_session()) {
return;
}
db_delete('sessions')
->condition('uid', $uid)
->execute();
}
/**
* Determines whether to save session data of the current request.
*
* This function allows the caller to temporarily disable writing of
* session data, should the request end while performing potentially
* dangerous operations, such as manipulating the global $user object.
* See http://drupal.org/node/218104 for usage.
*
* @param $status
* Disables writing of session data when FALSE, (re-)enables
* writing when TRUE.
*
* @return
* FALSE if writing session data has been disabled. Otherwise, TRUE.
*/
function drupal_save_session($status = NULL) {
// PHP session ID, session, and cookie handling happens in the global scope.
// This value has to persist across calls to drupal_static_reset(), since a
// potentially wrong or disallowed session would be written otherwise.
static $save_session = TRUE;
if (isset($status)) {
$save_session = $status;
}
return $save_session;
}
......@@ -586,7 +586,7 @@ public static function languageManager() {
* @return \Drupal\Core\Access\CsrfTokenGenerator
* The CSRF token manager.
*
* @see drupal_session_start()
* @see \Drupal\Core\Session\SessionManager::start()
*/
public static function csrfToken() {
return static::$container->get('csrf_token');
......
......@@ -69,7 +69,7 @@ public function setCurrentUser(AccountInterface $current_user = NULL) {
* 'drupal_private_key' configuration variable.
*
* @see drupal_get_hash_salt()
* @see drupal_session_start()
* @see \Drupal\Core\Session\SessionManager::start()
*/
public function get($value = '') {
return Crypt::hmacBase64($value, session_id() . $this->privateKey->get() . drupal_get_hash_salt());
......
......@@ -8,6 +8,7 @@
namespace Drupal\Core\Authentication\Provider;
use Drupal\Core\Authentication\AuthenticationProviderInterface;
use Drupal\Core\Session\SessionManagerInterface;
use Drupal\Component\Utility\Settings;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
......@@ -17,6 +18,23 @@
*/
class Cookie implements AuthenticationProviderInterface {
/**
* The session manager.
*
* @var \Drupal\Core\Session\SessionManagerInterface
*/
protected $sessionManager;
/**
* Constructs a new Cookie authentication provider instance.
*
* @param \Drupal\Core\Session\SessionManagerInterface $session_manager
* The session manager.
*/
public function __construct(SessionManagerInterface $session_manager) {
$this->sessionManager = $session_manager;
}
/**
* {@inheritdoc}
*/
......@@ -30,9 +48,7 @@ public function applies(Request $request) {
public function authenticate(Request $request) {
// Global $user is deprecated, but the session system is still based on it.
global $user;
require_once DRUPAL_ROOT . '/' . Settings::get('session_inc', 'core/includes/session.inc');
drupal_session_initialize();
if (drupal_session_started()) {
if ($this->sessionManager->initialize()->isStarted()) {
return $user;
}
return NULL;
......@@ -42,7 +58,7 @@ public function authenticate(Request $request) {
* {@inheritdoc}
*/
public function cleanup(Request $request) {
drupal_session_commit();
$this->sessionManager->save();
}
/**
......
......@@ -13,6 +13,7 @@
use Drupal\Core\Queue\QueueFactory;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Session\AnonymousUserSession;
use Drupal\Core\Session\SessionManagerInterface;
/**
* The Drupal core Cron service.
......@@ -54,6 +55,13 @@ class Cron implements CronInterface {
*/
protected $currentUser;
/**
* The session manager.
*
* @var \Drupal\Core\Session\SessionManagerInterface
*/
protected $sessionManager;
/**
* Constructs a cron object.
*
......@@ -67,13 +75,16 @@ class Cron implements CronInterface {
* The state service.
* @param \Drupal\Core\Session\AccountProxyInterface $current_user
* The current user.
* @param \Drupal\Core\Session\SessionManagerInterface $session_manager
* The session manager.
*/
public function __construct(ModuleHandlerInterface $module_handler, LockBackendInterface $lock, QueueFactory $queue_factory, StateInterface $state, AccountProxyInterface $current_user) {
public function __construct(ModuleHandlerInterface $module_handler, LockBackendInterface $lock, QueueFactory $queue_factory, StateInterface $state, AccountProxyInterface $current_user, SessionManagerInterface $session_manager) {
$this->moduleHandler = $module_handler;
$this->lock = $lock;
$this->queueFactory = $queue_factory;
$this->state = $state;
$this->currentUser = $current_user;
$this->sessionManager = $session_manager;
}
/**
......@@ -84,8 +95,8 @@ public function run() {
@ignore_user_abort(TRUE);
// Prevent session information from being saved while cron is running.
$original_session_saving = drupal_save_session();
drupal_save_session(FALSE);
$original_session_saving = $this->sessionManager->isEnabled();
$this->sessionManager->disable();
// Force the current user to anonymous to ensure consistent permissions on
// cron runs.
......@@ -157,7 +168,9 @@ public function run() {
// Restore the user.
$this->currentUser->setAccount($original_user);
drupal_save_session($original_session_saving);
if ($original_session_saving) {
$this->sessionManager->enable();
}
return $return;
}
......
......@@ -11,7 +11,7 @@
use Drupal\Component\Utility\Settings;
use Drupal\Core\Database\Connection;
use Drupal\Core\Utility\Error;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Default session handler.
......@@ -19,11 +19,18 @@
class SessionHandler implements \SessionHandlerInterface {
/**
* The request.
* The session manager.
*
* @var \Symfony\Component\HttpFoundation\Request
* @var \Drupal\Core\Session\SessionManagerInterface
*/
protected $request;
protected $sessionManager;
/**
* The request stack.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* The database connection.
......@@ -42,13 +49,16 @@ class SessionHandler implements \SessionHandlerInterface {
/**
* Constructs a new SessionHandler instance.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request.
* @param \Drupal\Core\Session\SessionManagerInterface $session_manager
* The session manager.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
* @param \Drupal\Core\Database\Connection $connection
* The database connection.
*/
public function __construct(Request $request, Connection $connection) {
$this->request = $request;
public function __construct(SessionManagerInterface $session_manager, RequestStack $request_stack, Connection $connection) {
$this->sessionManager = $session_manager;
$this->requestStack = $request_stack;
$this->connection = $connection;
}
......@@ -68,7 +78,7 @@ public function read($sid) {
// Handle the case of first time visitors and clients that don't store
// cookies (eg. web crawlers).
$insecure_session_name = substr(session_name(), 1);
$cookies = $this->request->cookies;
$cookies = $this->requestStack->getCurrentRequest()->cookies;
if (!$cookies->has(session_name()) && !$cookies->has($insecure_session_name)) {
$user = new UserSession();
return '';
......@@ -81,7 +91,7 @@ public function read($sid) {
// that is in the user's cookie is hashed before being stored in the
// database as a security measure. Thus, we have to hash it to match the
// database.
if ($this->request->isSecure()) {
if ($this->requestStack->getCurrentRequest()->isSecure()) {
// Try to load a session using the HTTPS-only secure session id.
$values = $this->connection->query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.ssid = :ssid", array(
':ssid' => Crypt::hashBase64($sid),
......@@ -143,7 +153,7 @@ public function write($sid, $value) {
// The exception handler is not active at this point, so we need to do it
// manually.
try {
if (!drupal_save_session()) {
if (!$this->sessionManager->isEnabled()) {
// We don't have anything to do if we are not allowed to save the
// session.
return TRUE;
......@@ -160,18 +170,17 @@ public function write($sid, $value) {
// Either ssid or sid or both will be added from $key below.
$fields = array(
'uid' => $user->id(),
'hostname' => $this->request->getClientIP(),
'hostname' => $this->requestStack->getCurrentRequest()->getClientIP(),
'session' => $value,
'timestamp' => REQUEST_TIME,
);
// Use the session ID as 'sid' and an empty string as 'ssid' by default.
// _drupal_session_read() does not allow empty strings so that's a safe
// default.
// read() does not allow empty strings so that's a safe default.
$key = array('sid' => Crypt::hashBase64($sid), 'ssid' => '');
// On HTTPS connections, use the session ID as both 'sid' and 'ssid'.
if ($this->request->isSecure()) {
if ($this->requestStack->getCurrentRequest()->isSecure()) {
$key['ssid'] = Crypt::hashBase64($sid);
$cookies = $this->request->cookies;
$cookies = $this->requestStack->getCurrentRequest()->cookies;
// The "secure pages" setting allows a site to simultaneously use both
// secure and insecure session cookies. If enabled and both cookies
// are presented then use both keys. The session ID from the cookie is
......@@ -228,17 +237,17 @@ public function destroy($sid) {
global $user;
// Nothing to do if we are not allowed to change the session.
if (!drupal_save_session()) {
if (!$this->sessionManager->isEnabled()) {
return TRUE;
}
$is_https = $this->request->isSecure();
$is_https = $this->requestStack->getCurrentRequest()->isSecure();
// Delete session data.
$this->connection->delete('sessions')
->condition($is_https ? 'ssid' : 'sid', Crypt::hashBase64($sid))
->execute();
// Reset $_SESSION and $user to prevent a new session from being started
// in drupal_session_commit().
// in \Drupal\Core\Session\SessionManager::save().
$_SESSION = array();
$user = new AnonymousUserSession();
......@@ -277,8 +286,8 @@ public function gc($lifetime) {
* Force the secure value of the cookie.
*/
protected function deleteCookie($name, $secure = NULL) {
$cookies = $this->request->cookies;
if ($cookies->has($name) || (!$this->request->isSecure() && $secure === TRUE)) {
$cookies = $this->requestStack->getCurrentRequest()->cookies;
if ($cookies->has($name) || (!$this->requestStack->getCurrentRequest()->isSecure() && $secure === TRUE)) {
$params = session_get_cookie_params();
if ($secure !== NULL) {
$params['secure'] = $secure;
......
<?php
/**
* @file
* Contains \Drupal\Core\Session\SessionManager.
*/
namespace Drupal\Core\Session;
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\Settings;
use Drupal\Core\Database\Connection;
use Drupal\Core\Session\AnonymousUserSession;
use Drupal\Core\Session\SessionHandler;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Manages user sessions.
*/
class SessionManager implements SessionManagerInterface {
/**
* The request stack.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* The database connection to use.
*
* @var \Drupal\Core\Database\Connection
*/
protected $connection;
/**
* Whether a lazy session has been started.
*
* @var bool
*/
protected $lazySession;
/**
* Whether session management is enabled or temporarily disabled.
*
* PHP session ID, session, and cookie handling happens in the global scope.
* This value has to persist, since a potentially wrong or disallowed session
* would be written otherwise.
*
* @var bool
*/
protected static $enabled = TRUE;
/**
* Whether the session has been started.
*
* @var bool
*/
protected static $started = FALSE;
/**
* Constructs a new session manager instance.
*
* @param \Symfony\Component\HttpFoundation\RequestStack $request
* The request stack.
* @param \Drupal\Core\Database\Connection $connection
* The database connection.
*/
public function __construct(RequestStack $request_stack, Connection $connection) {
$this->requestStack = $request_stack;
$this->connection = $connection;
}
/**
* {@inheritdoc}
*/
public function initialize() {
global $user;
// Register the default session handler.
// @todo Extract session storage from session handler into a service.
$handler = new SessionHandler($this, $this->requestStack, $this->connection);
session_set_save_handler($handler, TRUE);
$is_https = $this->requestStack->getCurrentRequest()->isSecure();
$cookies = $this->requestStack->getCurrentRequest()->cookies;
if (($cookies->has(session_name()) && ($session_name = $cookies->get(session_name()))) || ($is_https && Settings::get('mixed_mode_sessions', FALSE) && ($cookies->has(substr(session_name(), 1))) && ($session_name = $cookies->get(substr(session_name(), 1))))) {
// If a session cookie exists, initialize the session. Otherwise the
// session is only started on demand in save(), making
// anonymous users not use a session cookie unless something is stored in
// $_SESSION. This allows HTTP proxies to cache anonymous pageviews.
$this->start();
if ($user->isAuthenticated() || !empty($_SESSION)) {
drupal_page_is_cacheable(FALSE);
}
}
else {
// Set a session identifier for this request. This is necessary because
// we lazily start sessions at the end of this request, and some
// processes (like drupal_get_token()) needs to know the future
// session ID in advance.
$this->lazySession = TRUE;
$user = new AnonymousUserSession();
// Less random sessions (which are much faster to generate) are used for
// anonymous users than are generated in regenerate() when
// a user becomes authenticated.
session_id(Crypt::randomBytesBase64());
if ($is_https && Settings::get('mixed_mode_sessions', FALSE)) {