Completed
Push — master ( dd3676...77be64 )
by Neomerx
02:27
created

Settings::isRequestCredentialsSupported()   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 \Neomerx\Cors\Log\LoggerAwareTrait;
20
use \Psr\Http\Message\RequestInterface;
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_SCHEME = 'scheme';
49
50
    /** Settings key */
51
    const KEY_SERVER_ORIGIN_HOST = 'host';
52
53
    /** Settings key */
54
    const KEY_SERVER_ORIGIN_PORT = 'port';
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
    /**
89
     * @param array $settings
1 ignored issue
show
Documentation introduced by
Should the type for parameter $settings not be null|array? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
90
     */
91 19
    public function __construct(array $settings = null)
92
    {
93 19
        $this->setSettings($settings !== null ? $settings : $this->getDefaultSettings());
94 19
    }
95
96
    /**
97
     * @inheritdoc
98
     */
99 8
    public function getSettings()
100
    {
101 8
        return $this->settings;
102
    }
103
104
    /**
105
     * @inheritdoc
106
     */
107 19
    public function setSettings(array $settings)
108
    {
109 19
        $this->settings = $settings;
110 19
    }
111
112
    /**
113
     * @inheritdoc
114
     */
115 12
    public function getServerOrigin()
116
    {
117 12
        return $this->settings[self::KEY_SERVER_ORIGIN];
118
    }
119
120
    /**
121
     * @inheritdoc
122
     */
123 19
    public function setServerOrigin($origin)
124
    {
125 19
        $this->settings[self::KEY_SERVER_ORIGIN] = is_string($origin) === true ? parse_url($origin) : $origin;
126
127 19
        return $this;
128
    }
129
130
    /**
131
     * @inheritdoc
132
     */
133 3
    public function isPreFlightCanBeCached(RequestInterface $request)
134
    {
135 3
        return $this->getPreFlightCacheMaxAge($request) > 0;
136
    }
137
138
    /**
139
     * @inheritdoc
140
     */
141 3
    public function getPreFlightCacheMaxAge(RequestInterface $request)
142
    {
143 3
        return $this->settings[self::KEY_FLIGHT_CACHE_MAX_AGE];
144
    }
145
146
    /**
147
     * @inheritdoc
148
     */
149 10
    public function setPreFlightCacheMaxAge($cacheMaxAge)
150
    {
151 10
        $this->settings[self::KEY_FLIGHT_CACHE_MAX_AGE] = $cacheMaxAge;
152
153 10
        return $this;
154
    }
155
156
    /**
157
     * @inheritdoc
158
     */
159 2
    public function isForceAddAllowedMethodsToPreFlightResponse()
160
    {
161 2
        return $this->settings[self::KEY_IS_FORCE_ADD_METHODS];
162
    }
163
164
    /**
165
     * @inheritdoc
166
     */
167 10
    public function setForceAddAllowedMethodsToPreFlightResponse($forceFlag)
168
    {
169 10
        $this->settings[self::KEY_IS_FORCE_ADD_METHODS] = $forceFlag;
170
171 10
        return $this;
172
    }
173
174
    /**
175
     * @inheritdoc
176
     */
177 1
    public function isForceAddAllowedHeadersToPreFlightResponse()
178
    {
179 1
        return $this->settings[self::KEY_IS_FORCE_ADD_HEADERS];
180
    }
181
182
    /**
183
     * @inheritdoc
184
     */
185 10
    public function setForceAddAllowedHeadersToPreFlightResponse($forceFlag)
186
    {
187 10
        $this->settings[self::KEY_IS_FORCE_ADD_HEADERS] = $forceFlag;
188
189 10
        return $this;
190
    }
191
192
    /**
193
     * @inheritdoc
194
     */
195 4
    public function isRequestCredentialsSupported(RequestInterface $request)
196
    {
197 4
        return $this->settings[self::KEY_IS_USING_CREDENTIALS];
198
    }
199
200
    /**
201
     * @inheritdoc
202
     */
203 19
    public function setRequestCredentialsSupported($isSupported)
204
    {
205 19
        $this->settings[self::KEY_IS_USING_CREDENTIALS] = $isSupported;
206
207 19
        return $this;
208
    }
209
210
    /**
211
     * @inheritdoc
212
     */
213 8
    public function isRequestOriginAllowed(ParsedUrlInterface $requestOrigin)
214
    {
215
        // check if all origins are allowed with '*'
216
        $isAllowed =
217 8
            isset($this->settings[self::KEY_ALLOWED_ORIGINS][CorsResponseHeaders::VALUE_ALLOW_ORIGIN_ALL]);
218
219 8
        if ($isAllowed === false) {
220 8
            $requestOriginStr = strtolower($requestOrigin->getOrigin());
221 8
            $isAllowed        = isset($this->settings[self::KEY_ALLOWED_ORIGINS][$requestOriginStr]);
222 8
        }
223
224 8
        return $isAllowed;
225
    }
