‪TYPO3CMS  ‪main
PolicyTest.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use PHPUnit\Framework\Attributes\DataProvider;
21 use PHPUnit\Framework\Attributes\Test;
33 use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
34 
35 final class ‪PolicyTest extends FunctionalTestCase
36 {
38  protected bool ‪$initializeDatabase = false;
39 
40  protected function ‪setUp(): void
41  {
42  parent::setUp();
43  $this->nonce = new ‪ConsumableNonce();
44  }
45 
46  #[Test]
47  public function ‪hashProxyIsCompiled(): void
48  {
49  // ```
50  // cat typo3/sysext/core/Tests/Unit/Security/ContentSecurityPolicy/Fixtures/app-fixture.js \
51  // | openssl dgst -binary -sha256 | openssl base64 -A
52  // dawsv3oUbEz6NVoOxXFAu0k7W3I/PS6NucUIAmvoIng=
53  // ```
54  $hashProxy = ‪HashProxy::glob(
55  'EXT:core/Tests/Unit/Security/ContentSecurityPolicy/Fixtures/*.js'
56  );
57  $policy = (new ‪Policy())->extend(Directive::ScriptSrc, $hashProxy);
58  self::assertSame("script-src 'sha256-dawsv3oUbEz6NVoOxXFAu0k7W3I/PS6NucUIAmvoIng='", $policy->compile($this->nonce));
59  }
60 
61  #[Test]
62  public function ‪hashValueIsCompiled(): void
63  {
64  $hash = hash('sha256', 'test', true);
65  $hashB64 = base64_encode($hash);
66  $policy = (new ‪Policy())->extend(Directive::ScriptSrc, ‪HashValue::create($hash));
67  self::assertSame("script-src 'sha256-$hashB64'", $policy->compile($this->nonce));
68  }
69 
70  #[Test]
71  public function ‪constructorSetsdefaultDirective(): void
72  {
73  $policy = (new ‪Policy(SourceKeyword::self));
74  self::assertSame("default-src 'self'", $policy->compile($this->nonce));
75  }
76 
77  #[Test]
78  public function ‪defaultDirectiveIsModified(): void
79  {
80  $policy = (new ‪Policy(SourceKeyword::self))
81  ->default(SourceKeyword::none);
82  self::assertSame("default-src 'none'", $policy->compile($this->nonce));
83  }
84 
85  #[Test]
86  public function ‪defaultDirectiveConsidersVeto(): void
87  {
88  $policy = (new ‪Policy(SourceKeyword::self))
89  ->default(SourceKeyword::unsafeEval, SourceKeyword::none);
90  self::assertSame("default-src 'none'", $policy->compile($this->nonce));
91  }
92 
93  #[Test]
94  public function ‪newDirectiveExtendsDefault(): void
95  {
96  $policy = (new ‪Policy(SourceKeyword::self))
97  ->extend(Directive::ScriptSrc, SourceKeyword::unsafeInline);
98  self::assertSame("default-src 'self'; script-src 'self' 'unsafe-inline'", $policy->compile($this->nonce));
99  }
100 
101  #[Test]
103  {
104  $policy = (new ‪Policy(SourceKeyword::self))
105  ->extend(Directive::Sandbox)
106  ->extend(Directive::TrustedTypes);
107  self::assertSame("default-src 'self'; sandbox; trusted-types", $policy->compile($this->nonce));
108  }
109 
110  public static function ‪ancestorInheritanceIsAppliedFromMutationsDataProvider(): \Generator
111  {
112  yield 'script-src in inherited from default-src' => [
114  new ‪Mutation(MutationMode::Set, Directive::DefaultSrc, SourceKeyword::self),
115  new ‪Mutation(MutationMode::InheritOnce, Directive::ScriptSrc),
116  new ‪Mutation(MutationMode::Append, Directive::ScriptSrc, SourceKeyword::unsafeInline),
117  ),
118  "default-src 'self'; script-src 'self' 'unsafe-inline'",
119  ];
120  yield 'script-src is inherited just once from default-src' => [
122  new ‪Mutation(MutationMode::Set, Directive::DefaultSrc, SourceKeyword::self),
123  new ‪Mutation(MutationMode::InheritOnce, Directive::ScriptSrc),
124  new ‪Mutation(MutationMode::Append, Directive::ScriptSrc, SourceKeyword::unsafeInline),
125  new ‪Mutation(MutationMode::Set, Directive::DefaultSrc, SourceScheme::data),
126  new ‪Mutation(MutationMode::InheritOnce, Directive::ScriptSrc),
127  ),
128  "default-src data:; script-src 'self' 'unsafe-inline'",
129  ];
130  yield 'script-src is inherited just once (via extend) from default-src' => [
132  new ‪Mutation(MutationMode::Set, Directive::DefaultSrc, SourceKeyword::self),
133  new ‪Mutation(MutationMode::Extend, Directive::ScriptSrc, SourceKeyword::unsafeInline),
134  new ‪Mutation(MutationMode::Set, Directive::DefaultSrc, SourceScheme::data),
135  new ‪Mutation(MutationMode::Extend, Directive::ScriptSrc, SourceKeyword::unsafeInline),
136  ),
137  "default-src data:; script-src 'self' 'unsafe-inline'",
138  ];
139  yield 'script-src is inherited again from default-src' => [
141  new ‪Mutation(MutationMode::Set, Directive::DefaultSrc, SourceKeyword::self),
142  new ‪Mutation(MutationMode::InheritOnce, Directive::ScriptSrc),
143  new ‪Mutation(MutationMode::Append, Directive::ScriptSrc, SourceKeyword::unsafeInline),
144  new ‪Mutation(MutationMode::Set, Directive::DefaultSrc, SourceScheme::data),
145  new ‪Mutation(MutationMode::InheritAgain, Directive::ScriptSrc),
146  ),
147  "default-src data:; script-src data: 'self' 'unsafe-inline'",
148  ];
149  }
150 
151  #[DataProvider('ancestorInheritanceIsAppliedFromMutationsDataProvider')]
152  #[Test]
153  public function ‪ancestorInheritanceIsAppliedFromMutations(‪MutationCollection $mutations, string $expectation): void
154  {
155  $policy = (new ‪Policy())->mutate($mutations);
156  self::assertSame($expectation, $policy->compile($this->nonce));
157  }
158 
159  #[Test]
160  public function ‪newDirectiveDoesNotExtendDefault(): void
161  {
162  $policy = (new ‪Policy(SourceKeyword::self))
163  ->set(Directive::ScriptSrc, SourceKeyword::unsafeInline);
164  self::assertSame("default-src 'self'; script-src 'unsafe-inline'", $policy->compile($this->nonce));
165  }
166 
167  #[Test]
168  public function ‪directiveIsReduced(): void
169  {
170  $policy = (new ‪Policy())
171  ->set(Directive::ScriptSrc, SourceKeyword::self, SourceKeyword::unsafeInline)
172  ->reduce(Directive::ScriptSrc, SourceKeyword::self);
173  self::assertSame("script-src 'unsafe-inline'", $policy->compile($this->nonce));
174  }
175 
176  #[Test]
177  public function ‪sourceSchemeIsCompiled(): void
178  {
179  $policy = (new ‪Policy(SourceKeyword::self, SourceScheme::blob));
180  self::assertSame("default-src 'self' blob:", $policy->compile($this->nonce));
181  }
182 
183  #[Test]
184  public function ‪nonceProxyIsCompiled(): void
185  {
186  $policy = (new ‪Policy(SourceKeyword::self, SourceKeyword::nonceProxy));
187  self::assertSame("default-src 'self' 'nonce-{$this->nonce->value}'", $policy->compile($this->nonce));
188  }
189 
193  #[Test]
194  public function ‪strictDynamicIsApplied(): void
195  {
196  $policy = (new ‪Policy(SourceKeyword::self, SourceKeyword::strictDynamic))
197  ->extend(Directive::ScriptSrc, SourceKeyword::strictDynamic)
198  ->extend(Directive::StyleSrc, SourceKeyword::strictDynamic);
199  self::assertSame(
200  "default-src 'self'; script-src 'self' 'strict-dynamic' 'nonce-{$this->nonce->value}'",
201  $policy->compile($this->nonce)
202  );
203  }
204 
205  #[Test]
206  public function ‪directiveIsRemoved(): void
207  {
208  $policy = (new ‪Policy(SourceKeyword::self))
209  ->remove(Directive::DefaultSrc);
210  self::assertSame('', $policy->compile($this->nonce));
211  }
212 
213  #[Test]
214  public function ‪superfluousDirectivesArePurged(): void
215  {
216  $policy = (new ‪Policy(SourceKeyword::self, SourceScheme::data))
217  ->set(Directive::ScriptSrc, SourceKeyword::self, SourceScheme::data);
218  self::assertSame("default-src 'self' data:", $policy->compile($this->nonce));
219  }
220 
221  #[Test]
222  public function ‪backendPolicyIsCompiled(): void
223  {
224  $policy = (new ‪Policy())
225  ->default(SourceKeyword::self)
226  ->extend(Directive::ScriptSrc, SourceKeyword::nonceProxy)
227  ->extend(Directive::StyleSrc, SourceKeyword::unsafeInline)
228  ->set(Directive::StyleSrcAttr, SourceKeyword::unsafeInline)
229  ->extend(Directive::ImgSrc, SourceScheme::data)
230  ->set(Directive::WorkerSrc, SourceKeyword::self, SourceScheme::blob)
231  ->extend(Directive::FrameSrc, SourceScheme::blob);
232  self::assertSame(
233  "default-src 'self'; script-src 'self' 'nonce-{$this->nonce->value}'; "
234  . "style-src 'self' 'unsafe-inline'; style-src-attr 'unsafe-inline'; "
235  . "img-src 'self' data:; worker-src 'self' blob:; frame-src 'self' blob:",
236  $policy->compile($this->nonce)
237  );
238  }
239 
240  #[Test]
242  {
243  $policy = (new ‪Policy())
244  ->extend(Directive::ScriptSrc, SourceKeyword::unsafeInline, SourceScheme::data, new ‪UriValue('https://example.org'))
245  ->extend(Directive::StyleSrc, SourceKeyword::unsafeInline, SourceScheme::blob, new ‪UriValue('https://example.com/path'));
246 
247  self::assertTrue($policy->containsDirective(Directive::ScriptSrc, SourceScheme::data, new ‪UriValue('https://example.org')));
248  self::assertTrue($policy->containsDirective(Directive::StyleSrc, SourceScheme::blob, new ‪UriValue('https://example.com/path')));
249  self::assertFalse($policy->containsDirective(Directive::ConnectSrc, SourceScheme::https));
250  }
251 
252  #[Test]
254  {
255  $policy = (new ‪Policy())
256  ->extend(Directive::ScriptSrc, SourceKeyword::unsafeInline, SourceScheme::data, new ‪UriValue('*.example.org'))
257  ->extend(Directive::StyleSrc, SourceKeyword::unsafeInline, SourceScheme::blob, new ‪UriValue('https://*.example.com'));
258 
259  self::assertTrue($policy->coversDirective(Directive::ScriptSrc, SourceScheme::data, new ‪UriValue('https://sub.example.org/path/file.js')));
260  self::assertTrue($policy->coversDirective(Directive::StyleSrc, SourceScheme::blob, new ‪UriValue('https://sub.example.com/path/file.css')));
261  self::assertFalse($policy->coversDirective(Directive::ConnectSrc, SourceScheme::https));
262  }
263 
264  #[Test]
265  public function ‪containedPolicyIsDetermined(): void
266  {
267  $policy = (new ‪Policy())
268  ->extend(Directive::ScriptSrc, SourceKeyword::unsafeInline, SourceScheme::data, new ‪UriValue('https://example.org'))
269  ->extend(Directive::StyleSrc, SourceKeyword::unsafeInline, SourceScheme::blob, new ‪UriValue('https://example.com/path'));
270  $other = (new ‪Policy())
271  ->extend(Directive::ScriptSrc, SourceScheme::data, new ‪UriValue('https://example.org'))
272  ->extend(Directive::StyleSrc, SourceScheme::blob, new ‪UriValue('https://example.com/path'));
273  self::assertTrue($policy->contains($other));
274  self::assertFalse($other->contains($policy));
275  }
276 }
‪TYPO3\CMS\Core\Tests\Functional\Security\ContentSecurityPolicy\PolicyTest\nonceProxyIsCompiled
‪nonceProxyIsCompiled()
Definition: PolicyTest.php:184
‪TYPO3\CMS\Core\Tests\Functional\Security\ContentSecurityPolicy\PolicyTest
Definition: PolicyTest.php:36
‪TYPO3\CMS\Core\Tests\Functional\Security\ContentSecurityPolicy\PolicyTest\superfluousDirectivesArePurged
‪superfluousDirectivesArePurged()
Definition: PolicyTest.php:214
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Directive
‪Directive
Definition: Directive.php:25
‪TYPO3\CMS\Core\Tests\Functional\Security\ContentSecurityPolicy\PolicyTest\nonAncestorDirectiveDoesNotExtendDefault
‪nonAncestorDirectiveDoesNotExtendDefault()
Definition: PolicyTest.php:102
‪TYPO3\CMS\Core\Tests\Functional\Security\ContentSecurityPolicy\PolicyTest\strictDynamicIsApplied
‪strictDynamicIsApplied()
Definition: PolicyTest.php:194
‪TYPO3\CMS\Core\Tests\Functional\Security\ContentSecurityPolicy\PolicyTest\hashValueIsCompiled
‪hashValueIsCompiled()
Definition: PolicyTest.php:62
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\HashValue
Definition: HashValue.php:27
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\ConsumableNonce
Definition: ConsumableNonce.php:24
‪TYPO3\CMS\Core\Tests\Functional\Security\ContentSecurityPolicy\PolicyTest\constructorSetsdefaultDirective
‪constructorSetsdefaultDirective()
Definition: PolicyTest.php:71
‪TYPO3\CMS\Core\Tests\Functional\Security\ContentSecurityPolicy\PolicyTest\ancestorInheritanceIsAppliedFromMutationsDataProvider
‪static ancestorInheritanceIsAppliedFromMutationsDataProvider()
Definition: PolicyTest.php:110
‪TYPO3\CMS\Core\Tests\Functional\Security\ContentSecurityPolicy\PolicyTest\sourceSchemeIsCompiled
‪sourceSchemeIsCompiled()
Definition: PolicyTest.php:177
‪TYPO3\CMS\Core\Tests\Functional\Security\ContentSecurityPolicy\PolicyTest\newDirectiveDoesNotExtendDefault
‪newDirectiveDoesNotExtendDefault()
Definition: PolicyTest.php:160
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Policy
Definition: Policy.php:31
‪TYPO3\CMS\Core\Tests\Functional\Security\ContentSecurityPolicy\PolicyTest\defaultDirectiveConsidersVeto
‪defaultDirectiveConsidersVeto()
Definition: PolicyTest.php:86
‪TYPO3\CMS\Core\Tests\Functional\Security\ContentSecurityPolicy\PolicyTest\directiveIsRemoved
‪directiveIsRemoved()
Definition: PolicyTest.php:206
‪TYPO3\CMS\Core\Tests\Functional\Security\ContentSecurityPolicy\PolicyTest\containedDirectiveSourcesAreDetermined
‪containedDirectiveSourcesAreDetermined()
Definition: PolicyTest.php:241
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationMode
‪MutationMode
Definition: MutationMode.php:24
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\HashValue\create
‪static create(string $value, HashType $type=HashType::sha256)
Definition: HashValue.php:30
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\HashProxy
Definition: HashProxy.php:32
‪TYPO3\CMS\Core\Tests\Functional\Security\ContentSecurityPolicy
Definition: ModelServiceTest.php:18
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\HashProxy\glob
‪static glob(string $glob)
Definition: HashProxy.php:42
‪TYPO3\CMS\Core\Tests\Functional\Security\ContentSecurityPolicy\PolicyTest\$nonce
‪ConsumableNonce $nonce
Definition: PolicyTest.php:37
‪TYPO3\CMS\Core\Tests\Functional\Security\ContentSecurityPolicy\PolicyTest\setUp
‪setUp()
Definition: PolicyTest.php:40
‪TYPO3\CMS\Core\Tests\Functional\Security\ContentSecurityPolicy\PolicyTest\hashProxyIsCompiled
‪hashProxyIsCompiled()
Definition: PolicyTest.php:47
‪TYPO3\CMS\Core\Tests\Functional\Security\ContentSecurityPolicy\PolicyTest\defaultDirectiveIsModified
‪defaultDirectiveIsModified()
Definition: PolicyTest.php:78
‪TYPO3\CMS\Core\Tests\Functional\Security\ContentSecurityPolicy\PolicyTest\containedPolicyIsDetermined
‪containedPolicyIsDetermined()
Definition: PolicyTest.php:265
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceKeyword
‪SourceKeyword
Definition: SourceKeyword.php:25
‪TYPO3\CMS\Core\Tests\Functional\Security\ContentSecurityPolicy\PolicyTest\$initializeDatabase
‪bool $initializeDatabase
Definition: PolicyTest.php:38
‪TYPO3\CMS\Core\Tests\Functional\Security\ContentSecurityPolicy\PolicyTest\ancestorInheritanceIsAppliedFromMutations
‪ancestorInheritanceIsAppliedFromMutations(MutationCollection $mutations, string $expectation)
Definition: PolicyTest.php:153
‪TYPO3\CMS\Core\Tests\Functional\Security\ContentSecurityPolicy\PolicyTest\directiveIsReduced
‪directiveIsReduced()
Definition: PolicyTest.php:168
‪TYPO3\CMS\Core\Tests\Functional\Security\ContentSecurityPolicy\PolicyTest\newDirectiveExtendsDefault
‪newDirectiveExtendsDefault()
Definition: PolicyTest.php:94
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\UriValue
Definition: UriValue.php:29
‪TYPO3\CMS\Core\Tests\Functional\Security\ContentSecurityPolicy\PolicyTest\coveredDirectiveSourcesAreDetermined
‪coveredDirectiveSourcesAreDetermined()
Definition: PolicyTest.php:253
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceScheme
‪SourceScheme
Definition: SourceScheme.php:25
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Mutation
Definition: Mutation.php:26
‪TYPO3\CMS\Core\Tests\Functional\Security\ContentSecurityPolicy\PolicyTest\backendPolicyIsCompiled
‪backendPolicyIsCompiled()
Definition: PolicyTest.php:222
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationCollection
Definition: MutationCollection.php:24