Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitf46e6cb

Browse files
fabpotnicolas-grekas
authored andcommitted
[HttpFoundation][FrameworkBundle] fix support for samesite in session cookies
1 parentadacae6 commitf46e6cb

File tree

13 files changed

+196
-39
lines changed

13 files changed

+196
-39
lines changed

‎src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
useSymfony\Component\Config\Definition\ConfigurationInterface;
2121
useSymfony\Component\DependencyInjection\Exception\LogicException;
2222
useSymfony\Component\Form\Form;
23+
useSymfony\Component\HttpFoundation\Cookie;
2324
useSymfony\Component\Lock\Lock;
2425
useSymfony\Component\Lock\Store\SemaphoreStore;
2526
useSymfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
@@ -490,6 +491,7 @@ private function addSessionSection(ArrayNodeDefinition $rootNode)
490491
->scalarNode('cookie_domain')->end()
491492
->booleanNode('cookie_secure')->end()
492493
->booleanNode('cookie_httponly')->defaultTrue()->end()
494+
->enumNode('cookie_samesite')->values([null, Cookie::SAMESITE_LAX, Cookie::SAMESITE_STRICT])->defaultNull()->end()
493495
->booleanNode('use_cookies')->end()
494496
->scalarNode('gc_divisor')->end()
495497
->scalarNode('gc_probability')->defaultValue(1)->end()

