8000 [FrameworkBundle][HttpFoundation] Add `_stateless` · symfony/symfony@b8e83bb · GitHub
[go: up one dir, main page]

Skip to content

Commit b8e83bb

Browse files
committed
[FrameworkBundle][HttpFoundation] Add _stateless
1 parent 2b68d53 commit b8e83bb

File tree

7 files changed

+94
-5
lines changed

7 files changed

+94
-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
@@ -66,7 +66,9 @@
6666
<argument type="service_locator">
6767
<argument key="session" type="service" id="session" on-invalid="ignore" />
6868
<argument key="initialized_session" type="service" id="session" on-invalid="ignore_uninitialized" />
69+
<argument key="logger" type="service" id="logger" on-invalid="ignore" />
6970
</argument>
71+
<argument>%kernel.debug%</argument>
7072
</service>
7173

7274
<!-- for BC -->

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,7 @@ public function testNullSessionHandler()
504504
$this->assertNull($container->getDefinition('session.storage.native')->getArgument(1));
505505
$this->assertNull($container->getDefinition('session.storage.php_bridge')->getArgument(0));
506506

507-
$expected = ['session', 'initialized_session'];
507+
$expected = ['session', 'initialized_session', 'logger'];
508508
$this->assertEquals($expected, array_keys($container->getDefinition('session_listener')->getArgument(0)->getValues()));
509509
}
510510

@@ -1301,7 +1301,7 @@ public function testSessionCookieSecureAuto()
13011301
{
13021302
$container = $this->createContainerFromFile('session_cookie_secure_auto');
13031303

1304-
$expected = ['session', 'initialized_session', 'session_storage', 'request_stack'];
1304+
$expected = ['session', 'initialized_session', 'logger', 'session_storage', 'request_stack'];
13051305
$this->assertEquals($expected, array_keys($container->getDefinition('session_listener')->getArgument(0)->getValues()));
13061306
}
13071307

src/Symfony/Component/HttpKernel/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CHANGELOG
55
-----
66

77
* allowed using public aliases to reference controllers
8+
* added session usage reporting for request with `true` `_stateless` attribute
89

910
5.0.0
1011
-----

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
1919
use Symfony\Component\HttpKernel\Event\RequestEvent;
2020
use Symfony\Component\HttpKernel\Event\ResponseEvent;
21+
use Symfony\Component\HttpKernel\Exception\UnexpectedSessionUsageException;
2122
use Symfony\Component\HttpKernel\KernelEvents;
2223

