8000 Fix session cookie handling in cli context · symfony/symfony@aac0ccb · GitHub
[go: up one dir, main page]

Skip to content

Commit aac0ccb

Browse files
Fix session cookie handling in cli context
1 parent 38cb35a commit aac0ccb

File tree

5 files changed

+88
-5
lines changed

5 files changed

+88
-5
lines changed

src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@
7070
<argument key="session" type="service" id="session" on-invalid="ignore" />
7171
<argument key="initialized_session" type="service" id="session" on-invalid="ignore_uninitialized" />
7272
</argument>
73+
<argument>%kernel.debug%</argument>
74+
<argument>%session.storage.options%</argument>
7375
</service>
7476

7577
<service id="session.save_listener" class="Symfony\Component\HttpKernel\EventListener\SaveSessionListener">

src/Symfony/Component/HttpFoundation/Request.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ class Request
179179
protected $format;
180180

181181
/**
182-
* @var SessionInterface|callable
182+
* @var SessionInterface|callable(): SessionInterface
183183
*/
184184
protected $session;
185185

@@ -418,7 +418,7 @@ public static function create($uri, $method = 'GET', $parameters = [], $cookies
418418
* to keep BC with an existing system. It should not be used for any
419419
* other purpose.
420420
*
421-
* @param callable|null $callable A PHP callable
421+
* @param callable():SessionInterface|null $callable A PHP callable
422422
*/
423423
public static function setFactory($callable)
424424
{
@@ -720,6 +720,12 @@ public function getSession()
720720
$session = $this->session;
721721
if (!$session instanceof SessionInterface && null !== $session) {
722722
$this->setSession($session = $session());
723+
/*
724+
* For supporting sessions in php runtime with runners like roadrunner or swoole the session
725+
* cookie need read from the cookie bag and set on the session storage.
726+
*/
727+
$sessionId = $this->cookies->get($session->getName(), '');
728+
$session->setId($sessionId);
723729
}
724730

725731
if (null === $session) {
@@ -763,6 +769,8 @@ public function setSession(SessionInterface $session)
763769

764770
/**
765771
* @internal
772+
*
773+
* @param callable(): SessionInterface $factory
766774
*/
767775
public function setSessionFactory(callable $factory)
768776
{

src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313

1414
use Psr\Container\ContainerInterface;
1515
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
16+
use Symfony\Component\HttpFoundation\Cookie;
1617
use Symfony\Component\HttpFoundation\Session\Session;
1718
use Symfony\Component\HttpFoundation\Session\SessionInterface;
19+
use Symfony\Component\HttpFoundation\Session\SessionUtils;
1820
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
1921
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
2022
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
@@ -42,9 +44,18 @@ abstract class AbstractSessionListener implements EventSubscriberInterface
4244
protected $container;
4345
private $sessionUsageStack = [];
4446

45-
public function __construct(ContainerInterface $container = null)
47+
/**
48+
* @var array<string, mixed>
49+
*/
50+
private $sessionOptions;
51+
52+
/**
53+
* @param array<string, mixed> $sessionOptions
54+
*/
55+
public function __construct(ContainerInterface $container = null, bool $debug = false, array $sessionOptions = [])
4656
{
4757
$this->container = $container;
58+
$this->sessionOptions = $sessionOptions;
4859
}
4960

5061
public function onKernelRequest(GetResponseEvent $event)
@@ -115,6 +126,39 @@ public function onKernelResponse(FilterResponseEvent $event)
115126
* it is saved will just restart it.
116127
*/
117128
$session->save();
129+
130+
/*
131+
* For supporting sessions in php runtime with runners like roadrunner or swoole the session
132+
* cookie need to be written on the response object and should not be written by PHP itself.
133+
*/
134+
$sessionName = $session->getName();
135+
$sessionId = $session->getId();
136+
$sessionCookiePath = $this->sessionOptions['cookie_path'] ?? '/';
137+
$popSessionCookie = SessionUtils::popSessionCookie($sessionName, $sessionId);
138+
139+
if (0 === \strpos($popSessionCookie, \sprintf('Set-Cookie: %s=deleted;', $sessionName))) {
140+
$response->headers->removeCookie($sessionName, $sessionCookiePath);
141+
} else {
142+
$expire = 0;
143+
$lifetime = $this->sessionOptions['cookie_lifetime'] ?? null;
144+
if ($lifetime) {
145+
$expire = time() + $lifetime;
146+
}
147+
148+
$response->headers->setCookie(
149+
Cookie::create(
150+
$sessionName,
151+
$sessionId,
152+
$expire,
153+
$sessionCookiePath,
154+
$this->sessionOptions['cookie_domain'] ?? null,
155+
$this->sessionOptions['cookie_secure'] ?? null,
156+
$this->sessionOptions['cookie_httponly'] ?? true,
157+
false,
158+
$this->sessionOptions['cookie_samesite'] ?? Cookie::SAMESITE_LAX
159+
)
160+
);
161+
}
118162
}
119163
}
120164

src/Symfony/Component/HttpKernel/EventListener/SessionListener.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,12 @@
2929
*/
3030
class SessionListener extends AbstractSessionListener
3131
{
32-
public function __construct(ContainerInterface $container)
32+
/**
33+
* @param array<string, mixed> $sessionOptions
34+
*/
35+
public function __construct(ContainerInterface $container, bool $debug = false, array $sessionOptions = [])
3336
{
34-
$this->container = $container;
37+
parent::__construct($container, $debug, $sessionOptions);
3538
}
3639

3740
public function onKernelRequest(GetResponseEvent $event)

src/Symfony/Component/HttpKernel/Tests/EventListener/SessionListenerTest.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,32 @@ public function testResponseIsStillPublicIfSessionStartedAndHeaderPresent()
120120
$this->assertFalse($response->headers->has(AbstractSessionListener::NO_AUTO_CACHE_CONTROL_HEADER));
121121
}
122122

123+
public function testSessionSaveAndResponseHasSessionCookie()
124+
{
125+
$session = $this->getMockBuilder(Session::class)->disableOriginalConstructor()->getMock();
126+
$session->expects($this->exactly(2))->method('getUsageIndex')->will($this->onConsecutiveCalls(0, 1));
127+
$session->expects($this->exactly(1))->method('getId')->willReturn('123456');
128+
$session->expects($this->exactly(1))->method('getName')->willReturn('PHPSESSID');
129+
$session->expects($this->exactly(1))->method('save');
130+
$session->expects($this->exactly(1))->method('isStarted')->willReturn(true);
131+
132+
$container = new Container();
133+
$container->set('initialized_session', $session);
134+
135+
$listener = new SessionListener($container);
136+
$kernel = $this->getMockBuilder(HttpKernelInterface::class)->disableOriginalConstructor()->getMock();
137+
138+
$request = new Request();
139+
$listener->onKernelRequest(new RequestEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST));
140+
141+
$response = new Response();
142+
$listener->onKernelResponse(new ResponseEvent($kernel, new Request(), HttpKernelInterface::MASTER_REQUEST, $response));
143+
144+
$cookies = $response->headers->getCookies();
145+
$this->assertSame('PHPSESSID', $cookies[0]->getName());
146+
$this->assertSame('123456', $cookies[0]->getValue());
147+
}
148+
123149
public function testUninitializedSession()
124150
{
125151
$kernel = $this->createMock(HttpKernelInterface::class);

0 commit comments

Comments
 (0)
0