Skip to content

Commit 0518ff1

Browse files
rullzerChristophWurst
authored andcommitted
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 <[email protected]>
1 parent 1bbb26e commit 0518ff1

File tree

9 files changed

+166
-6
lines changed

9 files changed

+166
-6
lines changed

lib/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,7 @@
369369
'OCP\\Search\\PagedProvider' => $baseDir . '/lib/public/Search/PagedProvider.php',
370370
'OCP\\Search\\Provider' => $baseDir . '/lib/public/Search/Provider.php',
371371
'OCP\\Search\\Result' => $baseDir . '/lib/public/Search/Result.php',
372+
'OCP\\Security\\CSP\\AddContentSecurityPolicyEvent' => $baseDir . '/lib/public/Security/CSP/AddContentSecurityPolicyEvent.php',
372373
'OCP\\Security\\IContentSecurityPolicyManager' => $baseDir . '/lib/public/Security/IContentSecurityPolicyManager.php',
373374
'OCP\\Security\\ICredentialsManager' => $baseDir . '/lib/public/Security/ICredentialsManager.php',
374375
'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
@@ -403,6 +403,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
403403
'OCP\\Search\\PagedProvider' => __DIR__ . '/../../..' . '/lib/public/Search/PagedProvider.php',
404404
'OCP\\Search\\Provider' => __DIR__ . '/../../..' . '/lib/public/Search/Provider.php',
405405
'OCP\\Search\\Result' => __DIR__ . '/../../..' . '/lib/public/Search/Result.php',
406+
'OCP\\Security\\CSP\\AddContentSecurityPolicyEvent' => __DIR__ . '/../../..' . '/lib/public/Security/CSP/AddContentSecurityPolicyEvent.php',
406407
'OCP\\Security\\IContentSecurityPolicyManager' => __DIR__ . '/../../..' . '/lib/public/Security/IContentSecurityPolicyManager.php',
407408
'OCP\\Security\\ICredentialsManager' => __DIR__ . '/../../..' . '/lib/public/Security/ICredentialsManager.php',
408409
'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\Security\CSP\AddContentSecurityPolicyEvent;
2829
use OCP\Security\IContentSecurityPolicyManager;
30+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
2931

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

36+
/** @var EventDispatcherInterface */
37+
private $dispatcher;
38+
39+
public function __construct(EventDispatcherInterface $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
@@ -1031,10 +1031,8 @@ public function __construct($webRoot, \OC\Config $config) {
10311031
$this->registerService(SessionStorage::class, function (Server $c) {
10321032
return new SessionStorage($c->getSession());
10331033
});
1034-
$this->registerService(\OCP\Security\IContentSecurityPolicyManager::class, function (Server $c) {
1035-
return new ContentSecurityPolicyManager();
1036-
});
1037-
$this->registerAlias('ContentSecurityPolicyManager', \OCP\Security\IContentSecurityPolicyManager::class);
1034+
$this->registerAlias(\OCP\Security\IContentSecurityPolicyManager::class, \OC\Security\CSP\ContentSecurityPolicyManager::class);
1035+
$this->registerAlias('ContentSecurityPolicyManager', \OC\Security\CSP\ContentSecurityPolicyManager::class);
10381036

10391037
$this->registerService('ContentSecurityPolicyNonceManager', function (Server $c) {
10401038
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 AddContentSecurityPolicyEvent
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 <[email protected]>
5+
*
6+
* @author Roeland Jago Douma <[email protected]>
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 Symfony\Component\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 AddContentSecurityPolicyEvent 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 <[email protected]>
5+
*
6+
* @author Roeland Jago Douma <[email protected]>
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: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,23 @@
2323

2424

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

27-
class ContentSecurityPolicyManagerTest extends \Test\TestCase {
2836
/** @var ContentSecurityPolicyManager */
2937
private $contentSecurityPolicyManager;
3038

3139
public function setUp() {
3240
parent::setUp();
33-
$this->contentSecurityPolicyManager = new ContentSecurityPolicyManager();
41+
$this->dispatcher = new EventDispatcher();
42+
$this->contentSecurityPolicyManager = new ContentSecurityPolicyManager($this->dispatcher);
3443
}
3544

3645
public function testAddDefaultPolicy() {
@@ -69,4 +78,44 @@ public function testGetDefaultPolicyWithPolicies() {
6978
$this->assertSame($expectedStringPolicy, $this->contentSecurityPolicyManager->getDefaultPolicy()->buildPolicy());
7079
}
7180

81+
public function testGetDefaultPolicyWithPoliciesViaEvent() {
82+
$this->dispatcher->addListener(AddContentSecurityPolicyEvent::class, function(AddContentSecurityPolicyEvent $e) {
83+
$policy = new \OCP\AppFramework\Http\ContentSecurityPolicy();
84+
$policy->addAllowedFontDomain('mydomain.com');
85+
$policy->addAllowedImageDomain('anotherdomain.de');
86+
87+
$e->addPolicy($policy);
88+
});
89+
90+
$this->dispatcher->addListener(AddContentSecurityPolicyEvent::class, function(AddContentSecurityPolicyEvent $e) {
91+
$policy = new \OCP\AppFramework\Http\ContentSecurityPolicy();
92+
$policy->addAllowedFontDomain('example.com');
93+
$policy->addAllowedImageDomain('example.org');
94+
$policy->allowInlineScript(true);
95+
$policy->allowEvalScript(true);
96+
$e->addPolicy($policy);
97+
});
98+
99+
$this->dispatcher->addListener(AddContentSecurityPolicyEvent::class, function(AddContentSecurityPolicyEvent $e) {
100+
$policy = new \OCP\AppFramework\Http\EmptyContentSecurityPolicy();
101+
$policy->addAllowedChildSrcDomain('childdomain');
102+
$policy->addAllowedFontDomain('anotherFontDomain');
103+
$e->addPolicy($policy);
104+
});
105+
106+
$expected = new \OC\Security\CSP\ContentSecurityPolicy();
107+
$expected->allowInlineScript(true);
108+
$expected->allowEvalScript(true);
109+
$expected->addAllowedFontDomain('mydomain.com');
110+
$expected->addAllowedFontDomain('example.com');
111+
$expected->addAllowedFontDomain('anotherFontDomain');
112+
$expected->addAllowedImageDomain('anotherdomain.de');
113+
$expected->addAllowedImageDomain('example.org');
114+
$expected->addAllowedChildSrcDomain('childdomain');
115+
$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'";
116+
117+
$this->assertEquals($expected, $this->contentSecurityPolicyManager->getDefaultPolicy());
118+
$this->assertSame($expectedStringPolicy, $this->contentSecurityPolicyManager->getDefaultPolicy()->buildPolicy());
119+
}
120+
72121
}

0 commit comments

Comments
 (0)