Passed
Push — master ( 7a143e...62bba1 )
by Jean-Christophe
05:53
created

ContentSecurity::addHeaderToResponse()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 3
c 1
b 0
f 1
dl 0
loc 5
rs 10
cc 2
nc 2
nop 1
1
<?php
2
namespace Ubiquity\security\csp;
3
4
use Ubiquity\utils\http\UResponse;
5
6
/**
7
 * Creates a Content Security Policy object.
8
 * Ubiquity\security\csp$ContentSecurity
9
 * This class is part of Ubiquity
10
 *
11
 * @author jc
12
 * @version 1.0.0
13
 *
14
 */
15
class ContentSecurity {
16
17
	const HEADER = 'Content-Security-Policy';
18
19
	const DEBUG_HEADER = 'Content-Security-Policy-Report-Only';
20
21
	private array $policies = [];
22
23
	private $header = self::HEADER;
24
25
	public function __construct(?bool $reportOnly = null) {
26
		if (isset($reportOnly)) {
27
			$this->reportOnly($reportOnly);
28
		}
29
	}
30
31
	public function addPolicy(string $directive, string ...$values): self {
32
		$policies = $this->policies[$directive] ?? [];
33
		foreach ($values as $v) {
34
			if (\in_array($v, CspValues::QUOTED)) {
35
				$v = "'$v'";
36
			}
37
			$policies[$v] = true;
38
		}
39
		$this->policies[$directive] = $policies;
40
		return $this;
41
	}
42
43
	public function addPolicyDefault(string $directive, string ...$values): self {
44
		$default = \array_keys($this->policies[CspDirectives::DEFAULT_SRC] ?? []);
45
		$values = \array_merge($default, $values);
46
		$this->addPolicy($directive, ...$values);
47
		return $this;
48
	}
49
50
	public function addNonce(string $nonce, string ...$directives): self {
51
		foreach ($directives as $directive) {
52
			$this->addPolicy($directive, "'nonce-$nonce'", CspValues::STRICT_DYNAMIC);
53
		}
54
		return $this;
55
	}
56
57
	public function addNonceDefault(string $nonce, string ...$directives): self {
58
		foreach ($directives as $directive) {
59
			$this->addPolicyDefault($directive, "'nonce-$nonce'", CspValues::STRICT_DYNAMIC);
60
		}
61
		return $this;
62
	}
63
64
	public function setDefaultSrc(string ...$policies) {
65
		return $this->addPolicy(CspDirectives::DEFAULT_SRC, ...$policies);
66
	}
67
68
	public function generate(): string {
69
		$strs = '';
70
		foreach ($this->policies as $directive => $policy) {
71
			$policies = \array_keys($policy);
72
			$strs .= $directive . ' ' . \implode(' ', $policies) . ';';
73
		}
74
		return $strs;
75
	}
76
77
	public function reportOnly(?bool $reportOnly = true): self {
78
		if (isset($reportOnly)) {
79
			$this->header = $reportOnly ? self::DEBUG_HEADER : self::HEADER;
80
		}
81
		return $this;
82
	}
83
84
	public function addHeaderToResponse(?bool $reportOnly = null): void {
85
		if (isset($reportOnly)) {
86
			$this->reportOnly($reportOnly);
87
		}
88
		UResponse::header($this->header, $this->generate(), false);
89
	}
90
91
	public static function nonce($nonce, string ...$directives): ContentSecurity {
92
		$csp = new self();
93
		return $csp->addNonce($nonce, ...$directives);
94
	}
95
96
	public static function all(): ContentSecurity {
97
		$csp = new self();
98
		return $csp->addPolicy(CspDirectives::DEFAULT_SRC, CspValues::SELF);
99
	}
100
101
	/**
102
	 * @return array
103
	 */
104
	public function getPolicies(): array {
105
		return $this->policies;
106
	}
107
108
	public static function defaultUbiquity(): ContentSecurity {
109
		$csp = new self();
110
		$csp->addPolicy(CspDirectives::DEFAULT_SRC, 'self', 'cdn.jsdelivr.net', 'cdnjs.cloudflare.com');
111
		$csp->addPolicyDefault(CspDirectives::FONT_SRC, 'fonts.googleapis.com', 'fonts.gstatic.com', 'data:');
112
		$csp->addPolicyDefault(CspDirectives::STYLE_SRC, CspValues::UNSAFE_INLINE, 'fonts.googleapis.com');
113
		$csp->addPolicyDefault(CspDirectives::SCRIPT_SRC_ELM, CspValues::UNSAFE_INLINE);
114
		$csp->addPolicy(CspDirectives::IMG_SRC, 'data:');
115
		return $csp;
116
	}
117
118
	/**
119
	 * @param array $policies
120
	 */
121
	public function setPolicies(array $policies): void {
122
		$this->policies = $policies;
123
	}
124
125
}
126