Completed
Push — master ( 373630...52012b )
by Roeland
12:43
created

addAllowedWorkerSrcDomain()   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
	/**
80
	 * Whether inline JavaScript snippets are allowed or forbidden
81
	 * @param bool $state
82
	 * @return $this
83
	 * @since 8.1.0
84
	 * @deprecated 10.0 CSP tokens are now used
85
	 */
86
	public function allowInlineScript($state = false) {
87
		$this->inlineScriptAllowed = $state;
88
		return $this;
89
	}
90
91
	/**
92
	 * Use the according JS nonce
93
	 *
94
	 * @param string $nonce
95
	 * @return $this
96
	 * @since 11.0.0
97
	 */
98
	public function useJsNonce($nonce) {
99
		$this->useJsNonce = $nonce;
100
		return $this;
101
	}
102
103
	/**
104
	 * Whether eval in JavaScript is allowed or forbidden
105
	 * @param bool $state
106
	 * @return $this
107
	 * @since 8.1.0
108
	 */
109
	public function allowEvalScript($state = true) {
110
		$this->evalScriptAllowed = $state;
111
		return $this;
112
	}
113
114
	/**
115
	 * Allows to execute JavaScript files from a specific domain. Use * to
116
	 * allow JavaScript from all domains.
117
	 * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
118
	 * @return $this
119
	 * @since 8.1.0
120
	 */
121
	public function addAllowedScriptDomain($domain) {
122
		$this->allowedScriptDomains[] = $domain;
123
		return $this;
124
	}
125
126
	/**
127
	 * Remove the specified allowed script domain from the allowed domains.
128
	 *
129
	 * @param string $domain
130
	 * @return $this
131
	 * @since 8.1.0
132
	 */
133
	public function disallowScriptDomain($domain) {
134
		$this->allowedScriptDomains = array_diff($this->allowedScriptDomains, [$domain]);
135
		return $this;
136
	}
137
138
	/**
139
	 * Whether inline CSS snippets are allowed or forbidden
140
	 * @param bool $state
141
	 * @return $this
142
	 * @since 8.1.0
143
	 */
144
	public function allowInlineStyle($state = true) {
145
		$this->inlineStyleAllowed = $state;
146
		return $this;
147
	}
148
149
	/**
150
	 * Allows to execute CSS files from a specific domain. Use * to allow
151
	 * CSS from all domains.
152
	 * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
153
	 * @return $this
154
	 * @since 8.1.0
155
	 */
156
	public function addAllowedStyleDomain($domain) {
157
		$this->allowedStyleDomains[] = $domain;
158
		return $this;
159
	}
160
161
	/**
162
	 * Remove the specified allowed style domain from the allowed domains.
163
	 *
164
	 * @param string $domain
165
	 * @return $this
166
	 * @since 8.1.0
167
	 */
168
	public function disallowStyleDomain($domain) {
169
		$this->allowedStyleDomains = array_diff($this->allowedStyleDomains, [$domain]);
170
		return $this;
171
	}
172
173
	/**
174
	 * Allows using fonts from a specific domain. Use * to allow
175
	 * fonts from all domains.
176
	 * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
177
	 * @return $this
178
	 * @since 8.1.0
179
	 */
180
	public function addAllowedFontDomain($domain) {
181
		$this->allowedFontDomains[] = $domain;
182
		return $this;
183
	}
184
185
	/**
186
	 * Remove the specified allowed font domain from the allowed domains.
187
	 *
188
	 * @param string $domain
189
	 * @return $this
190
	 * @since 8.1.0
191
	 */
192
	public function disallowFontDomain($domain) {
193
		$this->allowedFontDomains = array_diff($this->allowedFontDomains, [$domain]);
194
		return $this;
195
	}
196
197
	/**
198
	 * Allows embedding images from a specific domain. Use * to allow
199
	 * images from all domains.
200
	 * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
201
	 * @return $this
202
	 * @since 8.1.0
203
	 */
204
	public function addAllowedImageDomain($domain) {
205
		$this->allowedImageDomains[] = $domain;
206
		return $this;
207
	}
208
209
	/**
210
	 * Remove the specified allowed image domain from the allowed domains.
211
	 *
212
	 * @param string $domain
213
	 * @return $this
214
	 * @since 8.1.0
215
	 */
216
	public function disallowImageDomain($domain) {
217
		$this->allowedImageDomains = array_diff($this->allowedImageDomains, [$domain]);
218
		return $this;
219
	}
