Skip to content

Commit 905bf3f

Browse files
committed
Add an event to edit the CSP
This introduces and event that can be listend to when we actually use the CSP. This means that apps no longer have to always inject their CSP but only do so when it is required. Yay for being lazy. Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
1 parent eb092bb commit 905bf3f

File tree

9 files changed

+167
-6
lines changed

9 files changed

+167
-6
lines changed

lib/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,7 @@
373373
'OCP\\Search\\PagedProvider' => $baseDir . '/lib/public/Search/PagedProvider.php',
374374
'OCP\\Search\\Provider' => $baseDir . '/lib/public/Search/Provider.php',
375375
'OCP\\Search\\Result' => $baseDir . '/lib/public/Search/Result.php',
376+
'OCP\\Security\\CSP\\AddContentSecurityPolicyEvent' => $baseDir . '/lib/public/Security/CSP/AddContentSecurityPolicyEvent.php',
376377
'OCP\\Security\\IContentSecurityPolicyManager' => $baseDir . '/lib/public/Security/IContentSecurityPolicyManager.php',
377378
'OCP\\Security\\ICredentialsManager' => $baseDir . '/lib/public/Security/ICredentialsManager.php',
378379
'OCP\\Security\\ICrypto' => $baseDir . '/lib/public/Security/ICrypto.php',

lib/composer/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
407407
'OCP\\Search\\PagedProvider' => __DIR__ . '/../../..' . '/lib/public/Search/PagedProvider.php',
408408
'OCP\\Search\\Provider' => __DIR__ . '/../../..' . '/lib/public/Search/Provider.php',
409409
'OCP\\Search\\Result' => __DIR__ . '/../../..' . '/lib/public/Search/Result.php',
410+
'OCP\\Security\\CSP\\AddContentSecurityPolicyEvent' => __DIR__ . '/../../..' . '/lib/public/Security/CSP/AddContentSecurityPolicyEvent.php',
410411
'OCP\\Security\\IContentSecurityPolicyManager' => __DIR__ . '/../../..' . '/lib/public/Security/IContentSecurityPolicyManager.php',
411412
'OCP\\Security\\ICredentialsManager' => __DIR__ . '/../../..' . '/lib/public/Security/ICredentialsManager.php',
412413
'OCP\\Security\\ICrypto' => __DIR__ . '/../../..' . '/lib/public/Security/ICrypto.php',

lib/private/Security/CSP/ContentSecurityPolicyManager.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,21 @@
2525

2626
use OCP\AppFramework\Http\ContentSecurityPolicy;
2727
use OCP\AppFramework\Http\EmptyContentSecurityPolicy;
28+
use OCP\EventDispatcher\IEventDispatcher;
29+
use OCP\Security\CSP\AddContentSecurityPolicyEvent;
2830
use OCP\Security\IContentSecurityPolicyManager;
2931

