Skip to content

Commit

Permalink
Merge pull request #64 from eddmann/symfony-cookie-updates
Browse files Browse the repository at this point in the history
Only `Set-Cookie` when Symfony session id changed
  • Loading branch information
mnapoli authored Sep 21, 2018
2 parents 622ebd8 + 1588cc2 commit 2d42ce7
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 48 deletions.
68 changes: 40 additions & 28 deletions src/Bridge/Symfony/SymfonyAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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');
}
}
72 changes: 52 additions & 20 deletions tests/Bridge/Symfony/SymfonyAdapterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
Expand All @@ -33,54 +38,69 @@ 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 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',
[],
[\session_name() => 'SESSIONID']
)
);

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()
Expand All @@ -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'];
Expand All @@ -125,5 +153,9 @@ public function onKernelException(GetResponseForExceptionEvent $event)
}
}
};

$kernel->boot();

return $kernel;
}
}

0 comments on commit 2d42ce7

Please sign in to comment.