Added csrf middleware
This commit is contained in:
parent
8236989be0
commit
23c0fae36f
|
@ -40,6 +40,7 @@ return [
|
||||||
|
|
||||||
// The application code
|
// The application code
|
||||||
\Engelsystem\Middleware\ErrorHandler::class,
|
\Engelsystem\Middleware\ErrorHandler::class,
|
||||||
|
\Engelsystem\Middleware\VerifyCsrfToken::class,
|
||||||
\Engelsystem\Middleware\RouteDispatcher::class,
|
\Engelsystem\Middleware\RouteDispatcher::class,
|
||||||
|
|
||||||
// Handle request
|
// Handle request
|
||||||
|
|
|
@ -44,6 +44,7 @@ function admin_user()
|
||||||
$html .= '<form action="'
|
$html .= '<form action="'
|
||||||
. page_link_to('admin_user', ['action' => 'save', 'id' => $user_id])
|
. page_link_to('admin_user', ['action' => 'save', 'id' => $user_id])
|
||||||
. '" method="post">' . "\n";
|
. '" method="post">' . "\n";
|
||||||
|
$html .= form_csrf();
|
||||||
$html .= '<table border="0">' . "\n";
|
$html .= '<table border="0">' . "\n";
|
||||||
$html .= '<input type="hidden" name="Type" value="Normal">' . "\n";
|
$html .= '<input type="hidden" name="Type" value="Normal">' . "\n";
|
||||||
$html .= '<tr><td>' . "\n";
|
$html .= '<tr><td>' . "\n";
|
||||||
|
@ -105,6 +106,7 @@ function admin_user()
|
||||||
$html .= 'Hier kannst Du das Passwort dieses Engels neu setzen:<form action="'
|
$html .= 'Hier kannst Du das Passwort dieses Engels neu setzen:<form action="'
|
||||||
. page_link_to('admin_user', ['action' => 'change_pw', 'id' => $user_id])
|
. page_link_to('admin_user', ['action' => 'change_pw', 'id' => $user_id])
|
||||||
. '" method="post">' . "\n";
|
. '" method="post">' . "\n";
|
||||||
|
$html .= form_csrf();
|
||||||
$html .= '<table>' . "\n";
|
$html .= '<table>' . "\n";
|
||||||
$html .= ' <tr><td>Passwort</td><td>' . '<input type="password" size="40" name="new_pw" value="" class="form-control"></td></tr>' . "\n";
|
$html .= ' <tr><td>Passwort</td><td>' . '<input type="password" size="40" name="new_pw" value="" class="form-control"></td></tr>' . "\n";
|
||||||
$html .= ' <tr><td>Wiederholung</td><td>' . '<input type="password" size="40" name="new_pw2" value="" class="form-control"></td></tr>' . "\n";
|
$html .= ' <tr><td>Wiederholung</td><td>' . '<input type="password" size="40" name="new_pw2" value="" class="form-control"></td></tr>' . "\n";
|
||||||
|
@ -135,6 +137,7 @@ function admin_user()
|
||||||
$html .= 'Hier kannst Du die Benutzergruppen des Engels festlegen:<form action="'
|
$html .= 'Hier kannst Du die Benutzergruppen des Engels festlegen:<form action="'
|
||||||
. page_link_to('admin_user', ['action' => 'save_groups', 'id' => $user_id])
|
. page_link_to('admin_user', ['action' => 'save_groups', 'id' => $user_id])
|
||||||
. '" method="post">' . "\n";
|
. '" method="post">' . "\n";
|
||||||
|
$html .= form_csrf();
|
||||||
$html .= '<table>';
|
$html .= '<table>';
|
||||||
|
|
||||||
$groups = DB::select('
|
$groups = DB::select('
|
||||||
|
|
|
@ -407,7 +407,18 @@ function form_element($label, $input, $for = '')
|
||||||
*/
|
*/
|
||||||
function form($elements, $action = '')
|
function form($elements, $action = '')
|
||||||
{
|
{
|
||||||
return '<form action="' . $action . '" enctype="multipart/form-data" method="post">' . join($elements) . '</form>';
|
return '<form action="' . $action . '" enctype="multipart/form-data" method="post">'
|
||||||
|
. form_csrf()
|
||||||
|
. join($elements)
|
||||||
|
. '</form>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function form_csrf()
|
||||||
|
{
|
||||||
|
return form_hidden('_token', session()->get('_token'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -14,3 +14,7 @@ require('./moment-countdown');
|
||||||
$(function () {
|
$(function () {
|
||||||
moment.locale($('html').attr('lang'));
|
moment.locale($('html').attr('lang'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$.ajaxSetup({
|
||||||
|
headers: {'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')}
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
{% extends "errors/default.twig" %}
|
||||||
|
|
||||||
|
{% block title %}{{ __("Authentication expired") }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="alert alert-warning">{{ __("The provided CSRF token is invalid or has expired") }}</div>
|
||||||
|
{% endblock %}
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
<meta charset="UTF-8"/>
|
<meta charset="UTF-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="{{ asset('assets/theme' ~ theme ~ '.css') }}"/>
|
<link rel="stylesheet" type="text/css" href="{{ asset('assets/theme' ~ theme ~ '.css') }}"/>
|
||||||
<script type="text/javascript" src="{{ asset('assets/vendor.js') }}"></script>
|
<script type="text/javascript" src="{{ asset('assets/vendor.js') }}"></script>
|
||||||
|
|
|
@ -96,7 +96,7 @@ class Response extends SymfonyResponse implements ResponseInterface
|
||||||
/**
|
/**
|
||||||
* Return an instance with the rendered content.
|
* Return an instance with the rendered content.
|
||||||
*
|
*
|
||||||
* THis method retains the immutability of the message and returns
|
* This method retains the immutability of the message and returns
|
||||||
* an instance with the updated status and headers
|
* an instance with the updated status and headers
|
||||||
*
|
*
|
||||||
* @param string $view
|
* @param string $view
|
||||||
|
@ -111,6 +111,14 @@ class Response extends SymfonyResponse implements ResponseInterface
|
||||||
throw new \InvalidArgumentException('Renderer not defined');
|
throw new \InvalidArgumentException('Renderer not defined');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->create($this->view->render($view, $data), $status, $headers);
|
$new = clone $this;
|
||||||
|
$new->setContent($this->view->render($view, $data));
|
||||||
|
$new->setStatusCode($status, ($status == $this->getStatusCode() ? $this->statusText : null));
|
||||||
|
|
||||||
|
foreach ($headers as $key => $values) {
|
||||||
|
$new = $new->withAddedHeader($key, $values);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $new;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,9 @@ namespace Engelsystem\Http;
|
||||||
use Engelsystem\Config\Config;
|
use Engelsystem\Config\Config;
|
||||||
use Engelsystem\Container\ServiceProvider;
|
use Engelsystem\Container\ServiceProvider;
|
||||||
use Engelsystem\Http\SessionHandlers\DatabaseHandler;
|
use Engelsystem\Http\SessionHandlers\DatabaseHandler;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use Symfony\Component\HttpFoundation\Session\Session;
|
use Symfony\Component\HttpFoundation\Session\Session;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||||
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
|
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
|
||||||
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
|
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
|
||||||
use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface;
|
use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface;
|
||||||
|
@ -21,6 +23,11 @@ class SessionServiceProvider extends ServiceProvider
|
||||||
$session = $this->app->make(Session::class);
|
$session = $this->app->make(Session::class);
|
||||||
$this->app->instance(Session::class, $session);
|
$this->app->instance(Session::class, $session);
|
||||||
$this->app->instance('session', $session);
|
$this->app->instance('session', $session);
|
||||||
|
$this->app->bind(SessionInterface::class, Session::class);
|
||||||
|
|
||||||
|
if (!$session->has('_token')) {
|
||||||
|
$session->set('_token', Str::random(42));
|
||||||
|
}
|
||||||
|
|
||||||
/** @var Request $request */
|
/** @var Request $request */
|
||||||
$request = $this->app->get('request');
|
$request = $this->app->get('request');
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
<?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\SessionInterface;
|
||||||
|
|
||||||
|
class VerifyCsrfToken implements MiddlewareInterface
|
||||||
|
{
|
||||||
|
/** @var SessionInterface */
|
||||||
|
protected $session;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param SessionInterface $session
|
||||||
|
*/
|
||||||
|
public function __construct(SessionInterface $session)
|
||||||
|
{
|
||||||
|
$this->session = $session;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify csrf tokens
|
||||||
|
*
|
||||||
|
* @param ServerRequestInterface $request
|
||||||
|
* @param RequestHandlerInterface $handler
|
||||||
|
* @return ResponseInterface
|
||||||
|
*/
|
||||||
|
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
$this->isReading($request)
|
||||||
|
|| $this->tokensMatch($request)
|
||||||
|
) {
|
||||||
|
return $handler->handle($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->notAuthorizedResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ServerRequestInterface $request
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function isReading(ServerRequestInterface $request): bool
|
||||||
|
{
|
||||||
|
return in_array(
|
||||||
|
$request->getMethod(),
|
||||||
|
['GET', 'HEAD', 'OPTIONS']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ServerRequestInterface $request
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function tokensMatch(ServerRequestInterface $request): bool
|
||||||
|
{
|
||||||
|
$token = null;
|
||||||
|
$body = $request->getParsedBody();
|
||||||
|
$header = $request->getHeader('X-CSRF-TOKEN');
|
||||||
|
|
||||||
|
if (is_array($body) && isset($body['_token'])) {
|
||||||
|
$token = $body['_token'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($header)) {
|
||||||
|
$header = array_shift($header);
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $token ?: $header;
|
||||||
|
$sessionToken = $this->session->get('_token');
|
||||||
|
|
||||||
|
return is_string($token)
|
||||||
|
&& is_string($sessionToken)
|
||||||
|
&& hash_equals($sessionToken, $token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ResponseInterface
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
protected function notAuthorizedResponse(): ResponseInterface
|
||||||
|
{
|
||||||
|
// The 419 code is used as "Page Expired" to differentiate from a 401 (not authorized)
|
||||||
|
return response()->withStatus(419, 'Authentication Token Mismatch');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Engelsystem\Renderer\Twig\Extensions;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||||
|
use Twig_Extension as TwigExtension;
|
||||||
|
use Twig_Function as TwigFunction;
|
||||||
|
|
||||||
|
class Csrf extends TwigExtension
|
||||||
|
{
|
||||||
|
/** @var SessionInterface */
|
||||||
|
protected $session;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param SessionInterface $session
|
||||||
|
*/
|
||||||
|
public function __construct(SessionInterface $session)
|
||||||
|
{
|
||||||
|
$this->session = $session;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return TwigFunction[]
|
||||||
|
*/
|
||||||
|
public function getFunctions()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new TwigFunction('csrf', [$this, 'getCsrfField'], ['is_safe' => ['html']]),
|
||||||
|
new TwigFunction('csrf_token', [$this, 'getCsrfToken']),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getCsrfField()
|
||||||
|
{
|
||||||
|
return sprintf('<input type="hidden" name="_token" value="%s">', $this->getCsrfToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getCsrfToken()
|
||||||
|
{
|
||||||
|
return $this->session->get('_token');
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,9 +4,10 @@ namespace Engelsystem\Renderer;
|
||||||
|
|
||||||
use Engelsystem\Config\Config as EngelsystemConfig;
|
use Engelsystem\Config\Config as EngelsystemConfig;
|
||||||
use Engelsystem\Container\ServiceProvider;
|
use Engelsystem\Container\ServiceProvider;
|
||||||
use Engelsystem\Renderer\Twig\Extensions\Authentication;
|
|
||||||
use Engelsystem\Renderer\Twig\Extensions\Assets;
|
use Engelsystem\Renderer\Twig\Extensions\Assets;
|
||||||
|
use Engelsystem\Renderer\Twig\Extensions\Authentication;
|
||||||
use Engelsystem\Renderer\Twig\Extensions\Config;
|
use Engelsystem\Renderer\Twig\Extensions\Config;
|
||||||
|
use Engelsystem\Renderer\Twig\Extensions\Csrf;
|
||||||
use Engelsystem\Renderer\Twig\Extensions\Globals;
|
use Engelsystem\Renderer\Twig\Extensions\Globals;
|
||||||
use Engelsystem\Renderer\Twig\Extensions\Legacy;
|
use Engelsystem\Renderer\Twig\Extensions\Legacy;
|
||||||
use Engelsystem\Renderer\Twig\Extensions\Session;
|
use Engelsystem\Renderer\Twig\Extensions\Session;
|
||||||
|
@ -23,6 +24,7 @@ class TwigServiceProvider extends ServiceProvider
|
||||||
'assets' => Assets::class,
|
'assets' => Assets::class,
|
||||||
'authentication' => Authentication::class,
|
'authentication' => Authentication::class,
|
||||||
'config' => Config::class,
|
'config' => Config::class,
|
||||||
|
'csrf' => Csrf::class,
|
||||||
'globals' => Globals::class,
|
'globals' => Globals::class,
|
||||||
'session' => Session::class,
|
'session' => Session::class,
|
||||||
'legacy' => Legacy::class,
|
'legacy' => Legacy::class,
|
||||||
|
|
|
@ -9,6 +9,7 @@ use Engelsystem\Http\SessionServiceProvider;
|
||||||
use Engelsystem\Test\Unit\ServiceProviderTest;
|
use Engelsystem\Test\Unit\ServiceProviderTest;
|
||||||
use PHPUnit_Framework_MockObject_MockObject as MockObject;
|
use PHPUnit_Framework_MockObject_MockObject as MockObject;
|
||||||
use Symfony\Component\HttpFoundation\Session\Session;
|
use Symfony\Component\HttpFoundation\Session\Session;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||||
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
|
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
|
||||||
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
|
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
|
||||||
use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface as StorageInterface;
|
use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface as StorageInterface;
|
||||||
|
@ -104,8 +105,16 @@ class SessionServiceProviderTest extends ServiceProviderTest
|
||||||
['driver' => 'pdo', 'name' => 'foobar']
|
['driver' => 'pdo', 'name' => 'foobar']
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->setExpects($app, 'bind', [StorageInterface::class, 'session.storage'], null, $this->atLeastOnce());
|
$app->expects($this->atLeastOnce())
|
||||||
|
->method('bind')
|
||||||
|
->withConsecutive(
|
||||||
|
[StorageInterface::class, 'session.storage'],
|
||||||
|
[SessionInterface::class, Session::class]
|
||||||
|
);
|
||||||
|
|
||||||
$this->setExpects($request, 'setSession', [$session], null, $this->atLeastOnce());
|
$this->setExpects($request, 'setSession', [$session], null, $this->atLeastOnce());
|
||||||
|
$this->setExpects($session, 'has', ['_token'], false, $this->atLeastOnce());
|
||||||
|
$this->setExpects($session, 'set', ['_token'], null, $this->atLeastOnce());
|
||||||
$this->setExpects($session, 'start', null, null, $this->atLeastOnce());
|
$this->setExpects($session, 'start', null, null, $this->atLeastOnce());
|
||||||
|
|
||||||
$serviceProvider->register();
|
$serviceProvider->register();
|
||||||
|
@ -142,10 +151,16 @@ class SessionServiceProviderTest extends ServiceProviderTest
|
||||||
[Session::class, $session],
|
[Session::class, $session],
|
||||||
['session', $session]
|
['session', $session]
|
||||||
);
|
);
|
||||||
|
$app->expects($this->atLeastOnce())
|
||||||
|
->method('bind')
|
||||||
|
->withConsecutive(
|
||||||
|
[StorageInterface::class, 'session.storage'],
|
||||||
|
[SessionInterface::class, Session::class]
|
||||||
|
);
|
||||||
|
|
||||||
$this->setExpects($app, 'bind', [StorageInterface::class, 'session.storage']);
|
|
||||||
$this->setExpects($app, 'get', ['request'], $request);
|
$this->setExpects($app, 'get', ['request'], $request);
|
||||||
$this->setExpects($request, 'setSession', [$session]);
|
$this->setExpects($request, 'setSession', [$session]);
|
||||||
|
$this->setExpects($session, 'has', ['_token'], true);
|
||||||
$this->setExpects($session, 'start');
|
$this->setExpects($session, 'start');
|
||||||
|
|
||||||
$serviceProvider = new SessionServiceProvider($app);
|
$serviceProvider = new SessionServiceProvider($app);
|
||||||
|
@ -160,7 +175,7 @@ class SessionServiceProviderTest extends ServiceProviderTest
|
||||||
$sessionStorage = $this->getMockForAbstractClass(StorageInterface::class);
|
$sessionStorage = $this->getMockForAbstractClass(StorageInterface::class);
|
||||||
return $this->getMockBuilder(Session::class)
|
return $this->getMockBuilder(Session::class)
|
||||||
->setConstructorArgs([$sessionStorage])
|
->setConstructorArgs([$sessionStorage])
|
||||||
->setMethods(['start'])
|
->setMethods(['start', 'has', 'set'])
|
||||||
->getMock();
|
->getMock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,128 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Engelsystem\Test\Unit\Middleware;
|
||||||
|
|
||||||
|
use Engelsystem\Middleware\VerifyCsrfToken;
|
||||||
|
use PHPUnit\Framework\MockObject\MockObject;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||||
|
|
||||||
|
class VerifyCsrfTokenTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @covers \Engelsystem\Middleware\VerifyCsrfToken::process
|
||||||
|
* @covers \Engelsystem\Middleware\VerifyCsrfToken::isReading
|
||||||
|
*/
|
||||||
|
public function testProcess()
|
||||||
|
{
|
||||||
|
/** @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);
|
||||||
|
|
||||||
|
/** @var VerifyCsrfToken|MockObject $middleware */
|
||||||
|
$middleware = $this->getMockBuilder(VerifyCsrfToken::class)
|
||||||
|
->disableOriginalConstructor()
|
||||||
|
->setMethods(['notAuthorizedResponse', 'tokensMatch'])
|
||||||
|
->getMock();
|
||||||
|
|
||||||
|
$middleware->expects($this->exactly(1))
|
||||||
|
->method('notAuthorizedResponse')
|
||||||
|
->willReturn($response);
|
||||||
|
|
||||||
|
$middleware->expects($this->exactly(2))
|
||||||
|
->method('tokensMatch')
|
||||||
|
->willReturnOnConsecutiveCalls(true, false);
|
||||||
|
|
||||||
|
// Results in true, false, false
|
||||||
|
$request->expects($this->exactly(3))
|
||||||
|
->method('getMethod')
|
||||||
|
->willReturnOnConsecutiveCalls('GET', 'POST', 'DELETE');
|
||||||
|
|
||||||
|
$middleware->process($request, $handler);
|
||||||
|
$middleware->process($request, $handler);
|
||||||
|
$middleware->process($request, $handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \Engelsystem\Middleware\VerifyCsrfToken::__construct
|
||||||
|
* @covers \Engelsystem\Middleware\VerifyCsrfToken::tokensMatch
|
||||||
|
*/
|
||||||
|
public function testTokensMatch()
|
||||||
|
{
|
||||||
|
/** @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);
|
||||||
|
/** @var ResponseInterface|MockObject $noAuthResponse */
|
||||||
|
$noAuthResponse = $this->getMockForAbstractClass(ResponseInterface::class);
|
||||||
|
/** @var SessionInterface|MockObject $session */
|
||||||
|
$session = $this->getMockForAbstractClass(SessionInterface::class);
|
||||||
|
|
||||||
|
/** @var VerifyCsrfToken|MockObject $middleware */
|
||||||
|
$middleware = $this->getMockBuilder(VerifyCsrfToken::class)
|
||||||
|
->setConstructorArgs([$session])
|
||||||
|
->setMethods(['isReading', 'notAuthorizedResponse'])
|
||||||
|
->getMock();
|
||||||
|
|
||||||
|
$middleware->expects($this->atLeastOnce())
|
||||||
|
->method('isReading')
|
||||||
|
->willReturn(false);
|
||||||
|
$middleware->expects($this->exactly(1))
|
||||||
|
->method('notAuthorizedResponse')
|
||||||
|
->willReturn($noAuthResponse);
|
||||||
|
|
||||||
|
$handler->expects($this->exactly(3))
|
||||||
|
->method('handle')
|
||||||
|
->willReturn($response);
|
||||||
|
|
||||||
|
$request->expects($this->exactly(4))
|
||||||
|
->method('getParsedBody')
|
||||||
|
->willReturnOnConsecutiveCalls(
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
['_token' => 'PostFooToken'],
|
||||||
|
['_token' => 'PostBarToken']
|
||||||
|
);
|
||||||
|
$request->expects($this->exactly(4))
|
||||||
|
->method('getHeader')
|
||||||
|
->with('X-CSRF-TOKEN')
|
||||||
|
->willReturnOnConsecutiveCalls(
|
||||||
|
[],
|
||||||
|
['HeaderFooToken'],
|
||||||
|
[],
|
||||||
|
['HeaderBarToken']
|
||||||
|
);
|
||||||
|
|
||||||
|
$session->expects($this->exactly(4))
|
||||||
|
->method('get')
|
||||||
|
->with('_token')
|
||||||
|
->willReturnOnConsecutiveCalls(
|
||||||
|
'NotAvailableToken',
|
||||||
|
'HeaderFooToken',
|
||||||
|
'PostFooToken',
|
||||||
|
'PostBarToken'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Not tokens
|
||||||
|
$this->assertEquals($noAuthResponse, $middleware->process($request, $handler));
|
||||||
|
// Header token
|
||||||
|
$this->assertEquals($response, $middleware->process($request, $handler));
|
||||||
|
// POST token
|
||||||
|
$this->assertEquals($response, $middleware->process($request, $handler));
|
||||||
|
// Header and POST tokens
|
||||||
|
$this->assertEquals($response, $middleware->process($request, $handler));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Engelsystem\Test\Unit\Renderer\Twig\Extensions;
|
||||||
|
|
||||||
|
use Engelsystem\Renderer\Twig\Extensions\Csrf;
|
||||||
|
use PHPUnit\Framework\MockObject\MockObject;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||||
|
|
||||||
|
class CsrfTest extends ExtensionTest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @covers \Engelsystem\Renderer\Twig\Extensions\Csrf::getFunctions
|
||||||
|
*/
|
||||||
|
public function testGetGlobals()
|
||||||
|
{
|
||||||
|
/** @var SessionInterface|MockObject $session */
|
||||||
|
$session = $this->createMock(SessionInterface::class);
|
||||||
|
|
||||||
|
$extension = new Csrf($session);
|
||||||
|
$functions = $extension->getFunctions();
|
||||||
|
|
||||||
|
$this->assertExtensionExists('csrf', [$extension, 'getCsrfField'], $functions, ['is_safe' => ['html']]);
|
||||||
|
$this->assertExtensionExists('csrf_token', [$extension, 'getCsrfToken'], $functions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \Engelsystem\Renderer\Twig\Extensions\Csrf::getCsrfField
|
||||||
|
*/
|
||||||
|
public function testGetCsrfField()
|
||||||
|
{
|
||||||
|
/** @var Csrf|MockObject $extension */
|
||||||
|
$extension = $this->getMockBuilder(Csrf::class)
|
||||||
|
->disableOriginalConstructor()
|
||||||
|
->setMethods(['getCsrfToken'])
|
||||||
|
->getMock();
|
||||||
|
|
||||||
|
$extension->expects($this->once())
|
||||||
|
->method('getCsrfToken')
|
||||||
|
->willReturn('SomeRandomCsrfToken');
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
'<input type="hidden" name="_token" value="SomeRandomCsrfToken">',
|
||||||
|
$extension->getCsrfField()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \Engelsystem\Renderer\Twig\Extensions\Csrf::__construct
|
||||||
|
* @covers \Engelsystem\Renderer\Twig\Extensions\Csrf::getCsrfToken
|
||||||
|
*/
|
||||||
|
public function testGetCsrfToken()
|
||||||
|
{
|
||||||
|
/** @var SessionInterface|MockObject $session */
|
||||||
|
$session = $this->createMock(SessionInterface::class);
|
||||||
|
$session->expects($this->once())
|
||||||
|
->method('get')
|
||||||
|
->with('_token')
|
||||||
|
->willReturn('SomeOtherCsrfToken');
|
||||||
|
|
||||||
|
$extension = new Csrf($session);
|
||||||
|
$this->assertEquals('SomeOtherCsrfToken', $extension->getCsrfToken());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue