Passed
Push — develop ( 5cfbbd...a66307 )
by nguereza
02:52
created

SecurityPolicy::csp()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 45
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 26
c 1
b 0
f 0
nc 7
nop 0
dl 0
loc 45
rs 8.5706
1
<?php
2
3
/**
4
 * Platine Framework
5
 *
6
 * Platine Framework is a lightweight, high-performance, simple and elegant PHP
7
 * Web framework
8
 *
9
 * This content is released under the MIT License (MIT)
10
 *
11
 * Copyright (c) 2020 Platine Framework
12
 * Copyright (c) 2015 - 2023 Paragon Initiative Enterprises
13
 *
14
 * Permission is hereby granted, free of charge, to any person obtaining a copy
15
 * of this software and associated documentation files (the "Software"), to deal
16
 * in the Software without restriction, including without limitation the rights
17
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
 * copies of the Software, and to permit persons to whom the Software is
19
 * furnished to do so, subject to the following conditions:
20
 *
21
 * The above copyright notice and this permission notice shall be included in all
22
 * copies or substantial portions of the Software.
23
 *
24
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
 * SOFTWARE.
31
 */
32
33
/**
34
 *  @file SecurityPolicy.php
35
 *
36
 *  The Security Policy class
37
 *
38
 *  @package    Platine\Framework\Security
39
 *  @author Platine Developers team
40
 *  @copyright  Copyright (c) 2020
41
 *  @license    http://opensource.org/licenses/MIT  MIT License
42
 *  @link   https://www.platine-php.com
43
 *  @version 1.0.0
44
 *  @filesource
45
 */
46
47
declare(strict_types=1);
48
49
namespace Platine\Framework\Security;
50
51
use Platine\Config\Config;
52
use Platine\Framework\Security\Policy\ClearSiteDataPolicy;
53
use Platine\Framework\Security\Policy\ContentSecurityPolicy;
54
use Platine\Framework\Security\Policy\FeaturePermissionPolicy;
55
use Platine\Framework\Security\Policy\StrictTransportSecurityPolicy;
56
use Platine\Route\Router;
57
use Platine\Stdlib\Helper\Json;
58
59
/**
60
 * @class SecurityPolicy
61
 * @package Platine\Framework\Security
62
 */
