summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIgor Scheller <igor.scheller@igorshp.de>2018-09-03 15:33:13 +0100
committermsquare <msquare@notrademark.de>2018-11-21 19:24:36 +0100
commit23c0fae36fb8159bcf8b95bae98555201146457e (patch)
tree6a169114a47391adb1da701f630bb27d73e925d2 /src
parent8236989be066c51c5f57884bcc42dbc387794651 (diff)
Added csrf middleware
Diffstat (limited to 'src')
-rw-r--r--src/Http/Response.php12
-rw-r--r--src/Http/SessionServiceProvider.php7
-rw-r--r--src/Middleware/VerifyCsrfToken.php90
-rw-r--r--src/Renderer/Twig/Extensions/Csrf.php48
-rw-r--r--src/Renderer/TwigServiceProvider.php4
5 files changed, 158 insertions, 3 deletions
diff --git a/src/Http/Response.php b/src/Http/Response.php
index 4edf644a..58cd7662 100644
--- a/src/Http/Response.php
+++ b/src/Http/Response.php
@@ -96,7 +96,7 @@ class Response extends SymfonyResponse implements ResponseInterface
/**
* 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
*
* @param string $view
@@ -111,6 +111,14 @@ class Response extends SymfonyResponse implements ResponseInterface
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;
}
}
diff --git a/src/Http/SessionServiceProvider.php b/src/Http/SessionServiceProvider.php
index c2e09624..4d779aa6 100644
--- a/src/Http/SessionServiceProvider.php
+++ b/src/Http/SessionServiceProvider.php
@@ -5,7 +5,9 @@ namespace Engelsystem\Http;
use Engelsystem\Config\Config;
use Engelsystem\Container\ServiceProvider;
use Engelsystem\Http\SessionHandlers\DatabaseHandler;
+use Illuminate\Support\Str;
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\NativeSessionStorage;
use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface;
@@ -21,6 +23,11 @@ class SessionServiceProvider extends ServiceProvider
$session = $this->app->make(Session::class);
$this->app->instance(Session::class, $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 */
$request = $this->app->get('request');
diff --git a/src/Middleware/VerifyCsrfToken.php b/src/Middleware/VerifyCsrfToken.php
new file mode 100644
index 00000000..cc0c1fbc
--- /dev/null
+++ b/src/Middleware/VerifyCsrfToken.php
@@ -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');
+ }
+}
diff --git a/src/Renderer/Twig/Extensions/Csrf.php b/src/Renderer/Twig/Extensions/Csrf.php
new file mode 100644
index 00000000..9f77df80
--- /dev/null
+++ b/src/Renderer/Twig/Extensions/Csrf.php
@@ -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');
+ }
+}
diff --git a/src/Renderer/TwigServiceProvider.php b/src/Renderer/TwigServiceProvider.php
index 49a0eb90..57ebe9e5 100644
--- a/src/Renderer/TwigServiceProvider.php
+++ b/src/Renderer/TwigServiceProvider.php
@@ -4,9 +4,10 @@ namespace Engelsystem\Renderer;
use Engelsystem\Config\Config as EngelsystemConfig;
use Engelsystem\Container\ServiceProvider;
-use Engelsystem\Renderer\Twig\Extensions\Authentication;
use Engelsystem\Renderer\Twig\Extensions\Assets;
+use Engelsystem\Renderer\Twig\Extensions\Authentication;
use Engelsystem\Renderer\Twig\Extensions\Config;
+use Engelsystem\Renderer\Twig\Extensions\Csrf;
use Engelsystem\Renderer\Twig\Extensions\Globals;
use Engelsystem\Renderer\Twig\Extensions\Legacy;
use Engelsystem\Renderer\Twig\Extensions\Session;
@@ -23,6 +24,7 @@ class TwigServiceProvider extends ServiceProvider
'assets' => Assets::class,
'authentication' => Authentication::class,
'config' => Config::class,
+ 'csrf' => Csrf::class,
'globals' => Globals::class,
'session' => Session::class,
'legacy' => Legacy::class,