SecurityPolicy::nonce()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 3
b 0
f 0
nc 1
nop 1
dl 0
loc 6
rs 10
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
 * @template T
63
 */
64
class SecurityPolicy
65
{
66
    /**
67
     * The nonce's for script-src and style-src
68
     * @var array<string, array<string>>
69
     */
70
    protected array $nonces = [
71
        'style' => [],
72
        'script' => [],
73
    ];
74
75
    /**
76
     * Create new instance
77
     * @param Config<T> $config
78
     * @param Router $router
79
     * @param array<string, mixed> $configurations
80
     */
81
    public function __construct(
82
        protected Config $config,
83
        protected Router $router,
84
        protected array $configurations = []
85
    ) {
86
    }
87
88
    /**
89
     * Return the headers to be used in response
90
     * @return array<string, string>
91
     */
92
    public function headers(): array
93
    {
94
        $headers = array_merge(
95
            $this->csp(),
96
            $this->features(),
97
            $this->hsts(),
98
            $this->clearSiteData(),
99
            $this->commons()
100
        );
101
102
        return array_filter($headers);
103
    }
104
105
    /**
106
     * Generate random nonce value for current request.
107
     * @param string $target
108
     * @return string
109
     */
110
    public function nonce(string $target = 'script'): string
111
    {
112
        $nonce = base64_encode(bin2hex(random_bytes(8)));
113
        $this->nonces[$target][] = $nonce;
114
115
        return $nonce;
116
    }
117
118
    /**
119
     * Return the Content Security Policy headers
120
     * @return array<string, string>
121
     */
122
    protected function csp(): array
123
    {
124
        $config = $this->configurations['csp'] ?? [];
125
        $isEnabled = $config['enable'] ?? false;
126
        if ($isEnabled === false) {
127
            return [];
128
        }
129
130
        $config['script-src']['nonces'] = $this->nonces['script'];
131
        $config['style-src']['nonces'] = $this->nonces['style'];
132
133
        if (count($config['report-uri'] ?? []) > 0) {
134
            $routes = $this->router->routes();
135
            $reportUri = [];
136
            foreach ($config['report-uri'] as $url) {
137
                if ($routes->has($url)) {
138
                    $url = $this->config->get('app.host') . $this->router->getUri($url)->getPath();
139
                }
140
141
                $reportUri[] = $url;
142
            }
143
144
            $config['report-uri'] = $reportUri;
145
        }
146
147
        $isReportOnly = $config['report-only'] ?? false;
148
        $header = $isReportOnly
149
                ? 'Content-Security-Policy-Report-Only'
150
                : 'Content-Security-Policy';
151
152
        $policy = new ContentSecurityPolicy($config);
153
154
        $headers = [$header => $policy->headers()];
155
156
        $reportTo = [];
157
        if ($config['report-to'] ?? false) {
158
            if (count($config['report-uri'] ?? []) > 0) {
159
                $reportTo['group'] = $config['report-to'];
160
                $reportTo['max_age'] = 1800; // TODO use configuration
161
162
                $reportTo['endpoints'] = [];
163
                foreach ($config['report-uri'] as $url) {
164
                    $reportTo['endpoints'][] = [
165
                        'url' => $url
166
                    ];
167
                }
168
                $headers['Report-To'] = Json::encode($reportTo);
169
            }
170
        }
171
172
        return $headers;
173
    }
174
175
    /**
176
     * Return the Permissions Policy headers
177
     * @return array<string, string>
178
     */
179
    protected function features(): array
180
    {
181
        $config = $this->configurations['features-permissions'] ?? [];
182
        $isEnabled = $config['enable'] ?? false;
183
        if ($isEnabled === false) {
184
            return [];
185
        }
186
187
         $policy = new FeaturePermissionPolicy($config);
188
189
        return ['Permissions-Policy' => $policy->headers()];
190
    }
191
192
    /**
193
     * Return the HSTS Policy headers
194
     * @return array<string, string>
195
     */
196
    protected function hsts(): array
197
    {
198
        $config = $this->configurations['hsts'] ?? [];
199
        $isEnabled = $config['enable'] ?? false;
200
        if ($isEnabled === false) {
201
            return [];
202
        }
203
204
         $policy = new StrictTransportSecurityPolicy($config);
205
206
        return ['Strict-Transport-Security' => $policy->headers()];
207
    }
208
209
210
    /**
211
     * Return the Clear Site Data Policy headers
212
     * @return array<string, string>
213
     */
214
    protected function clearSiteData(): array
215
    {
216
        $config = $this->configurations['clear-site-data'] ?? [];
217
        $isEnabled = $config['enable'] ?? false;
218
        if ($isEnabled === false) {
219
            return [];
220
        }
221
222
         $policy = new ClearSiteDataPolicy($config);
223
224
        return ['Clear-Site-Data' => $policy->headers()];
225
    }
226
227
    /**
228
     * Return the common security policies headers
229
     * @return array<string, string>
230
     */
231
    protected function commons(): array
232
    {
233
        return array_filter([
234
            'X-Content-Type-Options' => $this->configurations['x-content-type-options'] ?? 'nosniff',
235
            'X-Download-Options' => $this->configurations['x-download-options'] ?? 'noopen',
236
            'X-Frame-Options' => $this->configurations['x-frame-options'] ?? 'sameorigin',
237
            'X-Permitted-Cross-Domain-Policies' => $this->configurations['x-permitted-cross-domain-policies'] ?? 'none',
238
            'X-Powered-By' => $this->configurations['x-powered-by'] ?? '',
239
            'X-XSS-Protection' => $this->configurations['x-xss-protection'] ?? '1; mode=block',
240
            'Referrer-Policy' => $this->configurations['referrer-policy'] ?? 'no-referrer',
241
            'Server' => $this->configurations['server'] ?? '',
242
        ]);
243
    }
244
}
245