63
class SecurityPolicy
64
{
65
    /**
66
     * The application configuration
67
     * @var Config
68
     */
69
    protected Config $config;
70
71
72
    /**
73
     * The Router instance
74
     * @var Router
75
     */
76
    protected Router $router;
77
78
    /**
79
     * The configuration
80
     * @var array<string, mixed>
81
     */
82
    protected array $configurations = [];
83
84
    /**
85
     * The nonce's for script-src and style-src
86
     * @var array<string, array<string>>
87
     */
88
    protected array $nonces = [
89
        'style' => [],
90
        'script' => [],
91
    ];
92
93
    /**
94
     * Create new instance
95
     * @param Config $config
96
     * @param Router $router
97
     * @param array<string, mixed> $configurations
98
     */
99
    public function __construct(
100
        Config $config,
101
        Router $router,
102
        array $configurations = []
103
    ) {
104
        $this->config = $config;
105
        $this->router = $router;
106
        $this->configurations = $configurations;
107
    }
108
109
    /**
110
     * Return the headers to be used in response
111
     * @return array<string, string>
112
     */
113
    public function headers(): array
114
    {
115
        $headers = array_merge(
116
            $this->csp(),
117
            $this->features(),
118
            $this->hsts(),
119
            $this->clearSiteData(),
120
            $this->commons()
121
        );
122
123
        return array_filter($headers);
124
    }
125
126
    /**
127
     * Generate random nonce value for current request.
128
     * @param string $target
129
     * @return string
130
     */
131
    public function nonce(string $target = 'script'): string
132
    {
133
        $nonce = base64_encode(bin2hex(random_bytes(8)));
134
        $this->nonces[$target][] = $nonce;
135
136
        return $nonce;
137
    }
138
139
    /**
140
     * Return the Content Security Policy headers
141
     * @return array<string, string>
142
     */
143
    protected function csp(): array
144
    {
145
        $config = $this->configurations['csp'] ?? [];
146
        $isEnabled = $config['enable'] ?? false;
147
        if ($isEnabled === false) {
148
            return [];
149
        }
150
151
        $config['script-src']['nonces'] = $this->nonces['script'];
152
        $config['style-src']['nonces'] = $this->nonces['style'];
153
154
        $isReportOnly = $config['report-only'] ?? false;
155
        $header = $isReportOnly
156
                ? 'Content-Security-Policy-Report-Only'
157
                : 'Content-Security-Policy';
158
159
        $policy = new ContentSecurityPolicy($config);
160
161
        $headers = [$header => $policy->headers()];
162
163
        $reportTo = [];
164
        if ($config['report-to'] ?? false) {
165
            $reportTo['group'] = $config['report-to'];
166
            $reportTo['max_age'] = 1800; // TODO use configuration
167
168
            if (count($config['report-uri'] ?? []) > 0) {
169
                $reportTo['endpoints'] = [];
170
171
                $routes = $this->router->routes();
172
173
                foreach ($config['report-uri'] as $url) {
174
                    if ($routes->has($url)) {
175
                        $url = $this->config->get('app.host') . $routes->get($url)->path();
176
                    }
177
178
                    $reportTo['endpoints'][] = [
179
                        'url' => $url
180
                    ];
181
                }
182
            }
183
184
            $headers['Report-To'] = Json::encode($reportTo);
185
        }
186
187
        return $headers;
188
    }
189
190
    /**
191
     * Return the Permissions Policy headers
192
     * @return array<string, string>
193
     */
194
    protected function features(): array
195
    {
196
        $config = $this->configurations['features-permissions'] ?? [];
197
        $isEnabled = $config['enable'] ?? false;
198
        if ($isEnabled === false) {
199
            return [];
200
        }
201
202
         $policy = new FeaturePermissionPolicy($config);
203
204
        return ['Permissions-Policy' => $policy->headers()];
205
    }
206
207
    /**
208
     * Return the HSTS Policy headers
209
     * @return array<string, string>
210
     */
211
    protected function hsts(): array
212
    {
213
        $config = $this->configurations['hsts'] ?? [];
214
        $isEnabled = $config['enable'] ?? false;
215
        if ($isEnabled === false) {
216
            return [];
217
        }
218
219
         $policy = new StrictTransportSecurityPolicy($config);
220
221
        return ['Strict-Transport-Security' => $policy->headers()];
222
    }
223
224
225
    /**
226
     * Return the Clear Site Data Policy headers
227
     * @return array<string, string>
228
     */
229
    protected function clearSiteData(): array
230
    {
231
        $config = $this->configurations['clear-site-data'] ?? [];
232
        $isEnabled = $config['enable'] ?? false;
233
        if ($isEnabled === false) {
234
            return [];
235
        }
236
237
         $policy = new ClearSiteDataPolicy($config);
238
239
        return ['Clear-Site-Data' => $policy->headers()];
240
    }
241
242
    /**
243
     * Return the common security policies headers
244
     * @return array<string, string>
245
     */
246
    protected function commons(): array
247
    {
248
        return array_filter([
249
            'X-Content-Type-Options' => $this->configurations['x-content-type-options'] ?? 'nosniff',
250
            'X-Download-Options' => $this->configurations['x-download-options'] ?? 'noopen',
251
            'X-Frame-Options' => $this->configurations['x-frame-options'] ?? 'sameorigin',
252
            'X-Permitted-Cross-Domain-Policies' => $this->configurations['x-permitted-cross-domain-policies'] ?? 'none',
253
            'X-Powered-By' => $this->configurations['x-powered-by'] ?? '',
254
            'X-XSS-Protection' => $this->configurations['x-xss-protection'] ?? '1; mode=block',
255
            'Referrer-Policy' => $this->configurations['referrer-policy'] ?? 'no-referrer',
256
            'Server' => $this->configurations['server'] ?? '',
257
        ]);
258
    }
259
}
260