Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 29e4128

Browse files
[HttpKernel] Fix session handling: decouple "save" from setting response "private"
1 parent f95ac4f commit 29e4128

File tree

11 files changed

+156
-21
lines changed

11 files changed

+156
-21
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
<tag name="container.service_locator" />
6868
<argument type="collection">
6969
<argument key="session" type="service" id="session" on-invalid="ignore" />
70+
<argument key="initialized_session" type="service" id="session" on-invalid="ignore_uninitialized" />
7071
</argument>
7172
</service>
7273
</argument>

src/Symfony/Component/HttpFoundation/Request.php

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -824,7 +824,12 @@ public function get($key, $default = null)
824824
*/
825825
public function getSession()
826826
{
827-
return $this->session;
827+
$session = $this->session;
828+
if (!$session instanceof SessionInterface && null !== $session) {
829+
$this->setSession($session = $session());
830+
}
831+
832+
return $session;
828833
}
829834

830835
/**
@@ -836,7 +841,7 @@ public function getSession()
836841
public function hasPreviousSession()
837842
{
838843
// the check for $this->session avoids malicious users trying to fake a session cookie with proper name
839-
return $this->hasSession() && $this->cookies->has($this->session->getName());
844+
return $this->hasSession() && $this->cookies->has($this->getSession()->getName());
840845
}
841846

842847
/**
@@ -863,6 +868,14 @@ public function setSession(SessionInterface $session)
863868
$this->session = $session;
864869
}
865870

871+
/**
872+
* @internal
873+
*/
874+
public function setSessionFactory(callable $factory)
875+
{
876+
$this->session = $factory;
877+
}
878+
866879
/**
867880
* Returns the client IP addresses.
868881
*

src/Symfony/Component/HttpFoundation/Session/Session.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
2929
private $flashName;
3030
private $attributeName;
3131
private $data = array();
32+
private $hasStarted = false;
3233

3334
/**
3435
* @param SessionStorageInterface $storage A SessionStorageInterface instance
@@ -53,6 +54,8 @@ public function __construct(SessionStorageInterface $storage = null, AttributeBa
5354
*/
5455
public function start()
5556
{
57+
$this->hasStarted = true;
58+
5659
return $this->storage->start();
5760
}
5861

@@ -140,6 +143,16 @@ public function count()
140143
return count($this->getAttributeBag()->all());
141144
}
142145

146+
/**
147+
* @return bool
148+
*
149+
* @internal
150+
*/
151+
public function hasStarted()
152+
{
153+
return $this->hasStarted;
154+
}
155+
143156
/**
144157
* @return bool
145158
*
@@ -235,6 +248,8 @@ public function registerBag(SessionBagInterface $bag)
235248
*/
236249
public function getBag($name)
237250
{
251+
$this->hasStarted = true;
252+
238253
return $this->storage->getBag($name)->getBag();
239254
}
240255

@@ -257,6 +272,6 @@ public function getFlashBag()
257272
*/
258273
private function getAttributeBag()
259274
{
260-
return $this->storage->getBag($this->attributeName)->getBag();
275+
return $this->getBag($this->attributeName);
261276
}
262277
}

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\HttpKernel\EventListener;
1313

14+
use Symfony\Component\HttpFoundation\Session\Session;
1415
use Symfony\Component\HttpFoundation\Session\SessionInterface;
1516
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
1617
use Symfony\Component\HttpKernel\KernelEvents;
@@ -30,12 +31,9 @@ public function onKernelRequest(GetResponseEvent $event)
3031
}
3132

3233
$request = $event->getRequest();
33-
$session = $this->getSession();
34-
if (null === $session || $request->hasSession()) {
35-
return;
34+
if (!$request->hasSession()) {
35+
$request->setSessionFactory(function () { return $this->getSession(); });
3636
}
37-
38-
$request->setSession($session);
3937
}
4038

4139
public static function getSubscribedEvents()

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,10 @@ public function onKernelResponse(FilterResponseEvent $event)
6161
$session = $event->getRequest()->getSession();
6262
if ($session && $session->isStarted()) {
6363
$session->save();
64-
if (!$session instanceof Session || !\method_exists($session, 'isEmpty') || !$session->isEmpty()) {
65-
$params = session_get_cookie_params();
66-
$event->getResponse()->headers->setCookie(new Cookie($session->getName(), $session->getId(), 0 === $params['lifetime'] ? 0 : time() + $params['lifetime'], $params['path'], $params['domain'], $params['secure'], $params['httponly']));
67-
}
64+
}
65+
if ($session && ($session instanceof Session ? $session->hasStarted() && !$session->isEmpty() : $sessions->isStarted())) {
66+
$params = session_get_cookie_params();
67+
$event->getResponse()->headers->setCookie(new Cookie($session->getName(), $session->getId(), 0 === $params['lifetime'] ? 0 : time() + $params['lifetime'], $params['path'], $params['domain'], $params['secure'], $params['httponly']));
6868
}
6969
}
7070

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,6 @@ public function onKernelResponse(FilterResponseEvent $event)
5353
$session = $event->getRequest()->getSession();
5454
if ($session && $session->isStarted()) {
5555
$session->save();
56-
$event->getResponse()
57-
->setPrivate()
58-
->setMaxAge(0)
59-
->headers->addCacheControlDirective('must-revalidate');
6056
}
6157
}
6258

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
namespace Symfony\Component\HttpKernel\EventListener;
1313

1414
use Psr\Container\ContainerInterface;
15+
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
16+
use Symfony\Component\HttpKernel\KernelEvents;
1517

1618
/**
1719
* Sets the session in the request.
@@ -29,6 +31,24 @@ public function __construct(ContainerInterface $container)
2931
$this->container = $container;
3032
}
3133

34+
public function onKernelResponse(FilterResponseEvent $event)
35+
{
36+
if (!$event->isMasterRequest()) {
37+
return;
38+
}
39+
40+
if (!$session = $this->container->has('initialized_session') ? $this->container->get('initialized_session') : null) {
41+
return;
42+
}
43+
44+
if ($session->hasStarted()) {
45+
$event->getResponse()
46+
->setPrivate()
47+
->setMaxAge(0)
48+
->headers->addCacheControlDirective('must-revalidate');
49+
}
50+
}
51+
3252
protected function getSession()
3353
{
3454
if (!$this->container->has('session')) {
@@ -37,4 +57,12 @@ protected function getSession()
3757

3858
return $this->container->get('session');
3959
}
60+
61+
public static function getSubscribedEvents()
62+
{
63+
return parent::getSubscribedEvents() + array(
64+
// low priority to come after regular response listeners
65+
KernelEvents::RESPONSE => array(array('onKernelResponse', -1000)),
66+
);
67+
}
4068
}

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,5 @@ public function testSessionSavedAndResponsePrivate()
4545
$request->setSession($session);
4646
$response = new Response();
4747
$listener->onKernelResponse(new FilterResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST, $response));
48-
49-
$this->assertTrue($response->headers->hasCacheControlDirective('private'));
50-
$this->assertTrue($response->headers->hasCacheControlDirective('must-revalidate'));
51-
$this->assertSame('0', $response->headers->getCacheControlDirective('max-age'));
5248
}
5349
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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+
namespace Symfony\Component\HttpKernel\Tests\EventListener;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\DependencyInjection\Container;
16+
use Symfony\Component\HttpFoundation\Request;
17+
use Symfony\Component\HttpFoundation\Response;
18+
use Symfony\Component\HttpFoundation\Session\Session;
19+
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
20+
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
21+
use Symfony\Component\HttpKernel\EventListener\AbstractSessionListener;
22+
use Symfony\Component\HttpKernel\EventListener\SessionListener;
23+
use Symfony\Component\HttpKernel\HttpKernelInterface;
24+
25+
class SessionListenerTest extends TestCase
26+
{
27+
public function testOnlyTriggeredOnMasterRequest()
28+
{
29+
$listener = $this->getMockForAbstractClass(AbstractSessionListener::class);
30+
$event = $this->getMockBuilder(GetResponseEvent::class)->disableOriginalConstructor()->getMock();
31+
$event->expects($this->once())->method('isMasterRequest')->willReturn(false);
32+
$event->expects($this->never())->method('getRequest');
33+
34+
// sub request
35+
$listener->onKernelRequest($event);
36+
}
37+
38+
public function testSessionIsSet()
39+
{
40+
$session = $this->getMockBuilder(Session::class)->disableOriginalConstructor()->getMock();
41+
42+
$container = new Container();
43+
$container->set('session', $session);
44+
45+
$request = new Request();
46+
$listener = new SessionListener($container);
47+
48+
$event = $this->getMockBuilder(GetResponseEvent::class)->disableOriginalConstructor()->getMock();
49+
$event->expects($this->once())->method('isMasterRequest')->willReturn(true);
50+
$event->expects($this->once())->method('getRequest')->willReturn($request);
51+
52+
$listener->onKernelRequest($event);
53+
54+
$this->assertTrue($request->hasSession());
55+
$this->assertSame($session, $request->getSession());
56+
}
57+
58+
public function testResponseIsPrivate()
59+
{
60+
$session = $this->getMockBuilder(Session::class)->disableOriginalConstructor()->getMock();
61+
$session->expects($this->once())->method('hasStarted')->willReturn(true);
62+
63+
$container = new Container();
64+
$container->set('initialized_session', $session);
65+
66+
$listener = new SessionListener($container);
67+
$kernel = $this->getMockBuilder(HttpKernelInterface::class)->disableOriginalConstructor()->getMock();
68+
69+
$response = new Response();
70+
$listener->onKernelResponse(new FilterResponseEvent($kernel, new Request(), HttpKernelInterface::MASTER_REQUEST, $response));
71+
72+
$this->assertTrue($response->headers->hasCacheControlDirective('private'));
73+
$this->assertTrue($response->headers->hasCacheControlDirective('must-revalidate'));
74+
$this->assertSame('0', $response->headers->getCacheControlDirective('max-age'));
75+
}
76+
77+
public function testUninitilizedSession()
78+
{
79+
$event = $this->getMockBuilder(FilterResponseEvent::class)->disableOriginalConstructor()->getMock();
80+
$event->expects($this->once())->method('isMasterRequest')->willReturn(true);
81+
82+
$listener = new SessionListener(new Container());
83+
$listener->onKernelResponse($event);
84+
}
85+
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ private function sessionMustBeSaved()
131131

132132
private function sessionHasBeenStarted()
133133
{
134+
$this->session->expects($this->any())
135+
->method('hasStarted')
136+
->will($this->returnValue(true));
134137
$this->session->expects($this->once())
135138
->method('isStarted')
136139
->will($this->returnValue(true));

src/Symfony/Component/HttpKernel/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"require": {
1919
"php": "^5.5.9|>=7.0.8",
2020
"symfony/event-dispatcher": "~2.8|~3.0|~4.0",
21-
"symfony/http-foundation": "^3.3.11|~4.0",
21+
"symfony/http-foundation": "^3.4.4|^4.0.4",
2222
"symfony/debug": "~2.8|~3.0|~4.0",
2323
"psr/log": "~1.0"
2424
},

0 commit comments

Comments
 (0)