diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 34e68241ab..200f69eb66 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -1557,6 +1557,8 @@ function install_bootstrap_full() { $session = \Drupal::service('session'); \Drupal::request()->setSession($session); $session->start(); + // Ensure the session is maintained throughout the install. + $_SESSION['_drupal_installing'] = TRUE; } /** diff --git a/core/lib/Drupal/Core/Session/MetadataBag.php b/core/lib/Drupal/Core/Session/MetadataBag.php index ee0fbd4855..dad5dabdf1 100644 --- a/core/lib/Drupal/Core/Session/MetadataBag.php +++ b/core/lib/Drupal/Core/Session/MetadataBag.php @@ -51,7 +51,8 @@ public function getCsrfTokenSeed() { /** * Clear the CSRF token seed. */ - public function clearCsrfTokenSeed() { + public function stampNew($lifetime = NULL) { + parent::stampNew($lifetime); unset($this->meta[static::CSRF_TOKEN_SEED]); } diff --git a/core/lib/Drupal/Core/Session/SessionManager.php b/core/lib/Drupal/Core/Session/SessionManager.php index 607103109d..0fcb915f25 100644 --- a/core/lib/Drupal/Core/Session/SessionManager.php +++ b/core/lib/Drupal/Core/Session/SessionManager.php @@ -2,7 +2,6 @@ namespace Drupal\Core\Session; -use Drupal\Component\Utility\Crypt; use Drupal\Core\Database\Connection; use Drupal\Core\DependencyInjection\DependencySerializationTrait; use Symfony\Component\HttpFoundation\RequestStack; @@ -119,17 +118,6 @@ public function start() { } if (empty($result)) { - // Randomly generate a session identifier for this request. This is - // necessary because \Drupal\Core\TempStore\SharedTempStoreFactory::get() - // wants to know the future session ID of a lazily started session in - // advance. - // - // @todo: With current versions of PHP there is little reason to generate - // the session id from within application code. Consider using the - // default php session id instead of generating a custom one: - // https://www.drupal.org/node/2238561 - $this->setId(Crypt::randomBytesBase64()); - // Initialize the session global and attach the Symfony session bags. $_SESSION = []; $this->loadSession(); @@ -210,30 +198,7 @@ public function regenerate($destroy = FALSE, $lifetime = NULL) { 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'); - } - - if ($this->isStarted()) { - $old_session_id = $this->getId(); - } - session_id(Crypt::randomBytesBase64()); - - $this->getMetadataBag()->clearCsrfTokenSeed(); - - if (isset($old_session_id)) { - $params = session_get_cookie_params(); - $expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0; - setcookie($this->getName(), $this->getId(), $expire, $params['path'], $params['domain'], $params['secure'], $params['httponly']); - $this->migrateStoredSession($old_session_id); - } - - if (!$this->isStarted()) { - // Start the session when it doesn't exist yet. - $this->startNow(); - } + return parent::regenerate($destroy, $lifetime); } /** @@ -324,18 +289,4 @@ protected function getSessionDataMask() { return array_intersect_key($mask, $_SESSION); } - /** - * Migrates the current session to a new session id. - * - * @param string $old_session_id - * The old session ID. The new session ID is $this->getId(). - */ - protected function migrateStoredSession($old_session_id) { - $fields = ['sid' => Crypt::hashBase64($this->getId())]; - $this->connection->update('sessions') - ->fields($fields) - ->condition('sid', Crypt::hashBase64($old_session_id)) - ->execute(); - } - } diff --git a/core/lib/Drupal/Core/TempStore/PrivateTempStore.php b/core/lib/Drupal/Core/TempStore/PrivateTempStore.php index ea7ec3fc37..714021fd30 100644 --- a/core/lib/Drupal/Core/TempStore/PrivateTempStore.php +++ b/core/lib/Drupal/Core/TempStore/PrivateTempStore.php @@ -2,6 +2,7 @@ namespace Drupal\Core\TempStore; +use Drupal\Component\Utility\Crypt; use Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface; use Drupal\Core\Lock\LockBackendInterface; use Drupal\Core\Session\AccountProxyInterface; @@ -117,17 +118,16 @@ public function get($key) { * Thrown when a lock for the backend storage could not be acquired. */ public function set($key, $value) { - // Ensure that an anonymous user has a session created for them, as - // otherwise subsequent page loads will not be able to retrieve their - // tempstore data. if ($this->currentUser->isAnonymous()) { - // @todo when https://www.drupal.org/node/2865991 is resolved, use force - // start session API rather than setting an arbitrary value directly. + // Ensure that an anonymous user has a session created for them, as + // otherwise subsequent page loads will not be able to retrieve their + // tempstore data. Note this has to be done before the key is created as + // the owner is used in key creation. $this->startSession(); - $this->requestStack - ->getCurrentRequest() - ->getSession() - ->set('core.tempstore.private', TRUE); + $session = $this->requestStack->getCurrentRequest()->getSession(); + if (!$session->has('core.tempstore.private.owner')) { + $session->set('core.tempstore.private.owner', Crypt::randomBytesBase64()); + } } $key = $this->createkey($key); @@ -222,8 +222,10 @@ protected function createkey($key) { protected function getOwner() { $owner = $this->currentUser->id(); if ($this->currentUser->isAnonymous()) { + // Check to see if an owner key exists in the session. $this->startSession(); - $owner = $this->requestStack->getCurrentRequest()->getSession()->getId(); + $session = $this->requestStack->getCurrentRequest()->getSession(); + $owner = $session->get('core.tempstore.private.owner'); } return $owner; } diff --git a/core/lib/Drupal/Core/TempStore/SharedTempStore.php b/core/lib/Drupal/Core/TempStore/SharedTempStore.php index c131a80fd2..064ae1c1b2 100644 --- a/core/lib/Drupal/Core/TempStore/SharedTempStore.php +++ b/core/lib/Drupal/Core/TempStore/SharedTempStore.php @@ -147,7 +147,11 @@ public function setIfNotExists($key, $value) { 'data' => $value, 'updated' => (int) $this->requestStack->getMasterRequest()->server->get('REQUEST_TIME'), ]; - return $this->storage->setWithExpireIfNotExists($key, $value, $this->expire); + $set = $this->storage->setWithExpireIfNotExists($key, $value, $this->expire); + if ($set) { + $this->ensureAnonymousSession(); + } + return $set; } /** @@ -205,6 +209,7 @@ public function set($key, $value) { 'data' => $value, 'updated' => (int) $this->requestStack->getMasterRequest()->server->get('REQUEST_TIME'), ]; + $this->ensureAnonymousSession(); $this->storage->setWithExpire($key, $value, $this->expire); $this->lockBackend->release($key); } @@ -276,4 +281,15 @@ public function deleteIfOwner($key) { return FALSE; } + /** + * Stores the owner in session if the user is anonymous. + * + * This method should be called when a value is set. + */ + protected function ensureAnonymousSession() { + if (\Drupal::currentUser()->isAnonymous()) { + $this->requestStack->getCurrentRequest()->getSession()->set('core.tempstore.shared.owner', $this->owner); + } + } + } diff --git a/core/lib/Drupal/Core/TempStore/SharedTempStoreFactory.php b/core/lib/Drupal/Core/TempStore/SharedTempStoreFactory.php index 90e816a7dd..438e8beb57 100644 --- a/core/lib/Drupal/Core/TempStore/SharedTempStoreFactory.php +++ b/core/lib/Drupal/Core/TempStore/SharedTempStoreFactory.php @@ -2,6 +2,7 @@ namespace Drupal\Core\TempStore; +use Drupal\Component\Utility\Crypt; use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface; use Drupal\Core\Lock\LockBackendInterface; use Symfony\Component\HttpFoundation\RequestStack; @@ -76,7 +77,20 @@ public function get($collection, $owner = NULL) { // Use the currently authenticated user ID or the active user ID unless // the owner is overridden. if (!isset($owner)) { - $owner = \Drupal::currentUser()->id() ?: session_id(); + $account = \Drupal::currentUser(); + $owner = $account->id(); + if ($account->isAnonymous()) { + $has_session = $this->requestStack + ->getCurrentRequest() + ->hasSession(); + if (!$has_session) { + /** @var \Symfony\Component\HttpFoundation\Session\SessionInterface $session */ + $session = \Drupal::service('session'); + $this->requestStack->getCurrentRequest()->setSession($session); + $session->start(); + } + $owner = $this->requestStack->getCurrentRequest()->getSession()->get('core.tempstore.shared.owner', Crypt::randomBytesBase64()); + } } // Store the data for this collection in the database. diff --git a/core/modules/user/user.module b/core/modules/user/user.module index 053429f4b8..78fbdb2a20 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -561,8 +561,10 @@ function user_login_finalize(UserInterface $account) { // This is called before hook_user_login() in case one of those functions // fails or incorrectly does a redirect which would leave the old session // in place. - \Drupal::service('session')->migrate(); - \Drupal::service('session')->set('uid', $account->id()); + /** @var \Symfony\Component\HttpFoundation\Session\Session $session */ + $session = \Drupal::service('session'); + $session->migrate(TRUE); + $session->set('uid', $account->id()); \Drupal::moduleHandler()->invokeAll('user_login', [$account]); }