Passed
Push — master ( c2d151...ae6c4a )
by Jean-Christophe
01:08
created

ContentSecurity::addNonceDefault()   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 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
	/**
57
	 * Adds new values to a directive, re-using default-src actual values.
58
	 *
59
	 * @param string $directive
60
	 * @param string ...$values
61
	 * @return $this
62
	 */
63
	public function addPolicyDefault(string $directive, string ...$values): self {
64
		$default = \array_keys($this->policies[CspDirectives::DEFAULT_SRC] ?? []);
65
		$values = \array_merge($default, $values);
66
		$this->addPolicy($directive, ...$values);
67
		return $this;
68
	}
69
70
	/**
71
	 * Adds a nonce to the directives.
72
	 *
73
	 * @param string $nonce
74
	 * @param string ...$directives
75
	 * @return $this
76
	 */
77
	public function addNonce(string $nonce, string ...$directives): self {
78
		foreach ($directives as $directive) {
79
			$this->addPolicy($directive, "'nonce-$nonce'", CspValues::STRICT_DYNAMIC);
80
		}
81
		return $this;
82
	}
83
84
	/**
85
	 * Adds a nonce to a directive, re-using default-src actual values.
86
	 *
87
	 * @param string $nonce
88
	 * @param string ...$directives
89
	 * @return $this
90
	 */
91
	public function addNonceDefault(string $nonce, string ...$directives): self {
92
		foreach ($directives as $directive) {
93
			$this->addPolicyDefault($directive, "'nonce-$nonce'", CspValues::STRICT_DYNAMIC);
94
		}
95
		return $this;
96
	}
97
98
	/**
99
	 * Defines the policies for default-src directive.
100
	 *
101
	 * @param string ...$policies
102
	 * @return $this
103
	 */
104
	public function setDefaultSrc(string ...$policies): self {
105
		return $this->addPolicy(CspDirectives::DEFAULT_SRC, ...$policies);
106
	}
107
108
	/**
109
	 * Generates the header string.
110
	 *
111
	 * @return string
112
	 */
113
	public function generate(): string {
114
		$strs = '';
115
		foreach ($this->policies as $directive => $policy) {
116
			$policies = \array_keys($policy);
117
			$strs .= $directive . ' ' . \implode(' ', $policies) . ';';
118
		}
119
		return $strs;
120
	}
121
122
	/**
123
	 * Display a ContentSecurity object.
124
	 *
125
	 * @param callable $directiveCall
126
	 * @param callable $policyCall
127
	 * @return string
128
	 */
129
	public function display(callable $directiveCall, callable $policyCall): string {
130
		$strs = '';
131
		foreach ($this->policies as $directive => $policy) {
132
			$policies = \array_keys($policy);
133
			$strs .= $directiveCall($directive) . $policyCall(\implode(' ', $policies));
134
		}
135
		return $strs;
136
	}
137
138
	/**
139
	 * Sets reportOnly.
140
	 *
141
	 * @param bool|null $reportOnly
142
	 * @return $this
143
	 */
144
	public function reportOnly(?bool $reportOnly = true): self {
145
		if (isset($reportOnly)) {
146
			$this->header = $reportOnly ? self::DEBUG_HEADER : self::HEADER;
147
		}
148
		return $this;
149
	}
150
151
	/**
152
	 * Adds headers to the response.
153
	 *
154
	 * @param bool|null $reportOnly
155
	 */
156
	public function addHeaderToResponse(?bool $reportOnly = null): void {
157
		if (isset($reportOnly)) {
158
			$this->reportOnly($reportOnly);
159
		}
160
		UResponse::header($this->header, $this->generate(), false);
161
	}
162
163
	/**
164
	 * Creates a nonce and add it to some directives.
165
	 *
166
	 * @param
167
	 *        	$nonce
168
	 * @param string ...$directives
169
	 * @return ContentSecurity
170
	 */
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...
171
	public static function nonce($nonce, string ...$directives): ContentSecurity {
172
		$csp = new self();
173
		return $csp->addNonce($nonce, ...$directives);
174
	}
175
176
	/**
177
	 * Creates a new ContentSecurity object, with self in default-src.
178
	 *
179
	 * @return ContentSecurity
180
	 */
181
	public static function all(): ContentSecurity {
182
		$csp = new self();
183
		return $csp->addPolicy(CspDirectives::DEFAULT_SRC, CspValues::SELF);
184
	}
185
186
	/**
187
	 * Returns the actual policies.
188
	 *
189
	 * @return array
190
	 */
191
	public function getPolicies(): array {
192
		return $this->policies;
193
	}
194
195
	/**
196
	 * Creates a new ContentSecurity object for Ubiquity Webtools.
197
	 *
198
	 * @return ContentSecurity
199
	 */
200
	public static function defaultUbiquity(): ContentSecurity {
201
		$csp = new self();
202
		$csp->addPolicy(CspDirectives::DEFAULT_SRC, 'self', 'cdn.jsdelivr.net', 'cdnjs.cloudflare.com');
203
		$csp->addPolicyDefault(CspDirectives::FONT_SRC, 'fonts.googleapis.com', 'fonts.gstatic.com', 'data:');
204
		$csp->addPolicyDefault(CspDirectives::STYLE_SRC, CspValues::UNSAFE_INLINE, 'fonts.googleapis.com');
205
		$csp->addPolicyDefault(CspDirectives::SCRIPT_SRC_ELM, CspValues::UNSAFE_INLINE);
206
		$csp->addPolicy(CspDirectives::IMG_SRC, 'data:');
207
		return $csp;
208
	}
209
210
	/**
211
	 * Creates a new ContentSecurity object for Ubiquity Webtools in debug mode.
212
	 * 
213
	 * @param string $livereloadServer
214
	 * @return ContentSecurity
215
	 */
216
	public static function defaultUbiquityDebug(string $livereloadServer='127.0.0.1:35729'): ContentSecurity {
217
		$csp = self::defaultUbiquity();
218
		$config=Startup::$config;
219
		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...
220
			$csp->addPolicyDefault(CspDirectives::CONNECT_SRC,"ws://$livereloadServer");
221
			$csp->addPolicy(CspDirectives::SCRIPT_SRC_ELM,"http://$livereloadServer");
222
		}
223
		return $csp;
224
	}
225
}
226