CspProvider::addSecurityHeaders()   B
last analyzed

Complexity

Conditions 7
Paths 12

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
eloc 9
c 2
b 0
f 1
dl 0
loc 19
rs 8.8333
cc 7
nc 12
nop 1
1
<?php
2
3
namespace LeKoala\DeferBackend;
4
5
use SilverStripe\Control\Director;
6
use SilverStripe\Control\HTTPResponse;
7
use SilverStripe\Core\Config\Configurable;
8
use SilverStripe\View\TemplateGlobalProvider;
9
10
/**
11
 * A dead simple csp provider
12
 *
13
 * @link https://csp.withgoogle.com/docs/strict-csp.html
14
 * @link https://content-security-policy.com/strict-dynamic/
15
 */
16
class CspProvider implements TemplateGlobalProvider
17
{
18
    use Configurable;
19
20
    /**
21
     * @config
22
     * @var string
23
     */
24
    private static $default_referrer_policy = "no-referrer-when-downgrade";
25
26
    /**
27
     * @config
28
     * @var bool
29
     */
30
    private static $enable_hsts = true;
31
32
    /**
33
     * @config
34
     * @var string
35
     */
36
    private static $hsts_header = 'max-age=300; includeSubDomains; preload; always;';
37
38
    /**
39
     * @config
40
     * @var bool
41
     */
42
    private static $enable_csp = false;
43
44
    /**
45
     * @config
46
     * @var string
47
     */
48
    private static $frame_ancestors = "'self'";
49
50
    /**
51
     * @config
52
     * @var string
53
     */
54
    private static $frame_options = "SAMEORIGIN";
55
56
    /**
57
     * @config
58
     * @var string
59
     */
60
    private static $csp_report_uri = null;
61
62
    /**
63
     * @config
64
     * @var bool
65
     */
66
    private static $csp_report_only = true;
67
68
    /**
69
     * @var string
70
     */
71
    protected static $csp_nonce = null;
72
73
    /**
74
     * Allows calling getCspNonce in the template for script inclusion
75
     *
76
     * @return array<string,string>
77
     */
78
    public static function get_template_global_variables()
79
    {
80
        return [
81
            'getCspNonce' => 'getCspNonce'
82
        ];
83
    }
84
85
    /**
86
     * @link https://content-security-policy.com/nonce/
87
     * @return string
88
     */
89
    public static function getCspNonce()
90
    {
91
        if (!self::$csp_nonce) {
92
            self::$csp_nonce = str_replace(["/", "+"], "", base64_encode(random_bytes(18)));
93
        }
94
        return self::$csp_nonce;
95
    }
96
97
    /**
98
     * Allow setting nonce from an external source
99
     * @param string $nonce
100
     * @return void
101
     */
102
    public static function setCspNonce($nonce)
103
    {
104
        self::$csp_nonce = $nonce;
105
    }
106
107
    /**
108
     * @param HTTPResponse $response
109
     * @return HTTPResponse
110
     */
111
    public static function addSecurityHeaders(HTTPResponse $response)
112
    {
113
        $config = self::config();
114
115
        // @link https://web.dev/referrer-best-practices/
116
        if ($config->default_referrer_policy) {
117
            $response->addHeader('Referrer-Policy', $config->default_referrer_policy);
118
        }
119
        // enable HTTP Strict Transport Security
120
        if ($config->enable_hsts && $config->hsts_header && Director::is_https()) {
121
            $response->addHeader('Strict-Transport-Security', $config->hsts_header);
122
        }
123
        //
124
        if ($config->frame_options) {
125
            if (!$response->getHeader('X-Frame-Options')) {
126
                $response->addHeader('X-Frame-Options', $config->frame_options);
127
            }
128
        }
129
        return $response;
130
    }
131
132
    /**
133
     * Add CSP to the response using a flexible strict dynamic way
134
     *
135
     * @param HTTPResponse $response
136
     * @return void
137
     */
138
    public static function addCspHeaders(HTTPResponse $response)
139
    {
140
        // Only supported in https
141
        if (!Director::is_https()) {
142
            return;
143
        }
144
        $config = self::config();
145
146
        // Only add if enabled in config
147
        if (!$config->enable_csp) {
148
            return;
149
        }
150
151
        $csp = "default-src 'self' data:";
152
153
        $csp .= ';';
154
        $csp .= "script-src 'nonce-" . self::getCspNonce() . "' 'strict-dynamic' 'unsafe-inline' 'unsafe-eval' https: http:;";
155
        $csp .= "style-src * 'unsafe-inline';";
156
        $csp .= "object-src 'self';";
157
        $csp .= "img-src * data:;";
158
        $csp .= "font-src * data:;";
159
160
        // @link https://cheatsheetseries.owasp.org/cheatsheets/Clickjacking_Defense_Cheat_Sheet.html#content-security-policy-frame-ancestors-examples
161
        if ($config->frame_ancestors) {
162
            $csp .= "frame-ancestors " . $config->frame_ancestors . ";";
163
        }
164
165
        $report = $config->csp_report_uri;
166
        $reportOnly = $config->csp_report_only;
167
168
        if ($report) {
169
            $csp .= "report-uri "  . $report;
170
        }
171
172
        $headerName = 'Content-Security-Policy';
173
        if ($report && $reportOnly) {
174
            $headerName = 'Content-Security-Policy-Report-Only';
175
        }
176
        $response->addHeader($headerName, $csp);
177
    }
178
}
179