Passed
Push — master ( 77997d...7538ae )
by Thomas
03:44 queued 01:20
created

CspProvider   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 137
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 15
eloc 42
c 2
b 0
f 0
dl 0
loc 137
rs 10

5 Methods

Rating   Name   Duplication   Size   Complexity  
A get_template_global_variables() 0 4 1
A getCspNonce() 0 6 2
A addSecurityHeaders() 0 13 5
B addCspHeaders() 0 33 6
A setCspNonce() 0 3 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
 * Based on the concept here
14
 * @link https://websec.be/blog/cspstrictdynamic/
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 $csp_report_uri = null;
49
50
    /**
51
     * @config
52
     * @var bool
53
     */
54
    private static $csp_report_only = true;
55
56
    /**
57
     * @var string
58
     */
59
    protected static $csp_nonce = null;
60
61
    /**
62
     * Allows calling getCspNonce in the template for script inclusion
63
     *
64
     * @return array
65
     */
66
    public static function get_template_global_variables()
67
    {
68
        return [
69
            'getCspNonce'
70
        ];
71
    }
72
73
    /**
74
     * @link https://content-security-policy.com/nonce/
75
     * @return string
76
     */
77
    public static function getCspNonce()
78
    {
79
        if (!self::$csp_nonce) {
80
            self::$csp_nonce = str_replace(["/", "+"], "", base64_encode(random_bytes(18)));
81
        }
82
        return self::$csp_nonce;
83
    }
84
85
    /**
86
     * Allow setting nonce from an external source
87
     * @param  string $nonce
88
     * @return void
89
     */
90
    public static function setCspNonce($nonce)
91
    {
92
        self::$csp_nonce = $nonce;
93
    }
94
95
    /**
96
     * @param HTTPResponse $response
97
     * @return HTTPResponse
98
     */
99
    public static function addSecurityHeaders(HTTPResponse $response)
100
    {
101
        $config = self::config();
102
103
        // @link https://web.dev/referrer-best-practices/
104
        if ($config->default_referrer_policy) {
105
            $response->addHeader('Referrer-Policy', $config->default_referrer_policy);
106
        }
107
        // enable HTTP Strict Transport Security
108
        if ($config->enable_hsts && $config->hsts_header && Director::is_https()) {
109
            $response->addHeader('Strict-Transport-Security', $config->hsts_header);
110
        }
111
        return $response;
112
    }
113
114
    /**
115
     * Add CSP to the response using a flexible strict dynamic way
116
     *
117
     * @param HTTPResponse $response
118
     * @return HTTPResponse
119
     */
120
    public static function addCspHeaders(HTTPResponse $response)
121
    {
122
        if (!Director::is_https()) {
123
            return;
124
        }
125
        $config = self::config();
126
127
        if (!$config->enable_csp) {
128
            return;
129
        }
130
131
        $csp = "default-src 'self' data:";
132
133
        $csp .= ';';
134
        $csp .= "script-src 'nonce-" . self::getCspNonce() . "' 'strict-dynamic' 'unsafe-inline' 'unsafe-eval' https: http:;";
135
        $csp .= "style-src * 'unsafe-inline';";
136
        $csp .= "object-src 'self';";
137
        $csp .= "img-src * data:;";
138
        $csp .= "font-src * data:;";
139
140
        $report = $config->csp_report_uri;
141
        $reportOnly = $config->csp_report_only;
142
143
        if ($report) {
144
            $csp .= "report-uri "  . $report;
145
        }
146
147
        $headerName = 'Content-Security-Policy';
148
        if ($report && $reportOnly) {
149
            $headerName = 'Content-Security-Policy-Report-Only';
150
        }
151
        $response->addHeader($headerName, $csp);
152
        return $response;
153
    }
154
}
155