summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMorris Jobke <hey@morrisjobke.de>2016-10-25 14:46:00 +0200
committerGitHub <noreply@github.com>2016-10-25 14:46:00 +0200
commit89574367bcc57da5eda6d13ffcfd8a12de68ea26 (patch)
tree8947e9bdc83b55521a4fa52c40e82c6ee7646701
parent27ba46c40ed1d365965a1cb79ed7d8a38d759d2c (diff)
parentee8b8adf7a62fe4b3823cf803f5f37da3bc6a410 (diff)
downloadnextcloud-server-89574367bcc57da5eda6d13ffcfd8a12de68ea26.tar.gz
nextcloud-server-89574367bcc57da5eda6d13ffcfd8a12de68ea26.zip
Merge pull request #1871 from nextcloud/use-csp-nonces
Use CSP nonces
-rw-r--r--apps/theming/appinfo/app.php1
-rw-r--r--core/templates/layout.base.php2
-rw-r--r--core/templates/layout.guest.php2
-rw-r--r--core/templates/layout.user.php2
-rw-r--r--lib/composer/composer/autoload_classmap.php1
-rw-r--r--lib/composer/composer/autoload_static.php1
-rw-r--r--lib/private/AppFramework/DependencyInjection/DIContainer.php3
-rw-r--r--lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php29
-rw-r--r--lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php54
-rw-r--r--lib/private/Security/CSRF/CsrfToken.php10
-rw-r--r--lib/private/Security/CSRF/CsrfTokenManager.php13
-rw-r--r--lib/private/Server.php13
-rw-r--r--lib/public/AppFramework/Http/ContentSecurityPolicy.php2
-rw-r--r--lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php24
-rw-r--r--tests/lib/AppFramework/Http/EmptyContentSecurityPolicyTest.php24
-rw-r--r--tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php53
-rw-r--r--tests/lib/Security/CSP/ContentSecurityPolicyNonceManagerTest.php57
-rw-r--r--tests/lib/Security/CSRF/CsrfTokenManagerTest.php16
-rw-r--r--tests/lib/Security/CSRF/CsrfTokenTest.php7
19 files changed, 302 insertions, 12 deletions
diff --git a/apps/theming/appinfo/app.php b/apps/theming/appinfo/app.php
index e67092b642f..152504c4179 100644
--- a/apps/theming/appinfo/app.php
+++ b/apps/theming/appinfo/app.php
@@ -47,6 +47,7 @@ $linkToJs = \OC::$server->getURLGenerator()->linkToRoute(
'script',
[
'src' => $linkToJs,
+ 'nonce' => \OC::$server->getContentSecurityPolicyNonceManager()->getNonce()
], ''
);
diff --git a/core/templates/layout.base.php b/core/templates/layout.base.php
index 7301ae690cc..3f13523afcb 100644
--- a/core/templates/layout.base.php
+++ b/core/templates/layout.base.php
@@ -19,7 +19,7 @@
<link rel="stylesheet" href="<?php print_unescaped($cssfile); ?>" media="print">
<?php endforeach; ?>
<?php foreach ($_['jsfiles'] as $jsfile): ?>
- <script src="<?php print_unescaped($jsfile); ?>"></script>
+ <script nonce="<?php p(\OC::$server->getContentSecurityPolicyNonceManager()->getNonce()) ?>" src="<?php print_unescaped($jsfile); ?>"></script>
<?php endforeach; ?>
<?php print_unescaped($_['headers']); ?>
</head>
diff --git a/core/templates/layout.guest.php b/core/templates/layout.guest.php
index 58506353158..6d46ac6cf2c 100644
--- a/core/templates/layout.guest.php
+++ b/core/templates/layout.guest.php
@@ -20,7 +20,7 @@
<link rel="stylesheet" href="<?php print_unescaped($cssfile); ?>" media="print">
<?php endforeach; ?>
<?php foreach($_['jsfiles'] as $jsfile): ?>
- <script src="<?php print_unescaped($jsfile); ?>"></script>
+ <script nonce="<?php p(\OC::$server->getContentSecurityPolicyNonceManager()->getNonce()) ?>" src="<?php print_unescaped($jsfile); ?>"></script>
<?php endforeach; ?>
<?php print_unescaped($_['headers']); ?>
</head>
diff --git a/core/templates/layout.user.php b/core/templates/layout.user.php
index 285eb3ab5f3..d258e3582d0 100644
--- a/core/templates/layout.user.php
+++ b/core/templates/layout.user.php
@@ -27,7 +27,7 @@
<link rel="stylesheet" href="<?php print_unescaped($cssfile); ?>" media="print">
<?php endforeach; ?>
<?php foreach($_['jsfiles'] as $jsfile): ?>
- <script src="<?php print_unescaped($jsfile); ?>"></script>
+ <script nonce="<?php p(\OC::$server->getContentSecurityPolicyNonceManager()->getNonce()) ?>" src="<?php print_unescaped($jsfile); ?>"></script>
<?php endforeach; ?>
<?php print_unescaped($_['headers']); ?>
</head>
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index c10f1ca7e5f..0d2a782a867 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -674,6 +674,7 @@ return array(
'OC\\Security\\Bruteforce\\Throttler' => $baseDir . '/lib/private/Security/Bruteforce/Throttler.php',
'OC\\Security\\CSP\\ContentSecurityPolicy' => $baseDir . '/lib/private/Security/CSP/ContentSecurityPolicy.php',
'OC\\Security\\CSP\\ContentSecurityPolicyManager' => $baseDir . '/lib/private/Security/CSP/ContentSecurityPolicyManager.php',
+ 'OC\\Security\\CSP\\ContentSecurityPolicyNonceManager' => $baseDir . '/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php',
'OC\\Security\\CSRF\\CsrfToken' => $baseDir . '/lib/private/Security/CSRF/CsrfToken.php',
'OC\\Security\\CSRF\\CsrfTokenGenerator' => $baseDir . '/lib/private/Security/CSRF/CsrfTokenGenerator.php',
'OC\\Security\\CSRF\\CsrfTokenManager' => $baseDir . '/lib/private/Security/CSRF/CsrfTokenManager.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index 4b7020b59b5..43e3fc3de34 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -704,6 +704,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Security\\Bruteforce\\Throttler' => __DIR__ . '/../../..' . '/lib/private/Security/Bruteforce/Throttler.php',
'OC\\Security\\CSP\\ContentSecurityPolicy' => __DIR__ . '/../../..' . '/lib/private/Security/CSP/ContentSecurityPolicy.php',
'OC\\Security\\CSP\\ContentSecurityPolicyManager' => __DIR__ . '/../../..' . '/lib/private/Security/CSP/ContentSecurityPolicyManager.php',
+ 'OC\\Security\\CSP\\ContentSecurityPolicyNonceManager' => __DIR__ . '/../../..' . '/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php',
'OC\\Security\\CSRF\\CsrfToken' => __DIR__ . '/../../..' . '/lib/private/Security/CSRF/CsrfToken.php',
'OC\\Security\\CSRF\\CsrfTokenGenerator' => __DIR__ . '/../../..' . '/lib/private/Security/CSRF/CsrfTokenGenerator.php',
'OC\\Security\\CSRF\\CsrfTokenManager' => __DIR__ . '/../../..' . '/lib/private/Security/CSRF/CsrfTokenManager.php',
diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php
index 21d5eaa9503..97faa0edf49 100644
--- a/lib/private/AppFramework/DependencyInjection/DIContainer.php
+++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php
@@ -379,7 +379,8 @@ class DIContainer extends SimpleContainer implements IAppContainer {
$c['AppName'],
$app->isLoggedIn(),
$app->isAdminUser(),
- $app->getServer()->getContentSecurityPolicyManager()
+ $app->getServer()->getContentSecurityPolicyManager(),
+ $app->getServer()->getCsrfTokenManager()
);
});
diff --git a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php
index 5e253d0954a..6c33c0023ea 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\CSRF\CsrfTokenManager;
use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\EmptyContentSecurityPolicy;
use OCP\AppFramework\Http\RedirectResponse;
@@ -77,6 +78,8 @@ class SecurityMiddleware extends Middleware {
private $isAdminUser;
/** @var ContentSecurityPolicyManager */
private $contentSecurityPolicyManager;
+ /** @var CsrfTokenManager */
+ private $csrfTokenManager;
/**
* @param IRequest $request
@@ -88,6 +91,7 @@ class SecurityMiddleware extends Middleware {
* @param bool $isLoggedIn
* @param bool $isAdminUser
* @param ContentSecurityPolicyManager $contentSecurityPolicyManager
+ * @param CSRFTokenManager $csrfTokenManager
*/
public function __construct(IRequest $request,
ControllerMethodReflector $reflector,
@@ -97,7 +101,8 @@ class SecurityMiddleware extends Middleware {
$appName,
$isLoggedIn,
$isAdminUser,
- ContentSecurityPolicyManager $contentSecurityPolicyManager) {
+ ContentSecurityPolicyManager $contentSecurityPolicyManager,
+ CsrfTokenManager $csrfTokenManager) {
$this->navigationManager = $navigationManager;
$this->request = $request;
$this->reflector = $reflector;
@@ -107,6 +112,7 @@ class SecurityMiddleware extends Middleware {
$this->isLoggedIn = $isLoggedIn;
$this->isAdminUser = $isAdminUser;
$this->contentSecurityPolicyManager = $contentSecurityPolicyManager;
+ $this->csrfTokenManager = $csrfTokenManager;
}
@@ -171,6 +177,23 @@ 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
@@ -190,6 +213,10 @@ class SecurityMiddleware extends Middleware {
$defaultPolicy = $this->contentSecurityPolicyManager->getDefaultPolicy();
$defaultPolicy = $this->contentSecurityPolicyManager->mergePolicies($defaultPolicy, $policy);
+ if($this->browserSupportsCspV3()) {
+ $defaultPolicy->useJsNonce($this->csrfTokenManager->getToken()->getEncryptedValue());
+ }
+
$response->setContentSecurityPolicy($defaultPolicy);
return $response;
diff --git a/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php b/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php
new file mode 100644
index 00000000000..0482ea49e5c
--- /dev/null
+++ b/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @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\Security\CSP;
+
+use OC\Security\CSRF\CsrfTokenManager;
+
+/**
+ * @package OC\Security\CSP
+ */
+class ContentSecurityPolicyNonceManager {
+ /** @var CsrfTokenManager */
+ private $csrfTokenManager;
+ /** @var string */
+ private $nonce = '';
+
+ /**
+ * @param CsrfTokenManager $csrfTokenManager
+ */
+ public function __construct(CsrfTokenManager $csrfTokenManager) {
+ $this->csrfTokenManager = $csrfTokenManager;
+ }
+
+ /**
+ * Returns the current CSP nounce
+ *
+ * @return string
+ */
+ public function getNonce() {
+ if($this->nonce === '') {
+ $this->nonce = base64_encode($this->csrfTokenManager->getToken()->getEncryptedValue());
+ }
+
+ return $this->nonce;
+ }
+}
diff --git a/lib/private/Security/CSRF/CsrfToken.php b/lib/private/Security/CSRF/CsrfToken.php
index bf61e339f77..dce9a83b727 100644
--- a/lib/private/Security/CSRF/CsrfToken.php
+++ b/lib/private/Security/CSRF/CsrfToken.php
@@ -33,6 +33,8 @@ namespace OC\Security\CSRF;
class CsrfToken {
/** @var string */
private $value;
+ /** @var string */
+ private $encryptedValue = '';
/**
* @param string $value Value of the token. Can be encrypted or not encrypted.
@@ -48,8 +50,12 @@ class CsrfToken {
* @return string
*/
public function getEncryptedValue() {
- $sharedSecret = base64_encode(random_bytes(strlen($this->value)));
- return base64_encode($this->value ^ $sharedSecret) .':'.$sharedSecret;
+ if($this->encryptedValue === '') {
+ $sharedSecret = base64_encode(random_bytes(strlen($this->value)));
+ $this->encryptedValue = base64_encode($this->value ^ $sharedSecret) . ':' . $sharedSecret;
+ }
+
+ return $this->encryptedValue;
}
/**
diff --git a/lib/private/Security/CSRF/CsrfTokenManager.php b/lib/private/Security/CSRF/CsrfTokenManager.php
index d621cc2c29f..b43ca3d3679 100644
--- a/lib/private/Security/CSRF/CsrfTokenManager.php
+++ b/lib/private/Security/CSRF/CsrfTokenManager.php
@@ -34,6 +34,8 @@ class CsrfTokenManager {
private $tokenGenerator;
/** @var SessionStorage */
private $sessionStorage;
+ /** @var CsrfToken|null */
+ private $csrfToken = null;
/**
* @param CsrfTokenGenerator $tokenGenerator
@@ -51,6 +53,10 @@ class CsrfTokenManager {
* @return CsrfToken
*/
public function getToken() {
+ if(!is_null($this->csrfToken)) {
+ return $this->csrfToken;
+ }
+
if($this->sessionStorage->hasToken()) {
$value = $this->sessionStorage->getToken();
} else {
@@ -58,7 +64,8 @@ class CsrfTokenManager {
$this->sessionStorage->setToken($value);
}
- return new CsrfToken($value);
+ $this->csrfToken = new CsrfToken($value);
+ return $this->csrfToken;
}
/**
@@ -69,13 +76,15 @@ class CsrfTokenManager {
public function refreshToken() {
$value = $this->tokenGenerator->generateToken();
$this->sessionStorage->setToken($value);
- return new CsrfToken($value);
+ $this->csrfToken = new CsrfToken($value);
+ return $this->csrfToken;
}
/**
* Remove the current token from the storage.
*/
public function removeToken() {
+ $this->csrfToken = null;
$this->sessionStorage->removeToken();
}
diff --git a/lib/private/Server.php b/lib/private/Server.php
index 11558118d52..1ccc27802d2 100644
--- a/lib/private/Server.php
+++ b/lib/private/Server.php
@@ -73,6 +73,7 @@ use OC\Security\Bruteforce\Throttler;
use OC\Security\CertificateManager;
use OC\Security\CSP\ContentSecurityPolicyManager;
use OC\Security\Crypto;
+use OC\Security\CSP\ContentSecurityPolicyNonceManager;
use OC\Security\CSRF\CsrfTokenGenerator;
use OC\Security\CSRF\CsrfTokenManager;
use OC\Security\CSRF\TokenStorage\SessionStorage;
@@ -708,6 +709,11 @@ class Server extends ServerContainer implements IServerContainer {
$this->registerService('ContentSecurityPolicyManager', function (Server $c) {
return new ContentSecurityPolicyManager();
});
+ $this->registerService('ContentSecurityPolicyNonceManager', function(Server $c) {
+ return new ContentSecurityPolicyNonceManager(
+ $c->getCsrfTokenManager()
+ );
+ });
$this->registerService('ShareManager', function(Server $c) {
$config = $c->getConfig();
$factoryClass = $config->getSystemValue('sharing.managerFactory', '\OC\Share20\ProviderFactory');
@@ -1406,6 +1412,13 @@ class Server extends ServerContainer implements IServerContainer {
}
/**
+ * @return ContentSecurityPolicyNonceManager
+ */
+ public function getContentSecurityPolicyNonceManager() {
+ return $this->query('ContentSecurityPolicyNonceManager');
+ }
+
+ /**
* Not a public API as of 8.2, wait for 9.0
*
* @return \OCA\Files_External\Service\BackendService
diff --git a/lib/public/AppFramework/Http/ContentSecurityPolicy.php b/lib/public/AppFramework/Http/ContentSecurityPolicy.php
index 082aa0206c7..17844497f94 100644
--- a/lib/public/AppFramework/Http/ContentSecurityPolicy.php
+++ b/lib/public/AppFramework/Http/ContentSecurityPolicy.php
@@ -24,8 +24,6 @@
namespace OCP\AppFramework\Http;
-use OCP\AppFramework\Http;
-
/**
* Class ContentSecurityPolicy is a simple helper which allows applications to
* modify the Content-Security-Policy sent by ownCloud. Per default only JavaScript,
diff --git a/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php b/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php
index 4fca1588e7f..ae4ceef1923 100644
--- a/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php
+++ b/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php
@@ -38,6 +38,8 @@ use OCP\AppFramework\Http;
class EmptyContentSecurityPolicy {
/** @var bool Whether inline JS snippets are allowed */
protected $inlineScriptAllowed = null;
+ /** @var string Whether JS nonces should be used */
+ protected $useJsNonce = null;
/**
* @var bool Whether eval in JS scripts is allowed
* TODO: Disallow per default
@@ -74,6 +76,7 @@ class EmptyContentSecurityPolicy {
* @param bool $state
* @return $this
* @since 8.1.0
+ * @deprecated 10.0 CSP tokens are now used
*/
public function allowInlineScript($state = false) {
$this->inlineScriptAllowed = $state;
@@ -81,6 +84,18 @@ class EmptyContentSecurityPolicy {
}
/**
+ * Use the according JS nonce
+ *
+ * @param string $nonce
+ * @return $this
+ * @since 9.2.0
+ */
+ public function useJsNonce($nonce) {
+ $this->useJsNonce = $nonce;
+ return $this;
+ }
+
+ /**
* Whether eval in JavaScript is allowed or forbidden
* @param bool $state
* @return $this
@@ -323,6 +338,15 @@ class EmptyContentSecurityPolicy {
if(!empty($this->allowedScriptDomains) || $this->inlineScriptAllowed || $this->evalScriptAllowed) {
$policy .= 'script-src ';
+ if(is_string($this->useJsNonce)) {
+ $policy .= '\'nonce-'.base64_encode($this->useJsNonce).'\'';
+ $allowedScriptDomains = array_flip($this->allowedScriptDomains);
+ unset($allowedScriptDomains['\'self\'']);
+ $this->allowedScriptDomains = array_flip($allowedScriptDomains);
+ if(count($allowedScriptDomains) !== 0) {
+ $policy .= ' ';
+ }
+ }
if(is_array($this->allowedScriptDomains)) {
$policy .= implode(' ', $this->allowedScriptDomains);
}
diff --git a/tests/lib/AppFramework/Http/EmptyContentSecurityPolicyTest.php b/tests/lib/AppFramework/Http/EmptyContentSecurityPolicyTest.php
index 248c3d808d2..33e2315ed89 100644
--- a/tests/lib/AppFramework/Http/EmptyContentSecurityPolicyTest.php
+++ b/tests/lib/AppFramework/Http/EmptyContentSecurityPolicyTest.php
@@ -427,4 +427,28 @@ class EmptyContentSecurityPolicyTest extends \Test\TestCase {
$this->contentSecurityPolicy->disallowChildSrcDomain('www.owncloud.org')->disallowChildSrcDomain('www.owncloud.com');
$this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
}
+
+ public function testGetPolicyWithJsNonceAndScriptDomains() {
+ $expectedPolicy = "default-src 'none';script-src 'nonce-TXlKc05vbmNl' www.nextcloud.com www.nextcloud.org";
+
+ $this->contentSecurityPolicy->addAllowedScriptDomain('www.nextcloud.com');
+ $this->contentSecurityPolicy->useJsNonce('MyJsNonce');
+ $this->contentSecurityPolicy->addAllowedScriptDomain('www.nextcloud.org');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyWithJsNonceAndSelfScriptDomain() {
+ $expectedPolicy = "default-src 'none';script-src 'nonce-TXlKc05vbmNl'";
+
+ $this->contentSecurityPolicy->useJsNonce('MyJsNonce');
+ $this->contentSecurityPolicy->addAllowedScriptDomain("'self'");
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyWithoutJsNonceAndSelfScriptDomain() {
+ $expectedPolicy = "default-src 'none';script-src 'self'";
+
+ $this->contentSecurityPolicy->addAllowedScriptDomain("'self'");
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
}
diff --git a/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php b/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php
index 55bf3e46e07..b597317fca4 100644
--- a/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php
+++ b/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php
@@ -36,6 +36,8 @@ use OC\AppFramework\Middleware\Security\SecurityMiddleware;
use OC\AppFramework\Utility\ControllerMethodReflector;
use OC\Security\CSP\ContentSecurityPolicy;
use OC\Security\CSP\ContentSecurityPolicyManager;
+use OC\Security\CSRF\CsrfToken;
+use OC\Security\CSRF\CsrfTokenManager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\EmptyContentSecurityPolicy;
use OCP\AppFramework\Http\RedirectResponse;
@@ -72,6 +74,8 @@ class SecurityMiddlewareTest extends \Test\TestCase {
private $urlGenerator;
/** @var ContentSecurityPolicyManager|\PHPUnit_Framework_MockObject_MockObject */
private $contentSecurityPolicyManager;
+ /** @var CsrfTokenManager|\PHPUnit_Framework_MockObject_MockObject */
+ private $csrfTokenManager;
protected function setUp() {
parent::setUp();
@@ -83,6 +87,7 @@ class SecurityMiddlewareTest extends \Test\TestCase {
$this->urlGenerator = $this->createMock(IURLGenerator::class);
$this->request = $this->createMock(IRequest::class);
$this->contentSecurityPolicyManager = $this->createMock(ContentSecurityPolicyManager::class);
+ $this->csrfTokenManager = $this->createMock(CsrfTokenManager::class);
$this->middleware = $this->getMiddleware(true, true);
$this->secException = new SecurityException('hey', false);
$this->secAjaxException = new SecurityException('hey', true);
@@ -103,7 +108,8 @@ class SecurityMiddlewareTest extends \Test\TestCase {
'files',
$isLoggedIn,
$isAdminUser,
- $this->contentSecurityPolicyManager
+ $this->contentSecurityPolicyManager,
+ $this->csrfTokenManager
);
}
@@ -553,6 +559,10 @@ class SecurityMiddlewareTest extends \Test\TestCase {
}
public function testAfterController() {
+ $this->request
+ ->expects($this->once())
+ ->method('isUserAgent')
+ ->willReturn(false);
$response = $this->createMock(Response::class);
$defaultPolicy = new ContentSecurityPolicy();
$defaultPolicy->addAllowedImageDomain('defaultpolicy');
@@ -591,4 +601,45 @@ class SecurityMiddlewareTest extends \Test\TestCase {
$this->middleware->afterController($this->controller, 'test', $response);
}
+
+ public function testAfterControllerWithContentSecurityPolicy3Support() {
+ $this->request
+ ->expects($this->once())
+ ->method('isUserAgent')
+ ->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));
+ }
}
diff --git a/tests/lib/Security/CSP/ContentSecurityPolicyNonceManagerTest.php b/tests/lib/Security/CSP/ContentSecurityPolicyNonceManagerTest.php
new file mode 100644
index 00000000000..39d24807d5b
--- /dev/null
+++ b/tests/lib/Security/CSP/ContentSecurityPolicyNonceManagerTest.php
@@ -0,0 +1,57 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @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\Security\CSP;
+
+use OC\Security\CSP\ContentSecurityPolicyNonceManager;
+use OC\Security\CSRF\CsrfToken;
+use OC\Security\CSRF\CsrfTokenManager;
+use Test\TestCase;
+
+class ContentSecurityPolicyNonceManagerTest extends TestCase {
+ /** @var CsrfTokenManager */
+ private $csrfTokenManager;
+ /** @var ContentSecurityPolicyNonceManager */
+ private $nonceManager;
+
+ public function setUp() {
+ $this->csrfTokenManager = $this->createMock(CsrfTokenManager::class);
+ $this->nonceManager = new ContentSecurityPolicyNonceManager(
+ $this->csrfTokenManager
+ );
+ }
+
+ public function testGetNonce() {
+ $token = $this->createMock(CsrfToken::class);
+ $token
+ ->expects($this->once())
+ ->method('getEncryptedValue')
+ ->willReturn('MyToken');
+
+ $this->csrfTokenManager
+ ->expects($this->once())
+ ->method('getToken')
+ ->willReturn($token);
+
+ $this->assertSame('TXlUb2tlbg==', $this->nonceManager->getNonce());
+ $this->assertSame('TXlUb2tlbg==', $this->nonceManager->getNonce());
+ }
+}
diff --git a/tests/lib/Security/CSRF/CsrfTokenManagerTest.php b/tests/lib/Security/CSRF/CsrfTokenManagerTest.php
index ab19a43e91e..6f7842fdfd9 100644
--- a/tests/lib/Security/CSRF/CsrfTokenManagerTest.php
+++ b/tests/lib/Security/CSRF/CsrfTokenManagerTest.php
@@ -56,6 +56,22 @@ class CsrfTokenManagerTest extends \Test\TestCase {
$this->assertEquals($expected, $this->csrfTokenManager->getToken());
}
+ public function testGetTokenWithExistingTokenKeepsOnSecondRequest() {
+ $this->storageInterface
+ ->expects($this->once())
+ ->method('hasToken')
+ ->willReturn(true);
+ $this->storageInterface
+ ->expects($this->once())
+ ->method('getToken')
+ ->willReturn('MyExistingToken');
+
+ $expected = new \OC\Security\CSRF\CsrfToken('MyExistingToken');
+ $token = $this->csrfTokenManager->getToken();
+ $this->assertSame($token, $this->csrfTokenManager->getToken());
+ $this->assertSame($token, $this->csrfTokenManager->getToken());
+ }
+
public function testGetTokenWithoutExistingToken() {
$this->storageInterface
->expects($this->once())
diff --git a/tests/lib/Security/CSRF/CsrfTokenTest.php b/tests/lib/Security/CSRF/CsrfTokenTest.php
index da640ce5052..d19d1de916c 100644
--- a/tests/lib/Security/CSRF/CsrfTokenTest.php
+++ b/tests/lib/Security/CSRF/CsrfTokenTest.php
@@ -28,6 +28,13 @@ class CsrfTokenTest extends \Test\TestCase {
$this->assertSame(':', $csrfToken->getEncryptedValue()[16]);
}
+ public function testGetEncryptedValueStaysSameOnSecondRequest() {
+ $csrfToken = new \OC\Security\CSRF\CsrfToken('MyCsrfToken');
+ $tokenValue = $csrfToken->getEncryptedValue();
+ $this->assertSame($tokenValue, $csrfToken->getEncryptedValue());
+ $this->assertSame($tokenValue, $csrfToken->getEncryptedValue());
+ }
+
public function testGetDecryptedValue() {
$csrfToken = new \OC\Security\CSRF\CsrfToken('XlQhHjgWCgBXAEI0Khl+IQEiCXN2LUcDHAQTQAc1HQs=:qgkUlg8l3m8WnkOG4XM9Az33pAt1vSVMx4hcJFsxdqc=');
$this->assertSame('/3JKTq2ldmzcDr1f5zDJ7Wt0lEgqqfKF', $csrfToken->getDecryptedValue());