commit
89574367bc
|
@ -47,6 +47,7 @@ $linkToJs = \OC::$server->getURLGenerator()->linkToRoute(
|
||||||
'script',
|
'script',
|
||||||
[
|
[
|
||||||
'src' => $linkToJs,
|
'src' => $linkToJs,
|
||||||
|
'nonce' => \OC::$server->getContentSecurityPolicyNonceManager()->getNonce()
|
||||||
], ''
|
], ''
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<link rel="stylesheet" href="<?php print_unescaped($cssfile); ?>" media="print">
|
<link rel="stylesheet" href="<?php print_unescaped($cssfile); ?>" media="print">
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php foreach ($_['jsfiles'] as $jsfile): ?>
|
<?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 endforeach; ?>
|
||||||
<?php print_unescaped($_['headers']); ?>
|
<?php print_unescaped($_['headers']); ?>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
<link rel="stylesheet" href="<?php print_unescaped($cssfile); ?>" media="print">
|
<link rel="stylesheet" href="<?php print_unescaped($cssfile); ?>" media="print">
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php foreach($_['jsfiles'] as $jsfile): ?>
|
<?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 endforeach; ?>
|
||||||
<?php print_unescaped($_['headers']); ?>
|
<?php print_unescaped($_['headers']); ?>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
<link rel="stylesheet" href="<?php print_unescaped($cssfile); ?>" media="print">
|
<link rel="stylesheet" href="<?php print_unescaped($cssfile); ?>" media="print">
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php foreach($_['jsfiles'] as $jsfile): ?>
|
<?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 endforeach; ?>
|
||||||
<?php print_unescaped($_['headers']); ?>
|
<?php print_unescaped($_['headers']); ?>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -674,6 +674,7 @@ return array(
|
||||||
'OC\\Security\\Bruteforce\\Throttler' => $baseDir . '/lib/private/Security/Bruteforce/Throttler.php',
|
'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\\ContentSecurityPolicy' => $baseDir . '/lib/private/Security/CSP/ContentSecurityPolicy.php',
|
||||||
'OC\\Security\\CSP\\ContentSecurityPolicyManager' => $baseDir . '/lib/private/Security/CSP/ContentSecurityPolicyManager.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\\CsrfToken' => $baseDir . '/lib/private/Security/CSRF/CsrfToken.php',
|
||||||
'OC\\Security\\CSRF\\CsrfTokenGenerator' => $baseDir . '/lib/private/Security/CSRF/CsrfTokenGenerator.php',
|
'OC\\Security\\CSRF\\CsrfTokenGenerator' => $baseDir . '/lib/private/Security/CSRF/CsrfTokenGenerator.php',
|
||||||
'OC\\Security\\CSRF\\CsrfTokenManager' => $baseDir . '/lib/private/Security/CSRF/CsrfTokenManager.php',
|
'OC\\Security\\CSRF\\CsrfTokenManager' => $baseDir . '/lib/private/Security/CSRF/CsrfTokenManager.php',
|
||||||
|
|
|
@ -704,6 +704,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
||||||
'OC\\Security\\Bruteforce\\Throttler' => __DIR__ . '/../../..' . '/lib/private/Security/Bruteforce/Throttler.php',
|
'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\\ContentSecurityPolicy' => __DIR__ . '/../../..' . '/lib/private/Security/CSP/ContentSecurityPolicy.php',
|
||||||
'OC\\Security\\CSP\\ContentSecurityPolicyManager' => __DIR__ . '/../../..' . '/lib/private/Security/CSP/ContentSecurityPolicyManager.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\\CsrfToken' => __DIR__ . '/../../..' . '/lib/private/Security/CSRF/CsrfToken.php',
|
||||||
'OC\\Security\\CSRF\\CsrfTokenGenerator' => __DIR__ . '/../../..' . '/lib/private/Security/CSRF/CsrfTokenGenerator.php',
|
'OC\\Security\\CSRF\\CsrfTokenGenerator' => __DIR__ . '/../../..' . '/lib/private/Security/CSRF/CsrfTokenGenerator.php',
|
||||||
'OC\\Security\\CSRF\\CsrfTokenManager' => __DIR__ . '/../../..' . '/lib/private/Security/CSRF/CsrfTokenManager.php',
|
'OC\\Security\\CSRF\\CsrfTokenManager' => __DIR__ . '/../../..' . '/lib/private/Security/CSRF/CsrfTokenManager.php',
|
||||||
|
|
|
@ -379,7 +379,8 @@ class DIContainer extends SimpleContainer implements IAppContainer {
|
||||||
$c['AppName'],
|
$c['AppName'],
|
||||||
$app->isLoggedIn(),
|
$app->isLoggedIn(),
|
||||||
$app->isAdminUser(),
|
$app->isAdminUser(),
|
||||||
$app->getServer()->getContentSecurityPolicyManager()
|
$app->getServer()->getContentSecurityPolicyManager(),
|
||||||
|
$app->getServer()->getCsrfTokenManager()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@ use OC\AppFramework\Middleware\Security\Exceptions\NotLoggedInException;
|
||||||
use OC\AppFramework\Middleware\Security\Exceptions\StrictCookieMissingException;
|
use OC\AppFramework\Middleware\Security\Exceptions\StrictCookieMissingException;
|
||||||
use OC\AppFramework\Utility\ControllerMethodReflector;
|
use OC\AppFramework\Utility\ControllerMethodReflector;
|
||||||
use OC\Security\CSP\ContentSecurityPolicyManager;
|
use OC\Security\CSP\ContentSecurityPolicyManager;
|
||||||
|
use OC\Security\CSRF\CsrfTokenManager;
|
||||||
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
||||||
use OCP\AppFramework\Http\EmptyContentSecurityPolicy;
|
use OCP\AppFramework\Http\EmptyContentSecurityPolicy;
|
||||||
use OCP\AppFramework\Http\RedirectResponse;
|
use OCP\AppFramework\Http\RedirectResponse;
|
||||||
|
@ -77,6 +78,8 @@ class SecurityMiddleware extends Middleware {
|
||||||
private $isAdminUser;
|
private $isAdminUser;
|
||||||
/** @var ContentSecurityPolicyManager */
|
/** @var ContentSecurityPolicyManager */
|
||||||
private $contentSecurityPolicyManager;
|
private $contentSecurityPolicyManager;
|
||||||
|
/** @var CsrfTokenManager */
|
||||||
|
private $csrfTokenManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param IRequest $request
|
* @param IRequest $request
|
||||||
|
@ -88,6 +91,7 @@ class SecurityMiddleware extends Middleware {
|
||||||
* @param bool $isLoggedIn
|
* @param bool $isLoggedIn
|
||||||
* @param bool $isAdminUser
|
* @param bool $isAdminUser
|
||||||
* @param ContentSecurityPolicyManager $contentSecurityPolicyManager
|
* @param ContentSecurityPolicyManager $contentSecurityPolicyManager
|
||||||
|
* @param CSRFTokenManager $csrfTokenManager
|
||||||
*/
|
*/
|
||||||
public function __construct(IRequest $request,
|
public function __construct(IRequest $request,
|
||||||
ControllerMethodReflector $reflector,
|
ControllerMethodReflector $reflector,
|
||||||
|
@ -97,7 +101,8 @@ class SecurityMiddleware extends Middleware {
|
||||||
$appName,
|
$appName,
|
||||||
$isLoggedIn,
|
$isLoggedIn,
|
||||||
$isAdminUser,
|
$isAdminUser,
|
||||||
ContentSecurityPolicyManager $contentSecurityPolicyManager) {
|
ContentSecurityPolicyManager $contentSecurityPolicyManager,
|
||||||
|
CsrfTokenManager $csrfTokenManager) {
|
||||||
$this->navigationManager = $navigationManager;
|
$this->navigationManager = $navigationManager;
|
||||||
$this->request = $request;
|
$this->request = $request;
|
||||||
$this->reflector = $reflector;
|
$this->reflector = $reflector;
|
||||||
|
@ -107,6 +112,7 @@ class SecurityMiddleware extends Middleware {
|
||||||
$this->isLoggedIn = $isLoggedIn;
|
$this->isLoggedIn = $isLoggedIn;
|
||||||
$this->isAdminUser = $isAdminUser;
|
$this->isAdminUser = $isAdminUser;
|
||||||
$this->contentSecurityPolicyManager = $contentSecurityPolicyManager;
|
$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
|
* Performs the default CSP modifications that may be injected by other
|
||||||
* applications
|
* applications
|
||||||
|
@ -190,6 +213,10 @@ class SecurityMiddleware extends Middleware {
|
||||||
$defaultPolicy = $this->contentSecurityPolicyManager->getDefaultPolicy();
|
$defaultPolicy = $this->contentSecurityPolicyManager->getDefaultPolicy();
|
||||||
$defaultPolicy = $this->contentSecurityPolicyManager->mergePolicies($defaultPolicy, $policy);
|
$defaultPolicy = $this->contentSecurityPolicyManager->mergePolicies($defaultPolicy, $policy);
|
||||||
|
|
||||||
|
if($this->browserSupportsCspV3()) {
|
||||||
|
$defaultPolicy->useJsNonce($this->csrfTokenManager->getToken()->getEncryptedValue());
|
||||||
|
}
|
||||||
|
|
||||||
$response->setContentSecurityPolicy($defaultPolicy);
|
$response->setContentSecurityPolicy($defaultPolicy);
|
||||||
|
|
||||||
return $response;
|
return $response;
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,6 +33,8 @@ namespace OC\Security\CSRF;
|
||||||
class CsrfToken {
|
class CsrfToken {
|
||||||
/** @var string */
|
/** @var string */
|
||||||
private $value;
|
private $value;
|
||||||
|
/** @var string */
|
||||||
|
private $encryptedValue = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $value Value of the token. Can be encrypted or not encrypted.
|
* @param string $value Value of the token. Can be encrypted or not encrypted.
|
||||||
|
@ -48,8 +50,12 @@ class CsrfToken {
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getEncryptedValue() {
|
public function getEncryptedValue() {
|
||||||
$sharedSecret = base64_encode(random_bytes(strlen($this->value)));
|
if($this->encryptedValue === '') {
|
||||||
return base64_encode($this->value ^ $sharedSecret) .':'.$sharedSecret;
|
$sharedSecret = base64_encode(random_bytes(strlen($this->value)));
|
||||||
|
$this->encryptedValue = base64_encode($this->value ^ $sharedSecret) . ':' . $sharedSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->encryptedValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -34,6 +34,8 @@ class CsrfTokenManager {
|
||||||
private $tokenGenerator;
|
private $tokenGenerator;
|
||||||
/** @var SessionStorage */
|
/** @var SessionStorage */
|
||||||
private $sessionStorage;
|
private $sessionStorage;
|
||||||
|
/** @var CsrfToken|null */
|
||||||
|
private $csrfToken = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param CsrfTokenGenerator $tokenGenerator
|
* @param CsrfTokenGenerator $tokenGenerator
|
||||||
|
@ -51,6 +53,10 @@ class CsrfTokenManager {
|
||||||
* @return CsrfToken
|
* @return CsrfToken
|
||||||
*/
|
*/
|
||||||
public function getToken() {
|
public function getToken() {
|
||||||
|
if(!is_null($this->csrfToken)) {
|
||||||
|
return $this->csrfToken;
|
||||||
|
}
|
||||||
|
|
||||||
if($this->sessionStorage->hasToken()) {
|
if($this->sessionStorage->hasToken()) {
|
||||||
$value = $this->sessionStorage->getToken();
|
$value = $this->sessionStorage->getToken();
|
||||||
} else {
|
} else {
|
||||||
|
@ -58,7 +64,8 @@ class CsrfTokenManager {
|
||||||
$this->sessionStorage->setToken($value);
|
$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() {
|
public function refreshToken() {
|
||||||
$value = $this->tokenGenerator->generateToken();
|
$value = $this->tokenGenerator->generateToken();
|
||||||
$this->sessionStorage->setToken($value);
|
$this->sessionStorage->setToken($value);
|
||||||
return new CsrfToken($value);
|
$this->csrfToken = new CsrfToken($value);
|
||||||
|
return $this->csrfToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the current token from the storage.
|
* Remove the current token from the storage.
|
||||||
*/
|
*/
|
||||||
public function removeToken() {
|
public function removeToken() {
|
||||||
|
$this->csrfToken = null;
|
||||||
$this->sessionStorage->removeToken();
|
$this->sessionStorage->removeToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -73,6 +73,7 @@ use OC\Security\Bruteforce\Throttler;
|
||||||
use OC\Security\CertificateManager;
|
use OC\Security\CertificateManager;
|
||||||
use OC\Security\CSP\ContentSecurityPolicyManager;
|
use OC\Security\CSP\ContentSecurityPolicyManager;
|
||||||
use OC\Security\Crypto;
|
use OC\Security\Crypto;
|
||||||
|
use OC\Security\CSP\ContentSecurityPolicyNonceManager;
|
||||||
use OC\Security\CSRF\CsrfTokenGenerator;
|
use OC\Security\CSRF\CsrfTokenGenerator;
|
||||||
use OC\Security\CSRF\CsrfTokenManager;
|
use OC\Security\CSRF\CsrfTokenManager;
|
||||||
use OC\Security\CSRF\TokenStorage\SessionStorage;
|
use OC\Security\CSRF\TokenStorage\SessionStorage;
|
||||||
|
@ -708,6 +709,11 @@ class Server extends ServerContainer implements IServerContainer {
|
||||||
$this->registerService('ContentSecurityPolicyManager', function (Server $c) {
|
$this->registerService('ContentSecurityPolicyManager', function (Server $c) {
|
||||||
return new ContentSecurityPolicyManager();
|
return new ContentSecurityPolicyManager();
|
||||||
});
|
});
|
||||||
|
$this->registerService('ContentSecurityPolicyNonceManager', function(Server $c) {
|
||||||
|
return new ContentSecurityPolicyNonceManager(
|
||||||
|
$c->getCsrfTokenManager()
|
||||||
|
);
|
||||||
|
});
|
||||||
$this->registerService('ShareManager', function(Server $c) {
|
$this->registerService('ShareManager', function(Server $c) {
|
||||||
$config = $c->getConfig();
|
$config = $c->getConfig();
|
||||||
$factoryClass = $config->getSystemValue('sharing.managerFactory', '\OC\Share20\ProviderFactory');
|
$factoryClass = $config->getSystemValue('sharing.managerFactory', '\OC\Share20\ProviderFactory');
|
||||||
|
@ -1405,6 +1411,13 @@ class Server extends ServerContainer implements IServerContainer {
|
||||||
return $this->query('ContentSecurityPolicyManager');
|
return $this->query('ContentSecurityPolicyManager');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ContentSecurityPolicyNonceManager
|
||||||
|
*/
|
||||||
|
public function getContentSecurityPolicyNonceManager() {
|
||||||
|
return $this->query('ContentSecurityPolicyNonceManager');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Not a public API as of 8.2, wait for 9.0
|
* Not a public API as of 8.2, wait for 9.0
|
||||||
*
|
*
|
||||||
|
|
|
@ -24,8 +24,6 @@
|
||||||
|
|
||||||
namespace OCP\AppFramework\Http;
|
namespace OCP\AppFramework\Http;
|
||||||
|
|
||||||
use OCP\AppFramework\Http;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class ContentSecurityPolicy is a simple helper which allows applications to
|
* Class ContentSecurityPolicy is a simple helper which allows applications to
|
||||||
* modify the Content-Security-Policy sent by ownCloud. Per default only JavaScript,
|
* modify the Content-Security-Policy sent by ownCloud. Per default only JavaScript,
|
||||||
|
|
|
@ -38,6 +38,8 @@ use OCP\AppFramework\Http;
|
||||||
class EmptyContentSecurityPolicy {
|
class EmptyContentSecurityPolicy {
|
||||||
/** @var bool Whether inline JS snippets are allowed */
|
/** @var bool Whether inline JS snippets are allowed */
|
||||||
protected $inlineScriptAllowed = null;
|
protected $inlineScriptAllowed = null;
|
||||||
|
/** @var string Whether JS nonces should be used */
|
||||||
|
protected $useJsNonce = null;
|
||||||
/**
|
/**
|
||||||
* @var bool Whether eval in JS scripts is allowed
|
* @var bool Whether eval in JS scripts is allowed
|
||||||
* TODO: Disallow per default
|
* TODO: Disallow per default
|
||||||
|
@ -74,12 +76,25 @@ class EmptyContentSecurityPolicy {
|
||||||
* @param bool $state
|
* @param bool $state
|
||||||
* @return $this
|
* @return $this
|
||||||
* @since 8.1.0
|
* @since 8.1.0
|
||||||
|
* @deprecated 10.0 CSP tokens are now used
|
||||||
*/
|
*/
|
||||||
public function allowInlineScript($state = false) {
|
public function allowInlineScript($state = false) {
|
||||||
$this->inlineScriptAllowed = $state;
|
$this->inlineScriptAllowed = $state;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
* Whether eval in JavaScript is allowed or forbidden
|
||||||
* @param bool $state
|
* @param bool $state
|
||||||
|
@ -323,6 +338,15 @@ class EmptyContentSecurityPolicy {
|
||||||
|
|
||||||
if(!empty($this->allowedScriptDomains) || $this->inlineScriptAllowed || $this->evalScriptAllowed) {
|
if(!empty($this->allowedScriptDomains) || $this->inlineScriptAllowed || $this->evalScriptAllowed) {
|
||||||
$policy .= 'script-src ';
|
$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)) {
|
if(is_array($this->allowedScriptDomains)) {
|
||||||
$policy .= implode(' ', $this->allowedScriptDomains);
|
$policy .= implode(' ', $this->allowedScriptDomains);
|
||||||
}
|
}
|
||||||
|
|
|
@ -427,4 +427,28 @@ class EmptyContentSecurityPolicyTest extends \Test\TestCase {
|
||||||
$this->contentSecurityPolicy->disallowChildSrcDomain('www.owncloud.org')->disallowChildSrcDomain('www.owncloud.com');
|
$this->contentSecurityPolicy->disallowChildSrcDomain('www.owncloud.org')->disallowChildSrcDomain('www.owncloud.com');
|
||||||
$this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
|
$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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,8 @@ use OC\AppFramework\Middleware\Security\SecurityMiddleware;
|
||||||
use OC\AppFramework\Utility\ControllerMethodReflector;
|
use OC\AppFramework\Utility\ControllerMethodReflector;
|
||||||
use OC\Security\CSP\ContentSecurityPolicy;
|
use OC\Security\CSP\ContentSecurityPolicy;
|
||||||
use OC\Security\CSP\ContentSecurityPolicyManager;
|
use OC\Security\CSP\ContentSecurityPolicyManager;
|
||||||
|
use OC\Security\CSRF\CsrfToken;
|
||||||
|
use OC\Security\CSRF\CsrfTokenManager;
|
||||||
use OCP\AppFramework\Controller;
|
use OCP\AppFramework\Controller;
|
||||||
use OCP\AppFramework\Http\EmptyContentSecurityPolicy;
|
use OCP\AppFramework\Http\EmptyContentSecurityPolicy;
|
||||||
use OCP\AppFramework\Http\RedirectResponse;
|
use OCP\AppFramework\Http\RedirectResponse;
|
||||||
|
@ -72,6 +74,8 @@ class SecurityMiddlewareTest extends \Test\TestCase {
|
||||||
private $urlGenerator;
|
private $urlGenerator;
|
||||||
/** @var ContentSecurityPolicyManager|\PHPUnit_Framework_MockObject_MockObject */
|
/** @var ContentSecurityPolicyManager|\PHPUnit_Framework_MockObject_MockObject */
|
||||||
private $contentSecurityPolicyManager;
|
private $contentSecurityPolicyManager;
|
||||||
|
/** @var CsrfTokenManager|\PHPUnit_Framework_MockObject_MockObject */
|
||||||
|
private $csrfTokenManager;
|
||||||
|
|
||||||
protected function setUp() {
|
protected function setUp() {
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
@ -83,6 +87,7 @@ class SecurityMiddlewareTest extends \Test\TestCase {
|
||||||
$this->urlGenerator = $this->createMock(IURLGenerator::class);
|
$this->urlGenerator = $this->createMock(IURLGenerator::class);
|
||||||
$this->request = $this->createMock(IRequest::class);
|
$this->request = $this->createMock(IRequest::class);
|
||||||
$this->contentSecurityPolicyManager = $this->createMock(ContentSecurityPolicyManager::class);
|
$this->contentSecurityPolicyManager = $this->createMock(ContentSecurityPolicyManager::class);
|
||||||
|
$this->csrfTokenManager = $this->createMock(CsrfTokenManager::class);
|
||||||
$this->middleware = $this->getMiddleware(true, true);
|
$this->middleware = $this->getMiddleware(true, true);
|
||||||
$this->secException = new SecurityException('hey', false);
|
$this->secException = new SecurityException('hey', false);
|
||||||
$this->secAjaxException = new SecurityException('hey', true);
|
$this->secAjaxException = new SecurityException('hey', true);
|
||||||
|
@ -103,7 +108,8 @@ class SecurityMiddlewareTest extends \Test\TestCase {
|
||||||
'files',
|
'files',
|
||||||
$isLoggedIn,
|
$isLoggedIn,
|
||||||
$isAdminUser,
|
$isAdminUser,
|
||||||
$this->contentSecurityPolicyManager
|
$this->contentSecurityPolicyManager,
|
||||||
|
$this->csrfTokenManager
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -553,6 +559,10 @@ class SecurityMiddlewareTest extends \Test\TestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAfterController() {
|
public function testAfterController() {
|
||||||
|
$this->request
|
||||||
|
->expects($this->once())
|
||||||
|
->method('isUserAgent')
|
||||||
|
->willReturn(false);
|
||||||
$response = $this->createMock(Response::class);
|
$response = $this->createMock(Response::class);
|
||||||
$defaultPolicy = new ContentSecurityPolicy();
|
$defaultPolicy = new ContentSecurityPolicy();
|
||||||
$defaultPolicy->addAllowedImageDomain('defaultpolicy');
|
$defaultPolicy->addAllowedImageDomain('defaultpolicy');
|
||||||
|
@ -591,4 +601,45 @@ class SecurityMiddlewareTest extends \Test\TestCase {
|
||||||
|
|
||||||
$this->middleware->afterController($this->controller, 'test', $response);
|
$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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -56,6 +56,22 @@ class CsrfTokenManagerTest extends \Test\TestCase {
|
||||||
$this->assertEquals($expected, $this->csrfTokenManager->getToken());
|
$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() {
|
public function testGetTokenWithoutExistingToken() {
|
||||||
$this->storageInterface
|
$this->storageInterface
|
||||||
->expects($this->once())
|
->expects($this->once())
|
||||||
|
|
|
@ -28,6 +28,13 @@ class CsrfTokenTest extends \Test\TestCase {
|
||||||
$this->assertSame(':', $csrfToken->getEncryptedValue()[16]);
|
$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() {
|
public function testGetDecryptedValue() {
|
||||||
$csrfToken = new \OC\Security\CSRF\CsrfToken('XlQhHjgWCgBXAEI0Khl+IQEiCXN2LUcDHAQTQAc1HQs=:qgkUlg8l3m8WnkOG4XM9Az33pAt1vSVMx4hcJFsxdqc=');
|
$csrfToken = new \OC\Security\CSRF\CsrfToken('XlQhHjgWCgBXAEI0Khl+IQEiCXN2LUcDHAQTQAc1HQs=:qgkUlg8l3m8WnkOG4XM9Az33pAt1vSVMx4hcJFsxdqc=');
|
||||||
$this->assertSame('/3JKTq2ldmzcDr1f5zDJ7Wt0lEgqqfKF', $csrfToken->getDecryptedValue());
|
$this->assertSame('/3JKTq2ldmzcDr1f5zDJ7Wt0lEgqqfKF', $csrfToken->getDecryptedValue());
|
||||||
|
|
Loading…
Reference in New Issue