220
221
	/**
222
	 * To which remote domains the JS connect to.
223
	 * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
224
	 * @return $this
225
	 * @since 8.1.0
226
	 */
227
	public function addAllowedConnectDomain($domain) {
228
		$this->allowedConnectDomains[] = $domain;
229
		return $this;
230
	}
231
232
	/**
233
	 * Remove the specified allowed connect domain from the allowed domains.
234
	 *
235
	 * @param string $domain
236
	 * @return $this
237
	 * @since 8.1.0
238
	 */
239
	public function disallowConnectDomain($domain) {
240
		$this->allowedConnectDomains = array_diff($this->allowedConnectDomains, [$domain]);
241
		return $this;
242
	}
243
244
	/**
245
	 * From which domains media elements can be embedded.
246
	 * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
247
	 * @return $this
248
	 * @since 8.1.0
249
	 */
250
	public function addAllowedMediaDomain($domain) {
251
		$this->allowedMediaDomains[] = $domain;
252
		return $this;
253
	}
254
255
	/**
256
	 * Remove the specified allowed media domain from the allowed domains.
257
	 *
258
	 * @param string $domain
259
	 * @return $this
260
	 * @since 8.1.0
261
	 */
262
	public function disallowMediaDomain($domain) {
263
		$this->allowedMediaDomains = array_diff($this->allowedMediaDomains, [$domain]);
264
		return $this;
265
	}
266
267
	/**
268
	 * From which domains objects such as <object>, <embed> or <applet> are executed
269
	 * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
270
	 * @return $this
271
	 * @since 8.1.0
272
	 */
273
	public function addAllowedObjectDomain($domain) {
274
		$this->allowedObjectDomains[] = $domain;
275
		return $this;
276
	}
277
278
	/**
279
	 * Remove the specified allowed object domain from the allowed domains.
280
	 *
281
	 * @param string $domain
282
	 * @return $this
283
	 * @since 8.1.0
284
	 */
285
	public function disallowObjectDomain($domain) {
286
		$this->allowedObjectDomains = array_diff($this->allowedObjectDomains, [$domain]);
287
		return $this;
288
	}
289
290
	/**
291
	 * Which domains can be embedded in an iframe
292
	 * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
293
	 * @return $this
294
	 * @since 8.1.0
295
	 */
296
	public function addAllowedFrameDomain($domain) {
297
		$this->allowedFrameDomains[] = $domain;
298
		return $this;
299
	}
300
301
	/**
302
	 * Remove the specified allowed frame domain from the allowed domains.
303
	 *
304
	 * @param string $domain
305
	 * @return $this
306
	 * @since 8.1.0
307
	 */
308
	public function disallowFrameDomain($domain) {
309
		$this->allowedFrameDomains = array_diff($this->allowedFrameDomains, [$domain]);
310
		return $this;
311
	}
312
313
	/**
314
	 * Domains from which web-workers and nested browsing content can load elements
315
	 * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
316
	 * @return $this
317
	 * @since 8.1.0
318
	 * @deprecated 15.0.0 use addAllowedWorkerSrcDomains or addAllowedFrameDomain
319
	 */
320
	public function addAllowedChildSrcDomain($domain) {
321
		$this->allowedChildSrcDomains[] = $domain;
322
		return $this;
323
	}
324
325
	/**
326
	 * Remove the specified allowed child src domain from the allowed domains.
327
	 *
328
	 * @param string $domain
329
	 * @return $this
330
	 * @since 8.1.0
331
	 * @deprecated 15.0.0 use the WorkerSrcDomains or FrameDomain
332
	 */
333
	public function disallowChildSrcDomain($domain) {
334
		$this->allowedChildSrcDomains = array_diff($this->allowedChildSrcDomains, [$domain]);
335
		return $this;
336
	}
337
338
	/**
339
	 * Domains which can embed an iFrame of the Nextcloud instance
340
	 *
341
	 * @param string $domain
342
	 * @return $this
343
	 * @since 13.0.0
344
	 */
345
	public function addAllowedFrameAncestorDomain($domain) {
346
		$this->allowedFrameAncestors[] = $domain;
347
		return $this;
348
	}
349
350
	/**
351
	 * Domains which can embed an iFrame of the Nextcloud instance
352
	 *
353
	 * @param string $domain
354
	 * @return $this
355
	 * @since 13.0.0
356
	 */
357
	public function disallowFrameAncestorDomain($domain) {
358
		$this->allowedFrameAncestors = array_diff($this->allowedFrameAncestors, [$domain]);
359
		return $this;
360
	}
