Skip to content

Commit

Permalink
Add public API to give developers the possibility to adjust the globa…
Browse files Browse the repository at this point in the history
…l 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'
```
  • Loading branch information
LukasReschke committed Jan 28, 2016
1 parent 8b3d7d0 commit 809ff5a
Show file tree
Hide file tree
Showing 15 changed files with 1,316 additions and 348 deletions.
10 changes: 6 additions & 4 deletions lib/private/appframework/dependencyinjection/dicontainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@

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;
Expand All @@ -43,8 +42,6 @@
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 {
Expand Down Expand Up @@ -255,6 +252,10 @@ public function __construct($appName, $urlParams = array()){
return $this->getServer()->getSession();
});

$this->registerService('OCP\\Security\\IContentSecurityPolicyManager', function($c) {
return $this->getServer()->getContentSecurityPolicyManager();
});

$this->registerService('ServerContainer', function ($c) {
return $this->getServer();
});
Expand Down Expand Up @@ -319,7 +320,8 @@ public function __construct($appName, $urlParams = array()){
$app->getServer()->getLogger(),
$c['AppName'],
$app->isLoggedIn(),
$app->isAdminUser()
$app->isAdminUser(),
$app->getServer()->getContentSecurityPolicyManager()
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
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;
Expand All @@ -52,15 +54,24 @@
* 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
Expand All @@ -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,
Expand All @@ -79,7 +91,8 @@ public function __construct(IRequest $request,
ILogger $logger,
$appName,
$isLoggedIn,
$isAdminUser) {
$isAdminUser,
ContentSecurityPolicyManager $contentSecurityPolicyManager) {
$this->navigationManager = $navigationManager;
$this->request = $request;
$this->reflector = $reflector;
Expand All @@ -88,6 +101,7 @@ public function __construct(IRequest $request,
$this->logger = $logger;
$this->isLoggedIn = $isLoggedIn;
$this->isAdminUser = $isAdminUser;
$this->contentSecurityPolicyManager = $contentSecurityPolicyManager;
}


Expand Down Expand Up @@ -139,6 +153,25 @@ public function beforeController($controller, $methodName) {

}

/**
* 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
Expand Down
199 changes: 199 additions & 0 deletions lib/private/security/csp/contentsecuritypolicy.php
Original file line number Diff line number Diff line change
@@ -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;
}

}
73 changes: 73 additions & 0 deletions lib/private/security/csp/contentsecuritypolicymanager.php
Original file line number Diff line number Diff line change
@@ -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;
}
}

0 comments on commit 809ff5a

Please sign in to comment.