226
227
    /**
228
     * @inheritdoc
229
     */
230 19
    public function setRequestAllowedOrigins(array $origins)
231
    {
232 19
        $this->settings[self::KEY_ALLOWED_ORIGINS] = [];
233 19
        foreach ($origins as $origin => $enabled) {
234 19
            $lcOrigin                                             = strtolower($origin);
235 19
            $this->settings[self::KEY_ALLOWED_ORIGINS][$lcOrigin] = $enabled;
236 19
        }
237
238 19
        return $this;
239
    }
240
241
    /**
242
     * @inheritdoc
243
     */
244 5
    public function isRequestMethodSupported($method)
245
    {
246 5
        $isAllowed = isset($this->settings[self::KEY_ALLOWED_METHODS][$method]);
247
248 5
        return $isAllowed;
249
    }
250
251
    /**
252
     * @inheritdoc
253
     */
254 5
    public function isRequestAllHeadersSupported($headers)
255
    {
256 5
        $allSupported = true;
257
258 5
        if (isset($this->settings[self::KEY_ALLOWED_HEADERS][self::VALUE_ALLOW_ALL_HEADERS]) === true) {
259 1
            return $allSupported;
260
        }
261
262 4
        foreach ($headers as $header) {
263 4
            $lcHeader = strtolower($header);
264 4
            if (isset($this->settings[self::KEY_ALLOWED_HEADERS][$lcHeader]) === false) {
265 2
                $allSupported = false;
266 2
                $this->logInfo(
267 2
                    'Request header is not allowed. Check config settings for Allowed Headers.',
268 2
                    ['header' => $header]
269 2
                );
270 2
                break;
271
            }
272 4
        }
273
274 4
        return $allSupported;
275
    }
276
277
    /**
278
     * @inheritdoc
279
     */
280 3
    public function getRequestAllowedMethods(RequestInterface $request, $requestMethod)
281
    {
282 3
        return implode(', ', $this->getEnabledItems($this->settings[self::KEY_ALLOWED_METHODS]));
283
    }
284
285
    /**
286
     * @inheritdoc
287
     */
288 19
    public function setRequestAllowedMethods(array $methods)
289
    {
290 19
        $this->settings[self::KEY_ALLOWED_METHODS] = [];
291 19
        foreach ($methods as $method => $enabled) {
292 19
            $this->settings[self::KEY_ALLOWED_METHODS][$method] = $enabled;
293 19
        }
294
295 19
        return $this;
296
    }
297
298
    /**
299
     * @inheritdoc
300
     */
301 3
    public function getRequestAllowedHeaders(RequestInterface $request, array $requestHeaders)
302
    {
303 3
        return implode(', ', $this->getEnabledItems($this->settings[self::KEY_ALLOWED_HEADERS]));
304
    }
305
306
    /**
307
     * @inheritdoc
308
     */
309 19
    public function setRequestAllowedHeaders(array $headers)
310
    {
311 19
        $this->settings[self::KEY_ALLOWED_HEADERS] = [];
312 19
        foreach ($headers as $header => $enabled) {
313 19
            $lcHeader                                             = strtolower($header);
314 19
            $this->settings[self::KEY_ALLOWED_HEADERS][$lcHeader] = $enabled;
315 19
        }
316
317 19
        return $this;
318
    }
319
320
    /**
321
     * @inheritdoc
322
     */
323 2
    public function getResponseExposedHeaders(RequestInterface $request)
324
    {
325 2
        return $this->getEnabledItems($this->settings[self::KEY_EXPOSED_HEADERS]);
326
    }
327
328
    /**
329
     * @inheritdoc
330
     */
331 19
    public function setResponseExposedHeaders(array $headers)
332
    {
333 19
        $this->settings[self::KEY_EXPOSED_HEADERS] = $headers;
334
335 19
        return $this;
336
    }
337
338
    /**
339
     * @inheritdoc
340
     */
341 11
    public function isCheckHost()
342
    {
343 11
        return $this->settings[self::KEY_IS_CHECK_HOST];
344
    }
345
346
    /**
347
     * @inheritdoc
348
     */
349 19
    public function setCheckHost($checkFlag)
350
    {
351 19
        $this->settings[self::KEY_IS_CHECK_HOST] = $checkFlag;
352
353 19
        return $this;
354
    }
355
356
    /**
357
     * Select only enabled items from $list.
358
     *
359
     * @param array $list
360
     *
361
     * @return array
362
     */
363 6
    protected function getEnabledItems(array $list)
364
    {
365 6
        $items = [];
366
367 6
        foreach ($list as $item => $enabled) {
368 6
            if ($enabled === true) {
369 6
                $items[] = $item;
370 6
            }
371 6
        }
372
373 6
        return $items;
374
    }
