aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorThomas Müller <thomas.mueller@tmit.eu>2016-02-01 10:10:40 +0100
committerThomas Müller <thomas.mueller@tmit.eu>2016-02-01 10:10:40 +0100
commit32067ac49b6f891471a6b5672c903a390b4ac26d (patch)
treef657896888f3cdf59a377fc6ea828f8dea31df11 /lib
parenta025b2865f54a586151f84d5259a41850252c21a (diff)
parent809ff5ac95080b02d1ba4bba76efab59ff70122b (diff)
downloadnextcloud-server-32067ac49b6f891471a6b5672c903a390b4ac26d.tar.gz
nextcloud-server-32067ac49b6f891471a6b5672c903a390b4ac26d.zip
Merge pull request #21989 from owncloud/make-csp-modifiable
Add public API to give developers the possibility to adjust the global CSP defaults
Diffstat (limited to 'lib')
-rw-r--r--lib/private/appframework/dependencyinjection/dicontainer.php10
-rw-r--r--lib/private/appframework/middleware/security/securitymiddleware.php37
-rw-r--r--lib/private/security/csp/contentsecuritypolicy.php199
-rw-r--r--lib/private/security/csp/contentsecuritypolicymanager.php73
-rw-r--r--lib/private/server.php12
-rw-r--r--lib/public/appframework/controller.php2
-rw-r--r--lib/public/appframework/http/contentsecuritypolicy.php335
-rw-r--r--lib/public/appframework/http/emptycontentsecuritypolicy.php387
-rw-r--r--lib/public/iservercontainer.php7
-rw-r--r--lib/public/security/icontentsecuritypolicymanager.php50
10 files changed, 783 insertions, 329 deletions
diff --git a/lib/private/appframework/dependencyinjection/dicontainer.php b/lib/private/appframework/dependencyinjection/dicontainer.php
index 61a04482431..ff9da88cd81 100644
--- a/lib/private/appframework/dependencyinjection/dicontainer.php
+++ b/lib/private/appframework/dependencyinjection/dicontainer.php
@@ -32,7 +32,6 @@ namespace OC\AppFramework\DependencyInjection;
use OC;
use OC\AppFramework\Http;
-use OC\AppFramework\Http\Request;
use OC\AppFramework\Http\Dispatcher;
use OC\AppFramework\Http\Output;
use OC\AppFramework\Core\API;
@@ -43,8 +42,6 @@ use OC\AppFramework\Middleware\SessionMiddleware;
use OC\AppFramework\Utility\SimpleContainer;
use OCP\AppFramework\IApi;
use OCP\AppFramework\IAppContainer;
-use OCP\AppFramework\Middleware;
-use OCP\IServerContainer;
class DIContainer extends SimpleContainer implements IAppContainer {
@@ -255,6 +252,10 @@ class DIContainer extends SimpleContainer implements IAppContainer {
return $this->getServer()->getSession();
});
+ $this->registerService('OCP\\Security\\IContentSecurityPolicyManager', function($c) {
+ return $this->getServer()->getContentSecurityPolicyManager();
+ });
+
$this->registerService('ServerContainer', function ($c) {
return $this->getServer();
});
@@ -319,7 +320,8 @@ class DIContainer extends SimpleContainer implements IAppContainer {
$app->getServer()->getLogger(),
$c['AppName'],
$app->isLoggedIn(),
- $app->isAdminUser()
+ $app->isAdminUser(),
+ $app->getServer()->getContentSecurityPolicyManager()
);
});
diff --git a/lib/private/appframework/middleware/security/securitymiddleware.php b/lib/private/appframework/middleware/security/securitymiddleware.php
index 4ef043ad50f..f1ba98254f0 100644
--- a/lib/private/appframework/middleware/security/securitymiddleware.php
+++ b/lib/private/appframework/middleware/security/securitymiddleware.php
@@ -32,6 +32,8 @@ use OC\Appframework\Middleware\Security\Exceptions\CrossSiteRequestForgeryExcept
use OC\Appframework\Middleware\Security\Exceptions\NotAdminException;
use OC\Appframework\Middleware\Security\Exceptions\NotLoggedInException;
use OC\AppFramework\Utility\ControllerMethodReflector;
+use OC\Security\CSP\ContentSecurityPolicyManager;
+use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Middleware;
@@ -52,15 +54,24 @@ use OC\AppFramework\Middleware\Security\Exceptions\SecurityException;
* check fails
*/
class SecurityMiddleware extends Middleware {
-
+ /** @var INavigationManager */
private $navigationManager;
+ /** @var IRequest */
private $request;
+ /** @var ControllerMethodReflector */
private $reflector;
+ /** @var string */
private $appName;
+ /** @var IURLGenerator */
private $urlGenerator;
+ /** @var ILogger */
private $logger;
+ /** @var bool */
private $isLoggedIn;
+ /** @var bool */
private $isAdminUser;
+ /** @var ContentSecurityPolicyManager */
+ private $contentSecurityPolicyManager;
/**
* @param IRequest $request
@@ -71,6 +82,7 @@ class SecurityMiddleware extends Middleware {
* @param string $appName
* @param bool $isLoggedIn
* @param bool $isAdminUser
+ * @param ContentSecurityPolicyManager $contentSecurityPolicyManager
*/
public function __construct(IRequest $request,
ControllerMethodReflector $reflector,
@@ -79,7 +91,8 @@ class SecurityMiddleware extends Middleware {
ILogger $logger,
$appName,
$isLoggedIn,
- $isAdminUser) {
+ $isAdminUser,
+ ContentSecurityPolicyManager $contentSecurityPolicyManager) {
$this->navigationManager = $navigationManager;
$this->request = $request;
$this->reflector = $reflector;
@@ -88,6 +101,7 @@ class SecurityMiddleware extends Middleware {
$this->logger = $logger;
$this->isLoggedIn = $isLoggedIn;
$this->isAdminUser = $isAdminUser;
+ $this->contentSecurityPolicyManager = $contentSecurityPolicyManager;
}
@@ -139,6 +153,25 @@ class SecurityMiddleware extends Middleware {
}
+ /**
+ * 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) {
+ $policy = !is_null($response->getContentSecurityPolicy()) ? $response->getContentSecurityPolicy() : new ContentSecurityPolicy();
+
+ $defaultPolicy = $this->contentSecurityPolicyManager->getDefaultPolicy();
+ $defaultPolicy = $this->contentSecurityPolicyManager->mergePolicies($defaultPolicy, $policy);
+
+ $response->setContentSecurityPolicy($defaultPolicy);
+
+ return $response;
+ }
/**
* If an SecurityException is being caught, ajax requests return a JSON error
diff --git a/lib/private/security/csp/contentsecuritypolicy.php b/lib/private/security/csp/contentsecuritypolicy.php
new file mode 100644
index 00000000000..25eacfab1d6
--- /dev/null
+++ b/lib/private/security/csp/contentsecuritypolicy.php
@@ -0,0 +1,199 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+namespace OC\Security\CSP;
+
+/**
+ * Class ContentSecurityPolicy extends the public class and adds getter and setters.
+ * This is necessary since we don't want to expose the setters and getters to the
+ * public API.
+ *
+ * @package OC\Security\CSP
+ */
+class ContentSecurityPolicy extends \OCP\AppFramework\Http\ContentSecurityPolicy {
+ /**
+ * @return boolean
+ */
+ public function isInlineScriptAllowed() {
+ return $this->inlineScriptAllowed;
+ }
+
+ /**
+ * @param boolean $inlineScriptAllowed
+ */
+ public function setInlineScriptAllowed($inlineScriptAllowed) {
+ $this->inlineScriptAllowed = $inlineScriptAllowed;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function isEvalScriptAllowed() {
+ return $this->evalScriptAllowed;
+ }
+
+ /**
+ * @param boolean $evalScriptAllowed
+ */
+ public function setEvalScriptAllowed($evalScriptAllowed) {
+ $this->evalScriptAllowed = $evalScriptAllowed;
+ }
+
+ /**
+ * @return array
+ */
+ public function getAllowedScriptDomains() {
+ return $this->allowedScriptDomains;
+ }
+
+ /**
+ * @param array $allowedScriptDomains
+ */
+ public function setAllowedScriptDomains($allowedScriptDomains) {
+ $this->allowedScriptDomains = $allowedScriptDomains;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function isInlineStyleAllowed() {
+ return $this->inlineStyleAllowed;
+ }
+
+ /**
+ * @param boolean $inlineStyleAllowed
+ */
+ public function setInlineStyleAllowed($inlineStyleAllowed) {
+ $this->inlineStyleAllowed = $inlineStyleAllowed;
+ }
+
+ /**
+ * @return array
+ */
+ public function getAllowedStyleDomains() {
+ return $this->allowedStyleDomains;
+ }
+
+ /**
+ * @param array $allowedStyleDomains
+ */
+ public function setAllowedStyleDomains($allowedStyleDomains) {
+ $this->allowedStyleDomains = $allowedStyleDomains;
+ }
+
+ /**
+ * @return array
+ */
+ public function getAllowedImageDomains() {
+ return $this->allowedImageDomains;
+ }
+
+ /**
+ * @param array $allowedImageDomains
+ */
+ public function setAllowedImageDomains($allowedImageDomains) {
+ $this->allowedImageDomains = $allowedImageDomains;
+ }
+
+ /**
+ * @return array
+ */
+ public function getAllowedConnectDomains() {
+ return $this->allowedConnectDomains;
+ }
+
+ /**
+ * @param array $allowedConnectDomains
+ */
+ public function setAllowedConnectDomains($allowedConnectDomains) {
+ $this->allowedConnectDomains = $allowedConnectDomains;
+ }
+
+ /**
+ * @return array
+ */
+ public function getAllowedMediaDomains() {
+ return $this->allowedMediaDomains;
+ }
+
+ /**
+ * @param array $allowedMediaDomains
+ */
+ public function setAllowedMediaDomains($allowedMediaDomains) {
+ $this->allowedMediaDomains = $allowedMediaDomains;
+ }
+
+ /**
+ * @return array
+ */
+ public function getAllowedObjectDomains() {
+ return $this->allowedObjectDomains;
+ }
+
+ /**
+ * @param array $allowedObjectDomains
+ */
+ public function setAllowedObjectDomains($allowedObjectDomains) {
+ $this->allowedObjectDomains = $allowedObjectDomains;
+ }
+
+ /**
+ * @return array
+ */
+ public function getAllowedFrameDomains() {
+ return $this->allowedFrameDomains;
+ }
+
+ /**
+ * @param array $allowedFrameDomains
+ */
+ public function setAllowedFrameDomains($allowedFrameDomains) {
+ $this->allowedFrameDomains = $allowedFrameDomains;
+ }
+
+ /**
+ * @return array
+ */
+ public function getAllowedFontDomains() {
+ return $this->allowedFontDomains;
+ }
+
+ /**
+ * @param array $allowedFontDomains
+ */
+ public function setAllowedFontDomains($allowedFontDomains) {
+ $this->allowedFontDomains = $allowedFontDomains;
+ }
+
+ /**
+ * @return array
+ */
+ public function getAllowedChildSrcDomains() {
+ return $this->allowedChildSrcDomains;
+ }
+
+ /**
+ * @param array $allowedChildSrcDomains
+ */
+ public function setAllowedChildSrcDomains($allowedChildSrcDomains) {
+ $this->allowedChildSrcDomains = $allowedChildSrcDomains;
+ }
+
+}
diff --git a/lib/private/security/csp/contentsecuritypolicymanager.php b/lib/private/security/csp/contentsecuritypolicymanager.php
new file mode 100644
index 00000000000..760cd36e56b
--- /dev/null
+++ b/lib/private/security/csp/contentsecuritypolicymanager.php
@@ -0,0 +1,73 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\Security\CSP;
+
+use OCP\AppFramework\Http\ContentSecurityPolicy;
+use OCP\AppFramework\Http\EmptyContentSecurityPolicy;
+use OCP\Security\IContentSecurityPolicyManager;
+
+class ContentSecurityPolicyManager implements IContentSecurityPolicyManager {
+ /** @var ContentSecurityPolicy[] */
+ private $policies = [];
+
+ /** {@inheritdoc} */
+ public function addDefaultPolicy(EmptyContentSecurityPolicy $policy) {
+ $this->policies[] = $policy;
+ }
+
+ /**
+ * Get the configured default policy. This is not in the public namespace
+ * as it is only supposed to be used by core itself.
+ *
+ * @return ContentSecurityPolicy
+ */
+ public function getDefaultPolicy() {
+ $defaultPolicy = new \OC\Security\CSP\ContentSecurityPolicy();
+ foreach($this->policies as $policy) {
+ $defaultPolicy = $this->mergePolicies($defaultPolicy, $policy);
+ }
+ return $defaultPolicy;
+ }
+
+ /**
+ * Merges the first given policy with the second one
+ *
+ * @param ContentSecurityPolicy $defaultPolicy
+ * @param EmptyContentSecurityPolicy $originalPolicy
+ * @return ContentSecurityPolicy
+ */
+ public function mergePolicies(ContentSecurityPolicy $defaultPolicy,
+ EmptyContentSecurityPolicy $originalPolicy) {
+ foreach((object)(array)$originalPolicy as $name => $value) {
+ $setter = 'set'.ucfirst($name);
+ if(is_array($value)) {
+ $getter = 'get'.ucfirst($name);
+ $currentValues = is_array($defaultPolicy->$getter()) ? $defaultPolicy->$getter() : [];
+ $defaultPolicy->$setter(array_values(array_unique(array_merge($currentValues, $value))));
+ } elseif (is_bool($value)) {
+ $defaultPolicy->$setter($value);
+ }
+ }
+
+ return $defaultPolicy;
+ }
+}
diff --git a/lib/private/server.php b/lib/private/server.php
index d453a42d3a0..d3dbcba86ba 100644
--- a/lib/private/server.php
+++ b/lib/private/server.php
@@ -63,6 +63,7 @@ use OC\Lock\NoopLockingProvider;
use OC\Mail\Mailer;
use OC\Notification\Manager;
use OC\Security\CertificateManager;
+use OC\Security\CSP\ContentSecurityPolicyManager;
use OC\Security\Crypto;
use OC\Security\CSRF\CsrfTokenGenerator;
use OC\Security\CSRF\CsrfTokenManager;
@@ -74,6 +75,7 @@ use OC\Security\TrustedDomainHelper;
use OC\Session\CryptoWrapper;
use OC\Tagging\TagMapper;
use OCP\IServerContainer;
+use OCP\Security\IContentSecurityPolicyManager;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@@ -598,6 +600,9 @@ class Server extends ServerContainer implements IServerContainer {
$sessionStorage
);
});
+ $this->registerService('ContentSecurityPolicyManager', function (Server $c) {
+ return new ContentSecurityPolicyManager();
+ });
$this->registerService('ShareManager', function(Server $c) {
$config = $c->getConfig();
$factoryClass = $config->getSystemValue('sharing.managerFactory', '\OC\Share20\ProviderFactory');
@@ -1221,6 +1226,13 @@ class Server extends ServerContainer implements IServerContainer {
}
/**
+ * @return IContentSecurityPolicyManager
+ */
+ public function getContentSecurityPolicyManager() {
+ return $this->query('ContentSecurityPolicyManager');
+ }
+
+ /**
* Not a public API as of 8.2, wait for 9.0
*
* @return \OCA\Files_External\Service\BackendService
diff --git a/lib/public/appframework/controller.php b/lib/public/appframework/controller.php
index 973c9044684..c3744683300 100644
--- a/lib/public/appframework/controller.php
+++ b/lib/public/appframework/controller.php
@@ -72,7 +72,7 @@ abstract class Controller {
* @since 6.0.0 - parameter $appName was added in 7.0.0 - parameter $app was removed in 7.0.0
*/
public function __construct($appName,
- IRequest $request){
+ IRequest $request) {
$this->appName = $appName;
$this->request = $request;
diff --git a/lib/public/appframework/http/contentsecuritypolicy.php b/lib/public/appframework/http/contentsecuritypolicy.php
index 35da4f05e80..7762ca809a2 100644
--- a/lib/public/appframework/http/contentsecuritypolicy.php
+++ b/lib/public/appframework/http/contentsecuritypolicy.php
@@ -38,17 +38,17 @@ use OCP\AppFramework\Http;
* @package OCP\AppFramework\Http
* @since 8.1.0
*/
-class ContentSecurityPolicy {
+class ContentSecurityPolicy extends EmptyContentSecurityPolicy {
/** @var bool Whether inline JS snippets are allowed */
- private $inlineScriptAllowed = false;
+ protected $inlineScriptAllowed = false;
/**
* @var bool Whether eval in JS scripts is allowed
* TODO: Disallow per default
* @link https://github.com/owncloud/core/issues/11925
*/
- private $evalScriptAllowed = true;
+ protected $evalScriptAllowed = true;
/** @var array Domains from which scripts can get loaded */
- private $allowedScriptDomains = [
+ protected $allowedScriptDomains = [
'\'self\'',
];
/**
@@ -56,342 +56,33 @@ class ContentSecurityPolicy {
* TODO: Disallow per default
* @link https://github.com/owncloud/core/issues/13458
*/
- private $inlineStyleAllowed = true;
+ protected $inlineStyleAllowed = true;
/** @var array Domains from which CSS can get loaded */
- private $allowedStyleDomains = [
+ protected $allowedStyleDomains = [
'\'self\'',
];
/** @var array Domains from which images can get loaded */
- private $allowedImageDomains = [
+ protected $allowedImageDomains = [
'\'self\'',
'data:',
'blob:',
];
/** @var array Domains to which connections can be done */
- private $allowedConnectDomains = [
+ protected $allowedConnectDomains = [
'\'self\'',
];
/** @var array Domains from which media elements can be loaded */
- private $allowedMediaDomains = [
+ protected $allowedMediaDomains = [
'\'self\'',
];
/** @var array Domains from which object elements can be loaded */
- private $allowedObjectDomains = [];
+ protected $allowedObjectDomains = [];
/** @var array Domains from which iframes can be loaded */
- private $allowedFrameDomains = [];
+ protected $allowedFrameDomains = [];
/** @var array Domains from which fonts can be loaded */
- private $allowedFontDomains = [
+ protected $allowedFontDomains = [
'\'self\'',
];
/** @var array Domains from which web-workers and nested browsing content can load elements */
- private $allowedChildSrcDomains = [];
-
- /**
- * Whether inline JavaScript snippets are allowed or forbidden
- * @param bool $state
- * @return $this
- * @since 8.1.0
- */
- public function allowInlineScript($state = false) {
- $this->inlineScriptAllowed = $state;
- return $this;
- }
-
- /**
- * Whether eval in JavaScript is allowed or forbidden
- * @param bool $state
- * @return $this
- * @since 8.1.0
- */
- public function allowEvalScript($state = true) {
- $this->evalScriptAllowed = $state;
- return $this;
- }
-
- /**
- * Allows to execute JavaScript files from a specific domain. Use * to
- * allow JavaScript from all domains.
- * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
- * @return $this
- * @since 8.1.0
- */
- public function addAllowedScriptDomain($domain) {
- $this->allowedScriptDomains[] = $domain;
- return $this;
- }
-
- /**
- * Remove the specified allowed script domain from the allowed domains.
- *
- * @param string $domain
- * @return $this
- * @since 8.1.0
- */
- public function disallowScriptDomain($domain) {
- $this->allowedScriptDomains = array_diff($this->allowedScriptDomains, [$domain]);
- return $this;
- }
-
- /**
- * Whether inline CSS snippets are allowed or forbidden
- * @param bool $state
- * @return $this
- * @since 8.1.0
- */
- public function allowInlineStyle($state = true) {
- $this->inlineStyleAllowed = $state;
- return $this;
- }
-
- /**
- * Allows to execute CSS files from a specific domain. Use * to allow
- * CSS from all domains.
- * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
- * @return $this
- * @since 8.1.0
- */
- public function addAllowedStyleDomain($domain) {
- $this->allowedStyleDomains[] = $domain;
- return $this;
- }
-
- /**
- * Remove the specified allowed style domain from the allowed domains.
- *
- * @param string $domain
- * @return $this
- * @since 8.1.0
- */
- public function disallowStyleDomain($domain) {
- $this->allowedStyleDomains = array_diff($this->allowedStyleDomains, [$domain]);
- return $this;
- }
-
- /**
- * Allows using fonts from a specific domain. Use * to allow
- * fonts from all domains.
- * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
- * @return $this
- * @since 8.1.0
- */
- public function addAllowedFontDomain($domain) {
- $this->allowedFontDomains[] = $domain;
- return $this;
- }
-
- /**
- * Remove the specified allowed font domain from the allowed domains.
- *
- * @param string $domain
- * @return $this
- * @since 8.1.0
- */
- public function disallowFontDomain($domain) {
- $this->allowedFontDomains = array_diff($this->allowedFontDomains, [$domain]);
- return $this;
- }
-
- /**
- * Allows embedding images from a specific domain. Use * to allow
- * images from all domains.
- * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
- * @return $this
- * @since 8.1.0
- */
- public function addAllowedImageDomain($domain) {
- $this->allowedImageDomains[] = $domain;
- return $this;
- }
-
- /**
- * Remove the specified allowed image domain from the allowed domains.
- *
- * @param string $domain
- * @return $this
- * @since 8.1.0
- */
- public function disallowImageDomain($domain) {
- $this->allowedImageDomains = array_diff($this->allowedImageDomains, [$domain]);
- return $this;
- }
-
- /**
- * To which remote domains the JS connect to.
- * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
- * @return $this
- * @since 8.1.0
- */
- public function addAllowedConnectDomain($domain) {
- $this->allowedConnectDomains[] = $domain;
- return $this;
- }
-
- /**
- * Remove the specified allowed connect domain from the allowed domains.
- *
- * @param string $domain
- * @return $this
- * @since 8.1.0
- */
- public function disallowConnectDomain($domain) {
- $this->allowedConnectDomains = array_diff($this->allowedConnectDomains, [$domain]);
- return $this;
- }
-
- /**
- * From which domains media elements can be embedded.
- * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
- * @return $this
- * @since 8.1.0
- */
- public function addAllowedMediaDomain($domain) {
- $this->allowedMediaDomains[] = $domain;
- return $this;
- }
-
- /**
- * Remove the specified allowed media domain from the allowed domains.
- *
- * @param string $domain
- * @return $this
- * @since 8.1.0
- */
- public function disallowMediaDomain($domain) {
- $this->allowedMediaDomains = array_diff($this->allowedMediaDomains, [$domain]);
- return $this;
- }
-
- /**
- * From which domains objects such as <object>, <embed> or <applet> are executed
- * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
- * @return $this
- * @since 8.1.0
- */
- public function addAllowedObjectDomain($domain) {
- $this->allowedObjectDomains[] = $domain;
- return $this;
- }
-
- /**
- * Remove the specified allowed object domain from the allowed domains.
- *
- * @param string $domain
- * @return $this
- * @since 8.1.0
- */
- public function disallowObjectDomain($domain) {
- $this->allowedObjectDomains = array_diff($this->allowedObjectDomains, [$domain]);
- return $this;
- }
-
- /**
- * Which domains can be embedded in an iframe
- * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
- * @return $this
- * @since 8.1.0
- */
- public function addAllowedFrameDomain($domain) {
- $this->allowedFrameDomains[] = $domain;
- return $this;
- }
-
- /**
- * Remove the specified allowed frame domain from the allowed domains.
- *
- * @param string $domain
- * @return $this
- * @since 8.1.0
- */
- public function disallowFrameDomain($domain) {
- $this->allowedFrameDomains = array_diff($this->allowedFrameDomains, [$domain]);
- return $this;
- }
-
- /**
- * Domains from which web-workers and nested browsing content can load elements
- * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
- * @return $this
- * @since 8.1.0
- */
- public function addAllowedChildSrcDomain($domain) {
- $this->allowedChildSrcDomains[] = $domain;
- return $this;
- }
-
- /**
- * Remove the specified allowed child src domain from the allowed domains.
- *
- * @param string $domain
- * @return $this
- * @since 8.1.0
- */
- public function disallowChildSrcDomain($domain) {
- $this->allowedChildSrcDomains = array_diff($this->allowedChildSrcDomains, [$domain]);
- return $this;
- }
-
- /**
- * Get the generated Content-Security-Policy as a string
- * @return string
- * @since 8.1.0
- */
- public function buildPolicy() {
- $policy = "default-src 'none';";
-
- if(!empty($this->allowedScriptDomains)) {
- $policy .= 'script-src ' . implode(' ', $this->allowedScriptDomains);
- if($this->inlineScriptAllowed) {
- $policy .= ' \'unsafe-inline\'';
- }
- if($this->evalScriptAllowed) {
- $policy .= ' \'unsafe-eval\'';
- }
- $policy .= ';';
- }
-
- if(!empty($this->allowedStyleDomains)) {
- $policy .= 'style-src ' . implode(' ', $this->allowedStyleDomains);
- if($this->inlineStyleAllowed) {
- $policy .= ' \'unsafe-inline\'';
- }
- $policy .= ';';
- }
-
- if(!empty($this->allowedImageDomains)) {
- $policy .= 'img-src ' . implode(' ', $this->allowedImageDomains);
- $policy .= ';';
- }
-
- if(!empty($this->allowedFontDomains)) {
- $policy .= 'font-src ' . implode(' ', $this->allowedFontDomains);
- $policy .= ';';
- }
-
- if(!empty($this->allowedConnectDomains)) {
- $policy .= 'connect-src ' . implode(' ', $this->allowedConnectDomains);
- $policy .= ';';
- }
-
- if(!empty($this->allowedMediaDomains)) {
- $policy .= 'media-src ' . implode(' ', $this->allowedMediaDomains);
- $policy .= ';';
- }
-
- if(!empty($this->allowedObjectDomains)) {
- $policy .= 'object-src ' . implode(' ', $this->allowedObjectDomains);
- $policy .= ';';
- }
-
- if(!empty($this->allowedFrameDomains)) {
- $policy .= 'frame-src ' . implode(' ', $this->allowedFrameDomains);
- $policy .= ';';
- }
-
- if(!empty($this->allowedChildSrcDomains)) {
- $policy .= 'child-src ' . implode(' ', $this->allowedChildSrcDomains);
- $policy .= ';';
- }
-
- return rtrim($policy, ';');
- }
+ protected $allowedChildSrcDomains = [];
}
diff --git a/lib/public/appframework/http/emptycontentsecuritypolicy.php b/lib/public/appframework/http/emptycontentsecuritypolicy.php
new file mode 100644
index 00000000000..33860dcdb0f
--- /dev/null
+++ b/lib/public/appframework/http/emptycontentsecuritypolicy.php
@@ -0,0 +1,387 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author sualko <klaus@jsxc.org>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCP\AppFramework\Http;
+
+use OCP\AppFramework\Http;
+
+/**
+ * Class EmptyContentSecurityPolicy is a simple helper which allows applications
+ * to modify the Content-Security-Policy sent by ownCloud. Per default the policy
+ * is forbidding everything.
+ *
+ * As alternative with sane exemptions look at ContentSecurityPolicy
+ *
+ * @see \OCP\AppFramework\Http\ContentSecurityPolicy
+ * @package OCP\AppFramework\Http
+ * @since 9.0.0
+ */
+class EmptyContentSecurityPolicy {
+ /** @var bool Whether inline JS snippets are allowed */
+ protected $inlineScriptAllowed = null;
+ /**
+ * @var bool Whether eval in JS scripts is allowed
+ * TODO: Disallow per default
+ * @link https://github.com/owncloud/core/issues/11925
+ */
+ protected $evalScriptAllowed = null;
+ /** @var array Domains from which scripts can get loaded */
+ protected $allowedScriptDomains = null;
+ /**
+ * @var bool Whether inline CSS is allowed
+ * TODO: Disallow per default
+ * @link https://github.com/owncloud/core/issues/13458
+ */
+ protected $inlineStyleAllowed = null;
+ /** @var array Domains from which CSS can get loaded */
+ protected $allowedStyleDomains = null;
+ /** @var array Domains from which images can get loaded */
+ protected $allowedImageDomains = null;
+ /** @var array Domains to which connections can be done */
+ protected $allowedConnectDomains = null;
+ /** @var array Domains from which media elements can be loaded */
+ protected $allowedMediaDomains = null;
+ /** @var array Domains from which object elements can be loaded */
+ protected $allowedObjectDomains = null;
+ /** @var array Domains from which iframes can be loaded */
+ protected $allowedFrameDomains = null;
+ /** @var array Domains from which fonts can be loaded */
+ protected $allowedFontDomains = null;
+ /** @var array Domains from which web-workers and nested browsing content can load elements */
+ protected $allowedChildSrcDomains = null;
+
+ /**
+ * Whether inline JavaScript snippets are allowed or forbidden
+ * @param bool $state
+ * @return $this
+ * @since 8.1.0
+ */
+ public function allowInlineScript($state = false) {
+ $this->inlineScriptAllowed = $state;
+ return $this;
+ }
+
+ /**
+ * Whether eval in JavaScript is allowed or forbidden
+ * @param bool $state
+ * @return $this
+ * @since 8.1.0
+ */
+ public function allowEvalScript($state = true) {
+ $this->evalScriptAllowed = $state;
+ return $this;
+ }
+
+ /**
+ * Allows to execute JavaScript files from a specific domain. Use * to
+ * allow JavaScript from all domains.
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 8.1.0
+ */
+ public function addAllowedScriptDomain($domain) {
+ $this->allowedScriptDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Remove the specified allowed script domain from the allowed domains.
+ *
+ * @param string $domain
+ * @return $this
+ * @since 8.1.0
+ */
+ public function disallowScriptDomain($domain) {
+ $this->allowedScriptDomains = array_diff($this->allowedScriptDomains, [$domain]);
+ return $this;
+ }
+
+ /**
+ * Whether inline CSS snippets are allowed or forbidden
+ * @param bool $state
+ * @return $this
+ * @since 8.1.0
+ */
+ public function allowInlineStyle($state = true) {
+ $this->inlineStyleAllowed = $state;
+ return $this;
+ }
+
+ /**
+ * Allows to execute CSS files from a specific domain. Use * to allow
+ * CSS from all domains.
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 8.1.0
+ */
+ public function addAllowedStyleDomain($domain) {
+ $this->allowedStyleDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Remove the specified allowed style domain from the allowed domains.
+ *
+ * @param string $domain
+ * @return $this
+ * @since 8.1.0
+ */
+ public function disallowStyleDomain($domain) {
+ $this->allowedStyleDomains = array_diff($this->allowedStyleDomains, [$domain]);
+ return $this;
+ }
+
+ /**
+ * Allows using fonts from a specific domain. Use * to allow
+ * fonts from all domains.
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 8.1.0
+ */
+ public function addAllowedFontDomain($domain) {
+ $this->allowedFontDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Remove the specified allowed font domain from the allowed domains.
+ *
+ * @param string $domain
+ * @return $this
+ * @since 8.1.0
+ */
+ public function disallowFontDomain($domain) {
+ $this->allowedFontDomains = array_diff($this->allowedFontDomains, [$domain]);
+ return $this;
+ }
+
+ /**
+ * Allows embedding images from a specific domain. Use * to allow
+ * images from all domains.
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 8.1.0
+ */
+ public function addAllowedImageDomain($domain) {
+ $this->allowedImageDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Remove the specified allowed image domain from the allowed domains.
+ *
+ * @param string $domain
+ * @return $this
+ * @since 8.1.0
+ */
+ public function disallowImageDomain($domain) {
+ $this->allowedImageDomains = array_diff($this->allowedImageDomains, [$domain]);
+ return $this;
+ }
+
+ /**
+ * To which remote domains the JS connect to.
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 8.1.0
+ */
+ public function addAllowedConnectDomain($domain) {
+ $this->allowedConnectDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Remove the specified allowed connect domain from the allowed domains.
+ *
+ * @param string $domain
+ * @return $this
+ * @since 8.1.0
+ */
+ public function disallowConnectDomain($domain) {
+ $this->allowedConnectDomains = array_diff($this->allowedConnectDomains, [$domain]);
+ return $this;
+ }
+
+ /**
+ * From which domains media elements can be embedded.
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 8.1.0
+ */
+ public function addAllowedMediaDomain($domain) {
+ $this->allowedMediaDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Remove the specified allowed media domain from the allowed domains.
+ *
+ * @param string $domain
+ * @return $this
+ * @since 8.1.0
+ */
+ public function disallowMediaDomain($domain) {
+ $this->allowedMediaDomains = array_diff($this->allowedMediaDomains, [$domain]);
+ return $this;
+ }
+
+ /**
+ * From which domains objects such as <object>, <embed> or <applet> are executed
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 8.1.0
+ */
+ public function addAllowedObjectDomain($domain) {
+ $this->allowedObjectDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Remove the specified allowed object domain from the allowed domains.
+ *
+ * @param string $domain
+ * @return $this
+ * @since 8.1.0
+ */
+ public function disallowObjectDomain($domain) {
+ $this->allowedObjectDomains = array_diff($this->allowedObjectDomains, [$domain]);
+ return $this;
+ }
+
+ /**
+ * Which domains can be embedded in an iframe
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 8.1.0
+ */
+ public function addAllowedFrameDomain($domain) {
+ $this->allowedFrameDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Remove the specified allowed frame domain from the allowed domains.
+ *
+ * @param string $domain
+ * @return $this
+ * @since 8.1.0
+ */
+ public function disallowFrameDomain($domain) {
+ $this->allowedFrameDomains = array_diff($this->allowedFrameDomains, [$domain]);
+ return $this;
+ }
+
+ /**
+ * Domains from which web-workers and nested browsing content can load elements
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 8.1.0
+ */
+ public function addAllowedChildSrcDomain($domain) {
+ $this->allowedChildSrcDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Remove the specified allowed child src domain from the allowed domains.
+ *
+ * @param string $domain
+ * @return $this
+ * @since 8.1.0
+ */
+ public function disallowChildSrcDomain($domain) {
+ $this->allowedChildSrcDomains = array_diff($this->allowedChildSrcDomains, [$domain]);
+ return $this;
+ }
+
+ /**
+ * Get the generated Content-Security-Policy as a string
+ * @return string
+ * @since 8.1.0
+ */
+ public function buildPolicy() {
+ $policy = "default-src 'none';";
+
+ if(!empty($this->allowedScriptDomains) || $this->inlineScriptAllowed || $this->evalScriptAllowed) {
+ $policy .= 'script-src ';
+ if(is_array($this->allowedScriptDomains)) {
+ $policy .= implode(' ', $this->allowedScriptDomains);
+ }
+ if($this->inlineScriptAllowed) {
+ $policy .= ' \'unsafe-inline\'';
+ }
+ if($this->evalScriptAllowed) {
+ $policy .= ' \'unsafe-eval\'';
+ }
+ $policy .= ';';
+ }
+
+ if(!empty($this->allowedStyleDomains) || $this->inlineStyleAllowed) {
+ $policy .= 'style-src ';
+ if(is_array($this->allowedStyleDomains)) {
+ $policy .= implode(' ', $this->allowedStyleDomains);
+ }
+ if($this->inlineStyleAllowed) {
+ $policy .= ' \'unsafe-inline\'';
+ }
+ $policy .= ';';
+ }
+
+ if(!empty($this->allowedImageDomains)) {
+ $policy .= 'img-src ' . implode(' ', $this->allowedImageDomains);
+ $policy .= ';';
+ }
+
+ if(!empty($this->allowedFontDomains)) {
+ $policy .= 'font-src ' . implode(' ', $this->allowedFontDomains);
+ $policy .= ';';
+ }
+
+ if(!empty($this->allowedConnectDomains)) {
+ $policy .= 'connect-src ' . implode(' ', $this->allowedConnectDomains);
+ $policy .= ';';
+ }
+
+ if(!empty($this->allowedMediaDomains)) {
+ $policy .= 'media-src ' . implode(' ', $this->allowedMediaDomains);
+ $policy .= ';';
+ }
+
+ if(!empty($this->allowedObjectDomains)) {
+ $policy .= 'object-src ' . implode(' ', $this->allowedObjectDomains);
+ $policy .= ';';
+ }
+
+ if(!empty($this->allowedFrameDomains)) {
+ $policy .= 'frame-src ' . implode(' ', $this->allowedFrameDomains);
+ $policy .= ';';
+ }
+
+ if(!empty($this->allowedChildSrcDomains)) {
+ $policy .= 'child-src ' . implode(' ', $this->allowedChildSrcDomains);
+ $policy .= ';';
+ }
+
+ return rtrim($policy, ';');
+ }
+}
diff --git a/lib/public/iservercontainer.php b/lib/public/iservercontainer.php
index ce1364cc4ea..de48daeef88 100644
--- a/lib/public/iservercontainer.php
+++ b/lib/public/iservercontainer.php
@@ -42,6 +42,7 @@
// use OCP namespace for all classes that are considered public.
// This means that they should be used by apps instead of the internal ownCloud classes
namespace OCP;
+use OCP\Security\IContentSecurityPolicyManager;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@@ -512,4 +513,10 @@ interface IServerContainer {
* @since 9.0.0
*/
public function getShareManager();
+
+ /**
+ * @return IContentSecurityPolicyManager
+ * @since 9.0.0
+ */
+ public function getContentSecurityPolicyManager();
}
diff --git a/lib/public/security/icontentsecuritypolicymanager.php b/lib/public/security/icontentsecuritypolicymanager.php
new file mode 100644
index 00000000000..10f1efe995f
--- /dev/null
+++ b/lib/public/security/icontentsecuritypolicymanager.php
@@ -0,0 +1,50 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCP\Security;
+use OCP\AppFramework\Http\EmptyContentSecurityPolicy;
+
+/**
+ * Used for Content Security Policy manipulations
+ *
+ * @package OCP\Security
+ * @since 9.0.0
+ */
+interface IContentSecurityPolicyManager {
+ /**
+ * Allows to inject something into the default content policy. This is for
+ * example useful when you're injecting Javascript code into a view belonging
+ * to another controller and cannot modify its Content-Security-Policy itself.
+ * Note that the adjustment is only applied to applications that use AppFramework
+ * controllers.
+ *
+ * To use this from your `app.php` use `\OC::$server->getContentSecurityPolicyManager()->addDefaultPolicy($policy)`,
+ * $policy has to be of type `\OCP\AppFramework\Http\ContentSecurityPolicy`.
+ *
+ * WARNING: Using this API incorrectly may make the instance more insecure.
+ * Do think twice before adding whitelisting resources. Please do also note
+ * that it is not possible to use the `disallowXYZ` functions.
+ *
+ * @param EmptyContentSecurityPolicy $policy
+ * @since 9.0.0
+ */
+ public function addDefaultPolicy(EmptyContentSecurityPolicy $policy);
+}