2324
/**
@@ -40,11 +41,13 @@ abstract class AbstractSessionListener implements EventSubscriberInterface
4041
const NO_AUTO_CACHE_CONTROL_HEADER = 'Symfony-Session-NoAutoCacheControl';
4142

4243
protected $container;
44+
private $debug;
4345
private $sessionUsageStack = [];
4446

45-
public function __construct(ContainerInterface $container = null)
47+
public function __construct(ContainerInterface $container = null, bool $debug = true)
4648
{
4749
$this->container = $container;
50+
$this->debug = $debug;
4851
}
4952

5053
public function onKernelRequest(RequestEvent $event)
@@ -83,6 +86,10 @@ public function onKernelResponse(ResponseEvent $event)
8386
}
8487

8588
if ($session instanceof Session ? $session->getUsageIndex() !== end($this->sessionUsageStack) : $session->isStarted()) {
89+
if ($event->getRequest()->attributes->get('_stateless', false)) {
90+
$this->reportUnexpectedSessionUse();
91+
}
92+
8693
if ($autoCacheControl) {
8794
$response
8895
->setExpires(new \DateTime())
@@ -145,4 +152,21 @@ public static function getSubscribedEvents(): array
145152
* @return SessionInterface|null A SessionInterface instance or null if no session is available
146153
*/
147154
abstract protected function getSession();
155+
156+
/**
157+
* Report that the session was unexpectedly used.
158+
*
159+
* @throws UnexpectedSessionUsageException
160+
*/
161+
private function reportUnexpectedSessionUse(): void
162+
{
163+
$message = 'Session was used while the request was declared stateless.';
164+
if ($this->debug) {
165+
throw new UnexpectedSessionUsageException($message);
166+
}
167+
168+
if ($this->container->has('logger')) {
169+
$this->container->get('logger')->warning($message);
170+
}
171+
}
148172
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@
2828
*/
2929
class SessionListener extends AbstractSessionListener
3030
{
31-
public function __construct(ContainerInterface $container)
31+
public function __construct(ContainerInterface $container, bool $debug = true)
3232
{
33-
$this->container = $container;
33+
parent::__construct($container, $debug);
3434
}
3535

3636
protected function getSession(): ?SessionInterface
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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+
namespace Symfony\Component\HttpKernel\Exception;
13+
14+
/**
15+
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
16+
*/
17+
class UnexpectedSessionUsageException extends \LogicException
18+
{
19+
}

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

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

1414
use PHPUnit\Framework\TestCase;
15+
use Psr\Log\LoggerInterface;
1516
use Symfony\Component\DependencyInjection\Container;
1617
use Symfony\Component\DependencyInjection\ServiceLocator;
1718
use Symfony\Component\HttpFoundation\Request;
@@ -24,6 +25,7 @@
2425
use Symfony\Component\HttpKernel\Event\ResponseEvent;
2526
use Symfony\Component\HttpKernel\EventListener\AbstractSessionListener;
2627
use Symfony\Component\HttpKernel\EventListener\SessionListener;
28+
use Symfony\Component\HttpKernel\Exception\UnexpectedSessionUsageException;
2729
use Symfony\Component\HttpKernel\HttpKernelInterface;
2830

2931
class SessionListenerTest extends TestCase
@@ -178,4 +180,45 @@ public function testSurrogateMasterRequestIsPublic()
178180
$this->assertTrue($response->headers->has('Expires'));
179181
$this->assertLessThanOrEqual((new \DateTime('now', new \DateTimeZone('UTC'))), (new \DateTime($response->headers->get('Expires'))));
180182
}
183+
184+
public function testSessionUsageExceptionIfStatelessAndSessionUsed()
185+
{
186+
$session = $this->getMockBuilder(Session::class)->disableOriginalConstructor()->getMock();
187+
$session->expects($this->exactly(2))->method('getUsageIndex')->will($this->onConsecutiveCalls(0, 1));
188+
189+
$container = new Container();
190+
$container->set('initialized_session', $session);
191+
192+
$listener = new SessionListener($container, true);
193+
$kernel = $this->getMockBuilder(HttpKernelInterface::class)->disableOriginalConstructor()->getMock();
194+
195+
$request = new Request();
196+
$request->attributes->set('_stateless', true);
197+
$listener->onKernelRequest(new RequestEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST));
198+
199+
$this->expectException(UnexpectedSessionUsageException::class);
200+
$listener->onKernelResponse(new ResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST, new Response()));
201+
}
202+
203+
public function testSessionUsageLogIfStatelessAndSessionUsed()
204+
{
205+
$session = $this->getMockBuilder(Session::class)->disableOriginalConstructor()->getMock();
206+
$session->expects($this->exactly(2))->method('getUsageIndex')->will($this->onConsecutiveCalls(0, 1));
207+
208+
$logger = $this->getMockBuilder(LoggerInterface::class)->disableOriginalConstructor()->getMock();
209+
$logger->expects($this->exactly(1))->method('warning');
210+
211+
$container = new Container();
212+
$container->set('initialized_session', $session);
213+
$containe 8400 r->set('logger', $logger);
214+
215+
$listener = new SessionListener($container, false);
216+
$kernel = $this->getMockBuilder(HttpKernelInterface::class)->disableOriginalConstructor()->getMock();
217+
218+
$request = new Request();
219+
$request->attributes->set('_stateless', true);
220+
$listener->onKernelRequest(new RequestEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST));
221+
222+
$listener->onKernelResponse(new ResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST, new Response()));
223+
}
181224
}

0 commit comments

Comments
 (0)
0