Completed
Push — master ( ae94c5...39338a )
by Morris
45:41 queued 29:43
created

addAllowedMediaDomain()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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