‎src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -867,7 +867,7 @@ private function registerSessionConfiguration(array $config, ContainerBuilder $c
867867
// session storage
868868
$container->setAlias('session.storage',$config['storage_id'])->setPrivate(true);
869869
$options = ['cache_limiter' =>'0'];
870-
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) {
870+
foreach (['name','cookie_lifetime','cookie_path','cookie_domain','cookie_secure','cookie_httponly','cookie_samesite','use_cookies','gc_maxlifetime','gc_probability','gc_divisor']as$key) {
871871
if (isset($config[$key])) {
872872
$options[$key] =$config[$key];
873873
}

‎src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,7 @@ protected static function getBundleDefaultConfig()
436436
'storage_id' =>'session.storage.native',
437437
'handler_id' =>'session.handler.native_file',
438438
'cookie_httponly' =>true,
439+
'cookie_samesite' =>null,
439440
'gc_probability' =>1,
440441
'save_path' =>'%kernel.cache_dir%/sessions',
441442
'metadata_update_threshold' =>'0',
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespaceSymfony\Component\HttpFoundation\Session;
13+
14+
/**
15+
* Session utility functions.
16+
*
17+
* @author Nicolas Grekas <p@tchwork.com>
18+
* @author Rémon van de Kamp <rpkamp@gmail.com>
19+
*
20+
* @internal
21+
*/
22+
finalclass SessionUtils
23+
{
24+
/**
25+
* Find the session header amongst the headers that are to be sent, remove it, and return
26+
* it so the caller can process it further.
27+
*/
28+
publicstaticfunctionpopSessionCookie($sessionName,$sessionId)
29+
{
30+
$sessionCookie =null;
31+
$sessionCookiePrefix =sprintf(' %s=',urlencode($sessionName));
32+
$sessionCookieWithId =sprintf('%s%s;',$sessionCookiePrefix,urlencode($sessionId));
33+
$otherCookies = [];
34+
foreach (headers_list()as$h) {
35+
if (0 !==stripos($h,'Set-Cookie:')) {
36+
continue;
37+
}
38+
if (11 ===strpos($h,$sessionCookiePrefix,11)) {
39+
$sessionCookie =$h;
40+
41+
if (11 !==strpos($h,$sessionCookieWithId,11)) {
42+
$otherCookies[] =$h;
43+
}
44+
}else {
45+
$otherCookies[] =$h;
46+
}
47+
}
48+
if (null ===$sessionCookie) {
49+
returnnull;
50+
}
51+
52+
header_remove('Set-Cookie');
53+
foreach ($otherCookiesas$h) {
54+
header($h,false);
55+
}
56+
57+
return$sessionCookie;
58+
}
59+
}

‎src/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractSessionHandler.php‎

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespaceSymfony\Component\HttpFoundation\Session\Storage\Handler;
1313

14+
useSymfony\Component\HttpFoundation\Session\SessionUtils;
15+
1416
/**
1517
* This abstract session handler provides a generic implementation
1618
* of the PHP 7.0 SessionUpdateTimestampHandlerInterface,
@@ -27,7 +29,7 @@ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \Sess
2729
private$igbinaryEmptyData;
2830

2931
/**
30-
*{@inheritdoc}
32+
*@return bool
3133
*/
3234
publicfunctionopen($savePath,$sessionName)
3335
{
@@ -62,7 +64,7 @@ abstract protected function doWrite($sessionId, $data);
6264
abstractprotectedfunctiondoDestroy($sessionId);
6365

6466
/**
65-
*{@inheritdoc}
67+
*@return bool
6668
*/
6769
publicfunctionvalidateId($sessionId)
6870
{
@@ -73,7 +75,7 @@ public function validateId($sessionId)
7375
}
7476

7577
/**
76-
*{@inheritdoc}
78+
*@return string
7779
*/
7880
publicfunctionread($sessionId)
7981
{
@@ -99,7 +101,7 @@ public function read($sessionId)
99101
}
100102

101103
/**
102-
*{@inheritdoc}
104+
*@return bool
103105
*/
104106
publicfunctionwrite($sessionId,$data)
105107
{
@@ -124,7 +126,7 @@ public function write($sessionId, $data)
124126
}
125127

126128
/**
127-
*{@inheritdoc}
129+
*@return bool
128130
*/
129131
publicfunctiondestroy($sessionId)
130132
{
@@ -135,31 +137,23 @@ public function destroy($sessionId)
135137
if (!$this->sessionName) {
136138
thrownew \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.',static::class));
137139
}
138-
$sessionCookie =sprintf(' %s=',urlencode($this->sessionName));
139-
$sessionCookieWithId =sprintf('%s%s;',$sessionCookie,urlencode($sessionId));
140-
$sessionCookieFound =false;
141-
$otherCookies = [];
142-
foreach (headers_list()as$h) {
143-
if (0 !==stripos($h,'Set-Cookie:')) {
144-
continue;
145-
}
146-
if (11 ===strpos($h,$sessionCookie,11)) {
147-
$sessionCookieFound =true;
148-
149-
if (11 !==strpos($h,$sessionCookieWithId,11)) {
150-
$otherCookies[] =$h;
151-
}
140+
$cookie = SessionUtils::popSessionCookie($this->sessionName,$sessionId);
141+
142+
/*
143+
* We send an invalidation Set-Cookie header (zero lifetime)
144+
* when either the session was started or a cookie with
145+
* the session name was sent by the client (in which case
146+
* we know it's invalid as a valid session cookie would've
147+
* started the session).
148+
*/
149+
if (null ===$cookie ||isset($_COOKIE[$this->sessionName])) {
150+
if (\PHP_VERSION_ID <70300) {
151+
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));
152152
}else {
153-
$otherCookies[] =$h;
154-
}
155-
}
156-
if ($sessionCookieFound) {
157-
header_remove('Set-Cookie');
158-
foreach ($otherCookiesas$h) {
159-
header($h,false);
153+
$params =session_get_cookie_params();
154+
unset($params['lifetime']);
155+
setcookie($this->sessionName,'',$params);
160156
}
161-
}else {
162-
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));
163157
}
164158
}
165159

‎src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php‎

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespaceSymfony\Component\HttpFoundation\Session\Storage;
1313

1414
useSymfony\Component\HttpFoundation\Session\SessionBagInterface;
15+
useSymfony\Component\HttpFoundation\Session\SessionUtils;
1516
useSymfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler;
1617
useSymfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
1718
useSymfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
@@ -48,6 +49,11 @@ class NativeSessionStorage implements SessionStorageInterface
4849
*/
4950
protected$metadataBag;
5051

52+
/**
53+
* @var string|null
54+
*/
55+
private$emulateSameSite;
56+
5157
/**
5258
* Depending on how you want the storage driver to behave you probably
5359
* want to override this constructor entirely.
@@ -67,6 +73,7 @@ class NativeSessionStorage implements SessionStorageInterface
6773
* cookie_lifetime, "0"
6874
* cookie_path, "/"
6975
* cookie_secure, ""
76+
* cookie_samesite, null
7077
* entropy_file, ""
7178
* entropy_length, "0"
7279
* gc_divisor, "100"
@@ -94,9 +101,7 @@ class NativeSessionStorage implements SessionStorageInterface
94101
* trans_sid_hosts, $_SERVER['HTTP_HOST']
95102
* trans_sid_tags, "a=href,area=href,frame=src,form="
96103
*
97-
* @param array $options Session configuration options
98-
* @param \SessionHandlerInterface|null $handler
99-
* @param MetadataBag $metaBag MetadataBag
104+
* @param AbstractProxy|\SessionHandlerInterface|null $handler
100105
*/
101106
publicfunction__construct(array$options = [],$handler =null,MetadataBag$metaBag =null)
102107
{
@@ -150,6 +155,13 @@ public function start()
150155
thrownew \RuntimeException('Failed to start the session');
151156
}
152157

158+
if (null !==$this->emulateSameSite) {
159+
$originalCookie = SessionUtils::popSessionCookie(session_name(),session_id());
160+
if (null !==$originalCookie) {
161+
header(sprintf('%s; SameSite=%s',$originalCookie,$this->emulateSameSite),false);
162+
}
163+
}
164+
153165
$this->loadSession();
154166

155167
returntrue;
@@ -215,6 +227,13 @@ public function regenerate($destroy = false, $lifetime = null)
215227
// @see https://bugs.php.net/70013
216228
$this->loadSession();
217229

230+
if (null !==$this->emulateSameSite) {
231+
$originalCookie = SessionUtils::popSessionCookie(session_name(),session_id());
232+
if (null !==$originalCookie) {
233+
header(sprintf('%s; SameSite=%s',$originalCookie,$this->emulateSameSite),false);
234+
}
235+
}
236+
218237
return$isRegenerated;
219238
}
220239

