diff --git a/src/Bridge/Symfony/SymfonyAdapter.php b/src/Bridge/Symfony/SymfonyAdapter.php index 228464335..429d7a12d 100644 --- a/src/Bridge/Symfony/SymfonyAdapter.php +++ b/src/Bridge/Symfony/SymfonyAdapter.php @@ -9,6 +9,8 @@ use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory; use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory; use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\TerminableInterface; @@ -31,55 +33,65 @@ public function __construct(HttpKernelInterface $httpKernel) public function handle(ServerRequestInterface $request): ResponseInterface { - $httpFoundationFactory = new HttpFoundationFactory; + $symfonyRequest = (new HttpFoundationFactory)->createRequest($request); - $symfonyRequest = $httpFoundationFactory->createRequest($request); - - $this->loadSessionFromCookies($symfonyRequest); + $requestSessionId = $this->loadSessionFromRequest($symfonyRequest); $symfonyResponse = $this->httpKernel->handle($symfonyRequest); - $this->addSessionCookieToResponse($symfonyResponse); + $this->addSessionCookieToResponseIfChanged($requestSessionId, $symfonyResponse); if ($this->httpKernel instanceof TerminableInterface) { $this->httpKernel->terminate($symfonyRequest, $symfonyResponse); } - $psr7Factory = new DiactorosFactory; - $response = $psr7Factory->createResponse($symfonyResponse); - - return $response; + return (new DiactorosFactory)->createResponse($symfonyResponse); } - /** - * @param $symfonyRequest - */ - private function loadSessionFromCookies($symfonyRequest): void + private function loadSessionFromRequest(Request $symfonyRequest): ?string { - if (!is_null($symfonyRequest->cookies->get(session_name()))) { - $this->httpKernel->getContainer()->get('session')->setId( - $symfonyRequest->cookies->get(session_name()) - ); + if ($this->hasSessionsDisabled()) { + return null; } + + $this->httpKernel->getContainer()->get('session')->setId( + $sessionId = $symfonyRequest->cookies->get(session_name()) + ); + + return $sessionId; } - /** - * @param $symfonyResponse - */ - private function addSessionCookieToResponse($symfonyResponse): void + private function addSessionCookieToResponseIfChanged(?string $requestSessionId, Response $symfonyResponse): void { + if ($this->hasSessionsDisabled()) { + return; + } + + $responseSessionId = $this->httpKernel->getContainer()->get('session')->getId(); + + if ($requestSessionId === $responseSessionId) { + return; + } + + $cookie = session_get_cookie_params(); + $symfonyResponse->headers->setCookie( new Cookie( session_name(), - $this->httpKernel->getContainer()->get('session')->getId(), - 0, - "/", - null, + $responseSessionId, + $cookie['lifetime'], + $cookie['path'], + $cookie['domain'], + $cookie['secure'], + $cookie['httponly'], false, - true, - false, - Cookie::SAMESITE_LAX + $cookie['samesite'] ?? null ) ); } + + private function hasSessionsDisabled(): bool + { + return false === $this->httpKernel->getContainer()->has('session'); + } } diff --git a/tests/Bridge/Symfony/SymfonyAdapterTest.php b/tests/Bridge/Symfony/SymfonyAdapterTest.php index bec0860cd..575d72dcc 100644 --- a/tests/Bridge/Symfony/SymfonyAdapterTest.php +++ b/tests/Bridge/Symfony/SymfonyAdapterTest.php @@ -11,6 +11,7 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -22,6 +23,10 @@ class SymfonyAdapterTest extends TestCase { + private const ROUTE_WITHOUT_SESSION = '/'; + private const ROUTE_WITH_SESSION = '/session'; + private const ROUTE_NOT_FOUND = '/not-found'; + public function setUp() { parent::setUp(); @@ -33,38 +38,55 @@ public function setUp() public function test Symfony applications are adapted() { $adapter = new SymfonyAdapter($this->createKernel()); - $response = $adapter->handle(new ServerRequest([], [], '/foo')); - self::assertSame('Hello world!', (string) $response->getBody()); + $response = $adapter->handle(new ServerRequest([], [], self::ROUTE_WITHOUT_SESSION)); + + self::assertEquals(200, $response->getStatusCode()); + self::assertEquals('Hello world!', (string) $response->getBody()); } public function test 404 are PSR7 responses and not exceptions() { $adapter = new SymfonyAdapter($this->createKernel()); - $response = $adapter->handle(new ServerRequest([], [], '/bar')); - self::assertSame(404, $response->getStatusCode()); + $response = $adapter->handle(new ServerRequest([], [], self::ROUTE_NOT_FOUND)); + + self::assertEquals(404, $response->getStatusCode()); + self::assertEquals('Not found', (string) $response->getBody()); } - public function test an active session is created() + public function test a session is not created when sessions not used() { $adapter = new SymfonyAdapter($this->createKernel()); - $response = $adapter->handle(new ServerRequest([], [], '/bar')); - self::assertArrayHasKey('Set-Cookie', $response->getHeaders()); + $response = $adapter->handle(new ServerRequest([], [], self::ROUTE_WITHOUT_SESSION)); + + self::assertArrayNotHasKey('Set-Cookie', $response->getHeaders()); } - public function test an active session is retrieved() + public function test an active session is created when sessions used() { - $kernel = $this->createKernel(); - $kernel->boot(); + $adapter = new SymfonyAdapter($kernel = $this->createKernel()); + + $response = $adapter->handle(new ServerRequest([], [], self::ROUTE_WITH_SESSION)); + + $symfonySessionId = $kernel->getContainer()->get('session')->getId(); + self::assertEquals($symfonySessionId, (string) $response->getBody()); + self::assertEquals( + sprintf("%s=%s; path=/", \session_name(), $symfonySessionId), + $response->getHeaders()['Set-Cookie'][0] + ); + } + + public function test an existing session is used when session provided() + { + $adapter = new SymfonyAdapter($this->createKernel()); - $adapter = new SymfonyAdapter($kernel); - $symfonyResponse = $adapter->handle( + $response = $adapter->handle( new ServerRequest( [], [], - '/bar', + self::ROUTE_WITH_SESSION, null, 'php://input', [], @@ -72,15 +94,13 @@ public function test an active session is retrieved() ) ); - self::assertSame( - sprintf("%s=SESSIONID; path=/; httponly; samesite=lax", \session_name()), - $symfonyResponse->getHeaders()['Set-Cookie'][0] - ); + self::assertArrayNotHasKey('Set-Cookie', $response->getHeaders()); + self::assertEquals('SESSIONID', (string) $response->getBody()); } private function createKernel(): HttpKernelInterface { - return new class('dev', false) extends Kernel implements EventSubscriberInterface { + $kernel = new class('dev', false) extends Kernel implements EventSubscriberInterface { use MicroKernelTrait; public function registerBundles() @@ -102,14 +122,22 @@ protected function configureContainer(ContainerBuilder $c) protected function configureRoutes(RouteCollectionBuilder $routes) { - $routes->add('/foo', 'kernel:testAction'); + $routes->add('/', 'kernel:testActionWithoutSession'); + $routes->add('/session', 'kernel:testActionWithSession'); } - public function testAction() + public function testActionWithoutSession() { return new Response('Hello world!'); } + public function testActionWithSession(Session $session) + { + $session->set('ACTIVATE', 'SESSIONS'); // ensure that Symfony starts/uses sessions + + return new Response($session->getId()); + } + public static function getSubscribedEvents() { return [KernelEvents::EXCEPTION => 'onKernelException']; @@ -125,5 +153,9 @@ public function onKernelException(GetResponseForExceptionEvent $event) } } }; + + $kernel->boot(); + + return $kernel; } }