You are here

class SessionManager in Zircon Profile 8

Same name and namespace in other branches
  1. 8.0 core/lib/Drupal/Core/Session/SessionManager.php \Drupal\Core\Session\SessionManager

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. sid-hashing) or relocated to the authentication subsystem.

Hierarchy

Expanded class hierarchy of SessionManager

1 string reference to 'SessionManager'
core.services.yml in core/core.services.yml
core/core.services.yml
1 service uses SessionManager
session_manager in core/core.services.yml
Drupal\Core\Session\SessionManager

File

core/lib/Drupal/Core/Session/SessionManager.php, line 33
Contains \Drupal\Core\Session\SessionManager.

Namespace

Drupal\Core\Session
View source
class SessionManager extends NativeSessionStorage implements SessionManagerInterface {
  use DependencySerializationTrait;

  /**
   * The request stack.
   *
   * @var \Symfony\Component\HttpFoundation\RequestStack
   */
  protected $requestStack;

  /**
   * The database connection to use.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected $connection;

  /**
   * The session configuration.
   *
   * @var \Drupal\Core\Session\SessionConfigurationInterface
   */
  protected $sessionConfiguration;

  /**
   * Whether a lazy session has been started.
   *
   * @var bool
   */
  protected $startedLazy;

  /**
   * The write safe session handler.
   *
   * @todo: This reference should be removed once all database queries
   *   are removed from the session manager class.
   *
   * @var \Drupal\Core\Session\WriteSafeSessionHandlerInterface
   */
  protected $writeSafeHandler;

  /**
   * Constructs a new session manager instance.
   *
   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
   *   The request stack.
   * @param \Drupal\Core\Database\Connection $connection
   *   The database connection.
   * @param \Drupal\Core\Session\MetadataBag $metadata_bag
   *   The session metadata bag.
   * @param \Drupal\Core\Session\SessionConfigurationInterface $session_configuration
   *   The session configuration interface.
   * @param \Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy|Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler|\SessionHandlerInterface|NULL $handler
   *   The object to register as a PHP session handler.
   *   @see \Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage::setSaveHandler()
   */
  public function __construct(RequestStack $request_stack, Connection $connection, MetadataBag $metadata_bag, SessionConfigurationInterface $session_configuration, $handler = NULL) {
    $options = array();
    $this->sessionConfiguration = $session_configuration;
    $this->requestStack = $request_stack;
    $this->connection = $connection;
    parent::__construct($options, $handler, $metadata_bag);

    // @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://www.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();
  }

  /**
   * {@inheritdoc}
   */
  public function start() {
    if (($this->started || $this->startedLazy) && !$this->closed) {
      return $this->started;
    }
    $request = $this->requestStack
      ->getCurrentRequest();
    $this
      ->setOptions($this->sessionConfiguration
      ->getOptions($request));
    if ($this->sessionConfiguration
      ->hasSession($request)) {

      // 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.
      $result = $this
        ->startNow();
    }
    if (empty($result)) {

      // Randomly generate a session identifier for this request. This is
      // necessary because \Drupal\user\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 = array();
      $this
        ->loadSession();

      // NativeSessionStorage::loadSession() sets started to TRUE, reset it to
      // FALSE here.
      $this->started = FALSE;
      $this->startedLazy = TRUE;
      $result = FALSE;
    }
    return $result;
  }

  /**
   * Forcibly start a PHP session.
   *
   * @return bool
   *   TRUE if the session is started.
   */
  protected function startNow() {
    if ($this
      ->isCli()) {
      return FALSE;
    }
    if ($this->startedLazy) {

      // Save current session data before starting it, as PHP will destroy it.
      $session_data = $_SESSION;
    }
    $result = parent::start();

    // Restore session data.
    if ($this->startedLazy) {
      $_SESSION = $session_data;
      $this
        ->loadSession();
    }
    return $result;
  }

