Passed
Push — master ( ae6c4a...572343 )
by Jean-Christophe
01:16
created

ContentSecurity::removePolicy()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 7
c 1
b 0
f 1
dl 0
loc 11
rs 10
cc 4
nc 5
nop 2
1
<?php
2
namespace Ubiquity\security\csp;
3
4
use Ubiquity\controllers\Startup;
5
use Ubiquity\utils\http\UResponse;
6
7
/**
8
 * Creates a Content Security Policy object.
9
 * Ubiquity\security\csp$ContentSecurity
10
 * This class is part of Ubiquity
11
 *
12
 * @author jc
13
 * @version 1.0.0
14
 *
15
 */
16
class ContentSecurity {
17
18
	const HEADER = 'Content-Security-Policy';
19
20
	const DEBUG_HEADER = 'Content-Security-Policy-Report-Only';
21
22
	private array $policies = [];
23
24
	private $header = self::HEADER;
25
26
	/**
27
	 * ContentSecurity constructor.
28
	 *
29
	 * @param bool|null $reportOnly
30
	 */
31
	public function __construct(?bool $reportOnly = null) {
32
		if (isset($reportOnly)) {
33
			$this->reportOnly($reportOnly);
34
		}
35
	}
36
37
	/**
38
	 * Adds new values to a directive.
39
	 *
40
	 * @param string $directive
41
	 * @param string ...$values
42
	 * @return $this
43
	 */
44
	public function addPolicy(string $directive, string ...$values): self {
45
		$policies = $this->policies[$directive] ?? [];
46
		foreach ($values as $v) {
47
			if (\in_array($v, CspValues::QUOTED)) {
48
				$v = "'$v'";
49
			}
50
			$policies[$v] = true;
51
		}
52
		$this->policies[$directive] = $policies;
53
		return $this;
54
	}
55
56
	public function removePolicy(string $directive, string ...$values): self {
57
		$policies = $this->policies[$directive] ?? [];
0 ignored issues
show
Unused Code introduced by
The assignment to $policies is dead and can be removed.
Loading history...
58
		foreach ($values as $v) {
59
			if (\in_array($v, CspValues::QUOTED)) {
60
				$v = "'$v'";
61
			}
62
			if (isset($this->policies[$directive][$v])) {
63
				unset($this->policies[$directive][$v]);
64
			}
65
		}
66
		return $this;
67
	}
68
69
	/**
70
	 * Adds new values to a directive, re-using default-src actual values.
71
	 *
72
	 * @param string $directive
73
	 * @param string ...$values
74
	 * @return $this
75
	 */
76
	public function addPolicyDefault(string $directive, string ...$values): self {
77
		$default = \array_keys($this->policies[CspDirectives::DEFAULT_SRC] ?? []);
78
		$values = \array_merge($default, $values);
79
		$this->addPolicy($directive, ...$values);
80
		return $this;
81
	}
82
83
	/**
84
	 * Adds a nonce to the directives.
85
	 *
86
	 * @param string $nonce
87
	 * @param string ...$directives
88
	 * @return $this
89
	 */
90
	public function addNonce(string $nonce, string ...$directives): self {
91
		foreach ($directives as $directive) {
92
			$this->addPolicy($directive, "'nonce-$nonce'", CspValues::STRICT_DYNAMIC);
93
		}
94
		return $this;
95
	}
96
97
	/**
98
	 * Adds a hash to the directives.
99
	 *
100
	 * @param string $hash
101
	 * @param string ...$directives
102
	 * @return $this
103
	 */
104
	public function addHash(string $hash, string ...$directives): self {
105
		foreach ($directives as $directive) {
106
			$this->addPolicy($directive, "'$hash'");
107
		}
108
		return $this;
109
	}
110
111
	/**
112
	 * Adds a nonce to a directive, re-using default-src actual values.
113
	 *
114
	 * @param string $nonce
115
	 * @param string ...$directives
116
	 * @return $this
117
	 */
118
	public function addNonceDefault(string $nonce, string ...$directives): self {
119
		foreach ($directives as $directive) {
120
			$this->addPolicyDefault($directive, "'nonce-$nonce'", CspValues::STRICT_DYNAMIC);
121
		}
122
		return $this;
123
	}
124
125
	/**
126
	 * Adds a hash to a directive, re-using default-src actual values.
127
	 *
128
	 * @param string $hash
129
	 * @param string ...$directives
130
	 * @return $this
131
	 */
132
	public function addHashDefault(string $hash, string ...$directives): self {
133
		foreach ($directives as $directive) {
134
			$this->addPolicyDefault($directive, "'$hash'");
135
		}
136
		return $this;
137
	}
138
139
	/**
140
	 * Defines the policies for default-src directive.
141
	 *
142
	 * @param string ...$policies
143
	 * @return $this
144
	 */
145
	public function setDefaultSrc(string ...$policies): self {
146
		return $this->addPolicy(CspDirectives::DEFAULT_SRC, ...$policies);
147
	}
148
149
	/**
150
	 * Generates the header string.
151
	 *
152
	 * @return string
153
	 */
154
	public function generate(): string {
155
		$strs = '';
156
		foreach ($this->policies as $directive => $policy) {
157
			$policies = \array_keys($policy);
158
			$strs .= $directive . ' ' . \implode(' ', $policies) . ';';
159
		}
160
		return $strs;
161
	}
162
163
	/**
164
	 * Display a ContentSecurity object.
165
	 *
166
	 * @param callable $directiveCall
167
	 * @param callable $policyCall
168
	 * @return string
169
	 */
170
	public function display(callable $directiveCall, callable $policyCall): string {
171
		$strs = '';
172
		foreach ($this->policies as $directive => $policy) {
173
			$policies = \array_keys($policy);
174
			$strs .= $directiveCall($directive) . $policyCall(\implode(' ', $policies));
175
		}
176
		return $strs;
177
	}
178
179
	/**
180
	 * Sets reportOnly.
181
	 *
182
	 * @param bool|null $reportOnly
183
	 * @return $this
184
	 */
185
	public function reportOnly(?bool $reportOnly = true): self {
186
		if (isset($reportOnly)) {
187
			$this->header = $reportOnly ? self::DEBUG_HEADER : self::HEADER;
188
		}
189
		return $this;
190
	}
191
192
	/**
193
	 * Adds headers to the response.
194
	 *
195
	 * @param bool|null $reportOnly
196
	 */
197
	public function addHeaderToResponse(?bool $reportOnly = null): void {
198
		if (isset($reportOnly)) {
199
			$this->reportOnly($reportOnly);
200
		}
201
		UResponse::header($this->header, $this->generate(), false);
202
	}
203
204
	/**
205
	 * Creates a nonce and add it to some directives.
206
	 *
207
	 * @param
208
	 *        	$nonce
209
	 * @param string ...$directives
210
	 * @return ContentSecurity
211
	 */
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...
212
	public static function nonce($nonce, string ...$directives): ContentSecurity {
213
		$csp = new self();
214
		return $csp->addNonce($nonce, ...$directives);
215
	}
216
217
	/**
218
	 * Creates a new ContentSecurity object, with self in default-src.
219
	 *
220
	 * @return ContentSecurity
221
	 */
222
	public static function all(): ContentSecurity {
223
		$csp = new self();
224
		return $csp->addPolicy(CspDirectives::DEFAULT_SRC, CspValues::SELF);
225
	}
226
227
	/**
228
	 * Returns the actual policies.
229
	 *
230
	 * @return array
231
	 */
232
	public function getPolicies(): array {
233
		return $this->policies;
234
	}
235
236
	/**
237
	 * Creates a new ContentSecurity object for Ubiquity Webtools.
238
	 *
239
	 * @return ContentSecurity
240
	 */
241
	public static function defaultUbiquity(): ContentSecurity {
242
		$csp = new self();
243
		$csp->addPolicy(CspDirectives::DEFAULT_SRC, 'self', 'cdn.jsdelivr.net', 'cdnjs.cloudflare.com');
244
		$csp->addPolicyDefault(CspDirectives::FONT_SRC, 'fonts.googleapis.com', 'fonts.gstatic.com', 'data:');
245
		$csp->addPolicyDefault(CspDirectives::STYLE_SRC, CspValues::UNSAFE_INLINE, 'fonts.googleapis.com');
246
		$csp->addPolicyDefault(CspDirectives::SCRIPT_SRC_ELM);
247
		$csp->addPolicy(CspDirectives::IMG_SRC, 'data:');
248
		return $csp;
249
	}
250
251
	/**
252
	 * Creates a new ContentSecurity object for Ubiquity Webtools in debug mode.
253
	 *
254
	 * @param string $livereloadServer
255
	 * @return ContentSecurity
256
	 */
257
	public static function defaultUbiquityDebug(string $livereloadServer = '127.0.0.1:35729'): ContentSecurity {
258
		$csp = self::defaultUbiquity();
259
		$config = Startup::$config;
260
		if ($config['debug'] && \Ubiquity\debug\LiveReload::hasLiveReload()) {
0 ignored issues
show
Bug introduced by
The type Ubiquity\debug\LiveReload was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
261
			$csp->addHash('sha256-8Xnt4HKk9Yhr0dEXwbeeEDZpkRMxqi9xGg43hnmUurY=', CspDirectives::SCRIPT_SRC_ELM);
262
			$csp->addPolicyDefault(CspDirectives::CONNECT_SRC, "ws://$livereloadServer");
263
			$csp->addPolicy(CspDirectives::SCRIPT_SRC_ELM, "http://$livereloadServer");
264
		}
265
		return $csp;
266
	}
267
}
268