[HttpFoundation] Make sessions secure and lazy
This commit is contained in:
parent
3f0a3f58a6
commit
347939c9b3
@ -126,6 +126,8 @@ Form
|
||||
FrameworkBundle
|
||||
---------------
|
||||
|
||||
* The `session.use_strict_mode` option has been deprecated and is enabled by default.
|
||||
|
||||
* The `cache:clear` command doesn't clear "app" PSR-6 cache pools anymore,
|
||||
but still clears "system" ones.
|
||||
Use the `cache:pool:clear` command to clear "app" pools instead.
|
||||
@ -235,18 +237,13 @@ HttpFoundation
|
||||
* The `Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler`
|
||||
class has been deprecated and will be removed in 4.0. Use the `\SessionHandler` class instead.
|
||||
|
||||
* The `Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy` class has been
|
||||
deprecated and will be removed in 4.0. Use your `\SessionHandlerInterface` implementation directly.
|
||||
* The `Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler` class has been
|
||||
deprecated and will be removed in 4.0. Implement `SessionUpdateTimestampHandlerInterface` or
|
||||
extend `AbstractSessionHandler` instead.
|
||||
|
||||
* The `Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy` class has been
|
||||
deprecated and will be removed in 4.0. Use your `\SessionHandlerInterface` implementation directly.
|
||||
|
||||
* The `Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy` class has been
|
||||
deprecated and will be removed in 4.0. Use your `\SessionHandlerInterface` implementation directly.
|
||||
|
||||
* `NativeSessionStorage::setSaveHandler()` now takes an instance of `\SessionHandlerInterface` as argument.
|
||||
Not passing it is deprecated and will throw a `TypeError` in 4.0.
|
||||
|
||||
* Using `Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler` with the legacy mongo extension
|
||||
has been deprecated and will be removed in 4.0. Use it with the mongodb/mongodb package and ext-mongodb instead.
|
||||
|
||||
|
@ -329,6 +329,8 @@ Form
|
||||
FrameworkBundle
|
||||
---------------
|
||||
|
||||
* The `session.use_strict_mode` option has been removed and strict mode is always enabled.
|
||||
|
||||
* The `validator.mapping.cache.doctrine.apc` service has been removed.
|
||||
|
||||
* The "framework.trusted_proxies" configuration option and the corresponding "kernel.trusted_proxies" parameter have been removed. Use the `Request::setTrustedProxies()` method in your front controller instead.
|
||||
@ -542,12 +544,11 @@ HttpFoundation
|
||||
* The ability to check only for cacheable HTTP methods using `Request::isMethodSafe()` is
|
||||
not supported anymore, use `Request::isMethodCacheable()` instead.
|
||||
|
||||
* The `Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler`,
|
||||
`Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy`,
|
||||
`Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy` and
|
||||
`Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy` classes have been removed.
|
||||
* The `Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler` class has been
|
||||
removed. Implement `SessionUpdateTimestampHandlerInterface` or extend `AbstractSessionHandler` instead.
|
||||
|
||||
* `NativeSessionStorage::setSaveHandler()` now requires an instance of `\SessionHandlerInterface` as argument.
|
||||
* The `Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler` and
|
||||
`Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy` classes have been removed.
|
||||
|
||||
* The `Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler` does not work with the legacy
|
||||
mongo extension anymore. It requires mongodb/mongodb package and ext-mongodb.
|
||||
|
@ -30,7 +30,7 @@
|
||||
"symfony/polyfill-intl-icu": "~1.0",
|
||||
"symfony/polyfill-mbstring": "~1.0",
|
||||
"symfony/polyfill-php56": "~1.0",
|
||||
"symfony/polyfill-php70": "~1.0",
|
||||
"symfony/polyfill-php70": "~1.6",
|
||||
"symfony/polyfill-util": "~1.0"
|
||||
},
|
||||
"replace": {
|
||||
|
@ -4,6 +4,7 @@ CHANGELOG
|
||||
3.4.0
|
||||
-----
|
||||
|
||||
* Session `use_strict_mode` is now enabled by default and the corresponding option has been deprecated
|
||||
* Made the `cache:clear` command to *not* clear "app" PSR-6 cache pools anymore,
|
||||
but to still clear "system" ones; use the `cache:pool:clear` command to clear "app" pools instead
|
||||
* Always register a minimalist logger that writes in `stderr`
|
||||
|
@ -462,11 +462,14 @@ class Configuration implements ConfigurationInterface
|
||||
->scalarNode('gc_divisor')->end()
|
||||
->scalarNode('gc_probability')->defaultValue(1)->end()
|
||||
->scalarNode('gc_maxlifetime')->end()
|
||||
->booleanNode('use_strict_mode')->end()
|
||||
->booleanNode('use_strict_mode')
|
||||
->defaultTrue()
|
||||
->setDeprecated('The "%path%.%node%" option is enabled by default and deprecated since Symfony 3.4. It will be always enabled in 4.0.')
|
||||
->end()
|
||||
->scalarNode('save_path')->defaultValue('%kernel.cache_dir%/sessions')->end()
|
||||
->integerNode('metadata_update_threshold')
|
||||
->defaultValue('0')
|
||||
->info('seconds to wait between 2 session metadata updates, it will also prevent the session handler to write if the session has not changed')
|
||||
->info('seconds to wait between 2 session metadata updates')
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
|
@ -916,14 +916,7 @@ class FrameworkExtension extends Extension
|
||||
$container->getDefinition('session.storage.native')->replaceArgument(1, null);
|
||||
$container->getDefinition('session.storage.php_bridge')->replaceArgument(0, null);
|
||||
} else {
|
||||
$handlerId = $config['handler_id'];
|
||||
|
||||
if ($config['metadata_update_threshold'] > 0) {
|
||||
$container->getDefinition('session.handler.write_check')->addArgument(new Reference($handlerId));
|
||||
$handlerId = 'session.handler.write_check';
|
||||
}
|
||||
|
||||
$container->setAlias('session.handler', $handlerId)->setPrivate(true);
|
||||
$container->setAlias('session.handler', $config['handler_id'])->setPrivate(true);
|
||||
}
|
||||
|
||||
$container->setParameter('session.save_path', $config['save_path']);
|
||||
|
@ -48,11 +48,17 @@
|
||||
<argument type="service" id="session.storage.metadata_bag" />
|
||||
</service>
|
||||
|
||||
<service id="session.handler.native_file" class="Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler">
|
||||
<argument>%session.save_path%</argument>
|
||||
<service id="session.handler.native_file" class="Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler">
|
||||
<argument type="service">
|
||||
<service class="Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler">
|
||||
<argument>%session.save_path%</argument>
|
||||
</service>
|
||||
</argument>
|
||||
</service>
|
||||
|
||||
<service id="session.handler.write_check" class="Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler" />
|
||||
<service id="session.handler.write_check" class="Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler">
|
||||
<deprecated>The "%service_id%" service is deprecated since Symfony 3.4 and will be removed in 4.0. Use the `session.lazy_write` ini setting instead.</deprecated>
|
||||
</service>
|
||||
|
||||
<service id="session_listener" class="Symfony\Component\HttpKernel\EventListener\SessionListener">
|
||||
<tag name="kernel.event_subscriber" />
|
||||
|
@ -301,6 +301,7 @@ class ConfigurationTest extends TestCase
|
||||
'gc_probability' => 1,
|
||||
'save_path' => '%kernel.cache_dir%/sessions',
|
||||
'metadata_update_threshold' => '0',
|
||||
'use_strict_mode' => true,
|
||||
),
|
||||
'request' => array(
|
||||
'enabled' => false,
|
||||
|
@ -4,8 +4,9 @@ CHANGELOG
|
||||
3.4.0
|
||||
-----
|
||||
|
||||
* deprecated the `NativeSessionHandler` class,
|
||||
* deprecated the `AbstractProxy`, `NativeProxy` and `SessionHandlerProxy` classes,
|
||||
* implemented PHP 7.0's `SessionUpdateTimestampHandlerInterface` with a new
|
||||
`AbstractSessionHandler` base class and a new `StrictSessionHandler` wrapper
|
||||
* deprecated the `WriteCheckSessionHandler`, `NativeSessionHandler` and `NativeProxy` classes
|
||||
* deprecated setting session save handlers that do not implement `\SessionHandlerInterface` in `NativeSessionStorage::setSaveHandler()`
|
||||
* deprecated using `MongoDbSessionHandler` with the legacy mongo extension; use it with the mongodb/mongodb package and ext-mongodb instead
|
||||
* deprecated `MemcacheSessionHandler`; use `MemcachedSessionHandler` instead
|
||||
|
@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
|
||||
|
||||
/**
|
||||
* This abstract session handler provides a generic implementation
|
||||
* of the PHP 7.0 SessionUpdateTimestampHandlerInterface,
|
||||
* enabling strict and lazy session handling.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
abstract class AbstractSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface
|
||||
{
|
||||
private $sessionName;
|
||||
private $prefetchId;
|
||||
private $prefetchData;
|
||||
private $newSessionId;
|
||||
private $igbinaryEmptyData;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function open($savePath, $sessionName)
|
||||
{
|
||||
$this->sessionName = $sessionName;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sessionId
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function doRead($sessionId);
|
||||
|
||||
/**
|
||||
* @param string $sessionId
|
||||
* @param string $data
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract protected function doWrite($sessionId, $data);
|
||||
|
||||
/**
|
||||
* @param string $sessionId
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract protected function doDestroy($sessionId);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateId($sessionId)
|
||||
{
|
||||
$this->prefetchData = $this->read($sessionId);
|
||||
$this->prefetchId = $sessionId;
|
||||
|
||||
return '' !== $this->prefetchData;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function read($sessionId)
|
||||
{
|
||||
if (null !== $this->prefetchId) {
|
||||
$prefetchId = $this->prefetchId;
|
||||
$prefetchData = $this->prefetchData;
|
||||
$this->prefetchId = $this->prefetchData = null;
|
||||
|
||||
if ($prefetchId === $sessionId || '' === $prefetchData) {
|
||||
$this->newSessionId = '' === $prefetchData ? $sessionId : null;
|
||||
|
||||
return $prefetchData;
|
||||
}
|
||||
}
|
||||
|
||||
$data = $this->doRead($sessionId);
|
||||
$this->newSessionId = '' === $data ? $sessionId : null;
|
||||
if (\PHP_VERSION_ID < 70000) {
|
||||
$this->prefetchData = $data;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function write($sessionId, $data)
|
||||
{
|
||||
if (\PHP_VERSION_ID < 70000 && $this->prefetchData) {
|
||||
$readData = $this->prefetchData;
|
||||
$this->prefetchData = null;
|
||||
|
||||
if ($readData === $data) {
|
||||
return $this->updateTimestamp($sessionId, $data);
|
||||
}
|
||||
}
|
||||
if (null === $this->igbinaryEmptyData) {
|
||||
// see https://github.com/igbinary/igbinary/issues/146
|
||||
$this->igbinaryEmptyData = \function_exists('igbinary_serialize') ? igbinary_serialize(array()) : '';
|
||||
}
|
||||
if ('' === $data || $this->igbinaryEmptyData === $data) {
|
||||
return $this->destroy($sessionId);
|
||||
}
|
||||
$this->newSessionId = null;
|
||||
|
||||
return $this->doWrite($sessionId, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function destroy($sessionId)
|
||||
{
|
||||
if (\PHP_VERSION_ID < 70000) {
|
||||
$this->prefetchData = null;
|
||||
}
|
||||
if (!headers_sent() && ini_get('session.use_cookies')) {
|
||||
if (!$this->sessionName) {
|
||||
throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', get_class($this)));
|
||||
}
|
||||
$sessionCookie = sprintf(' %s=', urlencode($this->sessionName));
|
||||
$sessionCookieWithId = sprintf('%s%s;', $sessionCookie, urlencode($sessionId));
|
||||
$sessionCookieFound = false;
|
||||
$otherCookies = array();
|
||||
foreach (headers_list() as $h) {
|
||||
if (0 !== stripos($h, 'Set-Cookie:')) {
|
||||
continue;
|
||||
}
|
||||
if (11 === strpos($h, $sessionCookie, 11)) {
|
||||
$sessionCookieFound = true;
|
||||
|
||||
if (11 !== strpos($h, $sessionCookieWithId, 11)) {
|
||||
$otherCookies[] = $h;
|
||||
}
|
||||
} else {
|
||||
$otherCookies[] = $h;
|
||||
}
|
||||
}
|
||||
if ($sessionCookieFound) {
|
||||
header_remove('Set-Cookie');
|
||||
foreach ($otherCookies as $h) {
|
||||
header('Set-Cookie:'.$h, false);
|
||||
}
|
||||
} else {
|
||||
setcookie($this->sessionName, '', 0, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), ini_get('session.cookie_secure'), ini_get('session.cookie_httponly'));
|
||||
}
|
||||
}
|
||||
|
||||
return $this->newSessionId === $sessionId || $this->doDestroy($sessionId);
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
|
||||
*
|
||||
* @author Drak <drak@zikula.org>
|
||||
*/
|
||||
class MemcachedSessionHandler implements \SessionHandlerInterface
|
||||
class MemcachedSessionHandler extends AbstractSessionHandler
|
||||
{
|
||||
/**
|
||||
* @var \Memcached Memcached driver
|
||||
@ -39,7 +39,7 @@ class MemcachedSessionHandler implements \SessionHandlerInterface
|
||||
/**
|
||||
* List of available options:
|
||||
* * prefix: The prefix to use for the memcached keys in order to avoid collision
|
||||
* * expiretime: The time to live in seconds
|
||||
* * expiretime: The time to live in seconds.
|
||||
*
|
||||
* @param \Memcached $memcached A \Memcached instance
|
||||
* @param array $options An associative array of Memcached options
|
||||
@ -60,14 +60,6 @@ class MemcachedSessionHandler implements \SessionHandlerInterface
|
||||
$this->prefix = isset($options['prefix']) ? $options['prefix'] : 'sf2s';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function open($savePath, $sessionName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@ -79,7 +71,7 @@ class MemcachedSessionHandler implements \SessionHandlerInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function read($sessionId)
|
||||
protected function doRead($sessionId)
|
||||
{
|
||||
return $this->memcached->get($this->prefix.$sessionId) ?: '';
|
||||
}
|
||||
@ -87,7 +79,15 @@ class MemcachedSessionHandler implements \SessionHandlerInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function write($sessionId, $data)
|
||||
public function updateTimestamp($sessionId, $data)
|
||||
{
|
||||
return $this->memcached->touch($this->prefix.$sessionId, time() + $this->ttl);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doWrite($sessionId, $data)
|
||||
{
|
||||
return $this->memcached->set($this->prefix.$sessionId, $data, time() + $this->ttl);
|
||||
}
|
||||
@ -95,7 +95,7 @@ class MemcachedSessionHandler implements \SessionHandlerInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function destroy($sessionId)
|
||||
protected function doDestroy($sessionId)
|
||||
{
|
||||
$result = $this->memcached->delete($this->prefix.$sessionId);
|
||||
|
||||
|
@ -19,7 +19,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
|
||||
* @see https://packagist.org/packages/mongodb/mongodb
|
||||
* @see http://php.net/manual/en/set.mongodb.php
|
||||
*/
|
||||
class MongoDbSessionHandler implements \SessionHandlerInterface
|
||||
class MongoDbSessionHandler extends AbstractSessionHandler
|
||||
{
|
||||
/**
|
||||
* @var \Mongo|\MongoClient|\MongoDB\Client
|
||||
@ -43,7 +43,7 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
|
||||
* * id_field: The field name for storing the session id [default: _id]
|
||||
* * data_field: The field name for storing the session data [default: data]
|
||||
* * time_field: The field name for storing the timestamp [default: time]
|
||||
* * expiry_field: The field name for storing the expiry-timestamp [default: expires_at]
|
||||
* * expiry_field: The field name for storing the expiry-timestamp [default: expires_at].
|
||||
*
|
||||
* It is strongly recommended to put an index on the `expiry_field` for
|
||||
* garbage-collection. Alternatively it's possible to automatically expire
|
||||
@ -92,14 +92,6 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
|
||||
), $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function open($savePath, $sessionName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@ -111,7 +103,7 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function destroy($sessionId)
|
||||
protected function doDestroy($sessionId)
|
||||
{
|
||||
$methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteOne' : 'remove';
|
||||
|
||||
@ -139,7 +131,7 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function write($sessionId, $data)
|
||||
protected function doWrite($sessionId, $data)
|
||||
{
|
||||
$expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime'));
|
||||
|
||||
@ -171,7 +163,34 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function read($sessionId)
|
||||
public function updateTimestamp($sessionId, $data)
|
||||
{
|
||||
$expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime'));
|
||||
|
||||
if ($this->mongo instanceof \MongoDB\Client) {
|
||||
$methodName = 'updateOne';
|
||||
$options = array();
|
||||
} else {
|
||||
$methodName = 'update';
|
||||
$options = array('multiple' => false);
|
||||
}
|
||||
|
||||
$this->getCollection()->$methodName(
|
||||
array($this->options['id_field'] => $sessionId),
|
||||
array('$set' => array(
|
||||
$this->options['time_field'] => $this->createDateTime(),
|
||||
$this->options['expiry_field'] => $expiry,
|
||||
)),
|
||||
$options
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doRead($sessionId)
|
||||
{
|
||||
$dbData = $this->getCollection()->findOne(array(
|
||||
$this->options['id_field'] => $sessionId,
|
||||
|
@ -11,12 +11,14 @@
|
||||
|
||||
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
|
||||
|
||||
@trigger_error('The '.__NAMESPACE__.'\NativeSessionHandler class is deprecated since version 3.4 and will be removed in 4.0. Use the \SessionHandler class instead.', E_USER_DEPRECATED);
|
||||
|
||||
/**
|
||||
* @deprecated since version 3.4, to be removed in 4.0. Use \SessionHandler instead.
|
||||
* @see http://php.net/sessionhandler
|
||||
*/
|
||||
class NativeSessionHandler extends \SessionHandler
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
@trigger_error('The '.__NAMESPACE__.'\NativeSessionHandler class is deprecated since version 3.4 and will be removed in 4.0. Use the \SessionHandler class instead.', E_USER_DEPRECATED);
|
||||
}
|
||||
}
|
||||
|
@ -16,16 +16,8 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
|
||||
*
|
||||
* @author Drak <drak@zikula.org>
|
||||
*/
|
||||
class NullSessionHandler implements \SessionHandlerInterface
|
||||
class NullSessionHandler extends AbstractSessionHandler
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function open($savePath, $sessionName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@ -37,15 +29,7 @@ class NullSessionHandler implements \SessionHandlerInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function read($sessionId)
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function write($sessionId, $data)
|
||||
public function validateId($sessionId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -53,7 +37,31 @@ class NullSessionHandler implements \SessionHandlerInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function destroy($sessionId)
|
||||
protected function doRead($sessionId)
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function updateTimestamp($sessionId, $data)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doWrite($sessionId, $data)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDestroy($sessionId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
|
||||
* @author Michael Williams <michael.williams@funsational.com>
|
||||
* @author Tobias Schultze <http://tobion.de>
|
||||
*/
|
||||
class PdoSessionHandler implements \SessionHandlerInterface
|
||||
class PdoSessionHandler extends AbstractSessionHandler
|
||||
{
|
||||
/**
|
||||
* No locking is done. This means sessions are prone to loss of data due to
|
||||
@ -260,11 +260,13 @@ class PdoSessionHandler implements \SessionHandlerInterface
|
||||
*/
|
||||
public function open($savePath, $sessionName)
|
||||
{
|
||||
$this->sessionExpired = false;
|
||||
|
||||
if (null === $this->pdo) {
|
||||
$this->connect($this->dsn ?: $savePath);
|
||||
}
|
||||
|
||||
return true;
|
||||
return parent::open($savePath, $sessionName);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -273,7 +275,7 @@ class PdoSessionHandler implements \SessionHandlerInterface
|
||||
public function read($sessionId)
|
||||
{
|
||||
try {
|
||||
return $this->doRead($sessionId);
|
||||
return parent::read($sessionId);
|
||||
} catch (\PDOException $e) {
|
||||
$this->rollback();
|
||||
|
||||
@ -296,7 +298,7 @@ class PdoSessionHandler implements \SessionHandlerInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function destroy($sessionId)
|
||||
protected function doDestroy($sessionId)
|
||||
{
|
||||
// delete the record associated with this id
|
||||
$sql = "DELETE FROM $this->table WHERE $this->idCol = :id";
|
||||
@ -317,7 +319,7 @@ class PdoSessionHandler implements \SessionHandlerInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function write($sessionId, $data)
|
||||
protected function doWrite($sessionId, $data)
|
||||
{
|
||||
$maxlifetime = (int) ini_get('session.gc_maxlifetime');
|
||||
|
||||
@ -372,6 +374,30 @@ class PdoSessionHandler implements \SessionHandlerInterface
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function updateTimestamp($sessionId, $data)
|
||||
{
|
||||
$maxlifetime = (int) ini_get('session.gc_maxlifetime');
|
||||
|
||||
try {
|
||||
$updateStmt = $this->pdo->prepare(
|
||||
"UPDATE $this->table SET $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id"
|
||||
);
|
||||
$updateStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
|
||||
$updateStmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT);
|
||||
$updateStmt->bindValue(':time', time(), \PDO::PARAM_INT);
|
||||
$updateStmt->execute();
|
||||
} catch (\PDOException $e) {
|
||||
$this->rollback();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@ -491,10 +517,8 @@ class PdoSessionHandler implements \SessionHandlerInterface
|
||||
*
|
||||
* @return string The session data
|
||||
*/
|
||||
private function doRead($sessionId)
|
||||
protected function doRead($sessionId)
|
||||
{
|
||||
$this->sessionExpired = false;
|
||||
|
||||
if (self::LOCK_ADVISORY === $this->lockMode) {
|
||||
$this->unlockStatements[] = $this->doAdvisoryLock($sessionId);
|
||||
}
|
||||
@ -517,7 +541,9 @@ class PdoSessionHandler implements \SessionHandlerInterface
|
||||
return is_resource($sessionRows[0][0]) ? stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0];
|
||||
}
|
||||
|
||||
if (self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) {
|
||||
if (!ini_get('session.use_strict_mode') && self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) {
|
||||
// In strict mode, session fixation is not possible: new sessions always start with a unique
|
||||
// random id, so that concurrency is not possible and this code path can be skipped.
|
||||
// Exclusive-reading of non-existent rows does not block, so we need to do an insert to block
|
||||
// until other connections to the session are committed.
|
||||
try {
|
||||
|
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
|
||||
|
||||
/**
|
||||
* Adds basic `SessionUpdateTimestampHandlerInterface` behaviors to another `SessionHandlerInterface`.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class StrictSessionHandler extends AbstractSessionHandler
|
||||
{
|
||||
private $handler;
|
||||
|
||||
public function __construct(\SessionHandlerInterface $handler)
|
||||
{
|
||||
if ($handler instanceof \SessionUpdateTimestampHandlerInterface) {
|
||||
throw new \LogicException(sprintf('"%s" is already an instance of "SessionUpdateTimestampHandlerInterface", you cannot wrap it with "%s".', get_class($handler), self::class));
|
||||
}
|
||||
|
||||
$this->handler = $handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function open($savePath, $sessionName)
|
||||
{
|
||||
parent::open($savePath, $sessionName);
|
||||
|
||||
return $this->handler->open($savePath, $sessionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doRead($sessionId)
|
||||
{
|
||||
return $this->handler->read($sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function updateTimestamp($sessionId, $data)
|
||||
{
|
||||
return $this->write($sessionId, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doWrite($sessionId, $data)
|
||||
{
|
||||
return $this->handler->write($sessionId, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDestroy($sessionId)
|
||||
{
|
||||
return $this->handler->destroy($sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
return $this->handler->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function gc($maxlifetime)
|
||||
{
|
||||
return $this->handler->gc($maxlifetime);
|
||||
}
|
||||
}
|
@ -11,10 +11,14 @@
|
||||
|
||||
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
|
||||
|
||||
@trigger_error(sprintf('The %s class is deprecated since version 3.4 and will be removed in 4.0. Implement `SessionUpdateTimestampHandlerInterface` or extend `AbstractSessionHandler` instead.', WriteCheckSessionHandler::class), E_USER_DEPRECATED);
|
||||
|
||||
/**
|
||||
* Wraps another SessionHandlerInterface to only write the session when it has been modified.
|
||||
*
|
||||
* @author Adrien Brault <adrien.brault@gmail.com>
|
||||
*
|
||||
* @deprecated since version 3.4, to be removed in 4.0. Implement `SessionUpdateTimestampHandlerInterface` or extend `AbstractSessionHandler` instead.
|
||||
*/
|
||||
class WriteCheckSessionHandler implements \SessionHandlerInterface
|
||||
{
|
||||
|
@ -11,8 +11,8 @@
|
||||
|
||||
namespace Symfony\Component\HttpFoundation\Session\Storage;
|
||||
|
||||
use Symfony\Component\Debug\Exception\ContextErrorException;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
|
||||
use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler;
|
||||
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
|
||||
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
|
||||
|
||||
@ -26,7 +26,7 @@ class NativeSessionStorage implements SessionStorageInterface
|
||||
/**
|
||||
* @var SessionBagInterface[]
|
||||
*/
|
||||
protected $bags;
|
||||
protected $bags = array();
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
@ -100,9 +100,9 @@ class NativeSessionStorage implements SessionStorageInterface
|
||||
public function __construct(array $options = array(), $handler = null, MetadataBag $metaBag = null)
|
||||
{
|
||||
$options += array(
|
||||
// disable by default because it's managed by HeaderBag (if used)
|
||||
'cache_limiter' => '',
|
||||
'cache_limiter' => 'private_no_expire',
|
||||
'use_cookies' => 1,
|
||||
'lazy_write' => 1,
|
||||
);
|
||||
|
||||
session_register_shutdown();
|
||||
@ -217,15 +217,31 @@ class NativeSessionStorage implements SessionStorageInterface
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$session = $_SESSION;
|
||||
|
||||
foreach ($this->bags as $bag) {
|
||||
if (empty($_SESSION[$key = $bag->getStorageKey()])) {
|
||||
unset($_SESSION[$key]);
|
||||
}
|
||||
}
|
||||
if (array($key = $this->metadataBag->getStorageKey()) === array_keys($_SESSION)) {
|
||||
unset($_SESSION[$key]);
|
||||
}
|
||||
|
||||
// Register custom error handler to catch a possible failure warning during session write
|
||||
set_error_handler(function ($errno, $errstr, $errfile, $errline, $errcontext) {
|
||||
throw new ContextErrorException($errstr, $errno, E_WARNING, $errfile, $errline, $errcontext);
|
||||
set_error_handler(function ($errno, $errstr, $errfile, $errline) {
|
||||
throw new \ErrorException($errstr, $errno, E_WARNING, $errfile, $errline);
|
||||
}, E_WARNING);
|
||||
|
||||
try {
|
||||
$e = null;
|
||||
session_write_close();
|
||||
} catch (\ErrorException $e) {
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
} catch (ContextErrorException $e) {
|
||||
$_SESSION = $session;
|
||||
}
|
||||
if (null !== $e) {
|
||||
// The default PHP error message is not very helpful, as it does not give any information on the current save handler.
|
||||
// Therefore, we catch this error and trigger a warning with a better error message
|
||||
$handler = $this->getSaveHandler();
|
||||
@ -233,7 +249,6 @@ class NativeSessionStorage implements SessionStorageInterface
|
||||
$handler = $handler->getHandler();
|
||||
}
|
||||
|
||||
restore_error_handler();
|
||||
trigger_error(sprintf('session_write_close(): Failed to write session data with %s handler', get_class($handler)), E_USER_WARNING);
|
||||
}
|
||||
|
||||
@ -386,13 +401,6 @@ class NativeSessionStorage implements SessionStorageInterface
|
||||
throw new \InvalidArgumentException('Must be instance of AbstractProxy; implement \SessionHandlerInterface; or be null.');
|
||||
}
|
||||
|
||||
if ($saveHandler instanceof AbstractProxy) {
|
||||
@trigger_error(
|
||||
'Using session save handlers that are instances of AbstractProxy is deprecated since version 3.4 and will be removed in 4.0.',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
}
|
||||
|
||||
if (headers_sent($file, $line)) {
|
||||
throw new \RuntimeException(sprintf('Failed to set the session handler because headers have already been sent by "%s" at line %d.', $file, $line));
|
||||
}
|
||||
@ -401,11 +409,13 @@ class NativeSessionStorage implements SessionStorageInterface
|
||||
if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) {
|
||||
$saveHandler = new SessionHandlerProxy($saveHandler);
|
||||
} elseif (!$saveHandler instanceof AbstractProxy) {
|
||||
$saveHandler = new SessionHandlerProxy(new \SessionHandler());
|
||||
$saveHandler = new SessionHandlerProxy(new StrictSessionHandler(new \SessionHandler()));
|
||||
}
|
||||
$this->saveHandler = $saveHandler;
|
||||
|
||||
if ($this->saveHandler instanceof \SessionHandlerInterface) {
|
||||
if ($this->saveHandler instanceof SessionHandlerProxy) {
|
||||
session_set_save_handler($this->saveHandler->getHandler(), false);
|
||||
} elseif ($this->saveHandler instanceof \SessionHandlerInterface) {
|
||||
session_set_save_handler($this->saveHandler, false);
|
||||
}
|
||||
}
|
||||
|
@ -11,11 +11,7 @@
|
||||
|
||||
namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy;
|
||||
|
||||
@trigger_error('The '.__NAMESPACE__.'\AbstractProxy class is deprecated since version 3.4 and will be removed in 4.0. Use your session handler implementation directly.', E_USER_DEPRECATED);
|
||||
|
||||
/**
|
||||
* @deprecated since version 3.4, to be removed in 4.0. Use your session handler implementation directly.
|
||||
*
|
||||
* @author Drak <drak@zikula.org>
|
||||
*/
|
||||
abstract class AbstractProxy
|
||||
|
@ -11,11 +11,7 @@
|
||||
|
||||
namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy;
|
||||
|
||||
@trigger_error('The '.__NAMESPACE__.'\SessionHandlerProxy class is deprecated since version 3.4 and will be removed in 4.0. Use your session handler implementation directly.', E_USER_DEPRECATED);
|
||||
|
||||
/**
|
||||
* @deprecated since version 3.4, to be removed in 4.0. Use your session handler implementation directly.
|
||||
*
|
||||
* @author Drak <drak@zikula.org>
|
||||
*/
|
||||
class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterface
|
||||
|
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @requires PHP 7.0
|
||||
*/
|
||||
class AbstractSessionHandlerTest extends TestCase
|
||||
{
|
||||
private static $server;
|
||||
|
||||
public static function setUpBeforeClass()
|
||||
{
|
||||
$spec = array(
|
||||
1 => array('file', '/dev/null', 'w'),
|
||||
2 => array('file', '/dev/null', 'w'),
|
||||
);
|
||||
if (!self::$server = @proc_open('exec php -S localhost:8053', $spec, $pipes, __DIR__.'/Fixtures')) {
|
||||
self::markTestSkipped('PHP server unable to start.');
|
||||
}
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass()
|
||||
{
|
||||
if (self::$server) {
|
||||
proc_terminate(self::$server);
|
||||
proc_close(self::$server);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideSession
|
||||
*/
|
||||
public function testSession($fixture)
|
||||
{
|
||||
$context = array('http' => array('header' => "Cookie: sid=123abc\r\n"));
|
||||
$context = stream_context_create($context);
|
||||
$result = file_get_contents(sprintf('http://localhost:8053/%s.php', $fixture), false, $context);
|
||||
|
||||
$this->assertStringEqualsFile(__DIR__.sprintf('/Fixtures/%s.expected', $fixture), $result);
|
||||
}
|
||||
|
||||
public function provideSession()
|
||||
{
|
||||
foreach (glob(__DIR__.'/Fixtures/*.php') as $file) {
|
||||
yield array(pathinfo($file, PATHINFO_FILENAME));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
use Symfony\Component\HttpFoundation\Session\Storage\Handler\AbstractSessionHandler;
|
||||
|
||||
$parent = __DIR__;
|
||||
while (!@file_exists($parent.'/vendor/autoload.php')) {
|
||||
if (!@file_exists($parent)) {
|
||||
// open_basedir restriction in effect
|
||||
break;
|
||||
}
|
||||
if ($parent === dirname($parent)) {
|
||||
echo "vendor/autoload.php not found\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$parent = dirname($parent);
|
||||
}
|
||||
|
||||
require $parent.'/vendor/autoload.php';
|
||||
|
||||
error_reporting(-1);
|
||||
ini_set('html_errors', 0);
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('session.gc_probability', 0);
|
||||
ini_set('session.serialize_handler', 'php');
|
||||
ini_set('session.cookie_lifetime', 0);
|
||||
ini_set('session.cookie_domain', '');
|
||||
ini_set('session.cookie_secure', '');
|
||||
ini_set('session.cookie_httponly', '');
|
||||
ini_set('session.use_cookies', 1);
|
||||
ini_set('session.use_only_cookies', 1);
|
||||
ini_set('session.cache_expire', 180);
|
||||
ini_set('session.cookie_path', '/');
|
||||
ini_set('session.cookie_domain', '');
|
||||
ini_set('session.cookie_secure', 1);
|
||||
ini_set('session.cookie_httponly', 1);
|
||||
ini_set('session.use_strict_mode', 1);
|
||||
ini_set('session.lazy_write', 1);
|
||||
ini_set('session.name', 'sid');
|
||||
ini_set('session.save_path', __DIR__);
|
||||
ini_set('session.cache_limiter', 'private_no_expire');
|
||||
|
||||
header_remove('X-Powered-By');
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
|
||||
register_shutdown_function(function () {
|
||||
echo "\n";
|
||||
header_remove('Last-Modified');
|
||||
session_write_close();
|
||||
print_r(headers_list());
|
||||
echo "shutdown\n";
|
||||
});
|
||||
ob_start();
|
||||
|
||||
class TestSessionHandler extends AbstractSessionHandler
|
||||
{
|
||||
private $data;
|
||||
|
||||
public function __construct($data = '')
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function open($path, $name)
|
||||
{
|
||||
echo __FUNCTION__, "\n";
|
||||
|
||||
return parent::open($path, $name);
|
||||
}
|
||||
|
||||
public function validateId($sessionId)
|
||||
{
|
||||
echo __FUNCTION__, "\n";
|
||||
|
||||
return parent::validateId($sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function read($sessionId)
|
||||
{
|
||||
echo __FUNCTION__, "\n";
|
||||
|
||||
return parent::read($sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function updateTimestamp($sessionId, $data)
|
||||
{
|
||||
echo __FUNCTION__, "\n";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function write($sessionId, $data)
|
||||
{
|
||||
echo __FUNCTION__, "\n";
|
||||
|
||||
return parent::write($sessionId, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function destroy($sessionId)
|
||||
{
|
||||
echo __FUNCTION__, "\n";
|
||||
|
||||
return parent::destroy($sessionId);
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
echo __FUNCTION__, "\n";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function gc($maxLifetime)
|
||||
{
|
||||
echo __FUNCTION__, "\n";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function doRead($sessionId)
|
||||
{
|
||||
echo __FUNCTION__.': ', $this->data, "\n";
|
||||
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
protected function doWrite($sessionId, $data)
|
||||
{
|
||||
echo __FUNCTION__.': ', $data, "\n";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function doDestroy($sessionId)
|
||||
{
|
||||
echo __FUNCTION__, "\n";
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
open
|
||||
validateId
|
||||
read
|
||||
doRead: abc|i:123;
|
||||
read
|
||||
|
||||
write
|
||||
destroy
|
||||
doDestroy
|
||||
close
|
||||
Array
|
||||
(
|
||||
[0] => Content-Type: text/plain; charset=utf-8
|
||||
[1] => Cache-Control: private, max-age=10800
|
||||
[2] => Set-Cookie: sid=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0; path=/; secure; HttpOnly
|
||||
)
|
||||
shutdown
|
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
require __DIR__.'/common.inc';
|
||||
|
||||
session_set_save_handler(new TestSessionHandler('abc|i:123;'), false);
|
||||
session_start();
|
||||
|
||||
unset($_SESSION['abc']);
|
@ -0,0 +1,14 @@
|
||||
open
|
||||
validateId
|
||||
read
|
||||
doRead: abc|i:123;
|
||||
read
|
||||
123
|
||||
updateTimestamp
|
||||
close
|
||||
Array
|
||||
(
|
||||
[0] => Content-Type: text/plain; charset=utf-8
|
||||
[1] => Cache-Control: private, max-age=10800
|
||||
)
|
||||
shutdown
|
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
require __DIR__.'/common.inc';
|
||||
|
||||
session_set_save_handler(new TestSessionHandler('abc|i:123;'), false);
|
||||
session_start();
|
||||
|
||||
echo $_SESSION['abc'];
|
@ -0,0 +1,24 @@
|
||||
open
|
||||
validateId
|
||||
read
|
||||
doRead: abc|i:123;
|
||||
read
|
||||
destroy
|
||||
doDestroy
|
||||
close
|
||||
open
|
||||
validateId
|
||||
read
|
||||
doRead: abc|i:123;
|
||||
read
|
||||
|
||||
write
|
||||
doWrite: abc|i:123;
|
||||
close
|
||||
Array
|
||||
(
|
||||
[0] => Content-Type: text/plain; charset=utf-8
|
||||
[1] => Cache-Control: private, max-age=10800
|
||||
[2] => Set-Cookie: sid=random_session_id; path=/; secure; HttpOnly
|
||||
)
|
||||
shutdown
|
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
require __DIR__.'/common.inc';
|
||||
|
||||
session_set_save_handler(new TestSessionHandler('abc|i:123;'), false);
|
||||
session_start();
|
||||
|
||||
session_regenerate_id(true);
|
||||
|
||||
ob_start(function ($buffer) { return str_replace(session_id(), 'random_session_id', $buffer); });
|
@ -0,0 +1,20 @@
|
||||
open
|
||||
validateId
|
||||
read
|
||||
doRead:
|
||||
read
|
||||
Array
|
||||
(
|
||||
[0] => bar
|
||||
)
|
||||
$_SESSION is not empty
|
||||
write
|
||||
destroy
|
||||
close
|
||||
$_SESSION is not empty
|
||||
Array
|
||||
(
|
||||
[0] => Content-Type: text/plain; charset=utf-8
|
||||
[1] => Cache-Control: private, max-age=10800
|
||||
)
|
||||
shutdown
|
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
require __DIR__.'/common.inc';
|
||||
|
||||
use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
|
||||
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
|
||||
|
||||
$storage = new NativeSessionStorage();
|
||||
$storage->setSaveHandler(new TestSessionHandler());
|
||||
$flash = new FlashBag();
|
||||
$storage->registerBag($flash);
|
||||
$storage->start();
|
||||
|
||||
$flash->add('foo', 'bar');
|
||||
|
||||
print_r($flash->get('foo'));
|
||||
echo empty($_SESSION) ? '$_SESSION is empty' : '$_SESSION is not empty';
|
||||
echo "\n";
|
||||
|
||||
$storage->save();
|
||||
|
||||
echo empty($_SESSION) ? '$_SESSION is empty' : '$_SESSION is not empty';
|
||||
|
||||
ob_start(function ($buffer) { return str_replace(session_id(), 'random_session_id', $buffer); });
|
@ -0,0 +1,15 @@
|
||||
open
|
||||
validateId
|
||||
read
|
||||
doRead: abc|i:123;
|
||||
read
|
||||
|
||||
updateTimestamp
|
||||
close
|
||||
Array
|
||||
(
|
||||
[0] => Content-Type: text/plain; charset=utf-8
|
||||
[1] => Cache-Control: private, max-age=10800
|
||||
[2] => Set-Cookie: abc=def
|
||||
)
|
||||
shutdown
|
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
require __DIR__.'/common.inc';
|
||||
|
||||
session_set_save_handler(new TestSessionHandler('abc|i:123;'), false);
|
||||
session_start();
|
||||
|
||||
setcookie('abc', 'def');
|
@ -25,6 +25,9 @@ use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandle
|
||||
*/
|
||||
class NativeSessionHandlerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @expectedDeprecation The Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler class is deprecated since version 3.4 and will be removed in 4.0. Use the \SessionHandler class instead.
|
||||
*/
|
||||
public function testConstruct()
|
||||
{
|
||||
$handler = new NativeSessionHandler();
|
||||
|
@ -160,6 +160,9 @@ class PdoSessionHandlerTest extends TestCase
|
||||
if (defined('HHVM_VERSION')) {
|
||||
$this->markTestSkipped('PHPUnit_MockObject cannot mock the PDOStatement class on HHVM. See https://github.com/sebastianbergmann/phpunit-mock-objects/pull/289');
|
||||
}
|
||||
if (ini_get('session.use_strict_mode')) {
|
||||
$this->markTestSkipped('Strict mode needs no locking for new sessions.');
|
||||
}
|
||||
|
||||
$pdo = new MockPdo('pgsql');
|
||||
$selectStmt = $this->getMockBuilder('PDOStatement')->getMock();
|
||||
|
@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Session\Storage\Handler\AbstractSessionHandler;
|
||||
use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler;
|
||||
|
||||
class StrictSessionHandlerTest extends TestCase
|
||||
{
|
||||
public function testOpen()
|
||||
{
|
||||
$handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
|
||||
$handler->expects($this->once())->method('open')
|
||||
->with('path', 'name')->willReturn(true);
|
||||
$proxy = new StrictSessionHandler($handler);
|
||||
|
||||
$this->assertInstanceof('SessionUpdateTimestampHandlerInterface', $proxy);
|
||||
$this->assertInstanceof(AbstractSessionHandler::class, $proxy);
|
||||
$this->assertTrue($proxy->open('path', 'name'));
|
||||
}
|
||||
|
||||
public function testCloseSession()
|
||||
{
|
||||
$handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
|
||||
$handler->expects($this->once())->method('close')
|
||||
->willReturn(true);
|
||||
$proxy = new StrictSessionHandler($handler);
|
||||
|
||||
$this->assertTrue($proxy->close());
|
||||
}
|
||||
|
||||
public function testValidateIdOK()
|
||||
{
|
||||
$handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
|
||||
$handler->expects($this->once())->method('read')
|
||||
->with('id')->willReturn('data');
|
||||
$proxy = new StrictSessionHandler($handler);
|
||||
|
||||
$this->assertTrue($proxy->validateId('id'));
|
||||
}
|
||||
|
||||
public function testValidateIdKO()
|
||||
{
|
||||
$handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
|
||||
$handler->expects($this->once())->method('read')
|
||||
->with('id')->willReturn('');
|
||||
$proxy = new StrictSessionHandler($handler);
|
||||
|
||||
$this->assertFalse($proxy->validateId('id'));
|
||||
}
|
||||
|
||||
public function testRead()
|
||||
{
|
||||
$handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
|
||||
$handler->expects($this->once())->method('read')
|
||||
->with('id')->willReturn('data');
|
||||
$proxy = new StrictSessionHandler($handler);
|
||||
|
||||
$this->assertSame('data', $proxy->read('id'));
|
||||
}
|
||||
|
||||
public function testReadWithValidateIdOK()
|
||||
{
|
||||
$handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
|
||||
$handler->expects($this->once())->method('read')
|
||||
->with('id')->willReturn('data');
|
||||
$proxy = new StrictSessionHandler($handler);
|
||||
|
||||
$this->assertTrue($proxy->validateId('id'));
|
||||
$this->assertSame('data', $proxy->read('id'));
|
||||
}
|
||||
|
||||
public function testReadWithValidateIdMismatch()
|
||||
{
|
||||
$handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
|
||||
$handler->expects($this->exactly(2))->method('read')
|
||||
->withConsecutive(array('id1'), array('id2'))
|
||||
->will($this->onConsecutiveCalls('data1', 'data2'));
|
||||
$proxy = new StrictSessionHandler($handler);
|
||||
|
||||
$this->assertTrue($proxy->validateId('id1'));
|
||||
$this->assertSame('data2', $proxy->read('id2'));
|
||||
}
|
||||
|
||||
public function testUpdateTimestamp()
|
||||
{
|
||||
$handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
|
||||
$handler->expects($this->once())->method('write')
|
||||
->with('id', 'data')->willReturn(true);
|
||||
$proxy = new StrictSessionHandler($handler);
|
||||
|
||||
$this->assertTrue($proxy->updateTimestamp('id', 'data'));
|
||||
}
|
||||
|
||||
public function testWrite()
|
||||
{
|
||||
$handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
|
||||
$handler->expects($this->once())->method('write')
|
||||
->with('id', 'data')->willReturn(true);
|
||||
$proxy = new StrictSessionHandler($handler);
|
||||
|
||||
$this->assertTrue($proxy->write('id', 'data'));
|
||||
}
|
||||
|
||||
public function testWriteEmptyNewSession()
|
||||
{
|
||||
$handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
|
||||
$handler->expects($this->once())->method('read')
|
||||
->with('id')->willReturn('');
|
||||
$handler->expects($this->never())->method('write');
|
||||
$handler->expects($this->never())->method('destroy');
|
||||
$proxy = new StrictSessionHandler($handler);
|
||||
|
||||
$this->assertFalse($proxy->validateId('id'));
|
||||
$this->assertSame('', $proxy->read('id'));
|
||||
$this->assertTrue($proxy->write('id', ''));
|
||||
}
|
||||
|
||||
public function testWriteEmptyExistingSession()
|
||||
{
|
||||
$handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
|
||||
$handler->expects($this->once())->method('read')
|
||||
->with('id')->willReturn('data');
|
||||
$handler->expects($this->never())->method('write');
|
||||
$handler->expects($this->once())->method('destroy')->willReturn(true);
|
||||
$proxy = new StrictSessionHandler($handler);
|
||||
|
||||
$this->assertSame('data', $proxy->read('id'));
|
||||
$this->assertTrue($proxy->write('id', ''));
|
||||
}
|
||||
|
||||
public function testDestroy()
|
||||
{
|
||||
$handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
|
||||
$handler->expects($this->once())->method('destroy')
|
||||
->with('id')->willReturn(true);
|
||||
$proxy = new StrictSessionHandler($handler);
|
||||
|
||||
$this->assertTrue($proxy->destroy('id'));
|
||||
}
|
||||
|
||||
public function testDestroyNewSession()
|
||||
{
|
||||
$handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
|
||||
$handler->expects($this->once())->method('read')
|
||||
->with('id')->willReturn('');
|
||||
$handler->expects($this->never())->method('destroy');
|
||||
$proxy = new StrictSessionHandler($handler);
|
||||
|
||||
$this->assertSame('', $proxy->read('id'));
|
||||
$this->assertTrue($proxy->destroy('id'));
|
||||
}
|
||||
|
||||
public function testDestroyNonEmptyNewSession()
|
||||
{
|
||||
$handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
|
||||
$handler->expects($this->once())->method('read')
|
||||
->with('id')->willReturn('');
|
||||
$handler->expects($this->once())->method('write')
|
||||
->with('id', 'data')->willReturn(true);
|
||||
$handler->expects($this->once())->method('destroy')
|
||||
->with('id')->willReturn(true);
|
||||
$proxy = new StrictSessionHandler($handler);
|
||||
|
||||
$this->assertSame('', $proxy->read('id'));
|
||||
$this->assertTrue($proxy->write('id', 'data'));
|
||||
$this->assertTrue($proxy->destroy('id'));
|
||||
}
|
||||
|
||||
public function testGc()
|
||||
{
|
||||
$handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
|
||||
$handler->expects($this->once())->method('gc')
|
||||
->with(123)->willReturn(true);
|
||||
$proxy = new StrictSessionHandler($handler);
|
||||
|
||||
$this->assertTrue($proxy->gc(123));
|
||||
}
|
||||
}
|
@ -16,6 +16,8 @@ use Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHa
|
||||
|
||||
/**
|
||||
* @author Adrien Brault <adrien.brault@gmail.com>
|
||||
*
|
||||
* @group legacy
|
||||
*/
|
||||
class WriteCheckSessionHandlerTest extends TestCase
|
||||
{
|
||||
|
@ -14,7 +14,7 @@ namespace Symfony\Component\HttpFoundation\Tests\Session\Storage;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
|
||||
use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
|
||||
use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler;
|
||||
use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler;
|
||||
use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler;
|
||||
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
|
||||
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
|
||||
@ -152,7 +152,7 @@ class NativeSessionStorageTest extends TestCase
|
||||
$this->iniSet('session.cache_limiter', 'nocache');
|
||||
|
||||
$storage = new NativeSessionStorage();
|
||||
$this->assertEquals('', ini_get('session.cache_limiter'));
|
||||
$this->assertEquals('private_no_expire', ini_get('session.cache_limiter'));
|
||||
}
|
||||
|
||||
public function testExplicitSessionCacheLimiter()
|
||||
@ -201,9 +201,9 @@ class NativeSessionStorageTest extends TestCase
|
||||
$this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
|
||||
$storage->setSaveHandler(null);
|
||||
$this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
|
||||
$storage->setSaveHandler(new SessionHandlerProxy(new NativeSessionHandler()));
|
||||
$storage->setSaveHandler(new SessionHandlerProxy(new NativeFileSessionHandler()));
|
||||
$this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
|
||||
$storage->setSaveHandler(new NativeSessionHandler());
|
||||
$storage->setSaveHandler(new NativeFileSessionHandler());
|
||||
$this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
|
||||
$storage->setSaveHandler(new SessionHandlerProxy(new NullSessionHandler()));
|
||||
$this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
|
||||
|
@ -18,8 +18,6 @@ use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
|
||||
/**
|
||||
* Test class for AbstractProxy.
|
||||
*
|
||||
* @group legacy
|
||||
*
|
||||
* @author Drak <drak@zikula.org>
|
||||
*/
|
||||
class AbstractProxyTest extends TestCase
|
||||
|
@ -21,7 +21,6 @@ use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
|
||||
*
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @group legacy
|
||||
*/
|
||||
class SessionHandlerProxyTest extends TestCase
|
||||
{
|
||||
|
@ -17,7 +17,8 @@
|
||||
],
|
||||
"require": {
|
||||
"php": "^5.5.9|>=7.0.8",
|
||||
"symfony/polyfill-mbstring": "~1.1"
|
||||
"symfony/polyfill-mbstring": "~1.1",
|
||||
"symfony/polyfill-php70": "~1.6"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/expression-language": "~2.8|~3.0|~4.0"
|
||||
|
@ -97,12 +97,18 @@ class NativeSessionTokenStorage implements TokenStorageInterface
|
||||
$this->startSession();
|
||||
}
|
||||
|
||||
$token = isset($_SESSION[$this->namespace][$tokenId])
|
||||
? (string) $_SESSION[$this->namespace][$tokenId]
|
||||
: null;
|
||||
if (!isset($_SESSION[$this->namespace][$tokenId])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$token = (string) $_SESSION[$this->namespace][$tokenId];
|
||||
|
||||
unset($_SESSION[$this->namespace][$tokenId]);
|
||||
|
||||
if (!$_SESSION[$this->namespace]) {
|
||||
unset($_SESSION[$this->namespace]);
|
||||
}
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user