Completed
Push — master ( beefc8...5791ab )
by Neomerx
03:11
created

Settings::getResponseExposedHeaders()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php namespace Neomerx\Cors\Strategies;
2
3
/**
4
 * Copyright 2015 [email protected] (www.neomerx.com)
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
use \Psr\Http\Message\RequestInterface;
20
use \Neomerx\Cors\Log\LoggerAwareTrait;
21
use \Neomerx\Cors\Contracts\Http\ParsedUrlInterface;
22
use \Neomerx\Cors\Contracts\Constants\CorsResponseHeaders;
23
use \Neomerx\Cors\Contracts\Strategies\SettingsStrategyInterface;
24
25
/**
26
 * Implements strategy as a simple set of setting identical for all resources and requests.
27
 *
28
 * @package Neomerx\Cors
29
 */
30
class Settings implements SettingsStrategyInterface
31
{
32
    use LoggerAwareTrait;
33
34
    /**
35
     * 'All' value for allowed origins.
36
     */
37
    const VALUE_ALLOW_ORIGIN_ALL = CorsResponseHeaders::VALUE_ALLOW_ORIGIN_ALL;
38
39
    /**
40
     * 'All' values for allowed headers.
41
     */
42
    const VALUE_ALLOW_ALL_HEADERS = '*';
43
44
    /** Settings key */
45
    const KEY_SERVER_ORIGIN = 0;
46
47
    /** Settings key */
48
    const KEY_SERVER_ORIGIN_SCHEMA = 0;
49
50
    /** Settings key */
51
    const KEY_SERVER_ORIGIN_HOST = 1;
52
53
    /** Settings key */
54
    const KEY_SERVER_ORIGIN_PORT = 2;
55
56
    /** Settings key */
57
    const KEY_ALLOWED_ORIGINS = 1;
58
59
    /** Settings key */
60
    const KEY_ALLOWED_METHODS = 2;
61
62
    /** Settings key */
63
    const KEY_ALLOWED_HEADERS = 3;
64
65
    /** Settings key */
66
    const KEY_EXPOSED_HEADERS = 4;
67
68
    /** Settings key */
69
    const KEY_IS_USING_CREDENTIALS = 5;
70
71
    /** Settings key */
72
    const KEY_FLIGHT_CACHE_MAX_AGE = 6;
73
74
    /** Settings key */
75
    const KEY_IS_FORCE_ADD_METHODS = 7;
76
77
    /** Settings key */
78
    const KEY_IS_FORCE_ADD_HEADERS = 8;
79
80
    /** Settings key */
81
    const KEY_IS_CHECK_HOST = 9;
82
83
    /**
84
     * @var array
85
     */
86
    private $settings = [
87
        /**
88
         * Array should be in parse_url() result format.
89
         *
90
         * @see http://php.net/manual/function.parse-url.php
91
         */
92
        self::KEY_SERVER_ORIGIN        => [
93
            self::KEY_SERVER_ORIGIN_SCHEMA => '',
94
            self::KEY_SERVER_ORIGIN_HOST   => ParsedUrlInterface::DEFAULT_PORT,
95
            self::KEY_SERVER_ORIGIN_PORT   => '',
96
        ],
97
98
        /**
99
         * A list of allowed request origins (lower-cased, no trail slashes).
100
         * Value `true` enables and value `null` disables origin.
101
         * If all origins '*' are enabled all settings for other origins are ignored.
102
         *
103
         * For example,
104
         *
105
         * [
106
         *     'http://example.com:123'     => true,
107
         *     'http://evil.com'            => null,
108
         *     self::VALUE_ALLOW_ORIGIN_ALL => null,
109
         * ];
110
         */
111
        self::KEY_ALLOWED_ORIGINS => [],
112
113
        /**
114
         * A list of allowed request methods (case sensitive). Value `true` enables and value `null` disables method.
115
         *
116
         * For example,
117
         *
118
         * [
119
         *     'GET'    => true,
120
         *     'PATCH'  => true,
121
         *     'POST'   => true,
122
         *     'PUT'    => null,
123
         *     'DELETE' => true,
124
         * ];
125
         *
126
         * Security Note: you have to remember CORS is not access control system and you should not expect all
127
         * cross-origin requests will have pre-flights. For so-called 'simple' methods with so-called 'simple'
128
         * headers request will be made without pre-flight. Thus you can not restrict such requests with CORS
129
         * and should use other means.
130
         * For example method 'GET' without any headers or with only 'simple' headers will not have pre-flight
131
         * request so disabling it will not restrict access to resource(s).
132
         *
133
         * You can read more on 'simple' methods at http://www.w3.org/TR/cors/#simple-method
134
         */
135
        self::KEY_ALLOWED_METHODS => [],
136
137
        /**
138
         * A list of allowed request headers (lower-cased). Value `true` enables and value `null` disables header.
139
         *
140
         * For example,
141
         *
142
         * $allowedHeaders = [
143
         *     'content-type'                => true,
144
         *     'x-custom-request-header'     => null,
145
         *     self::VALUE_ALLOW_ALL_HEADERS => null,
146
         * ];
147
         *
148
         * Security Note: you have to remember CORS is not access control system and you should not expect all
149
         * cross-origin requests will have pre-flights. For so-called 'simple' methods with so-called 'simple'
150
         * headers request will be made without pre-flight. Thus you can not restrict such requests with CORS
151
         * and should use other means.
152
         * For example method 'GET' without any headers or with only 'simple' headers will not have pre-flight
153
         * request so disabling it will not restrict access to resource(s).
154
         *
155
         * You can read more on 'simple' headers at http://www.w3.org/TR/cors/#simple-header
156
         */
157
        self::KEY_ALLOWED_HEADERS             => [],
158
159
        /**
160
         * A list of headers (case insensitive) which will be made accessible to user agent (browser) in response.
161
         * Value `true` enables and value `null` disables header.
162
         *
163
         * For example,
164
         *
165
         * [
166
         *     'Content-Type'             => true,
167
         *     'X-Custom-Response-Header' => true,
168
         *     'X-Disabled-Header'        => null,
169
         * ];
170
         */
171
        self::KEY_EXPOSED_HEADERS      => [],
172
173
        /**
174
         * If access with credentials is supported by the resource.
175
         */
176
        self::KEY_IS_USING_CREDENTIALS => false,
177
178
        /**
179
         * Pre-flight response cache max period in seconds.
180
         *
181
         * @var int
182
         */
183
        self::KEY_FLIGHT_CACHE_MAX_AGE => 0,
184
185
        /**
186
         * If allowed methods should be added to pre-flight response when
187
         * 'simple' method is requested (see #6.2.9 CORS).
188
         *
189
         * @see http://www.w3.org/TR/cors/#resource-preflight-requests
190
         */
191
        self::KEY_IS_FORCE_ADD_METHODS => false,
192
193
        /**
194
         * If allowed headers should be added when request headers are 'simple' and
195
         * non of them is 'Content-Type' (see #6.2.10 CORS).
196
         *
197
         * @see http://www.w3.org/TR/cors/#resource-preflight-requests
198
         *
199
         * @var bool
200
         */
201
        self::KEY_IS_FORCE_ADD_HEADERS => false,
202
203
        /**
204
         * If request 'Host' header should be checked against server's origin.
205
         *
206
         * @var bool
207
         */
208
        self::KEY_IS_CHECK_HOST => false,
209
    ];
210
211
    /**
212
     * @inheritdoc
213
     */
214 7
    public function getSettings()
215
    {
216 7
        return $this->settings;
217
    }
218
219
    /**
220
     * @inheritdoc
221
     */
222 7
    public function setSettings(array $settings)
223
    {
224 7
        $this->settings = $settings;
225 7
    }
226
227
    /**
228
     * @inheritdoc
229
     */
230 11
    public function getServerOrigin()
231
    {
232 11
        return $this->settings[self::KEY_SERVER_ORIGIN];
233
    }
234
235
    /**
236
     * @inheritdoc
237
     */
238 18
    public function setServerOrigin($origin)
239
    {
240 18
        $this->settings[self::KEY_SERVER_ORIGIN] = is_string($origin) === true ? parse_url($origin) : $origin;
241
242 18
        return $this;
243
    }
244
245
    /**
246
     * @inheritdoc
247
     */
248 3
    public function isPreFlightCanBeCached(RequestInterface $request)
249
    {
250 3
        return $this->getPreFlightCacheMaxAge($request) > 0;
251
    }
252
253
    /**
254
     * @inheritdoc
255
     */
256 3
    public function getPreFlightCacheMaxAge(RequestInterface $request)
257
    {
258 3
        return $this->settings[self::KEY_FLIGHT_CACHE_MAX_AGE];
259
    }
260
261
    /**
262
     * @inheritdoc
263
     */
264 9
    public function setPreFlightCacheMaxAge($cacheMaxAge)
265
    {
266 9
        $this->settings[self::KEY_FLIGHT_CACHE_MAX_AGE] = $cacheMaxAge;
267
268 9
        return $this;
269
    }
270
271
    /**
272
     * @inheritdoc
273
     */
274 2
    public function isForceAddAllowedMethodsToPreFlightResponse()
275
    {
276 2
        return $this->settings[self::KEY_IS_FORCE_ADD_METHODS];
277
    }
278
279
    /**
280
     * @inheritdoc
281
     */
282 9
    public function setForceAddAllowedMethodsToPreFlightResponse($forceFlag)
283
    {
284 9
        $this->settings[self::KEY_IS_FORCE_ADD_METHODS] = $forceFlag;
285
286 9
        return $this;
287
    }
288
289
    /**
290
     * @inheritdoc
291
     */
292 1
    public function isForceAddAllowedHeadersToPreFlightResponse()
293
    {
294 1
        return $this->settings[self::KEY_IS_FORCE_ADD_HEADERS];
295
    }
296
297
    /**
298
     * @inheritdoc
299
     */
300 9
    public function setForceAddAllowedHeadersToPreFlightResponse($forceFlag)
301
    {
302 9
        $this->settings[self::KEY_IS_FORCE_ADD_HEADERS] = $forceFlag;
303
304 9
        return $this;
305
    }
306
307
    /**
308
     * @inheritdoc
309
     */
310 4
    public function isRequestCredentialsSupported(RequestInterface $request)
311
    {
312 4
        return $this->settings[self::KEY_IS_USING_CREDENTIALS];
313
    }
314
315
    /**
316
     * @inheritdoc
317
     */
318 18
    public function setRequestCredentialsSupported($isSupported)
319
    {
320 18
        $this->settings[self::KEY_IS_USING_CREDENTIALS] = $isSupported;
321
322 18
        return $this;
323
    }
324
325
    /**
326
     * @inheritdoc
327
     */
328 8
    public function isRequestOriginAllowed(ParsedUrlInterface $requestOrigin)
329
    {
330
        // check if all origins are allowed with '*'
331
        $isAllowed =
332 8
            isset($this->settings[self::KEY_ALLOWED_ORIGINS][CorsResponseHeaders::VALUE_ALLOW_ORIGIN_ALL]);
333
334 8
        if ($isAllowed === false) {
335 8
            $requestOriginStr = strtolower($requestOrigin->getOrigin());
336 8
            $isAllowed        = isset($this->settings[self::KEY_ALLOWED_ORIGINS][$requestOriginStr]);
337 8
        }
338
339 8
        return $isAllowed;
340
    }
341
342
    /**
343
     * @inheritdoc
344
     */
345 18
    public function setRequestAllowedOrigins(array $origins)
346
    {
347 18
        $this->settings[self::KEY_ALLOWED_ORIGINS] = [];
348 18
        foreach ($origins as $origin => $enabled) {
349 18
            $lcOrigin                                             = strtolower($origin);
350 18
            $this->settings[self::KEY_ALLOWED_ORIGINS][$lcOrigin] = $enabled;
351 18
        }
352
353 18
        return $this;
354
    }
355
356
    /**
357
     * @inheritdoc
358
     */
359 5
    public function isRequestMethodSupported($method)
360
    {
361 5
        $isAllowed = isset($this->settings[self::KEY_ALLOWED_METHODS][$method]);
362
363 5
        return $isAllowed;
364
    }
365
366
    /**
367
     * @inheritdoc
368
     */
369 5
    public function isRequestAllHeadersSupported($headers)
370
    {
371 5
        $allSupported = true;
372
373 5
        if (isset($this->settings[self::KEY_ALLOWED_HEADERS][self::VALUE_ALLOW_ALL_HEADERS]) === true) {
374 1
            return $allSupported;
375
        }
376
377 4
        foreach ($headers as $header) {
378 4
            $lcHeader = strtolower($header);
379 4
            if (isset($this->settings[self::KEY_ALLOWED_HEADERS][$lcHeader]) === false) {
380 2
                $allSupported = false;
381 2
                $this->logInfo(
382 2
                    'Request header is not allowed. Check config settings for Allowed Headers.',
383 2
                    ['header' => $header]
384 2
                );
385 2
                break;
386
            }
387 4
        }
388
389 4
        return $allSupported;
390
    }
391
392
    /**
393
     * @inheritdoc
394
     */
395 3
    public function getRequestAllowedMethods(RequestInterface $request, $requestMethod)
396
    {
397 3
        return implode(', ', $this->getEnabledItems($this->settings[self::KEY_ALLOWED_METHODS]));
398
    }
399
400
    /**
401
     * @inheritdoc
402
     */
403 18
    public function setRequestAllowedMethods(array $methods)
404
    {
405 18
        $this->settings[self::KEY_ALLOWED_METHODS] = [];
406 18
        foreach ($methods as $method => $enabled) {
407 18
            $this->settings[self::KEY_ALLOWED_METHODS][$method] = $enabled;
408 18
        }
409
410 18
        return $this;
411
    }
412
413
    /**
414
     * @inheritdoc
415
     */
416 3
    public function getRequestAllowedHeaders(RequestInterface $request, array $requestHeaders)
417
    {
418 3
        return implode(', ', $this->getEnabledItems($this->settings[self::KEY_ALLOWED_HEADERS]));
419
    }
420
421
    /**
422
     * @inheritdoc
423
     */
424 18
    public function setRequestAllowedHeaders(array $headers)
425
    {
426 18
        $this->settings[self::KEY_ALLOWED_HEADERS] = [];
427 18
        foreach ($headers as $header => $enabled) {
428 18
            $lcHeader                                             = strtolower($header);
429 18
            $this->settings[self::KEY_ALLOWED_HEADERS][$lcHeader] = $enabled;
430 18
        }
431
432 18
        return $this;
433
    }
434
435
    /**
436
     * @inheritdoc
437
     */
438 2
    public function getResponseExposedHeaders(RequestInterface $request)
439
    {
440 2
        return $this->getEnabledItems($this->settings[self::KEY_EXPOSED_HEADERS]);
441
    }
442
443
    /**
444
     * @inheritdoc
445
     */
446 18
    public function setResponseExposedHeaders(array $headers)
447
    {
448 18
        $this->settings[self::KEY_EXPOSED_HEADERS] = $headers;
449
450 18
        return $this;
451
    }
452
453
    /**
454
     * @inheritdoc
455
     */
456 11
    public function isCheckHost()
457
    {
458 11
        return $this->settings[self::KEY_IS_CHECK_HOST];
459
    }
460
461
    /**
462
     * @inheritdoc
463
     */
464 18
    public function setCheckHost($checkFlag)
465
    {
466 18
        $this->settings[self::KEY_IS_CHECK_HOST] = $checkFlag;
467
468 18
        return $this;
469
    }
470
471
    /**
472
     * Select only enabled items from $list.
473
     *
474
     * @param array $list
475
     *
476
     * @return array
477
     */
478 6
    protected function getEnabledItems(array $list)
479
    {
480 6
        $items = [];
481
482 6
        foreach ($list as $item => $enabled) {
483 6
            if ($enabled === true) {
484 6
                $items[] = $item;
485 6
            }
486 6
        }
487
488 6
        return $items;
489
    }
490
}
491