Browse Source

Merge pull request #14122 from Tiborhajos/ticket-13493-backport-csp-middleware

Added 4.x backport for CspMiddleware and tests.
Mark Story 6 years ago
parent
commit
b3d55f3a2e

+ 5 - 3
composer.json

@@ -36,17 +36,19 @@
         "psr/log": "^1.0.0",
         "psr/simple-cache": "^1.0.0",
         "zendframework/zend-diactoros": "^1.4.0",
-        "paragonie/random_compat": "^2.0|9.99.99"
+        "paragonie/random_compat": "^1.4|^2.0|9.99.99"
     },
     "suggest": {
         "ext-openssl": "To use Security::encrypt() or have secure CSRF token generation.",
         "ext-curl": "To enable more efficient network calls in Http\\Client.",
-        "lib-ICU": "The intl PHP library, to use Text::transliterate() or Text::slug()"
+        "lib-ICU": "The intl PHP library, to use Text::transliterate() or Text::slug()",
+        "paragonie/csp-builder": "CSP builder, to use the CSP Middleware"
     },
     "require-dev": {
         "cakephp/cakephp-codesniffer": "^3.0",
         "cakephp/chronos": "^1.2.1",
-        "phpunit/phpunit": "^5.7.14|^6.0"
+        "phpunit/phpunit": "^5.7.14|^6.0",
+        "paragonie/csp-builder": "^1.4|^2.3"
     },
     "autoload": {
         "psr-4": {

+ 71 - 0
src/Http/Middleware/CspMiddleware.php

@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.9.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+namespace Cake\Http\Middleware;
+
+use ParagonIE\CSPBuilder\CSPBuilder;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use RuntimeException;
+
+/**
+ * Content Security Policy Middleware
+ */
+class CspMiddleware
+{
+    /**
+     * CSP Builder
+     *
+     * @var \ParagonIE\CSPBuilder\CSPBuilder $csp CSP Builder or config array
+     */
+    protected $csp;
+
+    /**
+     * Constructor
+     *
+     * @param \ParagonIE\CSPBuilder\CSPBuilder|array $csp CSP object or config array
+     * @throws \RuntimeException
+     */
+    public function __construct($csp)
+    {
+        if (!class_exists(CSPBuilder::class)) {
+            throw new RuntimeException('You must install paragonie/csp-builder to use CspMiddleware');
+        }
+
+        if (!$csp instanceof CSPBuilder) {
+            $csp = new CSPBuilder($csp);
+        }
+
+        $this->csp = $csp;
+    }
+
+    /**
+     * Apply the middleware.
+     *
+     * This will inject the CSP header into the response.
+     * @param ServerRequestInterface $requestInterface The Request.
+     * @param ResponseInterface $responseInterface The Response.
+     * @param callable $next Callback to invoke the next middleware.
+     * @return \Psr\Http\Message\MessageInterface
+     */
+    public function __invoke(ServerRequestInterface $requestInterface, ResponseInterface $responseInterface, callable $next)
+    {
+        $response = $this->csp->injectCSPHeader($responseInterface);
+
+        return $next($requestInterface, $response, $next);
+    }
+}

+ 99 - 0
tests/TestCase/Http/Middleware/CspMiddlewareTest.php

@@ -0,0 +1,99 @@
+<?php
+
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.9.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+namespace Cake\Test\TestCase\Http\Middleware;
+
+use Cake\Http\Middleware\CspMiddleware;
+use Cake\Http\Response;
+use Cake\Http\ServerRequest;
+use Cake\TestSuite\TestCase;
+use ParagonIE\CSPBuilder\CSPBuilder;
+
+/**
+ * Content Security Policy Middleware Test
+ */
+class CspMiddlewareTest extends TestCase
+{
+    /**
+     * testInvoke
+     *
+     * @return void
+     */
+    public function testProcess()
+    {
+        $request = new ServerRequest();
+
+        $middleware = new CspMiddleware([
+            'script-src' => [
+                'allow' => [
+                    'https://www.google-analytics.com',
+                ],
+                'self' => true,
+                'unsafe-inline' => false,
+                'unsafe-eval' => false,
+            ],
+        ]);
+
+        $next = function ($request, $response) {
+            $expected = [
+                'script-src \'self\' https://www.google-analytics.com; ',
+            ];
+            $headers = $response->getHeaders();
+            $this->assertNotEmpty($headers['Content-Security-Policy']);
+            $this->assertEquals($expected, $headers['Content-Security-Policy']);
+        };
+
+        $response = new Response();
+        $middleware($request, $response, $next);
+    }
+
+    /**
+     * testPassingACSPBuilderInstance
+     *
+     * @return void
+     */
+    public function testPassingACSPBuilderInstance()
+    {
+        $request = new ServerRequest();
+
+        $config = [
+            'script-src' => [
+                'allow' => [
+                    'https://www.google-analytics.com',
+                ],
+                'self' => true,
+                'unsafe-inline' => false,
+                'unsafe-eval' => false,
+            ],
+        ];
+
+        $cspBuilder = new CSPBuilder($config);
+        $middleware = new CspMiddleware($cspBuilder);
+
+        $next = function ($request, $response) {
+            $headers = $response->getHeaders();
+            $expected = [
+                'script-src \'self\' https://www.google-analytics.com; ',
+            ];
+
+            $this->assertNotEmpty($headers['Content-Security-Policy']);
+            $this->assertEquals($expected, $headers['Content-Security-Policy']);
+        };
+
+        $response = new Response();
+        $middleware($request, $response, $next);
+    }
+}