Issue #2238087 by znerol, YesCT: Rebase SessionManager onto Symfony NativeSessionStorage.
parent
fd0dcf9410
commit
37af5099cc
|
@ -767,9 +767,12 @@ services:
|
||||||
arguments: ['@authentication', '@request']
|
arguments: ['@authentication', '@request']
|
||||||
session_manager:
|
session_manager:
|
||||||
class: Drupal\Core\Session\SessionManager
|
class: Drupal\Core\Session\SessionManager
|
||||||
arguments: ['@request_stack', '@database']
|
arguments: ['@request_stack', '@database', '@session_manager.metadata_bag', '@settings']
|
||||||
tags:
|
tags:
|
||||||
- { name: persist }
|
- { name: persist }
|
||||||
|
session_manager.metadata_bag:
|
||||||
|
class: Drupal\Core\Session\MetadataBag
|
||||||
|
arguments: ['@settings']
|
||||||
asset.css.collection_renderer:
|
asset.css.collection_renderer:
|
||||||
class: Drupal\Core\Asset\CssCollectionRenderer
|
class: Drupal\Core\Asset\CssCollectionRenderer
|
||||||
arguments: [ '@state' ]
|
arguments: [ '@state' ]
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Contains \Drupal\Core\Session\MetadataBag.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Drupal\Core\Session;
|
||||||
|
|
||||||
|
use Drupal\Core\Site\Settings;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag as SymfonyMetadataBag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a container for application specific session metadata.
|
||||||
|
*/
|
||||||
|
class MetadataBag extends SymfonyMetadataBag {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new metadata bag instance.
|
||||||
|
*
|
||||||
|
* @param \Drupal\Core\Site\Settings $settings
|
||||||
|
* The settings instance.
|
||||||
|
*/
|
||||||
|
public function __construct(Settings $settings) {
|
||||||
|
$update_threshold = $settings->get('session_write_interval', 180);
|
||||||
|
parent::__construct('_sf2_meta', $update_threshold);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -12,11 +12,12 @@ use Drupal\Core\Database\Connection;
|
||||||
use Drupal\Core\Site\Settings;
|
use Drupal\Core\Site\Settings;
|
||||||
use Drupal\Core\Utility\Error;
|
use Drupal\Core\Utility\Error;
|
||||||
use Symfony\Component\HttpFoundation\RequestStack;
|
use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default session handler.
|
* Default session handler.
|
||||||
*/
|
*/
|
||||||
class SessionHandler implements \SessionHandlerInterface {
|
class SessionHandler extends AbstractProxy implements \SessionHandlerInterface {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The session manager.
|
* The session manager.
|
||||||
|
@ -39,13 +40,6 @@ class SessionHandler implements \SessionHandlerInterface {
|
||||||
*/
|
*/
|
||||||
protected $connection;
|
protected $connection;
|
||||||
|
|
||||||
/**
|
|
||||||
* An array containing the sid and data from last read.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $lastRead;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new SessionHandler instance.
|
* Constructs a new SessionHandler instance.
|
||||||
*
|
*
|
||||||
|
@ -77,9 +71,9 @@ class SessionHandler implements \SessionHandlerInterface {
|
||||||
|
|
||||||
// Handle the case of first time visitors and clients that don't store
|
// Handle the case of first time visitors and clients that don't store
|
||||||
// cookies (eg. web crawlers).
|
// cookies (eg. web crawlers).
|
||||||
$insecure_session_name = substr(session_name(), 1);
|
$insecure_session_name = $this->sessionManager->getInsecureName();
|
||||||
$cookies = $this->requestStack->getCurrentRequest()->cookies;
|
$cookies = $this->requestStack->getCurrentRequest()->cookies;
|
||||||
if (!$cookies->has(session_name()) && !$cookies->has($insecure_session_name)) {
|
if (!$cookies->has($this->getName()) && !$cookies->has($insecure_session_name)) {
|
||||||
$user = new UserSession();
|
$user = new UserSession();
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -136,11 +130,6 @@ class SessionHandler implements \SessionHandlerInterface {
|
||||||
$user = new UserSession();
|
$user = new UserSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the session that was read for comparison in self::write().
|
|
||||||
$this->lastRead = array(
|
|
||||||
'sid' => $sid,
|
|
||||||
'value' => $user->session,
|
|
||||||
);
|
|
||||||
return $user->session;
|
return $user->session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,48 +147,40 @@ class SessionHandler implements \SessionHandlerInterface {
|
||||||
// session.
|
// session.
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
// Check whether $_SESSION has been changed in this request.
|
|
||||||
$is_changed = empty($this->lastRead) || $this->lastRead['sid'] != $sid || $this->lastRead['value'] !== $value;
|
|
||||||
|
|
||||||
// For performance reasons, do not update the sessions table, unless
|
// Either ssid or sid or both will be added from $key below.
|
||||||
// $_SESSION has changed or more than 180 has passed since the last
|
$fields = array(
|
||||||
// update.
|
'uid' => $user->id(),
|
||||||
$needs_update = !$user->getLastAccessedTime() || REQUEST_TIME - $user->getLastAccessedTime() > Settings::get('session_write_interval', 180);
|
'hostname' => $this->requestStack->getCurrentRequest()->getClientIP(),
|
||||||
|
'session' => $value,
|
||||||
if ($is_changed || $needs_update) {
|
'timestamp' => REQUEST_TIME,
|
||||||
// Either ssid or sid or both will be added from $key below.
|
);
|
||||||
$fields = array(
|
// Use the session ID as 'sid' and an empty string as 'ssid' by default.
|
||||||
'uid' => $user->id(),
|
// read() does not allow empty strings so that's a safe default.
|
||||||
'hostname' => $this->requestStack->getCurrentRequest()->getClientIP(),
|
$key = array('sid' => Crypt::hashBase64($sid), 'ssid' => '');
|
||||||
'session' => $value,
|
// On HTTPS connections, use the session ID as both 'sid' and 'ssid'.
|
||||||
'timestamp' => REQUEST_TIME,
|
if ($this->requestStack->getCurrentRequest()->isSecure()) {
|
||||||
);
|
$key['ssid'] = Crypt::hashBase64($sid);
|
||||||
// Use the session ID as 'sid' and an empty string as 'ssid' by default.
|
$cookies = $this->requestStack->getCurrentRequest()->cookies;
|
||||||
// read() does not allow empty strings so that's a safe default.
|
// The "secure pages" setting allows a site to simultaneously use both
|
||||||
$key = array('sid' => Crypt::hashBase64($sid), 'ssid' => '');
|
// secure and insecure session cookies. If enabled and both cookies
|
||||||
// On HTTPS connections, use the session ID as both 'sid' and 'ssid'.
|
// are presented then use both keys. The session ID from the cookie is
|
||||||
if ($this->requestStack->getCurrentRequest()->isSecure()) {
|
// hashed before being stored in the database as a security measure.
|
||||||
$key['ssid'] = Crypt::hashBase64($sid);
|
if ($this->sessionManager->isMixedMode()) {
|
||||||
$cookies = $this->requestStack->getCurrentRequest()->cookies;
|
$insecure_session_name = $this->sessionManager->getInsecureName();
|
||||||
// The "secure pages" setting allows a site to simultaneously use both
|
if ($cookies->has($insecure_session_name)) {
|
||||||
// secure and insecure session cookies. If enabled and both cookies
|
$key['sid'] = Crypt::hashBase64($cookies->get($insecure_session_name));
|
||||||
// are presented then use both keys. The session ID from the cookie is
|
|
||||||
// hashed before being stored in the database as a security measure.
|
|
||||||
if (Settings::get('mixed_mode_sessions', FALSE)) {
|
|
||||||
$insecure_session_name = substr(session_name(), 1);
|
|
||||||
if ($cookies->has($insecure_session_name)) {
|
|
||||||
$key['sid'] = Crypt::hashBase64($cookies->get($insecure_session_name));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
elseif (Settings::get('mixed_mode_sessions', FALSE)) {
|
|
||||||
unset($key['ssid']);
|
|
||||||
}
|
|
||||||
$this->connection->merge('sessions')
|
|
||||||
->keys($key)
|
|
||||||
->fields($fields)
|
|
||||||
->execute();
|
|
||||||
}
|
}
|
||||||
|
elseif ($this->sessionManager->isMixedMode()) {
|
||||||
|
unset($key['ssid']);
|
||||||
|
}
|
||||||
|
$this->connection->merge('sessions')
|
||||||
|
->keys($key)
|
||||||
|
->fields($fields)
|
||||||
|
->execute();
|
||||||
|
|
||||||
// Likewise, do not update access time more than once per 180 seconds.
|
// Likewise, do not update access time more than once per 180 seconds.
|
||||||
if ($user->isAuthenticated() && REQUEST_TIME - $user->getLastAccessedTime() > Settings::get('session_write_interval', 180)) {
|
if ($user->isAuthenticated() && REQUEST_TIME - $user->getLastAccessedTime() > Settings::get('session_write_interval', 180)) {
|
||||||
$this->connection->update('users')
|
$this->connection->update('users')
|
||||||
|
@ -252,12 +233,12 @@ class SessionHandler implements \SessionHandlerInterface {
|
||||||
$user = new AnonymousUserSession();
|
$user = new AnonymousUserSession();
|
||||||
|
|
||||||
// Unset the session cookies.
|
// Unset the session cookies.
|
||||||
$this->deleteCookie(session_name());
|
$this->deleteCookie($this->getName());
|
||||||
if ($is_https) {
|
if ($is_https) {
|
||||||
$this->deleteCookie(substr(session_name(), 1), FALSE);
|
$this->deleteCookie($this->sessionManager->getInsecureName(), FALSE);
|
||||||
}
|
}
|
||||||
elseif (Settings::get('mixed_mode_sessions', FALSE)) {
|
elseif ($this->sessionManager->isMixedMode()) {
|
||||||
$this->deleteCookie('S' . session_name(), TRUE);
|
$this->deleteCookie('S' . $this->getName(), TRUE);
|
||||||
}
|
}
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,11 +13,36 @@ use Drupal\Core\Session\AnonymousUserSession;
|
||||||
use Drupal\Core\Session\SessionHandler;
|
use Drupal\Core\Session\SessionHandler;
|
||||||
use Drupal\Core\Site\Settings;
|
use Drupal\Core\Site\Settings;
|
||||||
use Symfony\Component\HttpFoundation\RequestStack;
|
use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag as SymfonyMetadataBag;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages user sessions.
|
* Manages user sessions.
|
||||||
|
*
|
||||||
|
* This class implements the custom session management code inherited from
|
||||||
|
* Drupal 7 on top of the corresponding Symfony component. Regrettably the name
|
||||||
|
* NativeSessionStorage is not quite accurate. In fact the responsibility for
|
||||||
|
* storing and retrieving session data has been extracted from it in Symfony 2.1
|
||||||
|
* but the class name was not changed.
|
||||||
|
*
|
||||||
|
* @todo
|
||||||
|
* In fact the NativeSessionStorage class already implements all of the
|
||||||
|
* functionality required by a typical Symfony application. Normally it is not
|
||||||
|
* necessary to subclass it at all. In order to reach the point where Drupal
|
||||||
|
* can use the Symfony session management unmodified, the code implemented
|
||||||
|
* here needs to be extracted either into a dedicated session handler proxy
|
||||||
|
* (e.g. mixed mode SSL, sid-hashing) or relocated to the authentication
|
||||||
|
* subsystem.
|
||||||
*/
|
*/
|
||||||
class SessionManager implements SessionManagerInterface {
|
class SessionManager extends NativeSessionStorage implements SessionManagerInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the session manager is operating in mixed mode SSL.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $mixedMode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The request stack.
|
* The request stack.
|
||||||
|
@ -54,14 +79,30 @@ class SessionManager implements SessionManagerInterface {
|
||||||
/**
|
/**
|
||||||
* Constructs a new session manager instance.
|
* Constructs a new session manager instance.
|
||||||
*
|
*
|
||||||
* @param \Symfony\Component\HttpFoundation\RequestStack $request
|
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
|
||||||
* The request stack.
|
* The request stack.
|
||||||
* @param \Drupal\Core\Database\Connection $connection
|
* @param \Drupal\Core\Database\Connection $connection
|
||||||
* The database connection.
|
* The database connection.
|
||||||
|
* @param \Symfony\Component\HttpFoundation\Session\Storage\MetadataBag $metadata_bag
|
||||||
|
* The session metadata bag.
|
||||||
|
* @param \Drupal\Core\Site\Settings $settings
|
||||||
|
* The settings instance.
|
||||||
*/
|
*/
|
||||||
public function __construct(RequestStack $request_stack, Connection $connection) {
|
public function __construct(RequestStack $request_stack, Connection $connection, SymfonyMetadataBag $metadata_bag, Settings $settings) {
|
||||||
|
parent::__construct();
|
||||||
$this->requestStack = $request_stack;
|
$this->requestStack = $request_stack;
|
||||||
$this->connection = $connection;
|
$this->connection = $connection;
|
||||||
|
$this->setMetadataBag($metadata_bag);
|
||||||
|
|
||||||
|
$this->setMixedMode($settings->get('mixed_mode_sessions', FALSE));
|
||||||
|
|
||||||
|
// @todo When not using the Symfony Session object, the list of bags in the
|
||||||
|
// NativeSessionStorage will remain uninitialized. This will lead to
|
||||||
|
// errors in NativeSessionHandler::loadSession. Remove this after
|
||||||
|
// https://drupal.org/node/2229145, when we will be using the Symfony
|
||||||
|
// session object (which registers an attribute bag with the
|
||||||
|
// manager upon instantiation).
|
||||||
|
$this->bags = array();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -72,12 +113,14 @@ class SessionManager implements SessionManagerInterface {
|
||||||
|
|
||||||
// Register the default session handler.
|
// Register the default session handler.
|
||||||
// @todo Extract session storage from session handler into a service.
|
// @todo Extract session storage from session handler into a service.
|
||||||
$handler = new SessionHandler($this, $this->requestStack, $this->connection);
|
$save_handler = new SessionHandler($this, $this->requestStack, $this->connection);
|
||||||
session_set_save_handler($handler, TRUE);
|
$write_check_handler = new WriteCheckSessionHandler($save_handler);
|
||||||
|
$this->setSaveHandler($write_check_handler);
|
||||||
|
|
||||||
$is_https = $this->requestStack->getCurrentRequest()->isSecure();
|
$is_https = $this->requestStack->getCurrentRequest()->isSecure();
|
||||||
$cookies = $this->requestStack->getCurrentRequest()->cookies;
|
$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))))) {
|
$insecure_session_name = $this->getInsecureName();
|
||||||
|
if (($cookies->has($this->getName()) && ($session_name = $cookies->get($this->getName()))) || ($is_https && $this->isMixedMode() && ($cookies->has($insecure_session_name) && ($session_name = $cookies->get($insecure_session_name))))) {
|
||||||
// If a session cookie exists, initialize the session. Otherwise the
|
// If a session cookie exists, initialize the session. Otherwise the
|
||||||
// session is only started on demand in save(), making
|
// session is only started on demand in save(), making
|
||||||
// anonymous users not use a session cookie unless something is stored in
|
// anonymous users not use a session cookie unless something is stored in
|
||||||
|
@ -94,12 +137,8 @@ class SessionManager implements SessionManagerInterface {
|
||||||
// session ID in advance.
|
// session ID in advance.
|
||||||
$this->lazySession = TRUE;
|
$this->lazySession = TRUE;
|
||||||
$user = new AnonymousUserSession();
|
$user = new AnonymousUserSession();
|
||||||
// Less random sessions (which are much faster to generate) are used for
|
$this->setId(Crypt::randomBytesBase64());
|
||||||
// anonymous users than are generated in regenerate() when
|
if ($is_https && $this->isMixedMode()) {
|
||||||
// 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();
|
$session_id = Crypt::randomBytesBase64();
|
||||||
$cookies->set($insecure_session_name, $session_id);
|
$cookies->set($insecure_session_name, $session_id);
|
||||||
}
|
}
|
||||||
|
@ -113,18 +152,20 @@ class SessionManager implements SessionManagerInterface {
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function start() {
|
public function start() {
|
||||||
if ($this->isCli() || $this->isStarted()) {
|
if (!$this->isEnabled() || $this->isCli()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Save current session data before starting it, as PHP will destroy it.
|
// Save current session data before starting it, as PHP will destroy it.
|
||||||
$session_data = isset($_SESSION) ? $_SESSION : NULL;
|
$session_data = isset($_SESSION) ? $_SESSION : NULL;
|
||||||
|
|
||||||
session_start();
|
$result = parent::start();
|
||||||
|
|
||||||
// Restore session data.
|
// Restore session data.
|
||||||
if (!empty($session_data)) {
|
if (!empty($session_data)) {
|
||||||
$_SESSION += $session_data;
|
$_SESSION += $session_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -141,7 +182,7 @@ class SessionManager implements SessionManagerInterface {
|
||||||
if ($user->isAnonymous() && $this->isSessionObsolete()) {
|
if ($user->isAnonymous() && $this->isSessionObsolete()) {
|
||||||
// There is no session data to store, destroy the session if it was
|
// There is no session data to store, destroy the session if it was
|
||||||
// previously started.
|
// previously started.
|
||||||
if ($this->isStarted()) {
|
if ($this->getSaveHandler()->isActive()) {
|
||||||
session_destroy();
|
session_destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,8 +191,8 @@ class SessionManager implements SessionManagerInterface {
|
||||||
// started.
|
// started.
|
||||||
if (!$this->isStarted()) {
|
if (!$this->isStarted()) {
|
||||||
$this->start();
|
$this->start();
|
||||||
if ($this->requestStack->getCurrentRequest()->isSecure() && Settings::get('mixed_mode_sessions', FALSE)) {
|
if ($this->requestStack->getCurrentRequest()->isSecure() && $this->isMixedMode()) {
|
||||||
$insecure_session_name = substr(session_name(), 1);
|
$insecure_session_name = $this->getInsecureName();
|
||||||
$params = session_get_cookie_params();
|
$params = session_get_cookie_params();
|
||||||
$expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0;
|
$expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0;
|
||||||
$cookie_params = $this->requestStack->getCurrentRequest()->cookies;
|
$cookie_params = $this->requestStack->getCurrentRequest()->cookies;
|
||||||
|
@ -159,21 +200,14 @@ class SessionManager implements SessionManagerInterface {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Write the session data.
|
// Write the session data.
|
||||||
session_write_close();
|
parent::save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function isStarted() {
|
public function regenerate($destroy = FALSE, $lifetime = NULL) {
|
||||||
return session_status() === \PHP_SESSION_ACTIVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function regenerate() {
|
|
||||||
global $user;
|
global $user;
|
||||||
|
|
||||||
// Nothing to do if we are not allowed to change the session.
|
// Nothing to do if we are not allowed to change the session.
|
||||||
|
@ -181,11 +215,17 @@ class SessionManager implements SessionManagerInterface {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We do not support the optional $destroy and $lifetime parameters as long
|
||||||
|
// as #2238561 remains open.
|
||||||
|
if ($destroy || isset($lifetime)) {
|
||||||
|
throw new \InvalidArgumentException('The optional parameters $destroy and $lifetime of SessionManager::regenerate() are not supported currently');
|
||||||
|
}
|
||||||
|
|
||||||
$is_https = $this->requestStack->getCurrentRequest()->isSecure();
|
$is_https = $this->requestStack->getCurrentRequest()->isSecure();
|
||||||
$cookies = $this->requestStack->getCurrentRequest()->cookies;
|
$cookies = $this->requestStack->getCurrentRequest()->cookies;
|
||||||
|
|
||||||
if ($is_https && Settings::get('mixed_mode_sessions', FALSE)) {
|
if ($is_https && $this->isMixedMode()) {
|
||||||
$insecure_session_name = substr(session_name(), 1);
|
$insecure_session_name = $this->getInsecureName();
|
||||||
if (!isset($this->lazySession) && $cookies->has($insecure_session_name)) {
|
if (!isset($this->lazySession) && $cookies->has($insecure_session_name)) {
|
||||||
$old_insecure_session_id = $cookies->get($insecure_session_name);
|
$old_insecure_session_id = $cookies->get($insecure_session_name);
|
||||||
}
|
}
|
||||||
|
@ -200,14 +240,13 @@ class SessionManager implements SessionManagerInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->isStarted()) {
|
if ($this->isStarted()) {
|
||||||
$old_session_id = session_id();
|
$old_session_id = $this->getId();
|
||||||
}
|
}
|
||||||
session_id(Crypt::randomBytesBase64());
|
session_id(Crypt::randomBytesBase64());
|
||||||
|
|
||||||
// @todo As soon as https://drupal.org/node/2238087 lands, the token seed
|
// @todo The token seed can be moved onto \Drupal\Core\Session\MetadataBag.
|
||||||
// can be moved onto Drupal\Core\Session\MetadataBag. The session manager
|
// The session manager then needs to notify the metadata bag when the
|
||||||
// then needs to notify the metadata bag when the token should be
|
// token should be regenerated. https://drupal.org/node/2256257
|
||||||
// regenerated.
|
|
||||||
if (!empty($_SESSION)) {
|
if (!empty($_SESSION)) {
|
||||||
unset($_SESSION['csrf_token_seed']);
|
unset($_SESSION['csrf_token_seed']);
|
||||||
}
|
}
|
||||||
|
@ -215,13 +254,13 @@ class SessionManager implements SessionManagerInterface {
|
||||||
if (isset($old_session_id)) {
|
if (isset($old_session_id)) {
|
||||||
$params = session_get_cookie_params();
|
$params = session_get_cookie_params();
|
||||||
$expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0;
|
$expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0;
|
||||||
setcookie(session_name(), session_id(), $expire, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
|
setcookie($this->getName(), $this->getId(), $expire, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
|
||||||
$fields = array('sid' => Crypt::hashBase64(session_id()));
|
$fields = array('sid' => Crypt::hashBase64($this->getId()));
|
||||||
if ($is_https) {
|
if ($is_https) {
|
||||||
$fields['ssid'] = Crypt::hashBase64(session_id());
|
$fields['ssid'] = Crypt::hashBase64($this->getId());
|
||||||
// If the "secure pages" setting is enabled, use the newly-created
|
// If the "secure pages" setting is enabled, use the newly-created
|
||||||
// insecure session identifier as the regenerated sid.
|
// insecure session identifier as the regenerated sid.
|
||||||
if (Settings::get('mixed_mode_sessions', FALSE)) {
|
if ($this->isMixedMode()) {
|
||||||
$fields['sid'] = Crypt::hashBase64($session_id);
|
$fields['sid'] = Crypt::hashBase64($session_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -235,7 +274,7 @@ class SessionManager implements SessionManagerInterface {
|
||||||
// the secure site but a session was active on the insecure site, update
|
// the secure site but a session was active on the insecure site, update
|
||||||
// the insecure session with the new session identifiers.
|
// the insecure session with the new session identifiers.
|
||||||
$this->connection->update('sessions')
|
$this->connection->update('sessions')
|
||||||
->fields(array('sid' => Crypt::hashBase64($session_id), 'ssid' => Crypt::hashBase64(session_id())))
|
->fields(array('sid' => Crypt::hashBase64($session_id), 'ssid' => Crypt::hashBase64($this->getId())))
|
||||||
->condition('sid', Crypt::hashBase64($old_insecure_session_id))
|
->condition('sid', Crypt::hashBase64($old_insecure_session_id))
|
||||||
->execute();
|
->execute();
|
||||||
}
|
}
|
||||||
|
@ -286,6 +325,27 @@ class SessionManager implements SessionManagerInterface {
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isMixedMode() {
|
||||||
|
return $this->mixedMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function setMixedMode($mixed_mode) {
|
||||||
|
$this->mixedMode = (bool) $mixed_mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getInsecureName() {
|
||||||
|
return substr($this->getName(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the current PHP process runs on CLI.
|
* Returns whether the current PHP process runs on CLI.
|
||||||
*
|
*
|
||||||
|
@ -305,11 +365,29 @@ class SessionManager implements SessionManagerInterface {
|
||||||
* destroyed.
|
* destroyed.
|
||||||
*/
|
*/
|
||||||
protected function isSessionObsolete() {
|
protected function isSessionObsolete() {
|
||||||
// Return early when $_SESSION is empty or not initialized.
|
$used_session_keys = array_filter($this->getSessionDataMask());
|
||||||
|
return empty($used_session_keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a map specifying which session key is containing user data.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* An array where keys correspond to the session keys and the values are
|
||||||
|
* booleans specifying whether the corresponding session key contains any
|
||||||
|
* user data.
|
||||||
|
*/
|
||||||
|
protected function getSessionDataMask() {
|
||||||
if (empty($_SESSION)) {
|
if (empty($_SESSION)) {
|
||||||
return TRUE;
|
return array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start out with a completely filled mask.
|
||||||
|
$mask = array_fill_keys(array_keys($_SESSION), TRUE);
|
||||||
|
|
||||||
|
// Ignore the metadata bag, it does not contain any user data.
|
||||||
|
$mask[$this->metadataBag->getStorageKey()] = FALSE;
|
||||||
|
|
||||||
// Ignore the CSRF token seed.
|
// Ignore the CSRF token seed.
|
||||||
//
|
//
|
||||||
// @todo Anonymous users should not get a CSRF token at any time, or if they
|
// @todo Anonymous users should not get a CSRF token at any time, or if they
|
||||||
|
@ -317,10 +395,18 @@ class SessionManager implements SessionManagerInterface {
|
||||||
// session once obsolete. Since that is not guaranteed to be the case,
|
// session once obsolete. Since that is not guaranteed to be the case,
|
||||||
// this check force-ignores the CSRF token, so as to avoid performance
|
// this check force-ignores the CSRF token, so as to avoid performance
|
||||||
// regressions.
|
// regressions.
|
||||||
// As soon as https://drupal.org/node/2238087 lands, the token seed can be
|
// The token seed can be moved onto \Drupal\Core\Session\MetadataBag. This
|
||||||
// moved onto \Drupal\Core\Session\MetadataBag. This will result in the
|
// will result in the CSRF token being ignored automatically.
|
||||||
// CSRF token to be ignored automatically.
|
// https://drupal.org/node/2256257
|
||||||
return count(array_diff_key($_SESSION, array('csrf_token_seed' => TRUE))) == 0;
|
$mask['csrf_token_seed'] = FALSE;
|
||||||
|
|
||||||
|
// Ignore attribute bags when they do not contain any data.
|
||||||
|
foreach ($this->bags as $bag) {
|
||||||
|
$key = $bag->getStorageKey();
|
||||||
|
$mask[$key] = empty($_SESSION[$key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_intersect_key($mask, $_SESSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,12 @@
|
||||||
|
|
||||||
namespace Drupal\Core\Session;
|
namespace Drupal\Core\Session;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines the session manager interface.
|
* Defines the session manager interface.
|
||||||
*/
|
*/
|
||||||
interface SessionManagerInterface {
|
interface SessionManagerInterface extends SessionStorageInterface {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the session handler, starting a session if needed.
|
* Initializes the session handler, starting a session if needed.
|
||||||
|
@ -19,28 +21,6 @@ interface SessionManagerInterface {
|
||||||
*/
|
*/
|
||||||
public function initialize();
|
public function initialize();
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts a session forcefully, preserving already set session data.
|
|
||||||
*/
|
|
||||||
public function start();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Commits the current session, if necessary.
|
|
||||||
*
|
|
||||||
* If an anonymous user already have an empty session, destroy it.
|
|
||||||
*/
|
|
||||||
public function save();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether a session has been started.
|
|
||||||
*/
|
|
||||||
public function isStarted();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when an anonymous user becomes authenticated or vice-versa.
|
|
||||||
*/
|
|
||||||
public function regenerate();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ends a specific user's session(s).
|
* Ends a specific user's session(s).
|
||||||
*
|
*
|
||||||
|
@ -77,4 +57,28 @@ interface SessionManagerInterface {
|
||||||
*/
|
*/
|
||||||
public function enable();
|
public function enable();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether mixed mode SSL sessions are enabled in the session manager.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
* Value of the mixed mode SSL sessions flag.
|
||||||
|
*/
|
||||||
|
public function isMixedMode();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables mixed mode SSL sessions in the session manager.
|
||||||
|
*
|
||||||
|
* @param bool $mixed_mode
|
||||||
|
* New value for the mixed mode SSL sessions flag.
|
||||||
|
*/
|
||||||
|
public function setMixedMode($mixed_mode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the insecure session when operating in mixed mode SSL.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* The name of the insecure session.
|
||||||
|
*/
|
||||||
|
public function getInsecureName();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue