‪TYPO3CMS  ‪main
Policy.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 
23 
30 class ‪Policy
31 {
36 
41  {
42  $this->directives = new ‪Map();
43  $directive = Directive::DefaultSrc;
44  $collection = $this->‪asMergedSourceCollection(...$sources);
45  $collection = $this->‪purgeNonApplicableSources($directive, $collection);
46  if (!$collection->isEmpty()) {
47  $this->directives[$directive] = $collection;
48  }
49  }
50 
51  public function ‪isEmpty(): bool
52  {
53  return count($this->directives) === 0;
54  }
55 
59  public function ‪mutate(‪MutationCollection|‪Mutation ...$mutations): self
60  {
61  $self = $this;
62  foreach ($mutations as $mutation) {
63  if ($mutation instanceof ‪MutationCollection) {
64  $self = $self->mutate(...$mutation->mutations);
65  } elseif ($mutation->mode === MutationMode::Set) {
66  $self = $self->set($mutation->directive, ...$mutation->sources);
67  } elseif ($mutation->mode === MutationMode::Append) {
68  $self = $self->append($mutation->directive, ...$mutation->sources);
69  } elseif ($mutation->mode === MutationMode::InheritOnce) {
70  $self = $self->inherit($mutation->directive);
71  } elseif ($mutation->mode === MutationMode::InheritAgain) {
72  $self = $self->inherit($mutation->directive, true);
73  } elseif ($mutation->mode === MutationMode::Extend) {
74  $self = $self->extend($mutation->directive, ...$mutation->sources);
75  } elseif ($mutation->mode === MutationMode::Reduce) {
76  $self = $self->reduce($mutation->directive, ...$mutation->sources);
77  } elseif ($mutation->mode === ‪MutationMode::Remove) {
78  $self = $self->remove($mutation->directive);
79  }
80  }
81  return $self;
82  }
83 
87  public function default(‪SourceCollection|‪SourceInterface ...$sources): self
88  {
89  return $this->set(Directive::DefaultSrc, ...$sources);
90  }
91 
95  public function ‪append(‪Directive $directive, ‪SourceCollection|‪SourceInterface ...$sources): self
96  {
97  $collection = $this->‪asMergedSourceCollection(...$sources);
98  $collection = $this->‪purgeNonApplicableSources($directive, $collection);
99  if ($collection->isEmpty() && !$directive->isStandAlone()) {
100  return $this;
101  }
102  $targetCollection = $this->‪asMergedSourceCollection(...array_filter([
103  $this->directives[$directive] ?? null,
104  $collection,
105  ]));
106  return $this->‪changeDirectiveSources($directive, $targetCollection);
107  }
108 
114  public function ‪inherit(‪Directive $directive, bool $again = false): self
115  {
116  $currentSources = $this->directives[$directive] ?? null;
117  if ($again || $currentSources === null) {
118  foreach ($directive->getAncestors() as $ancestorDirective) {
119  if ($this->‪has($ancestorDirective)) {
120  $ancestorCollection = $this->directives[$ancestorDirective];
121  break;
122  }
123  }
124  }
125  $targetCollection = $this->‪asMergedSourceCollection(...array_filter([
126  $ancestorCollection ?? null,
127  $currentSources,
128  ]));
129  return $this->‪changeDirectiveSources($directive, $targetCollection);
130  }
131 
135  public function ‪extend(‪Directive $directive, ‪SourceCollection|‪SourceInterface ...$sources): self
136  {
137  return $this->‪inherit($directive)->append($directive, ...$sources);
138  }
139 
140  public function ‪reduce(‪Directive $directive, ‪SourceCollection|‪SourceInterface ...$sources): self
141  {
142  if (!$this->‪has($directive)) {
143  return $this;
144  }
145  $collection = $this->‪asMergedSourceCollection(...$sources);
146  $targetCollection = $this->directives[$directive]->exclude($collection);
147  return $this->‪changeDirectiveSources($directive, $targetCollection);
148  }
149 
153  public function set(‪Directive $directive, ‪SourceCollection|‪SourceInterface ...$sources): self
154  {
155  $collection = $this->‪asMergedSourceCollection(...$sources);
156  $collection = $this->‪purgeNonApplicableSources($directive, $collection);
157  return $this->‪changeDirectiveSources($directive, $collection);
158  }
159 
163  public function remove(‪Directive $directive): self
164  {
165  if (!$this->‪has($directive)) {
166  return $this;
167  }
168  $target = clone $this;
169  unset($target->directives[$directive]);
170  return $target;
171  }
172 
176  public function ‪report(‪UriValue $reportUri): self
177  {
178  $target = $this->set(Directive::ReportUri, $reportUri);
179  $reportSample = SourceKeyword::reportSample;
180  foreach ($target->directives as $directive => $collection) {
181  if ($reportSample->isApplicable($directive)) {
182  $target->directives[$directive] = $collection->with($reportSample);
183  }
184  }
185  return $target;
186  }
187 
188  public function ‪has(‪Directive $directive): bool
189  {
190  return isset($this->directives[$directive]);
191  }
192 
197  public function ‪prepare(): self
198  {
199  $purged = false;
201  $comparator = [$this, 'compareSources'];
202  foreach (‪$directives as $directive => $collection) {
203  foreach ($directive->getAncestors() as $ancestorDirective) {
204  $ancestorCollection = ‪$directives[$ancestorDirective] ?? null;
205  if ($ancestorCollection !== null
206  && array_udiff($collection->sources, $ancestorCollection->sources, $comparator) === []
207  && array_udiff($ancestorCollection->sources, $collection->sources, $comparator) === []
208  ) {
209  $purged = true;
210  unset(‪$directives[$directive]);
211  continue 2;
212  }
213  }
214  }
215  foreach (‪$directives as $directive => $collection) {
216  // applies implicit changes to sources in case 'strict-dynamic' is used for applicable directives
217  if ($collection->contains(SourceKeyword::strictDynamic) && SourceKeyword::strictDynamic->isApplicable($directive)) {
218  ‪$directives[$directive] = SourceKeyword::strictDynamic->applySourceImplications($collection) ?? $collection;
219  }
220  }
221  if (!$purged) {
222  return $this;
223  }
224  $target = clone $this;
225  $target->directives = ‪$directives;
226  return $target;
227  }
228 
235  public function ‪compile(‪ConsumableNonce $nonce, ?‪FrontendInterface $cache = null): string
236  {
237  $policyParts = [];
238  $service = GeneralUtility::makeInstance(ModelService::class, $cache);
239  foreach ($this->‪prepare()->directives as $directive => $collection) {
240  $directiveParts = $service->compileSources($nonce, $collection);
241  if ($directiveParts !== [] || $directive->isStandAlone()) {
242  array_unshift($directiveParts, $directive->value);
243  $policyParts[] = implode(' ', $directiveParts);
244  }
245  }
246  return implode('; ', $policyParts);
247  }
248 
249  public function ‪containsDirective(‪Directive $directive, ‪SourceCollection|‪SourceInterface ...$sources): bool
250  {
251  $sources = $this->‪asMergedSourceCollection(...$sources);
252  return (bool)$this->directives[$directive]?->contains(...$sources->sources);
253  }
254 
255  public function ‪coversDirective(‪Directive $directive, ‪SourceCollection|‪SourceInterface ...$sources): bool
256  {
257  $sources = $this->‪asMergedSourceCollection(...$sources);
258  return (bool)$this->directives[$directive]?->covers(...$sources->sources);
259  }
260 
264  public function ‪contains(‪Policy $other): bool
265  {
266  if ($other->‪isEmpty()) {
267  return false;
268  }
269  foreach ($other->directives as $directive => $collection) {
270  if (!$this->‪containsDirective($directive, $collection)) {
271  return false;
272  }
273  }
274  return true;
275  }
276 
280  public function ‪covers(‪Policy $other): bool
281  {
282  if ($other->‪isEmpty()) {
283  return false;
284  }
285  foreach ($other->directives as $directive => $collection) {
286  if (!$this->‪coversDirective($directive, $collection)) {
287  return false;
288  }
289  }
290  return true;
291  }
292 
294  {
295  $service = GeneralUtility::makeInstance(ModelService::class);
296  return $service->serializeSource($a) <=> $service->serializeSource($b);
297  }
298 
299  protected function ‪changeDirectiveSources(‪Directive $directive, ‪SourceCollection $sources): self
300  {
301  if ($sources->‪isEmpty() && !$directive->isStandAlone()) {
302  return $this;
303  }
304  $target = clone $this;
305  $target->directives[$directive] = $sources;
306  return $target;
307  }
308 
310  {
311  $collections = array_filter($subjects, static fn($source) => $source instanceof ‪SourceCollection);
312  $sources = array_filter($subjects, static fn($source) => !$source instanceof ‪SourceCollection);
313  if ($sources !== []) {
314  $collections[] = new ‪SourceCollection(...$sources);
315  }
316  $target = new ‪SourceCollection();
317  foreach ($collections as $collection) {
318  $target = $target->merge($collection);
319  }
320  return $target;
321  }
322 
324  {
325  $sources = array_filter(
326  $collection->sources,
327  static fn(‪SourceInterface $source): bool => $source instanceof ‪SourceKeyword ? $source->isApplicable($directive) : true
328  );
329  return new ‪SourceCollection(...$sources);
330  }
331 }
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Policy\extend
‪extend(Directive $directive, SourceCollection|SourceInterface ... $sources)
Definition: Policy.php:135
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Directive
‪Directive
Definition: Directive.php:25
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Policy\__construct
‪__construct(SourceCollection|SourceInterface ... $sources)
Definition: Policy.php:40
‪TYPO3\CMS\Core\Http\Remove
‪@ Remove
Definition: SetCookieBehavior.php:27
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Policy\containsDirective
‪containsDirective(Directive $directive, SourceCollection|SourceInterface ... $sources)
Definition: Policy.php:249
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceCollection
Definition: SourceCollection.php:27
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Policy\contains
‪contains(Policy $other)
Definition: Policy.php:264
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Policy\covers
‪covers(Policy $other)
Definition: Policy.php:280
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Policy\prepare
‪prepare()
Definition: Policy.php:197
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Policy\changeDirectiveSources
‪changeDirectiveSources(Directive $directive, SourceCollection $sources)
Definition: Policy.php:299
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\ConsumableNonce
Definition: ConsumableNonce.php:24
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Policy\report
‪report(UriValue $reportUri)
Definition: Policy.php:176
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Policy\$directives
‪Map $directives
Definition: Policy.php:35
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Policy
Definition: Policy.php:31
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Policy\reduce
‪reduce(Directive $directive, SourceCollection|SourceInterface ... $sources)
Definition: Policy.php:140
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceInterface
Definition: SourceInterface.php:27
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Policy\has
‪has(Directive $directive)
Definition: Policy.php:188
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy
Definition: ConsumableNonce.php:18
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Policy\asMergedSourceCollection
‪asMergedSourceCollection(SourceCollection|SourceInterface ... $subjects)
Definition: Policy.php:309
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Policy\purgeNonApplicableSources
‪purgeNonApplicableSources(Directive $directive, SourceCollection $collection)
Definition: Policy.php:323
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Policy\isEmpty
‪isEmpty()
Definition: Policy.php:51
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceKeyword
‪SourceKeyword
Definition: SourceKeyword.php:25
‪TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
Definition: FrontendInterface.php:22
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Policy\mutate
‪mutate(MutationCollection|Mutation ... $mutations)
Definition: Policy.php:59
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Policy\compile
‪compile(ConsumableNonce $nonce, ?FrontendInterface $cache=null)
Definition: Policy.php:235
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Policy\append
‪append(Directive $directive, SourceCollection|SourceInterface ... $sources)
Definition: Policy.php:95
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\UriValue
Definition: UriValue.php:29
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Mutation
Definition: Mutation.php:26
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationCollection
Definition: MutationCollection.php:24
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Policy\compareSources
‪compareSources(SourceInterface $a, SourceInterface $b)
Definition: Policy.php:293
‪TYPO3\CMS\Core\Type\Map
Definition: Map.php:47
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceCollection\isEmpty
‪isEmpty()
Definition: SourceCollection.php:38
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Policy\coversDirective
‪coversDirective(Directive $directive, SourceCollection|SourceInterface ... $sources)
Definition: Policy.php:255
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Policy\inherit
‪inherit(Directive $directive, bool $again=false)
Definition: Policy.php:114