361
362
	/**
363
	 * Domain from which workers can be loaded
364
	 *
365
	 * @param string $domain
366
	 * @return $this
367
	 * @since 15.0.0
368
	 */
369
	public function addAllowedWorkerSrcDomain(string $domain) {
370
		$this->allowedWorkerSrcDomains[] = $domain;
371
		return $this;
372
	}
373
374
	/**
375
	 * Remove domain from which workers can be loaded
376
	 *
377
	 * @param string $domain
378
	 * @return $this
379
	 * @since 15.0.0
380
	 */
381
	public function disallowWorkerSrcDomain(string $domain) {
382
		$this->allowedWorkerSrcDomains = array_diff($this->allowedWorkerSrcDomains, [$domain]);
383
		return $this;
384
	}
385
386
	/**
387
	 * Get the generated Content-Security-Policy as a string
388
	 * @return string
389
	 * @since 8.1.0
390
	 */
391
	public function buildPolicy() {
392
		$policy = "default-src 'none';";
393
		$policy .= "base-uri 'none';";
394
		$policy .= "manifest-src 'self';";
395
396
		if(!empty($this->allowedScriptDomains) || $this->inlineScriptAllowed || $this->evalScriptAllowed) {
397
			$policy .= 'script-src ';
398
			if(is_string($this->useJsNonce)) {
399
				$policy .= '\'nonce-'.base64_encode($this->useJsNonce).'\'';
400
				$allowedScriptDomains = array_flip($this->allowedScriptDomains);
401
				unset($allowedScriptDomains['\'self\'']);
402
				$this->allowedScriptDomains = array_flip($allowedScriptDomains);
403
				if(count($allowedScriptDomains) !== 0) {
404
					$policy .= ' ';
405
				}
406
			}
407
			if(is_array($this->allowedScriptDomains)) {
408
				$policy .= implode(' ', $this->allowedScriptDomains);
409
			}
410
			if($this->inlineScriptAllowed) {
411
				$policy .= ' \'unsafe-inline\'';
412
			}
413
			if($this->evalScriptAllowed) {
414
				$policy .= ' \'unsafe-eval\'';
415
			}
416
			$policy .= ';';
417
		}
418
419
		if(!empty($this->allowedStyleDomains) || $this->inlineStyleAllowed) {
420
			$policy .= 'style-src ';
421
			if(is_array($this->allowedStyleDomains)) {
422
				$policy .= implode(' ', $this->allowedStyleDomains);
423
			}
424
			if($this->inlineStyleAllowed) {
425
				$policy .= ' \'unsafe-inline\'';
426
			}
427
			$policy .= ';';
428
		}
429
430
		if(!empty($this->allowedImageDomains)) {
431
			$policy .= 'img-src ' . implode(' ', $this->allowedImageDomains);
432
			$policy .= ';';
433
		}
434
435
		if(!empty($this->allowedFontDomains)) {
436
			$policy .= 'font-src ' . implode(' ', $this->allowedFontDomains);
437
			$policy .= ';';
438
		}
439
440
		if(!empty($this->allowedConnectDomains)) {
441
			$policy .= 'connect-src ' . implode(' ', $this->allowedConnectDomains);
442
			$policy .= ';';
443
		}
444
445
		if(!empty($this->allowedMediaDomains)) {
446
			$policy .= 'media-src ' . implode(' ', $this->allowedMediaDomains);
447
			$policy .= ';';
448
		}
449
450
		if(!empty($this->allowedObjectDomains)) {
451
			$policy .= 'object-src ' . implode(' ', $this->allowedObjectDomains);
452
			$policy .= ';';
453
		}
454
455
		if(!empty($this->allowedFrameDomains)) {
456
			$policy .= 'frame-src ' . implode(' ', $this->allowedFrameDomains);
457
			$policy .= ';';
458
		}
459
460
		if(!empty($this->allowedChildSrcDomains)) {
461
			$policy .= 'child-src ' . implode(' ', $this->allowedChildSrcDomains);
462
			$policy .= ';';
463
		}
464
465
		if(!empty($this->allowedFrameAncestors)) {
466
			$policy .= 'frame-ancestors ' . implode(' ', $this->allowedFrameAncestors);
467
			$policy .= ';';
468
		}
469
470
		if (!empty($this->allowedWorkerSrcDomains)) {
471
			$policy .= 'worker-src ' . implode(' ', $this->allowedWorkerSrcDomains);
472
			$policy .= ';';
473
		}
474
475
		return rtrim($policy, ';');
476
	}
477
}
478