3032
class ContentSecurityPolicyManager implements IContentSecurityPolicyManager {
3133
/** @var ContentSecurityPolicy[] */
3234
private $policies = [];
3335

36+
/** @var IEventDispatcher */
37+
private $dispatcher;
38+
39+
public function __construct(IEventDispatcher $dispatcher) {
40+
$this->dispatcher = $dispatcher;
41+
}
42+
3443
/** {@inheritdoc} */
3544
public function addDefaultPolicy(EmptyContentSecurityPolicy $policy) {
3645
$this->policies[] = $policy;
@@ -43,6 +52,9 @@ public function addDefaultPolicy(EmptyContentSecurityPolicy $policy) {
4352
* @return ContentSecurityPolicy
4453
*/
4554
public function getDefaultPolicy(): ContentSecurityPolicy {
55+
$event = new AddContentSecurityPolicyEvent($this);
56+
$this->dispatcher->dispatch(AddContentSecurityPolicyEvent::class, $event);
57+
4658
$defaultPolicy = new \OC\Security\CSP\ContentSecurityPolicy();
4759
foreach($this->policies as $policy) {
4860
$defaultPolicy = $this->mergePolicies($defaultPolicy, $policy);

lib/private/Server.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,10 +1029,8 @@ public function __construct($webRoot, \OC\Config $config) {
10291029
$this->registerService(SessionStorage::class, function (Server $c) {
10301030
return new SessionStorage($c->getSession());
10311031
});
1032-
$this->registerService(\OCP\Security\IContentSecurityPolicyManager::class, function (Server $c) {
1033-
return new ContentSecurityPolicyManager();
1034-
});
1035-
$this->registerAlias('ContentSecurityPolicyManager', \OCP\Security\IContentSecurityPolicyManager::class);
1032+
$this->registerAlias(\OCP\Security\IContentSecurityPolicyManager::class, \OC\Security\CSP\ContentSecurityPolicyManager::class);
1033+
$this->registerAlias('ContentSecurityPolicyManager', \OC\Security\CSP\ContentSecurityPolicyManager::class);
10361034

10371035
$this->registerService('ContentSecurityPolicyNonceManager', function (Server $c) {
10381036
return new ContentSecurityPolicyNonceManager(

lib/public/IServerContainer.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,7 @@ public function getShareManager();
534534
/**
535535
* @return IContentSecurityPolicyManager
536536
* @since 9.0.0
537+
* @deprecated 17.0.0 Use the AddCSPPolicyEvent
537538
*/
538539
public function getContentSecurityPolicyManager();
539540

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
declare(strict_types=1);
3+
/**
4+
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
5+
*
6+
* @author Roeland Jago Douma <roeland@famdouma.nl>
7+
*
8+
* @license GNU AGPL version 3 or any later version
9+
*
10+
* This program is free software: you can redistribute it and/or modify
11+
* it under the terms of the GNU Affero General Public License as
12+
* published by the Free Software Foundation, either version 3 of the
13+
* License, or (at your option) any later version.
14+
*
15+
* This program is distributed in the hope that it will be useful,
16+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
* GNU Affero General Public License for more details.
19+
*
20+
* You should have received a copy of the GNU Affero General Public License
21+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
22+
*
23+
*/
24+
25+
namespace OCP\Security\CSP;
26+
27+
use OC\Security\CSP\ContentSecurityPolicyManager;
28+
use OCP\AppFramework\Http\EmptyContentSecurityPolicy;
29+
use OCP\EventDispatcher\Event;
30+
31+
/**
32+
* @since 17.0.0
33+
*/
34+
class AddContentSecurityPolicyEvent extends Event {
35+
36+
/** @var ContentSecurityPolicyManager */
37+
private $policyManager;
38+
39+
/**
40+
* @since 17.0.0
41+
*/
42+
public function __construct(ContentSecurityPolicyManager $policyManager) {
43+
$this->policyManager = $policyManager;
44+
}
45+
46+
/**
47+
* @since 17.0.0
48+
*/
49+
public function addPolicy(EmptyContentSecurityPolicy $csp): void {
50+
$this->policyManager->addDefaultPolicy($csp);
51+
}
52+
}

lib/public/Security/IContentSecurityPolicyManager.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
*
2929
* @package OCP\Security
3030
* @since 9.0.0
31+
* @deprecated 17.0.0 listen to the AddCSPPolicyEvent to add a policy
3132
*/
3233
interface IContentSecurityPolicyManager {
3334
/**
@@ -46,6 +47,7 @@ interface IContentSecurityPolicyManager {
4647
*
4748
* @param EmptyContentSecurityPolicy $policy
4849
* @since 9.0.0
50+
* @deprecated 17.0.0 listen to the AddContentSecurityPolicyEvent to add a policy
4951
*/
5052
public function addDefaultPolicy(EmptyContentSecurityPolicy $policy);
5153
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
declare(strict_types=1);
3+
/**
4+
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
5+
*
6+
* @author Roeland Jago Douma <roeland@famdouma.nl>
7+
*
8+
* @license GNU AGPL version 3 or any later version
9+
*
10+
* This program is free software: you can redistribute it and/or modify
11+
* it under the terms of the GNU Affero General Public License as
12+
* published by the Free Software Foundation, either version 3 of the
13+
* License, or (at your option) any later version.
14+
*
15+
* This program is distributed in the hope that it will be useful,
16+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
* GNU Affero General Public License for more details.
19+
*
20+
* You should have received a copy of the GNU Affero General Public License
21+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
22+
*
23+
*/
24+
25+
namespace Test\Security\CSP;
26+
27+
use OC\Security\CSP\ContentSecurityPolicyManager;
28+
use OCP\AppFramework\Http\ContentSecurityPolicy;
29+
use OCP\Security\CSP\AddContentSecurityPolicyEvent;
30+
use Test\TestCase;
31+
32+
class AddContentSecurityPolicyEventTest extends TestCase {
33+
public function testAddEvent() {
34+
$cspManager = $this->createMock(ContentSecurityPolicyManager::class);
35+
$policy = $this->createMock(ContentSecurityPolicy::class);
36+
$event = new AddContentSecurityPolicyEvent($cspManager);
37+
38+
$cspManager->expects($this->once())
39+
->method('addDefaultPolicy')
40+
->with($policy);
41+
42+
$event->addPolicy($policy);
43+
}
44+
}

tests/lib/Security/CSP/ContentSecurityPolicyManagerTest.php

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,24 @@
2323

2424

2525
use OC\Security\CSP\ContentSecurityPolicyManager;
26+
use OCP\EventDispatcher\IEventDispatcher;
27+
use OCP\Security\CSP\AddContentSecurityPolicyEvent;
28+
use PHPUnit\Framework\MockObject\MockObject;
29+
use Symfony\Component\EventDispatcher\EventDispatcher;
30+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
31+
use Test\TestCase;
32+
33+
class ContentSecurityPolicyManagerTest extends TestCase {
34+
/** @var EventDispatcherInterface */
35+
private $dispatcher;
2636

27-
class ContentSecurityPolicyManagerTest extends \Test\TestCase {
2837
/** @var ContentSecurityPolicyManager */
2938
private $contentSecurityPolicyManager;
3039

3140
public function setUp() {
3241
parent::setUp();
33-
$this->contentSecurityPolicyManager = new ContentSecurityPolicyManager();
42+
$this->dispatcher = \OC::$server->query(IEventDispatcher::class);
43+
$this->contentSecurityPolicyManager = new ContentSecurityPolicyManager($this->dispatcher);
3444
}
3545

3646
public function testAddDefaultPolicy() {
@@ -69,4 +79,44 @@ public function testGetDefaultPolicyWithPolicies() {
6979
$this->assertSame($expectedStringPolicy, $this->contentSecurityPolicyManager->getDefaultPolicy()->buildPolicy());
7080
}
7181

82+
public function testGetDefaultPolicyWithPoliciesViaEvent() {
83+
$this->dispatcher->addListener(AddContentSecurityPolicyEvent::class, function(AddContentSecurityPolicyEvent $e) {
84+
$policy = new \OCP\AppFramework\Http\ContentSecurityPolicy();
85+
$policy->addAllowedFontDomain('mydomain.com');
86+
$policy->addAllowedImageDomain('anotherdomain.de');
87+
88+
$e->addPolicy($policy);
89+
});
90+
91+
$this->dispatcher->addListener(AddContentSecurityPolicyEvent::class, function(AddContentSecurityPolicyEvent $e) {
92+
$policy = new \OCP\AppFramework\Http\ContentSecurityPolicy();
93+
$policy->addAllowedFontDomain('example.com');
94+
$policy->addAllowedImageDomain('example.org');
95+
$policy->allowInlineScript(true);
96+
$policy->allowEvalScript(true);
97+
$e->addPolicy($policy);
98+
});
99+
100+
$this->dispatcher->addListener(AddContentSecurityPolicyEvent::class, function(AddContentSecurityPolicyEvent $e) {
101+
$policy = new \OCP\AppFramework\Http\EmptyContentSecurityPolicy();
102+
$policy->addAllowedChildSrcDomain('childdomain');
103+
$policy->addAllowedFontDomain('anotherFontDomain');
104+
$e->addPolicy($policy);
105+
});
106+
107+
$expected = new \OC\Security\CSP\ContentSecurityPolicy();
108+
$expected->allowInlineScript(true);
109+
$expected->allowEvalScript(true);
110+
$expected->addAllowedFontDomain('mydomain.com');
111+
$expected->addAllowedFontDomain('example.com');
112+
$expected->addAllowedFontDomain('anotherFontDomain');
113+
$expected->addAllowedImageDomain('anotherdomain.de');
114+
$expected->addAllowedImageDomain('example.org');
115+
$expected->addAllowedChildSrcDomain('childdomain');
116+
$expectedStringPolicy = "default-src 'none';base-uri 'none';manifest-src 'self';script-src 'self' 'unsafe-inline' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data: blob: anotherdomain.de example.org;font-src 'self' data: mydomain.com example.com anotherFontDomain;connect-src 'self';media-src 'self';child-src childdomain;frame-ancestors 'self'";
117+
118+
$this->assertEquals($expected, $this->contentSecurityPolicyManager->getDefaultPolicy());
119+
$this->assertSame($expectedStringPolicy, $this->contentSecurityPolicyManager->getDefaultPolicy()->buildPolicy());
120+
}
121+
72122
}

0 commit comments

Comments
 (0)