Passed
Push — master ( f1066f...5f6ac5 )
by Roeland
10:12
created

addAllowedFormActionDomain()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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