Skip to content

Commit 20a6ce1

Browse files
simonchrzfabpot
authored andcommitted
[HttpFoundation] Take php session.cookie settings into account
1 parent 52f997f commit 20a6ce1

File tree

2 files changed

+122
-8
lines changed

2 files changed

+122
-8
lines changed

EventListener/AbstractSessionListener.php

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,12 @@ public function onKernelResponse(ResponseEvent $event)
140140
*/
141141
$sessionName = $session->getName();
142142
$sessionId = $session->getId();
143-
$sessionCookiePath = $this->sessionOptions['cookie_path'] ?? '/';
144-
$sessionCookieDomain = $this->sessionOptions['cookie_domain'] ?? null;
145-
$sessionCookieSecure = $this->sessionOptions['cookie_secure'] ?? false;
146-
$sessionCookieHttpOnly = $this->sessionOptions['cookie_httponly'] ?? true;
147-
$sessionCookieSameSite = $this->sessionOptions['cookie_samesite'] ?? Cookie::SAMESITE_LAX;
143+
$sessionOptions = $this->getSessionOptions($this->sessionOptions);
144+
$sessionCookiePath = $sessionOptions['cookie_path'] ?? '/';
145+
$sessionCookieDomain = $sessionOptions['cookie_domain'] ?? null;
146+
$sessionCookieSecure = $sessionOptions['cookie_secure'] ?? false;
147+
$sessionCookieHttpOnly = $sessionOptions['cookie_httponly'] ?? true;
148+
$sessionCookieSameSite = $sessionOptions['cookie_samesite'] ?? Cookie::SAMESITE_LAX;
148149

149150
SessionUtils::popSessionCookie($sessionName, $sessionId);
150151

@@ -162,7 +163,7 @@ public function onKernelResponse(ResponseEvent $event)
162163
);
163164
} elseif ($sessionId !== $requestSessionCookieId) {
164165
$expire = 0;
165-
$lifetime = $this->sessionOptions['cookie_lifetime'] ?? null;
166+
$lifetime = $sessionOptions['cookie_lifetime'] ?? null;
166167
if ($lifetime) {
167168
$expire = time() + $lifetime;
168169
}
@@ -280,4 +281,23 @@ public function reset(): void
280281
* @return SessionInterface|null
281282
*/
282283
abstract protected function getSession();
284+
285+
private function getSessionOptions(array $sessionOptions): array
286+
{
287+
$mergedSessionOptions = [];
288+
289+
foreach (session_get_cookie_params() as $key => $value) {
290+
$mergedSessionOptions['cookie_'.$key] = $value;
291+
}
292+
293+
foreach ($sessionOptions as $key => $value) {
294+
// do the same logic as in the NativeSessionStorage
295+
if ('cookie_secure' === $key && 'auto' === $value) {
296+
continue;
297+
}
298+
$mergedSessionOptions[$key] = $value;
299+
}
300+
301+
return $mergedSessionOptions;
302+
}
283303
}

Tests/EventListener/SessionListenerTest.php

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Psr\Log\LoggerInterface;
1616
use Symfony\Component\DependencyInjection\Container;
1717
use Symfony\Component\DependencyInjection\ServiceLocator;
18+
use Symfony\Component\HttpFoundation\Cookie;
1819
use Symfony\Component\HttpFoundation\Request;
1920
use Symfony\Component\HttpFoundation\RequestStack;
2021
use Symfony\Component\HttpFoundation\Response;
@@ -33,6 +34,99 @@
3334

