Passed
Push — master ( 198b41...933826 )
by Julius
14:31 queued 13s
created

EmptyContentSecurityPolicy::disallowImageDomain()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Christoph Wurst <[email protected]>
6
 * @author Lukas Reschke <[email protected]>
7
 * @author Pavel Krasikov <[email protected]>
8
 * @author Pierre Rudloff <[email protected]>
9
 * @author Roeland Jago Douma <[email protected]>
10
 * @author Thomas Citharel <[email protected]>
11
 *
12
 * @license AGPL-3.0
13
 *
14
 * This code is free software: you can redistribute it and/or modify
15
 * it under the terms of the GNU Affero General Public License, version 3,
16
 * as published by the Free Software Foundation.
17
 *
18
 * This program is distributed in the hope that it will be useful,
19
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
 * GNU Affero General Public License for more details.
22
 *
23
 * You should have received a copy of the GNU Affero General Public License, version 3,
24
 * along with this program. If not, see <http://www.gnu.org/licenses/>
25
 *
26
 */
27
namespace OCP\AppFramework\Http;
28
29
/**
30
 * Class EmptyContentSecurityPolicy is a simple helper which allows applications
31
 * to modify the Content-Security-Policy sent by Nexcloud. Per default the policy
32
 * is forbidding everything.
33
 *
34
 * As alternative with sane exemptions look at ContentSecurityPolicy
35
 *
36
 * @see \OCP\AppFramework\Http\ContentSecurityPolicy
37
 * @since 9.0.0
38
 */
