[HttpFoundation][FrameworkBundle] fix support for samesite in session cookies
This commit is contained in:
parent
adacae6f54
commit
f46e6cb8a0
@ -20,6 +20,7 @@ use Symfony\Component\Config\Definition\Builder\TreeBuilder;
|
|||||||
use Symfony\Component\Config\Definition\ConfigurationInterface;
|
use Symfony\Component\Config\Definition\ConfigurationInterface;
|
||||||
use Symfony\Component\DependencyInjection\Exception\LogicException;
|
use Symfony\Component\DependencyInjection\Exception\LogicException;
|
||||||
use Symfony\Component\Form\Form;
|
use Symfony\Component\Form\Form;
|
||||||
|
use Symfony\Component\HttpFoundation\Cookie;
|
||||||
use Symfony\Component\Lock\Lock;
|
use Symfony\Component\Lock\Lock;
|
||||||
use Symfony\Component\Lock\Store\SemaphoreStore;
|
use Symfony\Component\Lock\Store\SemaphoreStore;
|
||||||
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
|
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
|
||||||
@ -490,6 +491,7 @@ class Configuration implements ConfigurationInterface
|
|||||||
->scalarNode('cookie_domain')->end()
|
->scalarNode('cookie_domain')->end()
|
||||||
->booleanNode('cookie_secure')->end()
|
->booleanNode('cookie_secure')->end()
|
||||||
->booleanNode('cookie_httponly')->defaultTrue()->end()
|
->booleanNode('cookie_httponly')->defaultTrue()->end()
|
||||||
|
->enumNode('cookie_samesite')->values([null, Cookie::SAMESITE_LAX, Cookie::SAMESITE_STRICT])->defaultNull()->end()
|
||||||
->booleanNode('use_cookies')->end()
|
->booleanNode('use_cookies')->end()
|
||||||
->scalarNode('gc_divisor')->end()
|
->scalarNode('gc_divisor')->end()
|
||||||
->scalarNode('gc_probability')->defaultValue(1)->end()
|
->scalarNode('gc_probability')->defaultValue(1)->end()
|
||||||
|
@ -867,7 +867,7 @@ class FrameworkExtension extends Extension
|
|||||||
// session storage
|
// session storage
|
||||||
$container->setAlias('session.storage', $config['storage_id'])->setPrivate(true);
|
$container->setAlias('session.storage', $config['storage_id'])->setPrivate(true);
|
||||||
$options = ['cache_limiter' => '0'];
|
$options = ['cache_limiter' => '0'];
|
||||||
foreach (['name', 'cookie_lifetime', 'cookie_path', 'cookie_domain', 'cookie_secure', 'cookie_httponly', 'use_cookies', 'gc_maxlifetime', 'gc_probability', 'gc_divisor', 'use_strict_mode'] as $key) {
|
foreach (['name', 'cookie_lifetime', 'cookie_path', 'cookie_domain', 'cookie_secure', 'cookie_httponly', 'cookie_samesite', 'use_cookies', 'gc_maxlifetime', 'gc_probability', 'gc_divisor'] as $key) {
|
||||||
if (isset($config[$key])) {
|
if (isset($config[$key])) {
|
||||||
$options[$key] = $config[$key];
|
$options[$key] = $config[$key];
|
||||||
}
|
}
|
||||||
|
@ -436,6 +436,7 @@ class ConfigurationTest extends TestCase
|
|||||||
'storage_id' => 'session.storage.native',
|
'storage_id' => 'session.storage.native',
|
||||||
'handler_id' => 'session.handler.native_file',
|
'handler_id' => 'session.handler.native_file',
|
||||||
'cookie_httponly' => true,
|
'cookie_httponly' => true,
|
||||||
|
'cookie_samesite' => null,
|
||||||
'gc_probability' => 1,
|
'gc_probability' => 1,
|
||||||
'save_path' => '%kernel.cache_dir%/sessions',
|
'save_path' => '%kernel.cache_dir%/sessions',
|
||||||
'metadata_update_threshold' => '0',
|
'metadata_update_threshold' => '0',
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
<?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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session utility functions.
|
||||||
|
*
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
|
* @author Rémon van de Kamp <rpkamp@gmail.com>
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class SessionUtils
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Find the session header amongst the headers that are to be sent, remove it, and return
|
||||||
|
* it so the caller can process it further.
|
||||||
|
*/
|
||||||
|
public static function popSessionCookie($sessionName, $sessionId)
|
||||||
|
{
|
||||||
|
$sessionCookie = null;
|
||||||
|
$sessionCookiePrefix = sprintf(' %s=', urlencode($sessionName));
|
||||||
|
$sessionCookieWithId = sprintf('%s%s;', $sessionCookiePrefix, urlencode($sessionId));
|
||||||
|
$otherCookies = [];
|
||||||
|
foreach (headers_list() as $h) {
|
||||||
|
if (0 !== stripos($h, 'Set-Cookie:')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (11 === strpos($h, $sessionCookiePrefix, 11)) {
|
||||||
|
$sessionCookie = $h;
|
||||||
|
|
||||||
|
if (11 !== strpos($h, $sessionCookieWithId, 11)) {
|
||||||
|
$otherCookies[] = $h;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$otherCookies[] = $h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (null === $sessionCookie) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
header_remove('Set-Cookie');
|
||||||
|
foreach ($otherCookies as $h) {
|
||||||
|
header($h, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sessionCookie;
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,8 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
|
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Session\SessionUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This abstract session handler provides a generic implementation
|
* This abstract session handler provides a generic implementation
|
||||||
* of the PHP 7.0 SessionUpdateTimestampHandlerInterface,
|
* of the PHP 7.0 SessionUpdateTimestampHandlerInterface,
|
||||||
@ -27,7 +29,7 @@ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \Sess
|
|||||||
private $igbinaryEmptyData;
|
private $igbinaryEmptyData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function open($savePath, $sessionName)
|
public function open($savePath, $sessionName)
|
||||||
{
|
{
|
||||||
@ -62,7 +64,7 @@ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \Sess
|
|||||||
abstract protected function doDestroy($sessionId);
|
abstract protected function doDestroy($sessionId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function validateId($sessionId)
|
public function validateId($sessionId)
|
||||||
{
|
{
|
||||||
@ -73,7 +75,7 @@ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \Sess
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function read($sessionId)
|
public function read($sessionId)
|
||||||
{
|
{
|
||||||
@ -99,7 +101,7 @@ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \Sess
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function write($sessionId, $data)
|
public function write($sessionId, $data)
|
||||||
{
|
{
|
||||||
@ -124,7 +126,7 @@ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \Sess
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function destroy($sessionId)
|
public function destroy($sessionId)
|
||||||
{
|
{
|
||||||
@ -135,32 +137,24 @@ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \Sess
|
|||||||
if (!$this->sessionName) {
|
if (!$this->sessionName) {
|
||||||
throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', static::class));
|
throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', static::class));
|
||||||
}
|
}
|
||||||
$sessionCookie = sprintf(' %s=', urlencode($this->sessionName));
|
$cookie = SessionUtils::popSessionCookie($this->sessionName, $sessionId);
|
||||||
$sessionCookieWithId = sprintf('%s%s;', $sessionCookie, urlencode($sessionId));
|
|
||||||
$sessionCookieFound = false;
|
|
||||||
$otherCookies = [];
|
|
||||||
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;
|
* We send an invalidation Set-Cookie header (zero lifetime)
|
||||||
}
|
* when either the session was started or a cookie with
|
||||||
|
* the session name was sent by the client (in which case
|
||||||
|
* we know it's invalid as a valid session cookie would've
|
||||||
|
* started the session).
|
||||||
|
*/
|
||||||
|
if (null === $cookie || isset($_COOKIE[$this->sessionName])) {
|
||||||
|
if (\PHP_VERSION_ID < 70300) {
|
||||||
|
setcookie($this->sessionName, '', 0, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), filter_var(ini_get('session.cookie_secure'), FILTER_VALIDATE_BOOLEAN), filter_var(ini_get('session.cookie_httponly'), FILTER_VALIDATE_BOOLEAN));
|
||||||
} else {
|
} else {
|
||||||
$otherCookies[] = $h;
|
$params = session_get_cookie_params();
|
||||||
|
unset($params['lifetime']);
|
||||||
|
setcookie($this->sessionName, '', $params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($sessionCookieFound) {
|
|
||||||
header_remove('Set-Cookie');
|
|
||||||
foreach ($otherCookies as $h) {
|
|
||||||
header($h, false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setcookie($this->sessionName, '', 0, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), filter_var(ini_get('session.cookie_secure'), FILTER_VALIDATE_BOOLEAN), filter_var(ini_get('session.cookie_httponly'), FILTER_VALIDATE_BOOLEAN));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->newSessionId === $sessionId || $this->doDestroy($sessionId);
|
return $this->newSessionId === $sessionId || $this->doDestroy($sessionId);
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
namespace Symfony\Component\HttpFoundation\Session\Storage;
|
namespace Symfony\Component\HttpFoundation\Session\Storage;
|
||||||
|
|
||||||
use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
|
use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\SessionUtils;
|
||||||
use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler;
|
use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler;
|
||||||
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
|
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
|
||||||
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
|
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
|
||||||
@ -48,6 +49,11 @@ class NativeSessionStorage implements SessionStorageInterface
|
|||||||
*/
|
*/
|
||||||
protected $metadataBag;
|
protected $metadataBag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
private $emulateSameSite;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Depending on how you want the storage driver to behave you probably
|
* Depending on how you want the storage driver to behave you probably
|
||||||
* want to override this constructor entirely.
|
* want to override this constructor entirely.
|
||||||
@ -67,6 +73,7 @@ class NativeSessionStorage implements SessionStorageInterface
|
|||||||
* cookie_lifetime, "0"
|
* cookie_lifetime, "0"
|
||||||
* cookie_path, "/"
|
* cookie_path, "/"
|
||||||
* cookie_secure, ""
|
* cookie_secure, ""
|
||||||
|
* cookie_samesite, null
|
||||||
* entropy_file, ""
|
* entropy_file, ""
|
||||||
* entropy_length, "0"
|
* entropy_length, "0"
|
||||||
* gc_divisor, "100"
|
* gc_divisor, "100"
|
||||||
@ -94,9 +101,7 @@ class NativeSessionStorage implements SessionStorageInterface
|
|||||||
* trans_sid_hosts, $_SERVER['HTTP_HOST']
|
* trans_sid_hosts, $_SERVER['HTTP_HOST']
|
||||||
* trans_sid_tags, "a=href,area=href,frame=src,form="
|
* trans_sid_tags, "a=href,area=href,frame=src,form="
|
||||||
*
|
*
|
||||||
* @param array $options Session configuration options
|
* @param AbstractProxy|\SessionHandlerInterface|null $handler
|
||||||
* @param \SessionHandlerInterface|null $handler
|
|
||||||
* @param MetadataBag $metaBag MetadataBag
|
|
||||||
*/
|
*/
|
||||||
public function __construct(array $options = [], $handler = null, MetadataBag $metaBag = null)
|
public function __construct(array $options = [], $handler = null, MetadataBag $metaBag = null)
|
||||||
{
|
{
|
||||||
@ -150,6 +155,13 @@ class NativeSessionStorage implements SessionStorageInterface
|
|||||||
throw new \RuntimeException('Failed to start the session');
|
throw new \RuntimeException('Failed to start the session');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (null !== $this->emulateSameSite) {
|
||||||
|
$originalCookie = SessionUtils::popSessionCookie(session_name(), session_id());
|
||||||
|
if (null !== $originalCookie) {
|
||||||
|
header(sprintf('%s; SameSite=%s', $originalCookie, $this->emulateSameSite), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$this->loadSession();
|
$this->loadSession();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -215,6 +227,13 @@ class NativeSessionStorage implements SessionStorageInterface
|
|||||||
// @see https://bugs.php.net/70013
|
// @see https://bugs.php.net/70013
|
||||||
$this->loadSession();
|
$this->loadSession();
|
||||||
|
|
||||||
|
if (null !== $this->emulateSameSite) {
|
||||||
|
$originalCookie = SessionUtils::popSessionCookie(session_name(), session_id());
|
||||||
|
if (null !== $originalCookie) {
|
||||||
|
header(sprintf('%s; SameSite=%s', $originalCookie, $this->emulateSameSite), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $isRegenerated;
|
return $isRegenerated;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,6 +242,7 @@ class NativeSessionStorage implements SessionStorageInterface
|
|||||||
*/
|
*/
|
||||||
public function save()
|
public function save()
|
||||||
{
|
{
|
||||||
|
// Store a copy so we can restore the bags in case the session was not left empty
|
||||||
$session = $_SESSION;
|
$session = $_SESSION;
|
||||||
|
|
||||||
foreach ($this->bags as $bag) {
|
foreach ($this->bags as $bag) {
|
||||||
@ -248,7 +268,11 @@ class NativeSessionStorage implements SessionStorageInterface
|
|||||||
session_write_close();
|
session_write_close();
|
||||||
} finally {
|
} finally {
|
||||||
restore_error_handler();
|
restore_error_handler();
|
||||||
$_SESSION = $session;
|
|
||||||
|
// Restore only if not empty
|
||||||
|
if ($_SESSION) {
|
||||||
|
$_SESSION = $session;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->closed = true;
|
$this->closed = true;
|
||||||
@ -347,7 +371,7 @@ class NativeSessionStorage implements SessionStorageInterface
|
|||||||
|
|
||||||
$validOptions = array_flip([
|
$validOptions = array_flip([
|
||||||
'cache_expire', 'cache_limiter', 'cookie_domain', 'cookie_httponly',
|
'cache_expire', 'cache_limiter', 'cookie_domain', 'cookie_httponly',
|
||||||
'cookie_lifetime', 'cookie_path', 'cookie_secure',
|
'cookie_lifetime', 'cookie_path', 'cookie_secure', 'cookie_samesite',
|
||||||
'entropy_file', 'entropy_length', 'gc_divisor',
|
'entropy_file', 'entropy_length', 'gc_divisor',
|
||||||
'gc_maxlifetime', 'gc_probability', 'hash_bits_per_character',
|
'gc_maxlifetime', 'gc_probability', 'hash_bits_per_character',
|
||||||
'hash_function', 'lazy_write', 'name', 'referer_check',
|
'hash_function', 'lazy_write', 'name', 'referer_check',
|
||||||
@ -360,6 +384,12 @@ class NativeSessionStorage implements SessionStorageInterface
|
|||||||
|
|
||||||
foreach ($options as $key => $value) {
|
foreach ($options as $key => $value) {
|
||||||
if (isset($validOptions[$key])) {
|
if (isset($validOptions[$key])) {
|
||||||
|
if ('cookie_samesite' === $key && \PHP_VERSION_ID < 70300) {
|
||||||
|
// PHP < 7.3 does not support same_site cookies. We will emulate it in
|
||||||
|
// the start() method instead.
|
||||||
|
$this->emulateSameSite = $value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
ini_set('url_rewriter.tags' !== $key ? 'session.'.$key : $key, $value);
|
ini_set('url_rewriter.tags' !== $key ? 'session.'.$key : $key, $value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -381,7 +411,7 @@ class NativeSessionStorage implements SessionStorageInterface
|
|||||||
* @see https://php.net/sessionhandlerinterface
|
* @see https://php.net/sessionhandlerinterface
|
||||||
* @see https://php.net/sessionhandler
|
* @see https://php.net/sessionhandler
|
||||||
*
|
*
|
||||||
* @param \SessionHandlerInterface|null $saveHandler
|
* @param AbstractProxy|\SessionHandlerInterface|null $saveHandler
|
||||||
*
|
*
|
||||||
* @throws \InvalidArgumentException
|
* @throws \InvalidArgumentException
|
||||||
*/
|
*/
|
||||||
|
@ -11,10 +11,11 @@ $_SESSION is not empty
|
|||||||
write
|
write
|
||||||
destroy
|
destroy
|
||||||
close
|
close
|
||||||
$_SESSION is not empty
|
$_SESSION is empty
|
||||||
Array
|
Array
|
||||||
(
|
(
|
||||||
[0] => Content-Type: text/plain; charset=utf-8
|
[0] => Content-Type: text/plain; charset=utf-8
|
||||||
[1] => Cache-Control: max-age=0, private, must-revalidate
|
[1] => Cache-Control: max-age=0, private, must-revalidate
|
||||||
|
[2] => Set-Cookie: sid=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0; path=/; secure; HttpOnly
|
||||||
)
|
)
|
||||||
shutdown
|
shutdown
|
||||||
|
@ -20,5 +20,6 @@ Array
|
|||||||
[0] => Content-Type: text/plain; charset=utf-8
|
[0] => Content-Type: text/plain; charset=utf-8
|
||||||
[1] => Cache-Control: max-age=10800, private, must-revalidate
|
[1] => Cache-Control: max-age=10800, private, must-revalidate
|
||||||
[2] => Set-Cookie: abc=def
|
[2] => Set-Cookie: abc=def
|
||||||
|
[3] => Set-Cookie: sid=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0; path=/; secure; HttpOnly
|
||||||
)
|
)
|
||||||
shutdown
|
shutdown
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
open
|
||||||
|
validateId
|
||||||
|
read
|
||||||
|
doRead:
|
||||||
|
read
|
||||||
|
|
||||||
|
write
|
||||||
|
doWrite: foo|s:3:"bar";
|
||||||
|
close
|
||||||
|
Array
|
||||||
|
(
|
||||||
|
[0] => Content-Type: text/plain; charset=utf-8
|
||||||
|
[1] => Cache-Control: max-age=0, private, must-revalidate
|
||||||
|
[2] => Set-Cookie: sid=random_session_id; path=/; secure; HttpOnly; SameSite=lax
|
||||||
|
)
|
||||||
|
shutdown
|
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require __DIR__.'/common.inc';
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
|
||||||
|
|
||||||
|
$storage = new NativeSessionStorage(['cookie_samesite' => 'lax']);
|
||||||
|
$storage->setSaveHandler(new TestSessionHandler());
|
||||||
|
$storage->start();
|
||||||
|
|
||||||
|
$_SESSION = ['foo' => 'bar'];
|
||||||
|
|
||||||
|
ob_start(function ($buffer) { return str_replace(session_id(), 'random_session_id', $buffer); });
|
@ -0,0 +1,23 @@
|
|||||||
|
open
|
||||||
|
validateId
|
||||||
|
read
|
||||||
|
doRead:
|
||||||
|
read
|
||||||
|
destroy
|
||||||
|
close
|
||||||
|
open
|
||||||
|
validateId
|
||||||
|
read
|
||||||
|
doRead:
|
||||||
|
read
|
||||||
|
|
||||||
|
write
|
||||||
|
doWrite: foo|s:3:"bar";
|
||||||
|
close
|
||||||
|
Array
|
||||||
|
(
|
||||||
|
[0] => Content-Type: text/plain; charset=utf-8
|
||||||
|
[1] => Cache-Control: max-age=0, private, must-revalidate
|
||||||
|
[2] => Set-Cookie: sid=random_session_id; path=/; secure; HttpOnly; SameSite=lax
|
||||||
|
)
|
||||||
|
shutdown
|
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require __DIR__.'/common.inc';
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
|
||||||
|
|
||||||
|
$storage = new NativeSessionStorage(['cookie_samesite' => 'lax']);
|
||||||
|
$storage->setSaveHandler(new TestSessionHandler());
|
||||||
|
$storage->start();
|
||||||
|
|
||||||
|
$_SESSION = ['foo' => 'bar'];
|
||||||
|
|
||||||
|
$storage->regenerate(true);
|
||||||
|
|
||||||
|
ob_start(function ($buffer) { return preg_replace('~_sf2_meta.*$~m', '', str_replace(session_id(), 'random_session_id', $buffer)); });
|
@ -36,7 +36,7 @@ class NativeSessionStorageTest extends TestCase
|
|||||||
protected function setUp()
|
protected function setUp()
|
||||||
{
|
{
|
||||||
$this->iniSet('session.save_handler', 'files');
|
$this->iniSet('session.save_handler', 'files');
|
||||||
$this->iniSet('session.save_path', $this->savePath = sys_get_temp_dir().'/sf2test');
|
$this->iniSet('session.save_path', $this->savePath = sys_get_temp_dir().'/sftest');
|
||||||
if (!is_dir($this->savePath)) {
|
if (!is_dir($this->savePath)) {
|
||||||
mkdir($this->savePath);
|
mkdir($this->savePath);
|
||||||
}
|
}
|
||||||
@ -167,6 +167,10 @@ class NativeSessionStorageTest extends TestCase
|
|||||||
'cookie_httponly' => false,
|
'cookie_httponly' => false,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (\PHP_VERSION_ID >= 70300) {
|
||||||
|
$options['cookie_samesite'] = 'lax';
|
||||||
|
}
|
||||||
|
|
||||||
$this->getStorage($options);
|
$this->getStorage($options);
|
||||||
$temp = session_get_cookie_params();
|
$temp = session_get_cookie_params();
|
||||||
$gco = [];
|
$gco = [];
|
||||||
@ -175,8 +179,6 @@ class NativeSessionStorageTest extends TestCase
|
|||||||
$gco['cookie_'.$key] = $value;
|
$gco['cookie_'.$key] = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
unset($gco['cookie_samesite']);
|
|
||||||
|
|
||||||
$this->assertEquals($options, $gco);
|
$this->assertEquals($options, $gco);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user