Don't save sessions permanently on api and metrics paths

closes #530 (Session on API calls)
This commit is contained in:
Igor Scheller 2018-12-28 03:28:33 +01:00 committed by msquare
parent 7b3901211a
commit 491ee37651
7 changed files with 195 additions and 2 deletions

View File

@ -24,6 +24,7 @@ return [
\Engelsystem\Renderer\TwigServiceProvider::class, \Engelsystem\Renderer\TwigServiceProvider::class,
\Engelsystem\Middleware\RouteDispatcherServiceProvider::class, \Engelsystem\Middleware\RouteDispatcherServiceProvider::class,
\Engelsystem\Middleware\RequestHandlerServiceProvider::class, \Engelsystem\Middleware\RequestHandlerServiceProvider::class,
\Engelsystem\Middleware\SessionHandlerServiceProvider::class,
// Additional services // Additional services
\Engelsystem\Mail\MailerServiceProvider::class, \Engelsystem\Mail\MailerServiceProvider::class,
@ -43,6 +44,7 @@ return [
\Engelsystem\Middleware\ErrorHandler::class, \Engelsystem\Middleware\ErrorHandler::class,
\Engelsystem\Middleware\VerifyCsrfToken::class, \Engelsystem\Middleware\VerifyCsrfToken::class,
\Engelsystem\Middleware\RouteDispatcher::class, \Engelsystem\Middleware\RouteDispatcher::class,
\Engelsystem\Middleware\SessionHandler::class,
// Handle request // Handle request
\Engelsystem\Middleware\RequestHandler::class, \Engelsystem\Middleware\RequestHandler::class,

View File

@ -50,7 +50,8 @@ class RouteDispatcher implements MiddlewareInterface
$path = $request->getPathInfo(); $path = $request->getPathInfo();
} }
$route = $this->dispatcher->dispatch($request->getMethod(), urldecode($path)); $path = urldecode($path);
$route = $this->dispatcher->dispatch($request->getMethod(), $path);
$status = $route[0]; $status = $route[0];
if ($status == FastRouteDispatcher::NOT_FOUND) { if ($status == FastRouteDispatcher::NOT_FOUND) {
@ -70,6 +71,7 @@ class RouteDispatcher implements MiddlewareInterface
$routeHandler = $route[1]; $routeHandler = $route[1];
$request = $request->withAttribute('route-request-handler', $routeHandler); $request = $request->withAttribute('route-request-handler', $routeHandler);
$request = $request->withAttribute('route-request-path', $path);
$vars = $route[2]; $vars = $route[2];
foreach ($vars as $name => $value) { foreach ($vars as $name => $value) {

View File

@ -0,0 +1,59 @@
<?php
namespace Engelsystem\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface;
class SessionHandler implements MiddlewareInterface
{
/** @var SessionStorageInterface */
protected $session;
/** @var string[] */
protected $paths = [];
/**
* @param SessionStorageInterface $session
* @param array $paths
*/
public function __construct(SessionStorageInterface $session, array $paths = [])
{
$this->paths = $paths;
$this->session = $session;
}
/**
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$return = $handler->handle($request);
$cookies = $request->getCookieParams();
if (
$this->session instanceof NativeSessionStorage
&& in_array($request->getAttribute('route-request-path'), $this->paths)
&& !isset($cookies[$this->session->getName()])
) {
$this->destroyNative();
}
return $return;
}
/**
* @return bool
* @codeCoverageIgnore
*/
protected function destroyNative()
{
return session_destroy();
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace Engelsystem\Middleware;
use Engelsystem\Container\ServiceProvider;
class SessionHandlerServiceProvider extends ServiceProvider
{
public function register()
{
$this->app
->when(SessionHandler::class)
->needs('$paths')
->give(function () {
return [
'/api',
'/ical',
'/metrics',
'/shifts-json-export',
'/stats',
];
});
}
}

View File

@ -32,10 +32,11 @@ class RouteDispatcherTest extends TestCase
->with('HEAD', '/foo!bar') ->with('HEAD', '/foo!bar')
->willReturn([FastRouteDispatcher::FOUND, $handler, ['foo' => 'bar', 'lorem' => 'ipsum']]); ->willReturn([FastRouteDispatcher::FOUND, $handler, ['foo' => 'bar', 'lorem' => 'ipsum']]);
$request->expects($this->exactly(3)) $request->expects($this->exactly(4))
->method('withAttribute') ->method('withAttribute')
->withConsecutive( ->withConsecutive(
['route-request-handler', $handler], ['route-request-handler', $handler],
['route-request-path', '/foo!bar'],
['foo', 'bar'], ['foo', 'bar'],
['lorem', 'ipsum'] ['lorem', 'ipsum']
) )

View File

@ -0,0 +1,44 @@
<?php
namespace Engelsystem\Test\Unit\Middleware;
use Engelsystem\Middleware\SessionHandler;
use Engelsystem\Middleware\SessionHandlerServiceProvider;
use Engelsystem\Test\Unit\ServiceProviderTest;
use Illuminate\Contracts\Container\ContextualBindingBuilder;
use PHPUnit\Framework\MockObject\MockObject;
class SessionHandlerServiceProviderTest extends ServiceProviderTest
{
/**
* @covers \Engelsystem\Middleware\SessionHandlerServiceProvider::register()
*/
public function testRegister()
{
/** @var ContextualBindingBuilder|MockObject $bindingBuilder */
$bindingBuilder = $this->createMock(ContextualBindingBuilder::class);
$app = $this->getApp(['when']);
$app->expects($this->once())
->method('when')
->with(SessionHandler::class)
->willReturn($bindingBuilder);
$bindingBuilder->expects($this->once())
->method('needs')
->with('$paths')
->willReturn($bindingBuilder);
$bindingBuilder->expects($this->once())
->method('give')
->willReturnCallback(function (callable $callable) {
$paths = $callable();
$this->assertTrue(is_array($paths));
$this->assertTrue(in_array('/metrics', $paths));
});
$serviceProvider = new SessionHandlerServiceProvider($app);
$serviceProvider->register();
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace Engelsystem\Test\Unit\Middleware;
use Engelsystem\Middleware\SessionHandler;
use PHPUnit\Framework\TestCase;
use PHPUnit_Framework_MockObject_MockObject as MockObject;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
class SessionHandlerTest extends TestCase
{
/**
* @covers \Engelsystem\Middleware\SessionHandler::__construct
* @covers \Engelsystem\Middleware\SessionHandler::process
*/
public function testProcess()
{
/** @var NativeSessionStorage|MockObject $sessionStorage */
$sessionStorage = $this->createMock(NativeSessionStorage::class);
/** @var ServerRequestInterface|MockObject $request */
$request = $this->getMockForAbstractClass(ServerRequestInterface::class);
/** @var RequestHandlerInterface|MockObject $handler */
$handler = $this->getMockForAbstractClass(RequestHandlerInterface::class);
/** @var ResponseInterface|MockObject $response */
$response = $this->getMockForAbstractClass(ResponseInterface::class);
$handler->expects($this->exactly(2))
->method('handle')
->with($request)
->willReturn($response);
$request->expects($this->exactly(2))
->method('getCookieParams')
->willReturnOnConsecutiveCalls([], ['SESSION' => 'BlaFoo']);
$request->expects($this->exactly(2))
->method('getAttribute')
->with('route-request-path')
->willReturn('/foo');
$sessionStorage->expects($this->exactly(2))
->method('getName')
->willReturn('SESSION');
/** @var SessionHandler|MockObject $middleware */
$middleware = $this->getMockBuilder(SessionHandler::class)
->setConstructorArgs([$sessionStorage, ['/foo']])
->setMethods(['destroyNative'])
->getMock();
$middleware->expects($this->once())
->method('destroyNative')
->willReturn(true);
$middleware->process($request, $handler);
$middleware->process($request, $handler);
}
}