@@ -223,6 +242,7 @@ public function regenerate($destroy = false, $lifetime = null)
223242
*/
224243
publicfunctionsave()
225244
{
245+
// Store a copy so we can restore the bags in case the session was not left empty
226246
$session =$_SESSION;
227247

228248
foreach ($this->bagsas$bag) {
@@ -248,7 +268,11 @@ public function save()
248268
session_write_close();
249269
}finally {
250270
restore_error_handler();
251-
$_SESSION =$session;
271+
272+
// Restore only if not empty
273+
if ($_SESSION) {
274+
$_SESSION =$session;
275+
}
252276
}
253277

254278
$this->closed =true;
@@ -347,7 +371,7 @@ public function setOptions(array $options)
347371

348372
$validOptions =array_flip([
349373
'cache_expire','cache_limiter','cookie_domain','cookie_httponly',
350-
'cookie_lifetime','cookie_path','cookie_secure',
374+
'cookie_lifetime','cookie_path','cookie_secure','cookie_samesite',
351375
'entropy_file','entropy_length','gc_divisor',
352376
'gc_maxlifetime','gc_probability','hash_bits_per_character',
353377
'hash_function','lazy_write','name','referer_check',
@@ -360,6 +384,12 @@ public function setOptions(array $options)
360384

361385
foreach ($optionsas$key =>$value) {
362386
if (isset($validOptions[$key])) {
387+
if ('cookie_samesite' ===$key && \PHP_VERSION_ID <70300) {
388+
// PHP < 7.3 does not support same_site cookies. We will emulate it in
389+
// the start() method instead.
390+
$this->emulateSameSite =$value;
391+
continue;
392+
}
363393
ini_set('url_rewriter.tags' !==$key ?'session.'.$key :$key,$value);
364394
}
365395
}
@@ -381,7 +411,7 @@ public function setOptions(array $options)
381411
* @see https://php.net/sessionhandlerinterface
382412
* @see https://php.net/sessionhandler
383413
*
384-
* @param \SessionHandlerInterface|null $saveHandler
414+
* @paramAbstractProxy|\SessionHandlerInterface|null $saveHandler
385415
*
386416
* @throws \InvalidArgumentException
387417
*/

‎src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/storage.expected‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ $_SESSION is not empty
1111
write
1212
destroy
1313
close
14-
$_SESSION isnotempty
14+
$_SESSION is empty
1515
Array
1616
(
1717
[0] => Content-Type: text/plain; charset=utf-8
1818
[1] => Cache-Control: max-age=0, private, must-revalidate
19+
[2] => Set-Cookie: sid=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0; path=/; secure; HttpOnly
1920
)
2021
shutdown

‎src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.expected‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@ Array
2020
[0] => Content-Type: text/plain; charset=utf-8
2121
[1] => Cache-Control: max-age=10800, private, must-revalidate
2222
[2] => Set-Cookie: abc=def
23+
[3] => Set-Cookie: sid=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0; path=/; secure; HttpOnly
2324
)
2425
shutdown
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
open
2+
validateId
3+
read
4+
doRead:
5+
read
6+
7+
write
8+
doWrite: foo|s:3:"bar";
9+
close
10+
Array
11+
(
12+
[0] => Content-Type: text/plain; charset=utf-8
13+
[1] => Cache-Control: max-age=0, private, must-revalidate
14+
[2] => Set-Cookie: sid=random_session_id; path=/; secure; HttpOnly; SameSite=lax
15+
)
16+
shutdown
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
require__DIR__.'/common.inc';
4+
5+
useSymfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
6+
7+
$storage =newNativeSessionStorage(['cookie_samesite' =>'lax']);
8+
$storage->setSaveHandler(newTestSessionHandler());
9+
$storage->start();
10+
11+
$_SESSION = ['foo' =>'bar'];
12+
13+
ob_start(function ($buffer) {returnstr_replace(session_id(),'random_session_id',$buffer); });

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp