Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
| Total | |
0.00% |
0 / 1 |
|
0.00% |
0 / 12 |
CRAP | |
0.00% |
0 / 115 |
| SessionManager | |
0.00% |
0 / 1 |
|
0.00% |
0 / 12 |
1332 | |
0.00% |
0 / 115 |
| __construct | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 7 |
|||
| start | |
0.00% |
0 / 1 |
42 | |
0.00% |
0 / 18 |
|||
| startNow | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 13 |
|||
| save | |
0.00% |
0 / 1 |
30 | |
0.00% |
0 / 16 |
|||
| regenerate | |
0.00% |
0 / 1 |
72 | |
0.00% |
0 / 21 |
|||
| delete | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 7 |
|||
| destroy | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 9 |
|||
| setWriteSafeHandler | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
| isCli | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
| isSessionObsolete | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 3 |
|||
| getSessionDataMask | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 11 |
|||
| migrateStoredSession | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 6 |
|||
| <?php | |
| /** | |
| * @file | |
| * Contains \Drupal\Core\Session\SessionManager. | |
| */ | |
| namespace Drupal\Core\Session; | |
| use Drupal\Component\Utility\Crypt; | |
| use Drupal\Core\Database\Connection; | |
| use Drupal\Core\DependencyInjection\DependencySerializationTrait; | |
| use Symfony\Component\HttpFoundation\RequestStack; | |
| use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; | |
| /** | |
| * 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. | |
| */ | |
| 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(). | |
| */ | |
| 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(); | |
| } | |
| } |