Passed
Push — master ( 82ae54...c2d151 )
by Jean-Christophe
01:20
created

ContentSecurity::reportOnly()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
c 0
b 0
f 0
dl 0
loc 5
rs 10
cc 3
nc 3
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
	/**
26
	 * ContentSecurity constructor.
27
	 *
28
	 * @param bool|null $reportOnly
29
	 */
30
	public function __construct(?bool $reportOnly = null) {
31
		if (isset($reportOnly)) {
32
			$this->reportOnly($reportOnly);
33
		}
34
	}
35
36
	/**
37
	 * Adds new values to a directive.
38
	 *
39
	 * @param string $directive
40
	 * @param string ...$values
41
	 * @return $this
42
	 */
43
	public function addPolicy(string $directive, string ...$values): self {
44
		$policies = $this->policies[$directive] ?? [];
45
		foreach ($values as $v) {
46
			if (\in_array($v, CspValues::QUOTED)) {
47
				$v = "'$v'";
48
			}
49
			$policies[$v] = true;
50
		}
51
		$this->policies[$directive] = $policies;
52
		return $this;
53
	}
54
55
	/**
56
	 * Adds new values to a directive, re-using default-src actual values.
57
	 *
58
	 * @param string $directive
59
	 * @param string ...$values
60
	 * @return $this
61
	 */
62
	public function addPolicyDefault(string $directive, string ...$values): self {
63
		$default = \array_keys($this->policies[CspDirectives::DEFAULT_SRC] ?? []);
64
		$values = \array_merge($default, $values);
65
		$this->addPolicy($directive, ...$values);
66
		return $this;
67
	}
68
69
	/**
70
	 * Adds a nonce to the directives.
71
	 *
72
	 * @param string $nonce
73
	 * @param string ...$directives
74
	 * @return $this
75
	 */
76
	public function addNonce(string $nonce, string ...$directives): self {
77
		foreach ($directives as $directive) {
78
			$this->addPolicy($directive, "'nonce-$nonce'", CspValues::STRICT_DYNAMIC);
79
		}
80
		return $this;
81
	}
82
83
	/**
84
	 * Adds a nonce to a directive, re-using default-src actual values.
85
	 *
86
	 * @param string $nonce
87
	 * @param string ...$directives
88
	 * @return $this
89
	 */
90
	public function addNonceDefault(string $nonce, string ...$directives): self {
91
		foreach ($directives as $directive) {
92
			$this->addPolicyDefault($directive, "'nonce-$nonce'", CspValues::STRICT_DYNAMIC);
93
		}
94
		return $this;
95
	}
96
97
	/**
98
	 * Defines the policies for default-src directive.
99
	 *
100
	 * @param string ...$policies
101
	 * @return $this
102
	 */
103
	public function setDefaultSrc(string ...$policies): self {
104
		return $this->addPolicy(CspDirectives::DEFAULT_SRC, ...$policies);
105
	}
106
107
	/**
108
	 * Generates the header string.
109
	 *
110
	 * @return string
111
	 */
112
	public function generate(): string {
113
		$strs = '';
114
		foreach ($this->policies as $directive => $policy) {
115
			$policies = \array_keys($policy);
116
			$strs .= $directive . ' ' . \implode(' ', $policies) . ';';
117
		}
118
		return $strs;
119
	}
120
121
	/**
122
	 * Display a ContentSecurity object.
123
	 *
124
	 * @param callable $directiveCall
125
	 * @param callable $policyCall
126
	 * @return string
127
	 */
128
	public function display(callable $directiveCall, callable $policyCall): string {
129
		$strs = '';
130
		foreach ($this->policies as $directive => $policy) {
131
			$policies = \array_keys($policy);
132
			$strs .= $directiveCall($directive) . $policyCall(\implode(' ', $policies));
133
		}
134
		return $strs;
135
	}
136
137
	/**
138
	 * Sets reportOnly.
139
	 *
140
	 * @param bool|null $reportOnly
141
	 * @return $this
142
	 */
143
	public function reportOnly(?bool $reportOnly = true): self {
144
		if (isset($reportOnly)) {
145
			$this->header = $reportOnly ? self::DEBUG_HEADER : self::HEADER;
146
		}
147
		return $this;
148
	}
149
150
	/**
151
	 * Adds headers to the response.
152
	 *
153
	 * @param bool|null $reportOnly
154
	 */
155
	public function addHeaderToResponse(?bool $reportOnly = null): void {
156
		if (isset($reportOnly)) {
157
			$this->reportOnly($reportOnly);
158
		}
159
		UResponse::header($this->header, $this->generate(), false);
160
	}
161
162
	/**
163
	 * Creates a nonce and add it to some directives.
164
	 *
165
	 * @param
166
	 *        	$nonce
167
	 * @param string ...$directives
168
	 * @return ContentSecurity
169
	 */
0 ignored issues
show
Documentation Bug introduced by
The doc comment $nonce at position 0 could not be parsed: Unknown type name '$nonce' at position 0 in $nonce.
Loading history...
170
	public static function nonce($nonce, string ...$directives): ContentSecurity {
171
		$csp = new self();
172
		return $csp->addNonce($nonce, ...$directives);
173
	}
174
175
	/**
176
	 * Creates a new ContentSecurity object, with self in default-src.
177
	 *
178
	 * @return ContentSecurity
179
	 */
180
	public static function all(): ContentSecurity {
181
		$csp = new self();
182
		return $csp->addPolicy(CspDirectives::DEFAULT_SRC, CspValues::SELF);
183
	}
184
185
	/**
186
	 * Returns the actual policies.
187
	 *
188
	 * @return array
189
	 */
190
	public function getPolicies(): array {
191
		return $this->policies;
192
	}
193
194
	/**
195
	 * Creates a new ContentSecurity object for Ubiquity Webtools.
196
	 *
197
	 * @return ContentSecurity
198
	 */
199
	public static function defaultUbiquity(): ContentSecurity {
200
		$csp = new self();
201
		$csp->addPolicy(CspDirectives::DEFAULT_SRC, 'self', 'cdn.jsdelivr.net', 'cdnjs.cloudflare.com');
202
		$csp->addPolicyDefault(CspDirectives::FONT_SRC, 'fonts.googleapis.com', 'fonts.gstatic.com', 'data:');
203
		$csp->addPolicyDefault(CspDirectives::STYLE_SRC, CspValues::UNSAFE_INLINE, 'fonts.googleapis.com');
204
		$csp->addPolicyDefault(CspDirectives::SCRIPT_SRC_ELM, CspValues::UNSAFE_INLINE);
205
		$csp->addPolicy(CspDirectives::IMG_SRC, 'data:');
206
		return $csp;
207
	}
208
}
209