39
class EmptyContentSecurityPolicy {
40
	/** @var bool Whether inline JS snippets are allowed */
41
	protected $inlineScriptAllowed = null;
42
	/** @var string Whether JS nonces should be used */
43
	protected $useJsNonce = null;
44
	/** @var bool Whether strict-dynamic should be used */
45
	protected $strictDynamicAllowed = null;
46
	/**
47
	 * @var bool Whether eval in JS scripts is allowed
48
	 * TODO: Disallow per default
49
	 * @link https://github.com/owncloud/core/issues/11925
50
	 */
51
	protected $evalScriptAllowed = null;
52
	/** @var array Domains from which scripts can get loaded */
53
	protected $allowedScriptDomains = null;
54
	/**
55
	 * @var bool Whether inline CSS is allowed
56
	 * TODO: Disallow per default
57
	 * @link https://github.com/owncloud/core/issues/13458
58
	 */
59
	protected $inlineStyleAllowed = null;
60
	/** @var array Domains from which CSS can get loaded */
61
	protected $allowedStyleDomains = null;
62
	/** @var array Domains from which images can get loaded */
63
	protected $allowedImageDomains = null;
64
	/** @var array Domains to which connections can be done */
65
	protected $allowedConnectDomains = null;
66
	/** @var array Domains from which media elements can be loaded */
67
	protected $allowedMediaDomains = null;
68
	/** @var array Domains from which object elements can be loaded */
69
	protected $allowedObjectDomains = null;
70
	/** @var array Domains from which iframes can be loaded */
71
	protected $allowedFrameDomains = null;
72
	/** @var array Domains from which fonts can be loaded */
73
	protected $allowedFontDomains = null;
74
	/** @var array Domains from which web-workers and nested browsing content can load elements */
75
	protected $allowedChildSrcDomains = null;
76
	/** @var array Domains which can embed this Nextcloud instance */
77
	protected $allowedFrameAncestors = null;
78
	/** @var array Domains from which web-workers can be loaded */
79
	protected $allowedWorkerSrcDomains = null;
80
	/** @var array Domains which can be used as target for forms */
81
	protected $allowedFormActionDomains = null;
82
83
	/** @var array Locations to report violations to */
84
	protected $reportTo = null;
85
86
	/**
87
	 * Whether inline JavaScript snippets are allowed or forbidden
88
	 * @param bool $state
89
	 * @return $this
90
	 * @since 8.1.0
91
	 * @deprecated 10.0 CSP tokens are now used
92
	 */
93
	public function allowInlineScript($state = false) {
94
		$this->inlineScriptAllowed = $state;
95
		return $this;
96
	}
97
98
	/**
99
	 * @param bool $state
100
	 * @return EmptyContentSecurityPolicy
101
	 * @since 24.0.0
102
	 */
103
	public function useStrictDynamic(bool $state = false): self {
104
		$this->strictDynamicAllowed = $state;
105
		return $this;
106
	}
107
108
	/**
109
	 * Use the according JS nonce
110
	 * This method is only for CSPMiddleware, custom values are ignored in mergePolicies of ContentSecurityPolicyManager
111
	 *
112
	 * @param string $nonce
113
	 * @return $this
114
	 * @since 11.0.0
115
	 */
116
	public function useJsNonce($nonce) {
117
		$this->useJsNonce = $nonce;
118
		return $this;
119
	}
120
121
	/**
122
	 * Whether eval in JavaScript is allowed or forbidden
123
	 * @param bool $state
124
	 * @return $this
125
	 * @since 8.1.0
126
	 * @deprecated Eval should not be used anymore. Please update your scripts. This function will stop functioning in a future version of Nextcloud.
127
	 */
128
	public function allowEvalScript($state = true) {
129
		$this->evalScriptAllowed = $state;
130
		return $this;
131
	}
132
133
	/**
134
	 * Allows to execute JavaScript files from a specific domain. Use * to
135
	 * allow JavaScript from all domains.
136
	 * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
137
	 * @return $this
138
	 * @since 8.1.0
139
	 */
140
	public function addAllowedScriptDomain($domain) {
141
		$this->allowedScriptDomains[] = $domain;
142
		return $this;
143
	}
144
145
	/**
146
	 * Remove the specified allowed script domain from the allowed domains.
147
	 *
148
	 * @param string $domain
149
	 * @return $this
150
	 * @since 8.1.0
151
	 */
152
	public function disallowScriptDomain($domain) {
153
		$this->allowedScriptDomains = array_diff($this->allowedScriptDomains, [$domain]);
154
		return $this;
155
	}
156
157
	/**
158
	 * Whether inline CSS snippets are allowed or forbidden
159
	 * @param bool $state
160
	 * @return $this
161
	 * @since 8.1.0
162
	 */
163
	public function allowInlineStyle($state = true) {
164
		$this->inlineStyleAllowed = $state;
165
		return $this;
166
	}
167
168
	/**
169
	 * Allows to execute CSS files from a specific domain. Use * to allow
170
	 * CSS from all domains.
171
	 * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
172
	 * @return $this
173
	 * @since 8.1.0
174
	 */
175
	public function addAllowedStyleDomain($domain) {
176
		$this->allowedStyleDomains[] = $domain;
177
		return $this;
178
	}
179
180
	/**
181
	 * Remove the specified allowed style domain from the allowed domains.
182
	 *
183
	 * @param string $domain
184
	 * @return $this
185
	 * @since 8.1.0
186
	 */
187
	public function disallowStyleDomain($domain) {
188
		$this->allowedStyleDomains = array_diff($this->allowedStyleDomains, [$domain]);
189
		return $this;
190
	}
191
192
	/**
193
	 * Allows using fonts from a specific domain. Use * to allow
194
	 * fonts from all domains.
195
	 * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
196
	 * @return $this
197
	 * @since 8.1.0
198
	 */
199
	public function addAllowedFontDomain($domain) {
200
		$this->allowedFontDomains[] = $domain;
201
		return $this;
202
	}
203
204
	/**
205
	 * Remove the specified allowed font domain from the allowed domains.
206
	 *
207
	 * @param string $domain
208
	 * @return $this
209
	 * @since 8.1.0
210
	 */
211
	public function disallowFontDomain($domain) {
212
		$this->allowedFontDomains = array_diff($this->allowedFontDomains, [$domain]);
213
		return $this;
214
	}
215
216
	/**
217
	 * Allows embedding images from a specific domain. Use * to allow
218
	 * images from all domains.
219
	 * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
220
	 * @return $this
221
	 * @since 8.1.0
222
	 */
223
	public function addAllowedImageDomain($domain) {
224
		$this->allowedImageDomains[] = $domain;
225
		return $this;
226
	}
227
228
	/**
229
	 * Remove the specified allowed image domain from the allowed domains.
230
	 *
231
	 * @param string $domain
232
	 * @return $this
233
	 * @since 8.1.0
234
	 */
235
	public function disallowImageDomain($domain) {
236
		$this->allowedImageDomains = array_diff($this->allowedImageDomains, [$domain]);
237
		return $this;
238
	}
239
240
	/**
241
	 * To which remote domains the JS connect to.
242
	 * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
243
	 * @return $this
244
	 * @since 8.1.0
245
	 */
246
	public function addAllowedConnectDomain($domain) {
247
		$this->allowedConnectDomains[] = $domain;
248
		return $this;
249
	}
250
251
	/**
252
	 * Remove the specified allowed connect domain from the allowed domains.
253
	 *
254
	 * @param string $domain
255
	 * @return $this
256
	 * @since 8.1.0
257
	 */
258
	public function disallowConnectDomain($domain) {
259
		$this->allowedConnectDomains = array_diff($this->allowedConnectDomains, [$domain]);
260
		return $this;
261
	}
262
263
	/**
264
	 * From which domains media elements can be embedded.
265
	 * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
266
	 * @return $this
267
	 * @since 8.1.0
268
	 */
269
	public function addAllowedMediaDomain($domain) {
270
		$this->allowedMediaDomains[] = $domain;
271
		return $this;
272
	}
273
274
	/**
275
	 * Remove the specified allowed media domain from the allowed domains.
276
	 *
277
	 * @param string $domain
278
	 * @return $this
279
	 * @since 8.1.0
280
	 */
281
	public function disallowMediaDomain($domain) {
282
		$this->allowedMediaDomains = array_diff($this->allowedMediaDomains, [$domain]);
283
		return $this;
284
	}
285
286
	/**
287
	 * From which domains objects such as <object>, <embed> or <applet> are executed
288
	 * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
289
	 * @return $this
290
	 * @since 8.1.0
291
	 */
292
	public function addAllowedObjectDomain($domain) {
293
		$this->allowedObjectDomains[] = $domain;
294
		return $this;
295
	}
296
297
	/**
298
	 * Remove the specified allowed object domain from the allowed domains.
299
	 *
300
	 * @param string $domain
301
	 * @return $this
302
	 * @since 8.1.0
303
	 */
304
	public function disallowObjectDomain($domain) {
305
		$this->allowedObjectDomains = array_diff($this->allowedObjectDomains, [$domain]);
306
		return $this;
307
	}
308
309
	/**
310
	 * Which domains can be embedded in an iframe
311
	 * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
312
	 * @return $this
313
	 * @since 8.1.0
314
	 */
315
	public function addAllowedFrameDomain($domain) {
316
		$this->allowedFrameDomains[] = $domain;
317
		return $this;
318
	}
319
320
	/**
321
	 * Remove the specified allowed frame domain from the allowed domains.
322
	 *
323
	 * @param string $domain
324
	 * @return $this
325
	 * @since 8.1.0
326
	 */
327
	public function disallowFrameDomain($domain) {
328
		$this->allowedFrameDomains = array_diff($this->allowedFrameDomains, [$domain]);
329
		return $this;
330
	}
331
332
	/**
333
	 * Domains from which web-workers and nested browsing content can load elements
334
	 * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
335
	 * @return $this
336
	 * @since 8.1.0
337
	 * @deprecated 15.0.0 use addAllowedWorkerSrcDomains or addAllowedFrameDomain
338
	 */
339
	public function addAllowedChildSrcDomain($domain) {
340
		$this->allowedChildSrcDomains[] = $domain;
341
		return $this;
342
	}
343
344
	/**
345
	 * Remove the specified allowed child src domain from the allowed domains.
346
	 *
347
	 * @param string $domain
348
	 * @return $this
349
	 * @since 8.1.0
350
	 * @deprecated 15.0.0 use the WorkerSrcDomains or FrameDomain
351
	 */
352
	public function disallowChildSrcDomain($domain) {
353
		$this->allowedChildSrcDomains = array_diff($this->allowedChildSrcDomains, [$domain]);
354
		return $this;
355
	}
356
357
	/**
358
	 * Domains which can embed an iFrame of the Nextcloud instance
359
	 *
360
	 * @param string $domain
361
	 * @return $this
362
	 * @since 13.0.0
363
	 */
364
	public function addAllowedFrameAncestorDomain($domain) {
365
		$this->allowedFrameAncestors[] = $domain;
366
		return $this;
367
	}
368
369
	/**
370
	 * Domains which can embed an iFrame of the Nextcloud instance
371
	 *
372
	 * @param string $domain
373
	 * @return $this
374
	 * @since 13.0.0
375
	 */
376
	public function disallowFrameAncestorDomain($domain) {
377
		$this->allowedFrameAncestors = array_diff($this->allowedFrameAncestors, [$domain]);
378
		return $this;
379
	}
380
381
	/**
382
	 * Domain from which workers can be loaded
383
	 *
384
	 * @param string $domain
385
	 * @return $this
386
	 * @since 15.0.0
387
	 */
388
	public function addAllowedWorkerSrcDomain(string $domain) {
389
		$this->allowedWorkerSrcDomains[] = $domain;
390
		return $this;
391
	}
392
393
	/**
394
	 * Remove domain from which workers can be loaded
395
	 *
396
	 * @param string $domain
397
	 * @return $this
398
	 * @since 15.0.0
399
	 */
400
	public function disallowWorkerSrcDomain(string $domain) {
401
		$this->allowedWorkerSrcDomains = array_diff($this->allowedWorkerSrcDomains, [$domain]);
402
		return $this;
403
	}
404
405
	/**
406
	 * Domain to where forms can submit
407
	 *
408
	 * @since 17.0.0
409
	 *
410
	 * @return $this
411
	 */
412
	public function addAllowedFormActionDomain(string $domain) {
413
		$this->allowedFormActionDomains[] = $domain;
414
		return $this;
415
	}
416
417
	/**
418
	 * Remove domain to where forms can submit
419
	 *
420
	 * @return $this
421
	 * @since 17.0.0
422
	 */
423
	public function disallowFormActionDomain(string $domain) {
424
		$this->allowedFormActionDomains = array_diff($this->allowedFormActionDomains, [$domain]);
425
		return $this;
426
	}
427
428
	/**
429
	 * Add location to report CSP violations to
430
	 *
431
	 * @param string $location
432
	 * @return $this
433
	 * @since 15.0.0
434
	 */
435
	public function addReportTo(string $location) {
436
		$this->reportTo[] = $location;
437
		return $this;
438
	}
439
440
	/**
441
	 * Get the generated Content-Security-Policy as a string
442
	 * @return string
443
	 * @since 8.1.0
444
	 */
445
	public function buildPolicy() {
446
		$policy = "default-src 'none';";
447
		$policy .= "base-uri 'none';";
448
		$policy .= "manifest-src 'self';";
449
450
		if (!empty($this->allowedScriptDomains) || $this->inlineScriptAllowed || $this->evalScriptAllowed) {
451
			$policy .= 'script-src ';
452
			if (is_string($this->useJsNonce)) {
0 ignored issues
show
introduced by
The condition is_string($this->useJsNonce) is always true.
Loading history...
453
				if ($this->strictDynamicAllowed) {
454
					$policy .= '\'strict-dynamic\' ';
455
				}
456
				$policy .= '\'nonce-'.base64_encode($this->useJsNonce).'\'';
457
				$allowedScriptDomains = array_flip($this->allowedScriptDomains);
458
				unset($allowedScriptDomains['\'self\'']);
459
				$this->allowedScriptDomains = array_flip($allowedScriptDomains);
460
				if (count($allowedScriptDomains) !== 0) {
461
					$policy .= ' ';
462
				}
463
			}
464
			if (is_array($this->allowedScriptDomains)) {
0 ignored issues
show
introduced by
The condition is_array($this->allowedScriptDomains) is always true.
Loading history...
465
				$policy .= implode(' ', $this->allowedScriptDomains);
466
			}
467
			if ($this->inlineScriptAllowed) {
468
				$policy .= ' \'unsafe-inline\'';
469
			}
470
			if ($this->evalScriptAllowed) {
471
				$policy .= ' \'unsafe-eval\'';
472
			}
473
			$policy .= ';';
474
		}
475
476
		if (!empty($this->allowedStyleDomains) || $this->inlineStyleAllowed) {
477
			$policy .= 'style-src ';
478
			if (is_array($this->allowedStyleDomains)) {
479
				$policy .= implode(' ', $this->allowedStyleDomains);
480
			}
481
			if ($this->inlineStyleAllowed) {
482
				$policy .= ' \'unsafe-inline\'';
483
			}
484
			$policy .= ';';
485
		}
486
487
		if (!empty($this->allowedImageDomains)) {
488
			$policy .= 'img-src ' . implode(' ', $this->allowedImageDomains);
489
			$policy .= ';';
490
		}
491
492
		if (!empty($this->allowedFontDomains)) {
493
			$policy .= 'font-src ' . implode(' ', $this->allowedFontDomains);
494
			$policy .= ';';
495
		}
496
497
		if (!empty($this->allowedConnectDomains)) {
498
			$policy .= 'connect-src ' . implode(' ', $this->allowedConnectDomains);
499
			$policy .= ';';
500
		}
501
502
		if (!empty($this->allowedMediaDomains)) {
503
			$policy .= 'media-src ' . implode(' ', $this->allowedMediaDomains);
504
			$policy .= ';';
505
		}
506
507
		if (!empty($this->allowedObjectDomains)) {
508
			$policy .= 'object-src ' . implode(' ', $this->allowedObjectDomains);
509
			$policy .= ';';
510
		}
511
512
		if (!empty($this->allowedFrameDomains)) {
513
			$policy .= 'frame-src ';
514
			$policy .= implode(' ', $this->allowedFrameDomains);
515
			$policy .= ';';
516
		}
517
518
		if (!empty($this->allowedChildSrcDomains)) {
519
			$policy .= 'child-src ' . implode(' ', $this->allowedChildSrcDomains);
520
			$policy .= ';';
521
		}
522
523
		if (!empty($this->allowedFrameAncestors)) {
524
			$policy .= 'frame-ancestors ' . implode(' ', $this->allowedFrameAncestors);
525
			$policy .= ';';
526
		} else {
527
			$policy .= 'frame-ancestors \'none\';';
528
		}
529
530
		if (!empty($this->allowedWorkerSrcDomains)) {
531
			$policy .= 'worker-src ' . implode(' ', $this->allowedWorkerSrcDomains);
532
			$policy .= ';';
533
		}
534
535
		if (!empty($this->allowedFormActionDomains)) {
536
			$policy .= 'form-action ' . implode(' ', $this->allowedFormActionDomains);
537
			$policy .= ';';
538
		}
539
540
		if (!empty($this->reportTo)) {
541
			$policy .= 'report-uri ' . implode(' ', $this->reportTo);
542
			$policy .= ';';
543
		}
544
545
		return rtrim($policy, ';');
546
	}
547
}
548