375
376
    /**
377
     * @return array
1 ignored issue
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<array|false|integer>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
378
     */
379 19
    protected function getDefaultSettings()
380
    {
381
        return [
382
            /**
383
             * Array should be in parse_url() result format.
384
             * @see http://php.net/manual/function.parse-url.php
385
             */
386 19
            self::KEY_SERVER_ORIGIN        => [
387 19
                self::KEY_SERVER_ORIGIN_SCHEME => '',
388 19
                self::KEY_SERVER_ORIGIN_HOST   => '',
389 19
                self::KEY_SERVER_ORIGIN_PORT   => ParsedUrlInterface::DEFAULT_PORT,
390 19
            ],
391
            /**
392
             * A list of allowed request origins (lower-cased, no trail slashes).
393
             * Value `true` enables and value `null` disables origin.
394
             * If all origins '*' are enabled all settings for other origins are ignored.
395
             * For example, [
396
             *     'http://example.com:123'     => true,
397
             *     'http://evil.com'            => null,
398
             *     self::VALUE_ALLOW_ORIGIN_ALL => null,
399
             * ];
400
             */
401 19
            self::KEY_ALLOWED_ORIGINS      => [],
402
            /**
403
             * A list of allowed request methods (case sensitive).
404
             * Value `true` enables and value `null` disables method.
405
             * For example, [
406
             *     'GET'    => true,
407
             *     'PATCH'  => true,
408
             *     'POST'   => true,
409
             *     'PUT'    => null,
410
             *     'DELETE' => true,
411
             * ];
412
             * Security Note: you have to remember CORS is not access control system and you should not expect all
413
             * cross-origin requests will have pre-flights. For so-called 'simple' methods with so-called 'simple'
414
             * headers request will be made without pre-flight. Thus you can not restrict such requests with CORS
415
             * and should use other means.
416
             * For example method 'GET' without any headers or with only 'simple' headers will not have pre-flight
417
             * request so disabling it will not restrict access to resource(s).
418
             * You can read more on 'simple' methods at http://www.w3.org/TR/cors/#simple-method
419
             */
420 19
            self::KEY_ALLOWED_METHODS      => [],
421
            /**
422
             * A list of allowed request headers (lower-cased). Value `true` enables and
423
             * value `null` disables header.
424
             * For example, [
425
             *     'content-type'                => true,
426
             *     'x-custom-request-header'     => null,
427
             *     self::VALUE_ALLOW_ALL_HEADERS => null,
428
             * ];
429
             * Security Note: you have to remember CORS is not access control system and you should not expect all
430
             * cross-origin requests will have pre-flights. For so-called 'simple' methods with so-called 'simple'
431
             * headers request will be made without pre-flight. Thus you can not restrict such requests with CORS
432
             * and should use other means.
433
             * For example method 'GET' without any headers or with only 'simple' headers will not have pre-flight
434
             * request so disabling it will not restrict access to resource(s).
435
             * You can read more on 'simple' headers at http://www.w3.org/TR/cors/#simple-header
436
             */
437 19
            self::KEY_ALLOWED_HEADERS      => [],
438
            /**
439
             * A list of headers (case insensitive) which will be made accessible to
440
             * user agent (browser) in response.
441
             * Value `true` enables and value `null` disables header.
442
             * For example, [
443
             *     'Content-Type'             => true,
444
             *     'X-Custom-Response-Header' => true,
445
             *     'X-Disabled-Header'        => null,
446
             * ];
447
             */
448 19
            self::KEY_EXPOSED_HEADERS      => [],
449
            /**
450
             * If access with credentials is supported by the resource.
451
             */
452 19
            self::KEY_IS_USING_CREDENTIALS => false,
453
            /**
454
             * Pre-flight response cache max period in seconds.
455
             */
456 19
            self::KEY_FLIGHT_CACHE_MAX_AGE => 0,
457
            /**
458
             * If allowed methods should be added to pre-flight response when
459
             * 'simple' method is requested (see #6.2.9 CORS).
460
             * @see http://www.w3.org/TR/cors/#resource-preflight-requests
461
             */
462 19
            self::KEY_IS_FORCE_ADD_METHODS => false,
463
            /**
464
             * If allowed headers should be added when request headers are 'simple' and
465
             * non of them is 'Content-Type' (see #6.2.10 CORS).
466
             * @see http://www.w3.org/TR/cors/#resource-preflight-requests
467
             */
468 19
            self::KEY_IS_FORCE_ADD_HEADERS => false,
469
            /**
470
             * If request 'Host' header should be checked against server's origin.
471
             */
472 19
            self::KEY_IS_CHECK_HOST        => false,
473 19
        ];
474
    }
475
}
476