  /**
   * {@inheritdoc}
   */
  public function save() {
    if ($this
      ->isCli()) {

      // We don't have anything to do if we are not allowed to save the session.
      return;
    }
    if ($this
      ->isSessionObsolete()) {

      // There is no session data to store, destroy the session if it was
      // previously started.
      if ($this
        ->getSaveHandler()
        ->isActive()) {
        $this
          ->destroy();
      }
    }
    else {

      // There is session data to store. Start the session if it is not already
      // started.
      if (!$this
        ->getSaveHandler()
        ->isActive()) {
        $this
          ->startNow();
      }

      // Write the session data.
      parent::save();
    }
    $this->startedLazy = FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function regenerate($destroy = FALSE, $lifetime = NULL) {

    // Nothing to do if we are not allowed to change the session.
    if ($this
      ->isCli()) {
      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();
    }
  }

  /**
   * {@inheritdoc}
   */
  public function delete($uid) {

    // Nothing to do if we are not allowed to change the session.
    if (!$this->writeSafeHandler
      ->isSessionWritable() || $this
      ->isCli()) {
      return;
    }
    $this->connection
      ->delete('sessions')
      ->condition('uid', $uid)
      ->execute();
  }

  /**
   * {@inheritdoc}
   */
  public function destroy() {
    session_destroy();

    // Unset the session cookies.
    $session_name = $this
      ->getName();
    $cookies = $this->requestStack
      ->getCurrentRequest()->cookies;
    if ($cookies
      ->has($session_name)) {
      $params = session_get_cookie_params();
      setcookie($session_name, '', REQUEST_TIME - 3600, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
      $cookies
        ->remove($session_name);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function setWriteSafeHandler(WriteSafeSessionHandlerInterface $handler) {
    $this->writeSafeHandler = $handler;
  }

  /**
   * Returns whether the current PHP process runs on CLI.
   *
   * Command line clients do not support cookies nor sessions.
   *
   * @return bool
   */
  protected function isCli() {
    return PHP_SAPI === 'cli';
  }

  /**
   * Determines whether the session contains user data.
   *
   * @return bool
   *   TRUE when the session does not contain any values and therefore can be
   *   destroyed.
   */
  protected function isSessionObsolete() {
    $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)) {
      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 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);
  }

  /**
   * 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() unless
   *   $new_insecure_session_id is not empty.
   */
  protected function migrateStoredSession($old_session_id) {
    $fields = array(
      'sid' => Crypt::hashBase64($this
        ->getId()),
    );
    $this->connection
      ->update('sessions')
      ->fields($fields)
      ->condition('sid', Crypt::hashBase64($old_session_id))
      ->execute();
  }

}

Members

Namesort descending Modifiers Type Description Overrides
DependencySerializationTrait::$_serviceIds protected property An array of service IDs keyed by property name used for serialization.
DependencySerializationTrait::__sleep public function 1
DependencySerializationTrait::__wakeup public function 2
NativeSessionStorage::$bags protected property Array of SessionBagInterface.
NativeSessionStorage::$closed protected property
NativeSessionStorage::$metadataBag protected property
NativeSessionStorage::$saveHandler protected property
NativeSessionStorage::$started protected property
NativeSessionStorage::clear public function Clear all session data in memory. Overrides SessionStorageInterface::clear 1
NativeSessionStorage::getBag public function Gets a SessionBagInterface by name. Overrides SessionStorageInterface::getBag
NativeSessionStorage::getId public function Returns the session ID. Overrides SessionStorageInterface::getId
NativeSessionStorage::getMetadataBag public function Gets the MetadataBag. Overrides SessionStorageInterface::getMetadataBag
NativeSessionStorage::getName public function Returns the session name. Overrides SessionStorageInterface::getName
NativeSessionStorage::getSaveHandler public function Gets the save handler instance.
NativeSessionStorage::isStarted public function Checks if the session is started. Overrides SessionStorageInterface::isStarted
NativeSessionStorage::loadSession protected function Load the session with attributes.
NativeSessionStorage::registerBag public function Registers a SessionBagInterface for use. Overrides SessionStorageInterface::registerBag
NativeSessionStorage::setId public function Sets the session ID. Overrides SessionStorageInterface::setId
NativeSessionStorage::setMetadataBag public function Sets the MetadataBag.
NativeSessionStorage::setName public function Sets the session name. Overrides SessionStorageInterface::setName
NativeSessionStorage::setOptions public function Sets session.* ini variables.
NativeSessionStorage::setSaveHandler public function Registers session save handler as a PHP session handler.
SessionManager::$connection protected property The database connection to use.
SessionManager::$requestStack protected property The request stack.
SessionManager::$sessionConfiguration protected property The session configuration.
SessionManager::$startedLazy protected property Whether a lazy session has been started.
SessionManager::$writeSafeHandler protected property The write safe session handler.
SessionManager::delete public function Ends a specific user's session(s). Overrides SessionManagerInterface::delete
SessionManager::destroy public function Destroys the current session and removes session cookies. Overrides SessionManagerInterface::destroy
SessionManager::getSessionDataMask protected function Returns a map specifying which session key is containing user data.
SessionManager::isCli protected function Returns whether the current PHP process runs on CLI.
SessionManager::isSessionObsolete protected function Determines whether the session contains user data.
SessionManager::migrateStoredSession protected function Migrates the current session to a new session id.
SessionManager::regenerate public function Regenerates id that represents this storage. Overrides NativeSessionStorage::regenerate
SessionManager::save public function Force the session to be saved and closed. Overrides NativeSessionStorage::save
SessionManager::setWriteSafeHandler public function Sets the write safe session handler. Overrides SessionManagerInterface::setWriteSafeHandler
SessionManager::start public function Starts the session. Overrides NativeSessionStorage::start
SessionManager::startNow protected function Forcibly start a PHP session.
SessionManager::__construct public function Constructs a new session manager instance. Overrides NativeSessionStorage::__construct