diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php index 97faa0edf4..8fe9b4dca0 100644 --- a/lib/private/AppFramework/DependencyInjection/DIContainer.php +++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php @@ -380,7 +380,8 @@ class DIContainer extends SimpleContainer implements IAppContainer { $app->isLoggedIn(), $app->isAdminUser(), $app->getServer()->getContentSecurityPolicyManager(), - $app->getServer()->getCsrfTokenManager() + $app->getServer()->getCsrfTokenManager(), + $app->getServer()->getContentSecurityPolicyNonceManager() ); }); diff --git a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php index 6c33c0023e..183e55740e 100644 --- a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php @@ -36,6 +36,7 @@ use OC\AppFramework\Middleware\Security\Exceptions\NotLoggedInException; use OC\AppFramework\Middleware\Security\Exceptions\StrictCookieMissingException; use OC\AppFramework\Utility\ControllerMethodReflector; use OC\Security\CSP\ContentSecurityPolicyManager; +use OC\Security\CSP\ContentSecurityPolicyNonceManager; use OC\Security\CSRF\CsrfTokenManager; use OCP\AppFramework\Http\ContentSecurityPolicy; use OCP\AppFramework\Http\EmptyContentSecurityPolicy; @@ -80,6 +81,8 @@ class SecurityMiddleware extends Middleware { private $contentSecurityPolicyManager; /** @var CsrfTokenManager */ private $csrfTokenManager; + /** @var ContentSecurityPolicyNonceManager */ + private $cspNonceManager; /** * @param IRequest $request @@ -92,6 +95,7 @@ class SecurityMiddleware extends Middleware { * @param bool $isAdminUser * @param ContentSecurityPolicyManager $contentSecurityPolicyManager * @param CSRFTokenManager $csrfTokenManager + * @param ContentSecurityPolicyNonceManager $cspNonceManager */ public function __construct(IRequest $request, ControllerMethodReflector $reflector, @@ -102,7 +106,8 @@ class SecurityMiddleware extends Middleware { $isLoggedIn, $isAdminUser, ContentSecurityPolicyManager $contentSecurityPolicyManager, - CsrfTokenManager $csrfTokenManager) { + CsrfTokenManager $csrfTokenManager, + ContentSecurityPolicyNonceManager $cspNonceManager) { $this->navigationManager = $navigationManager; $this->request = $request; $this->reflector = $reflector; @@ -113,6 +118,7 @@ class SecurityMiddleware extends Middleware { $this->isAdminUser = $isAdminUser; $this->contentSecurityPolicyManager = $contentSecurityPolicyManager; $this->csrfTokenManager = $csrfTokenManager; + $this->cspNonceManager = $cspNonceManager; } @@ -177,23 +183,6 @@ class SecurityMiddleware extends Middleware { } - private function browserSupportsCspV3() { - $browserWhitelist = [ - // Chrome 40+ - '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/[4-9][0-9].[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+$/', - // Firefox 45+ - '/^Mozilla\/5\.0 \([^)]+\) Gecko\/[0-9.]+ Firefox\/(4[5-9]|[5-9][0-9])\.[0-9.]+$/', - // Safari 10+ - '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Version\/1[0-9.]+ Safari\/[0-9.A-Z]+$/', - ]; - - if($this->request->isUserAgent($browserWhitelist)) { - return true; - } - - return false; - } - /** * Performs the default CSP modifications that may be injected by other * applications @@ -213,7 +202,7 @@ class SecurityMiddleware extends Middleware { $defaultPolicy = $this->contentSecurityPolicyManager->getDefaultPolicy(); $defaultPolicy = $this->contentSecurityPolicyManager->mergePolicies($defaultPolicy, $policy); - if($this->browserSupportsCspV3()) { + if($this->cspNonceManager->browserSupportsCspV3()) { $defaultPolicy->useJsNonce($this->csrfTokenManager->getToken()->getEncryptedValue()); } diff --git a/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php b/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php index 0482ea49e5..fe1c2e4404 100644 --- a/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php +++ b/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php @@ -22,6 +22,7 @@ namespace OC\Security\CSP; use OC\Security\CSRF\CsrfTokenManager; +use OCP\IRequest; /** * @package OC\Security\CSP @@ -29,14 +30,19 @@ use OC\Security\CSRF\CsrfTokenManager; class ContentSecurityPolicyNonceManager { /** @var CsrfTokenManager */ private $csrfTokenManager; + /** @var IRequest */ + private $request; /** @var string */ private $nonce = ''; /** * @param CsrfTokenManager $csrfTokenManager + * @param IRequest $request */ - public function __construct(CsrfTokenManager $csrfTokenManager) { + public function __construct(CsrfTokenManager $csrfTokenManager, + IRequest $request) { $this->csrfTokenManager = $csrfTokenManager; + $this->request = $request; } /** @@ -51,4 +57,25 @@ class ContentSecurityPolicyNonceManager { return $this->nonce; } + + /** + * Check if the browser supports CSP v3 + * @return bool + */ + public function browserSupportsCspV3() { + $browserWhitelist = [ + // Chrome 40+ + '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/[4-9][0-9].[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+$/', + // Firefox 45+ + '/^Mozilla\/5\.0 \([^)]+\) Gecko\/[0-9.]+ Firefox\/(4[5-9]|[5-9][0-9])\.[0-9.]+$/', + // Safari 10+ + '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Version\/1[0-9.]+ Safari\/[0-9.A-Z]+$/', + ]; + + if($this->request->isUserAgent($browserWhitelist)) { + return true; + } + + return false; + } } diff --git a/lib/private/Server.php b/lib/private/Server.php index 1ccc27802d..21ec311401 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -711,7 +711,8 @@ class Server extends ServerContainer implements IServerContainer { }); $this->registerService('ContentSecurityPolicyNonceManager', function(Server $c) { return new ContentSecurityPolicyNonceManager( - $c->getCsrfTokenManager() + $c->getCsrfTokenManager(), + $c->getRequest() ); }); $this->registerService('ShareManager', function(Server $c) { diff --git a/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php b/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php index b597317fca..1fdcf485c2 100644 --- a/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php +++ b/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php @@ -36,6 +36,7 @@ use OC\AppFramework\Middleware\Security\SecurityMiddleware; use OC\AppFramework\Utility\ControllerMethodReflector; 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; @@ -76,6 +77,8 @@ class SecurityMiddlewareTest extends \Test\TestCase { private $contentSecurityPolicyManager; /** @var CsrfTokenManager|\PHPUnit_Framework_MockObject_MockObject */ private $csrfTokenManager; + /** @var ContentSecurityPolicyNonceManager|\PHPUnit_Framework_MockObject_MockObject */ + private $cspNonceManager; protected function setUp() { parent::setUp(); @@ -88,6 +91,7 @@ class SecurityMiddlewareTest extends \Test\TestCase { $this->request = $this->createMock(IRequest::class); $this->contentSecurityPolicyManager = $this->createMock(ContentSecurityPolicyManager::class); $this->csrfTokenManager = $this->createMock(CsrfTokenManager::class); + $this->cspNonceManager = $this->createMock(ContentSecurityPolicyNonceManager::class); $this->middleware = $this->getMiddleware(true, true); $this->secException = new SecurityException('hey', false); $this->secAjaxException = new SecurityException('hey', true); @@ -109,7 +113,8 @@ class SecurityMiddlewareTest extends \Test\TestCase { $isLoggedIn, $isAdminUser, $this->contentSecurityPolicyManager, - $this->csrfTokenManager + $this->csrfTokenManager, + $this->cspNonceManager ); } @@ -559,9 +564,9 @@ class SecurityMiddlewareTest extends \Test\TestCase { } public function testAfterController() { - $this->request + $this->cspNonceManager ->expects($this->once()) - ->method('isUserAgent') + ->method('browserSupportsCspV3') ->willReturn(false); $response = $this->createMock(Response::class); $defaultPolicy = new ContentSecurityPolicy(); @@ -603,9 +608,9 @@ class SecurityMiddlewareTest extends \Test\TestCase { } public function testAfterControllerWithContentSecurityPolicy3Support() { - $this->request + $this->cspNonceManager ->expects($this->once()) - ->method('isUserAgent') + ->method('browserSupportsCspV3') ->willReturn(true); $token = $this->createMock(CsrfToken::class); $token diff --git a/tests/lib/Security/CSP/ContentSecurityPolicyNonceManagerTest.php b/tests/lib/Security/CSP/ContentSecurityPolicyNonceManagerTest.php index 39d24807d5..3211a5284f 100644 --- a/tests/lib/Security/CSP/ContentSecurityPolicyNonceManagerTest.php +++ b/tests/lib/Security/CSP/ContentSecurityPolicyNonceManagerTest.php @@ -24,6 +24,7 @@ namespace Test\Security\CSP; use OC\Security\CSP\ContentSecurityPolicyNonceManager; use OC\Security\CSRF\CsrfToken; use OC\Security\CSRF\CsrfTokenManager; +use OCP\IRequest; use Test\TestCase; class ContentSecurityPolicyNonceManagerTest extends TestCase { @@ -35,7 +36,8 @@ class ContentSecurityPolicyNonceManagerTest extends TestCase { public function setUp() { $this->csrfTokenManager = $this->createMock(CsrfTokenManager::class); $this->nonceManager = new ContentSecurityPolicyNonceManager( - $this->csrfTokenManager + $this->csrfTokenManager, + $this->createMock(IRequest::class) ); }