Browse Source
Split up security middleware
Split up security middleware
With upcoming work for the feature policy header. Splitting this in smaller classes that just do 1 thing makes sense. I rather have a few small classes that are tiny and do 1 thing right (and we all understand what is going on) than have big ones. Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>pull/16582/head
No known key found for this signature in database
GPG Key ID: F941078878347C0C
7 changed files with 238 additions and 140 deletions
-
1lib/composer/composer/autoload_classmap.php
-
1lib/composer/composer/autoload_static.php
-
10lib/private/AppFramework/DependencyInjection/DIContainer.php
-
80lib/private/AppFramework/Middleware/Security/CSPMiddleware.php
-
40lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php
-
149tests/lib/AppFramework/Middleware/Security/CSPMiddlewareTest.php
-
97tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php
@ -0,0 +1,80 @@ |
|||
<?php |
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl> |
|||
* |
|||
* @author Roeland Jago Douma <roeland@famdouma.nl> |
|||
* |
|||
* @license GNU AGPL version 3 or any later version |
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License as |
|||
* published by the Free Software Foundation, either version 3 of the |
|||
* License, or (at your option) any later version. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Affero General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Affero General Public License |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
* |
|||
*/ |
|||
|
|||
namespace OC\AppFramework\Middleware\Security; |
|||
|
|||
use OC\Security\CSP\ContentSecurityPolicyManager; |
|||
use OC\Security\CSP\ContentSecurityPolicyNonceManager; |
|||
use OC\Security\CSRF\CsrfTokenManager; |
|||
use OCP\AppFramework\Controller; |
|||
use OCP\AppFramework\Http\ContentSecurityPolicy; |
|||
use OCP\AppFramework\Http\EmptyContentSecurityPolicy; |
|||
use OCP\AppFramework\Http\Response; |
|||
use OCP\AppFramework\Middleware; |
|||
|
|||
class CSPMiddleware extends Middleware { |
|||
|
|||
/** @var ContentSecurityPolicyManager */ |
|||
private $contentSecurityPolicyManager; |
|||
/** @var ContentSecurityPolicyNonceManager */ |
|||
private $cspNonceManager; |
|||
/** @var CsrfTokenManager */ |
|||
private $csrfTokenManager; |
|||
|
|||
public function __construct(ContentSecurityPolicyManager $policyManager, |
|||
ContentSecurityPolicyNonceManager $cspNonceManager, |
|||
CsrfTokenManager $csrfTokenManager) { |
|||
$this->contentSecurityPolicyManager = $policyManager; |
|||
$this->cspNonceManager = $cspNonceManager; |
|||
$this->csrfTokenManager = $csrfTokenManager; |
|||
} |
|||
|
|||
/** |
|||
* Performs the default CSP modifications that may be injected by other |
|||
* applications |
|||
* |
|||
* @param Controller $controller |
|||
* @param string $methodName |
|||
* @param Response $response |
|||
* @return Response |
|||
*/ |
|||
public function afterController($controller, $methodName, Response $response): Response { |
|||
$policy = !is_null($response->getContentSecurityPolicy()) ? $response->getContentSecurityPolicy() : new ContentSecurityPolicy(); |
|||
|
|||
if (get_class($policy) === EmptyContentSecurityPolicy::class) { |
|||
return $response; |
|||
} |
|||
|
|||
$defaultPolicy = $this->contentSecurityPolicyManager->getDefaultPolicy(); |
|||
$defaultPolicy = $this->contentSecurityPolicyManager->mergePolicies($defaultPolicy, $policy); |
|||
|
|||
if($this->cspNonceManager->browserSupportsCspV3()) { |
|||
$defaultPolicy->useJsNonce($this->csrfTokenManager->getToken()->getEncryptedValue()); |
|||
} |
|||
|
|||
$response->setContentSecurityPolicy($defaultPolicy); |
|||
|
|||
return $response; |
|||
} |
|||
} |
|||
@ -0,0 +1,149 @@ |
|||
<?php |
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl> |
|||
* |
|||
* @author Roeland Jago Douma <roeland@famdouma.nl> |
|||
* |
|||
* @license GNU AGPL version 3 or any later version |
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License as |
|||
* published by the Free Software Foundation, either version 3 of the |
|||
* License, or (at your option) any later version. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Affero General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Affero General Public License |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
* |
|||
*/ |
|||
|
|||
namespace Test\AppFramework\Middleware\Security; |
|||
|
|||
use OC\AppFramework\Middleware\Security\CSPMiddleware; |
|||
use OC\Security\CSP\ContentSecurityPolicy; |
|||
use OC\Security\CSP\ContentSecurityPolicyManager; |
|||
use OC\Security\CSP\ContentSecurityPolicyNonceManager; |
|||
use OC\Security\CSRF\CsrfToken; |
|||
use OC\Security\CSRF\CsrfTokenManager; |
|||
use OCP\AppFramework\Controller; |
|||
use OCP\AppFramework\Http\EmptyContentSecurityPolicy; |
|||
use OCP\AppFramework\Http\Response; |
|||
use PHPUnit\Framework\MockObject\MockObject; |
|||
|
|||
class CSPMiddlewareTest extends \Test\TestCase { |
|||
|
|||
/** @var CSPMiddleware|MockObject */ |
|||
private $middleware; |
|||
/** @var Controller|MockObject */ |
|||
private $controller; |
|||
/** @var ContentSecurityPolicyManager|MockObject */ |
|||
private $contentSecurityPolicyManager; |
|||
/** @var CsrfTokenManager|MockObject */ |
|||
private $csrfTokenManager; |
|||
/** @var ContentSecurityPolicyNonceManager|MockObject */ |
|||
private $cspNonceManager; |
|||
|
|||
protected function setUp() { |
|||
parent::setUp(); |
|||
|
|||
$this->controller = $this->createMock(Controller::class); |
|||
$this->contentSecurityPolicyManager = $this->createMock(ContentSecurityPolicyManager::class); |
|||
$this->csrfTokenManager = $this->createMock(CsrfTokenManager::class); |
|||
$this->cspNonceManager = $this->createMock(ContentSecurityPolicyNonceManager::class); |
|||
$this->middleware = new CSPMiddleware( |
|||
$this->contentSecurityPolicyManager, |
|||
$this->cspNonceManager, |
|||
$this->csrfTokenManager |
|||
); |
|||
} |
|||
|
|||
public function testAfterController() { |
|||
$this->cspNonceManager |
|||
->expects($this->once()) |
|||
->method('browserSupportsCspV3') |
|||
->willReturn(false); |
|||
$response = $this->createMock(Response::class); |
|||
$defaultPolicy = new ContentSecurityPolicy(); |
|||
$defaultPolicy->addAllowedImageDomain('defaultpolicy'); |
|||
$currentPolicy = new ContentSecurityPolicy(); |
|||
$currentPolicy->addAllowedConnectDomain('currentPolicy'); |
|||
$mergedPolicy = new ContentSecurityPolicy(); |
|||
$mergedPolicy->addAllowedMediaDomain('mergedPolicy'); |
|||
$response |
|||
->expects($this->exactly(2)) |
|||
->method('getContentSecurityPolicy') |
|||
->willReturn($currentPolicy); |
|||
$this->contentSecurityPolicyManager |
|||
->expects($this->once()) |
|||
->method('getDefaultPolicy') |
|||
->willReturn($defaultPolicy); |
|||
$this->contentSecurityPolicyManager |
|||
->expects($this->once()) |
|||
->method('mergePolicies') |
|||
->with($defaultPolicy, $currentPolicy) |
|||
->willReturn($mergedPolicy); |
|||
$response->expects($this->once()) |
|||
->method('setContentSecurityPolicy') |
|||
->with($mergedPolicy); |
|||
|
|||
$this->middleware->afterController($this->controller, 'test', $response); |
|||
} |
|||
|
|||
public function testAfterControllerEmptyCSP() { |
|||
$response = $this->createMock(Response::class); |
|||
$emptyPolicy = new EmptyContentSecurityPolicy(); |
|||
$response->expects($this->any()) |
|||
->method('getContentSecurityPolicy') |
|||
->willReturn($emptyPolicy); |
|||
$response->expects($this->never()) |
|||
->method('setContentSecurityPolicy'); |
|||
|
|||
$this->middleware->afterController($this->controller, 'test', $response); |
|||
} |
|||
|
|||
public function testAfterControllerWithContentSecurityPolicy3Support() { |
|||
$this->cspNonceManager |
|||
->expects($this->once()) |
|||
->method('browserSupportsCspV3') |
|||
->willReturn(true); |
|||
$token = $this->createMock(CsrfToken::class); |
|||
$token |
|||
->expects($this->once()) |
|||
->method('getEncryptedValue') |
|||
->willReturn('MyEncryptedToken'); |
|||
$this->csrfTokenManager |
|||
->expects($this->once()) |
|||
->method('getToken') |
|||
->willReturn($token); |
|||
$response = $this->createMock(Response::class); |
|||
$defaultPolicy = new ContentSecurityPolicy(); |
|||
$defaultPolicy->addAllowedImageDomain('defaultpolicy'); |
|||
$currentPolicy = new ContentSecurityPolicy(); |
|||
$currentPolicy->addAllowedConnectDomain('currentPolicy'); |
|||
$mergedPolicy = new ContentSecurityPolicy(); |
|||
$mergedPolicy->addAllowedMediaDomain('mergedPolicy'); |
|||
$response |
|||
->expects($this->exactly(2)) |
|||
->method('getContentSecurityPolicy') |
|||
->willReturn($currentPolicy); |
|||
$this->contentSecurityPolicyManager |
|||
->expects($this->once()) |
|||
->method('getDefaultPolicy') |
|||
->willReturn($defaultPolicy); |
|||
$this->contentSecurityPolicyManager |
|||
->expects($this->once()) |
|||
->method('mergePolicies') |
|||
->with($defaultPolicy, $currentPolicy) |
|||
->willReturn($mergedPolicy); |
|||
$response->expects($this->once()) |
|||
->method('setContentSecurityPolicy') |
|||
->with($mergedPolicy); |
|||
|
|||
$this->assertEquals($response, $this->middleware->afterController($this->controller, 'test', $response)); |
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue