aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private
diff options
context:
space:
mode:
authorLukas Reschke <lukas@owncloud.com>2016-01-28 14:33:02 +0100
committerLukas Reschke <lukas@owncloud.com>2016-01-28 18:36:46 +0100
commit809ff5ac95080b02d1ba4bba76efab59ff70122b (patch)
treee0049e209a157acab5150daac19a77ac01fdf8f0 /lib/private
parent8b3d7d09d52ba169953d6a7d03ab570eb3ceed7a (diff)
downloadnextcloud-server-809ff5ac95080b02d1ba4bba76efab59ff70122b.tar.gz
nextcloud-server-809ff5ac95080b02d1ba4bba76efab59ff70122b.zip
Add public API to give developers the possibility to adjust the global CSP defaults
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`. To test this add something like the following into an `app.php` of any enabled app: ``` $manager = \OC::$server->getContentSecurityPolicyManager(); $policy = new \OCP\AppFramework\Http\ContentSecurityPolicy(false); $policy->addAllowedFrameDomain('asdf'); $policy->addAllowedScriptDomain('yolo.com'); $policy->allowInlineScript(false); $manager->addDefaultPolicy($policy); $policy = new \OCP\AppFramework\Http\ContentSecurityPolicy(false); $policy->addAllowedFontDomain('yolo.com'); $manager->addDefaultPolicy($policy); $policy = new \OCP\AppFramework\Http\ContentSecurityPolicy(false); $policy->addAllowedFrameDomain('banana.com'); $manager->addDefaultPolicy($policy); ``` If you now open the files app the policy should be: ``` Content-Security-Policy:default-src 'none';script-src yolo.com 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data: blob:;font-src yolo.com 'self';connect-src 'self';media-src 'self';frame-src asdf banana.com 'self' ```
Diffstat (limited to 'lib/private')
-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
5 files changed, 325 insertions, 6 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