3435
class SessionListenerTest extends TestCase
3536
{
37+
/**
38+
* @dataProvider provideSessionOptions
39+
* @runInSeparateProcess
40+
*/
41+
public function testSessionCookieOptions(array $phpSessionOptions, array $sessionOptions, array $expectedSessionOptions)
42+
{
43+
$session = $this->getMockBuilder(Session::class)->disableOriginalConstructor()->getMock();
44+
$session->expects($this->exactly(2))->method('getUsageIndex')->will($this->onConsecutiveCalls(0, 1));
45+
$session->expects($this->exactly(1))->method('getId')->willReturn('123456');
46+
$session->expects($this->exactly(1))->method('getName')->willReturn('PHPSESSID');
47+
$session->expects($this->exactly(1))->method('save');
48+
$session->expects($this->exactly(1))->method('isStarted')->willReturn(true);
49+
50+
if (isset($phpSessionOptions['samesite'])) {
51+
ini_set('session.cookie_samesite', $phpSessionOptions['samesite']);
52+
}
53+
session_set_cookie_params(0, $phpSessionOptions['path'] ?? null, $phpSessionOptions['domain'] ?? null, $phpSessionOptions['secure'] ?? null, $phpSessionOptions['httponly'] ?? null);
54+
55+
$container = new Container();
56+
$container->set('initialized_session', $session);
57+
58+
$listener = new SessionListener($container, false, $sessionOptions);
59+
$kernel = $this->getMockBuilder(HttpKernelInterface::class)->disableOriginalConstructor()->getMock();
60+
61+
$request = new Request();
62+
$listener->onKernelRequest(new RequestEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST));
63+
64+
$response = new Response();
65+
$listener->onKernelResponse(new ResponseEvent($kernel, new Request(), HttpKernelInterface::MAIN_REQUEST, $response));
66+
67+
$cookies = $response->headers->getCookies();
68+
$this->assertSame('PHPSESSID', $cookies[0]->getName());
69+
$this->assertSame('123456', $cookies[0]->getValue());
70+
$this->assertSame($expectedSessionOptions['cookie_path'], $cookies[0]->getPath());
71+
$this->assertSame($expectedSessionOptions['cookie_domain'], $cookies[0]->getDomain());
72+
$this->assertSame($expectedSessionOptions['cookie_secure'], $cookies[0]->isSecure());
73+
$this->assertSame($expectedSessionOptions['cookie_httponly'], $cookies[0]->isHttpOnly());
74+
$this->assertSame($expectedSessionOptions['cookie_samesite'], $cookies[0]->getSameSite());
75+
}
76+
77+
public function provideSessionOptions(): \Generator
78+
{
79+
if (\PHP_VERSION_ID > 70300) {
80+
yield 'set_samesite_by_php' => [
81+
'phpSessionOptions' => ['samesite' => Cookie::SAMESITE_STRICT],
82+
'sessionOptions' => ['cookie_path' => '/test/', 'cookie_domain' => '', 'cookie_secure' => true, 'cookie_httponly' => true],
83+
'expectedSessionOptions' => ['cookie_path' => '/test/', 'cookie_domain' => '', 'cookie_secure' => true, 'cookie_httponly' => true, 'cookie_samesite' => Cookie::SAMESITE_STRICT],
84+
];
85+
}
86+
87+
yield 'set_cookie_path_by_php' => [
88+
'phpSessionOptions' => ['path' => '/prod/'],
89+
'sessionOptions' => ['cookie_domain' => '', 'cookie_secure' => true, 'cookie_httponly' => true, 'cookie_samesite' => Cookie::SAMESITE_LAX],
90+
'expectedSessionOptions' => ['cookie_path' => '/prod/', 'cookie_domain' => '', 'cookie_secure' => true, 'cookie_httponly' => true, 'cookie_samesite' => Cookie::SAMESITE_LAX],
91+
];
92+
93+
yield 'set_cookie_secure_by_php' => [
94+
'phpSessionOptions' => ['secure' => true],
95+
'sessionOptions' => ['cookie_path' => '/test/', 'cookie_domain' => '', 'cookie_httponly' => true, 'cookie_samesite' => Cookie::SAMESITE_LAX],
96+
'expectedSessionOptions' => ['cookie_path' => '/test/', 'cookie_domain' => '', 'cookie_secure' => true, 'cookie_httponly' => true, 'cookie_samesite' => Cookie::SAMESITE_LAX],
97+
];
98+
99+
yield 'set_cookiesecure_auto_by_symfony_false_by_php' => [
100+
'phpSessionOptions' => ['secure' => false],
101+
'sessionOptions' => ['cookie_path' => '/test/', 'cookie_httponly' => 'auto', 'cookie_secure' => 'auto', 'cookie_samesite' => Cookie::SAMESITE_LAX],
102+
'expectedSessionOptions' => ['cookie_path' => '/test/', 'cookie_domain' => '', 'cookie_secure' => false, 'cookie_httponly' => true, 'cookie_samesite' => Cookie::SAMESITE_LAX],
103+
];
104+
105+
yield 'set_cookiesecure_auto_by_symfony_true_by_php' => [
106+
'phpSessionOptions' => ['secure' => true],
107+
'sessionOptions' => ['cookie_path' => '/test/', 'cookie_httponly' => 'auto', 'cookie_secure' => 'auto', 'cookie_samesite' => Cookie::SAMESITE_LAX],
108+
'expectedSessionOptions' => ['cookie_path' => '/test/', 'cookie_domain' => '', 'cookie_secure' => true, 'cookie_httponly' => true, 'cookie_samesite' => Cookie::SAMESITE_LAX],
109+
];
110+
111+
yield 'set_cookie_httponly_by_php' => [
112+
'phpSessionOptions' => ['httponly' => true],
113+
'sessionOptions' => ['cookie_path' => '/test/', 'cookie_domain' => '', 'cookie_secure' => true, 'cookie_samesite' => Cookie::SAMESITE_LAX],
114+
'expectedSessionOptions' => ['cookie_path' => '/test/', 'cookie_domain' => '', 'cookie_secure' => true, 'cookie_httponly' => true, 'cookie_samesite' => Cookie::SAMESITE_LAX],
115+
];
116+
117+
yield 'set_cookie_domain_by_php' => [
118+
'phpSessionOptions' => ['domain' => 'test.symfony'],
119+
'sessionOptions' => ['cookie_path' => '/test/', 'cookie_httponly' => true, 'cookie_secure' => true, 'cookie_samesite' => Cookie::SAMESITE_LAX],
120+
'expectedSessionOptions' => ['cookie_path' => '/test/', 'cookie_domain' => 'test.symfony', 'cookie_secure' => true, 'cookie_httponly' => true, 'cookie_samesite' => Cookie::SAMESITE_LAX],
121+
];
122+
123+
yield 'set_samesite_by_symfony' => [
124+
'phpSessionOptions' => ['samesite' => Cookie::SAMESITE_STRICT],
125+
'sessionOptions' => ['cookie_path' => '/test/', 'cookie_httponly' => true, 'cookie_secure' => true, 'cookie_samesite' => Cookie::SAMESITE_LAX],
126+
'expectedSessionOptions' => ['cookie_path' => '/test/', 'cookie_domain' => '', 'cookie_secure' => true, 'cookie_httponly' => true, 'cookie_samesite' => Cookie::SAMESITE_LAX],
127+
];
128+
}
129+
36130
public function testOnlyTriggeredOnMainRequest()
37131
{
38132
$listener = $this->getMockForAbstractClass(AbstractSessionListener::class);
@@ -160,10 +254,10 @@ public function testSessionSaveAndResponseHasSessionCookie()
160254
$kernel = $this->getMockBuilder(HttpKernelInterface::class)->disableOriginalConstructor()->getMock();
161255

162256
$request = new Request();
163-
$listener->onKernelRequest(new RequestEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST));
257+
$listener->onKernelRequest(new RequestEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST));
164258

165259
$response = new Response();
166-
$listener->onKernelResponse(new ResponseEvent($kernel, new Request(), HttpKernelInterface::MASTER_REQUEST, $response));
260+
$listener->onKernelResponse(new ResponseEvent($kernel, new Request(), HttpKernelInterface::MAIN_REQUEST, $response));
167261

168262
$cookies = $response->headers->getCookies();
169263
$this->assertSame('PHPSESSID', $cookies[0]->getName());

0 commit comments

Comments
 (0)