EnforceContentSecurity::encodeConfiguration()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 13
cts 13
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 11
nc 3
nop 1
crap 3
1
<?php namespace Stevenmaguire\Http\Middleware;
2
3
use Psr\Http\Message\ResponseInterface;
4
5
class EnforceContentSecurity
6
{
7
    use CspValidationTrait;
8
9
    /**
10
     * Configuration for session
11
     *
12
     * @var array
13
     */
14
    private $config = [];
15
16
    /**
17
     * Directive separator
18
     *
19
     * @var string
20
     */
21
    private $directiveSeparator = ';';
22
23
   /**
24
     * Security header name
25
     *
26
     * @var string
27
     */
28
    private $header = 'Content-Security-Policy';
29
30
    /**
31
     * Profiles
32
     *
33
     * @var array
34
     */
35
    private $profiles = [];
36
37
38
    /**
39
     * Source separator
40
     *
41
     * @var string
42
     */
43
    private $sourceSeparator = ' ';
44
45
    /**
46
     * Add content security policy header to response
47
     *
48
     * @param  ResponseInterface  $response
49
     *
50
     * @return ResponseInterface
51
     */
52 6
    protected function addPolicyHeader(ResponseInterface $response)
53
    {
54 6
        $this->loadDefaultProfiles();
55
56 6
        $currentHeader = $response->getHeader($this->header);
57
58 6
        $initialConfig = [];
59 6
        if (count($currentHeader)) {
60 6
            $initialConfig = $this->decodeConfiguration($currentHeader[0]);
61 6
        }
62
63 6
        $initialDirectives = $this->encodeConfiguration($initialConfig);
64
65 6
        $this->mergeProfileWithConfig($initialConfig);
66
67 6
        $newDirectives = $this->encodeConfiguration($this->config);
68
69 6
        if ($newDirectives != $initialDirectives) {
70 3
            $response = $response->withHeader($this->header, $newDirectives);
71 3
        }
72
73 6
        return $response;
74
    }
75
76
    /**
77
     * Decode a given string into configuration
78
     *
79
     * @param  string $string
80
     *
81
     * @return array
82
     */
83 6
    protected function decodeConfiguration($string)
84
    {
85 6
        $config = [];
86 6
        $directives = explode($this->directiveSeparator, $string);
87 6
        foreach ($directives as $directive) {
88 6
            $parts = array_filter(explode($this->sourceSeparator, $directive));
89 6
            $key = trim(array_shift($parts));
90 6
            $config[$key] = $parts;
91 6
        }
92
93 6
        return $config;
94
    }
95
96
    /**
97
     * Encode the current configuration as string
98
     *
99
     * @param  array $config
100
     *
101
     * @return string
102
     */
103 6
    protected function encodeConfiguration($config = [])
104
    {
105 6
        $value = [];
106 6
        ksort($config);
107 6
        foreach ($config as $directive => $values) {
108 6
            $values = array_unique($values);
109 6
            sort($values);
110 6
            array_unshift($values, $directive);
111 6
            $string = implode($this->sourceSeparator, $values);
112
113 6
            if ($string) {
114 5
                $value[] = $string;
115 5
            }
116 6
        }
117
118 6
        return implode($this->directiveSeparator . ' ', $value);
119
    }
120
121
122
    /**
123
     * Create array from value
124
     *
125
     * @param  mixed $value
126
     *
127
     * @return array
128
     */
129 6
    protected function getArrayFromValue($value, $separator = ',')
130
    {
131 6
        if (!is_array($value)) {
132 2
            $value = explode($separator, $value);
133 2
        }
134
135 6
        return $value;
136
    }
137
138
    /**
139
     * Retrieves array of currently configured content security policy directives.
140
     *
141
     * @return array
142
     */
143 2
    public static function getAvailableDirectives()
144
    {
145
        return [
146 2
            '\'self\'', 'base-uri','child-src','connect-src','default-src','font-src',
147 2
            'form-action','frame-ancestors','frame-src','img-src','manifest-src',
148 2
            'media-src','object-src','plugin-types','referrer','reflected-xss',
149 2
            'report-uri','sandbox','script-src','style-src','upgrade-insecure-requests',
150 2
        ];
151
    }
152
153
    /**
154
     * Gets header.
155
     *
156
     * @return string
157
     */
158 4
    public function getHeader()
159
    {
160 4
        return $this->header;
161
    }
162
163
    /**
164
     * Load default profiles
165
     *
166
     * @return void
167
     */
168 6
    protected function loadDefaultProfiles()
169
    {
170 6
        $defaultProfiles = [];
171
172 6
        if (isset($this->profiles['default'])) {
173 2
            $defaultProfiles = $this->getArrayFromValue(
174 2
                $this->profiles['default']
175 2
            );
176 2
        }
177
178 6
        array_map([$this, 'loadProfileByKey'], $defaultProfiles);
179 6
    }
180
181
    /**
182
     * Load a specific profile
183
     *
184
     * @param  string $key
185
     *
186
     * @return void
187
     */
188 3
    protected function loadProfileByKey($key)
189
    {
190 3
        if (isset($this->profiles['profiles'][$key])) {
191 3
            $profile = $this->profiles['profiles'][$key];
192
193 3
            if (is_array($profile)) {
194 3
                $this->mergeProfileWithConfig($profile);
195 3
            }
196 3
        }
197 3
    }
198
199
    /**
200
     * Merge a given profile with current configuration
201
     *
202
     * @param  array $profile
203
     *
204
     * @return void
205
     */
206 6
    protected function mergeProfileWithConfig(array $profile)
207
    {
208 6
        foreach ($profile as $directive => $values) {
209 6
            if (!isset($this->config[$directive])) {
210 6
                $this->config[$directive] = [];
211 6
            }
212
213 6
            $values = $this->getArrayFromValue($values);
214
215 6
            $this->config[$directive] = array_merge($this->config[$directive], $values);
216 6
        }
217 6
    }
218
219
    /**
220
     * Sets profiles.
221
     *
222
     * @param array  $profiles
223
     *
224
     * @return EnforceContentSecurity
225
     */
226 4
    protected function setProfiles($profiles = [])
227
    {
228 4
        $this->profiles = $profiles;
229
230 4
        return $this;
231
    }
232
}
233