Completed
Branch develop (a1a607)
by Neomerx
02:00
created

Settings::isRequestAllHeadersSupported()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
1
<?php declare(strict_types=1);
2
3
namespace Neomerx\Cors\Strategies;
4
5
/**
6
 * Copyright 2015-2019 [email protected]
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 * http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
21
use Neomerx\Cors\Contracts\AnalysisStrategyInterface;
22
use Neomerx\Cors\Contracts\Constants\SimpleResponseHeaders;
23
use Neomerx\Cors\Log\LoggerAwareTrait;
24
use Psr\Http\Message\RequestInterface;
25
26
/**
27
 * Implements strategy as a simple set of setting identical for all resources and requests.
28
 *
29
 * @package Neomerx\Cors
30
 *
31
 * @SuppressWarnings(PHPMD.TooManyFields)
32
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
33
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
34
 */
35
class Settings implements AnalysisStrategyInterface
36
{
37
    use LoggerAwareTrait;
38
39
    /**
40
     * @var string[]
41
     */
42
    const SIMPLE_LC_RESPONSE_HEADERS = [
43
        SimpleResponseHeaders::LC_ACCEPT_LANGUAGE,
44
        SimpleResponseHeaders::LC_CACHE_CONTROL,
45
        SimpleResponseHeaders::LC_CONTENT_LANGUAGE,
46
        SimpleResponseHeaders::LC_CONTENT_TYPE,
47
        SimpleResponseHeaders::LC_EXPIRES,
48
        SimpleResponseHeaders::LC_LAST_MODIFIED,
49
        SimpleResponseHeaders::LC_PRAGMA,
50
    ];
51
52
    /**
53
     * @var string
54
     */
55
    private $serverOriginScheme;
56
57
    /**
58
     * @var string
59
     */
60
    private $serverOriginHost;
61
62
    /**
63
     * @var null|int
64
     */
65
    private $serverOriginPort;
66
67
    /**
68
     * @var bool
69
     */
70
    private $isPreFlightCanBeCached;
71
72
    /**
73
     * @var int
74
     */
75
    private $preFlightCacheMaxAge;
76
77
    /**
78
     * @var bool
79
     */
80
    private $isForceAddMethods;
81
82
    /**
83
     * @var bool
84
     */
85
    private $isForceAddHeaders;
86
87
    /**
88
     * @var bool
89
     */
90
    private $isUseCredentials;
91
92
    /**
93
     * @var bool
94
     */
95
    private $areAllOriginsAllowed;
96
97
    /**
98
     * @var array
99
     */
100
    private $allowedOrigins;
101
102
    /**
103
     * @var bool
104
     */
105
    private $areAllMethodsAllowed;
106
107
    /**
108
     * @var array
109
     */
110
    private $allowedLcMethods;
111
112
    /**
113
     * @var string
114
     */
115
    private $allowedMethodsList;
116
117
    /**
118
     * @var bool
119
     */
120
    private $areAllHeadersAllowed;
121
122
    /**
123
     * @var array
124
     */
125
    private $allowedLcHeaders;
126
127
    /**
128
     * @var string
129
     */
130
    private $allowedHeadersList;
131
132
    /**
133
     * @var string
134
     */
135
    private $exposedHeadersList;
136
137
    /**
138
     * @var bool
139
     */
140
    private $isCheckHost;
141
142
    /**
143
     * Sort of default constructor, though made separate to be used optionally when no cached data available.
144
     *
145
     * @param string $scheme
146
     * @param string $host
147
     * @param int    $port
148
     *
149
     * @return self
0 ignored issues
show
Documentation introduced by
Should the return type not be \self?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
150
     */
151 16
    public function init(string $scheme, string $host, int $port): self
152
    {
153
        return $this
154 16
            ->setServerOrigin($scheme, $host, $port)
155 16
            ->setPreFlightCacheMaxAge(0)
156 16
            ->setCredentialsNotSupported()
157 16
            ->enableAllOriginsAllowed()
158 16
            ->setAllowedOrigins([])
159 16
            ->enableAllMethodsAllowed()
160 16
            ->setAllowedMethods([])
161 16
            ->enableAllHeadersAllowed()
162 16
            ->setAllowedHeaders([])
163 16
            ->setExposedHeaders([])
164 16
            ->disableAddAllowedMethodsToPreFlightResponse()
165 16
            ->disableAddAllowedHeadersToPreFlightResponse()
166 16
            ->disableCheckHost();
167
    }
168
169
    /**
170
     * Get internal data state. Can be used for data caching.
171
     *
172
     * @return array
173
     */
174 16
    public function getData(): array
175
    {
176
        return [
177 16
            $this->serverOriginScheme,
178 16
            $this->serverOriginHost,
179 16
            $this->serverOriginPort,
180 16
            $this->isPreFlightCanBeCached,
181 16
            $this->preFlightCacheMaxAge,
182 16
            $this->isForceAddMethods,
183 16
            $this->isForceAddHeaders,
184 16
            $this->isUseCredentials,
185 16
            $this->areAllOriginsAllowed,
186 16
            $this->allowedOrigins,
187 16
            $this->areAllMethodsAllowed,
188 16
            $this->allowedLcMethods,
189 16
            $this->allowedMethodsList,
190 16
            $this->areAllHeadersAllowed,
191 16
            $this->allowedLcHeaders,
192 16
            $this->allowedHeadersList,
193 16
            $this->exposedHeadersList,
194 16
            $this->isCheckHost,
195
        ];
196
    }
197
198
    /**
199
     * Set internal data state. Can be used for setting cached data.
200
     *
201
     * @param array $data
202
     *
203
     * @return self
204
     */
205 16
    public function setData(array $data): self
206
    {
207
        [
208 16
            $this->serverOriginScheme,
209 16
            $this->serverOriginHost,
210 16
            $this->serverOriginPort,
211 16
            $this->isPreFlightCanBeCached,
212 16
            $this->preFlightCacheMaxAge,
213 16
            $this->isForceAddMethods,
214 16
            $this->isForceAddHeaders,
215 16
            $this->isUseCredentials,
216 16
            $this->areAllOriginsAllowed,
217 16
            $this->allowedOrigins,
218 16
            $this->areAllMethodsAllowed,
219 16
            $this->allowedLcMethods,
220 16
            $this->allowedMethodsList,
221 16
            $this->areAllHeadersAllowed,
222 16
            $this->allowedLcHeaders,
223 16
            $this->allowedHeadersList,
224 16
            $this->exposedHeadersList,
225 16
            $this->isCheckHost,
226 16
        ] = $data;
227
228 16
        return $this;
229
    }
230
231
    /**
232
     * @inheritdoc
233
     */
234 2
    public function getServerOriginScheme(): string
235
    {
236 2
        return $this->serverOriginScheme;
237
    }
238
239
    /**
240
     * @inheritdoc
241
     */
242 14
    public function getServerOriginHost(): string
243
    {
244 14
        return $this->serverOriginHost;
245
    }
246
247
    /**
248
     * @inheritdoc
249
     */
250 15
    public function getServerOriginPort(): ?int
251
    {
252 15
        return $this->serverOriginPort;
253
    }
254
255
    /**
256
     * Set server Origin URL.
257
     *
258
     * @param string $scheme
259
     * @param string $host
260
     * @param int    $port
261
     *
262
     * @return self
263
     */
264 16
    public function setServerOrigin(string $scheme, string $host, int $port): self
265
    {
266 16
        assert(empty($scheme) === false);
267 16
        assert(empty($host) === false);
268 16
        assert(0 < $port && $port <= 0xFFFF);
269
270 16
        $this->serverOriginScheme = $scheme;
271 16
        $this->serverOriginHost   = $host;
272
273 16
        if (strcasecmp($scheme, 'http') === 0 && $port === 80) {
274 2
            $port = null;
275 16
        } elseif (strcasecmp($scheme, 'https') === 0 && $port === 443) {
276 1
            $port = null;
277
        }
278 16
        $this->serverOriginPort = $port;
279
280 16
        return $this;
281
    }
282
283
    /**
284
     * @inheritdoc
285
     */
286 2
    public function isPreFlightCanBeCached(RequestInterface $request): bool
287
    {
288 2
        return $this->isPreFlightCanBeCached;
289
    }
290
291
    /**
292
     * @inheritdoc
293
     */
294 2
    public function getPreFlightCacheMaxAge(RequestInterface $request): int
295
    {
296 2
        return $this->preFlightCacheMaxAge;
297
    }
298
299
    /**
300
     * Set pre-flight cache max period in seconds.
301
     *
302
     * @param int $cacheMaxAge
303
     *
304
     * @return self
305
     */
306 16
    public function setPreFlightCacheMaxAge(int $cacheMaxAge): self
307
    {
308 16
        assert($cacheMaxAge >= 0);
309
310 16
        $this->preFlightCacheMaxAge   = $cacheMaxAge;
311 16
        $this->isPreFlightCanBeCached = $cacheMaxAge > 0;
312
313 16
        return $this;
314
    }
315
316
    /**
317
     * @inheritdoc
318
     */
319 1
    public function isForceAddAllowedMethodsToPreFlightResponse(): bool
320
    {
321 1
        return $this->isForceAddMethods;
322
    }
323
324
    /**
325
     * If allowed headers should be added when request headers are 'simple' and
326
     * non of them is 'Content-Type' (see #6.2.10 CORS).
327
     *
328
     * @see http://www.w3.org/TR/cors/#resource-preflight-requests
329
     *
330
     * @return self
331
     */
332 1
    public function enableAddAllowedMethodsToPreFlightResponse(): self
333
    {
334 1
        $this->isForceAddMethods = true;
335
336 1
        return $this;
337
    }
338
339
    /**
340
     * If allowed headers should be added when request headers are 'simple' and
341
     * non of them is 'Content-Type' (see #6.2.10 CORS).
342
     *
343
     * @see http://www.w3.org/TR/cors/#resource-preflight-requests
344
     *
345
     * @return self
346
     */
347 16
    public function disableAddAllowedMethodsToPreFlightResponse(): self
348
    {
349 16
        $this->isForceAddMethods = false;
350
351 16
        return $this;
352
    }
353
354
    /**
355
     * @inheritdoc
356
     */
357 1
    public function isForceAddAllowedHeadersToPreFlightResponse(): bool
358
    {
359 1
        return $this->isForceAddHeaders;
360
    }
361
362
    /**
363
     * If allowed headers should be added when request headers are 'simple' and
364
     * non of them is 'Content-Type' (see #6.2.10 CORS).
365
     *
366
     * @see http://www.w3.org/TR/cors/#resource-preflight-requests
367
     *
368
     * @return self
369
     */
370 1
    public function enableAddAllowedHeadersToPreFlightResponse(): self
371
    {
372 1
        $this->isForceAddHeaders = true;
373
374 1
        return $this;
375
    }
376
377
    /**
378
     * If allowed headers should be added when request headers are 'simple' and
379
     * non of them is 'Content-Type' (see #6.2.10 CORS).
380
     *
381
     * @see http://www.w3.org/TR/cors/#resource-preflight-requests
382
     *
383
     * @return self
384
     */
385 16
    public function disableAddAllowedHeadersToPreFlightResponse(): self
386
    {
387 16
        $this->isForceAddHeaders = false;
388
389 16
        return $this;
390
    }
391
392
    /**
393
     * @inheritdoc
394
     */
395 4
    public function isRequestCredentialsSupported(RequestInterface $request): bool
396
    {
397 4
        return $this->isUseCredentials;
398
    }
399
400
    /**
401
     * If access with credentials is supported by the resource.
402
     *
403
     * @return self
404
     */
405 16
    public function setCredentialsSupported(): self
406
    {
407 16
        $this->isUseCredentials = true;
408
409 16
        return $this;
410
    }
411
412
    /**
413
     * If access with credentials is supported by the resource.
414
     *
415
     * @return self
416
     */
417 16
    public function setCredentialsNotSupported(): self
418
    {
419 16
        $this->isUseCredentials = false;
420
421 16
        return $this;
422
    }
423
424
    /**
425
     * @inheritdoc
426
     */
427 10
    public function isRequestOriginAllowed(string $requestOrigin): bool
428
    {
429
        return
430 10
            $this->areAllOriginsAllowed === true ||
431 10
            isset($this->allowedOrigins[strtolower($requestOrigin)]) === true;
432
    }
433
434
    /**
435
     * Enable all origins allowed.
436
     *
437
     * @return self
438
     */
439 16
    public function enableAllOriginsAllowed(): self
440
    {
441 16
        $this->areAllOriginsAllowed = true;
442
443 16
        return $this;
444
    }
445
446
    /**
447
     * Disable all origins allowed.
448
     *
449
     * @return self
450
     */
451 16
    public function disableAllOriginsAllowed(): self
452
    {
453 16
        $this->areAllOriginsAllowed = false;
454
455 16
        return $this;
456
    }
457
458
    /**
459
     * Set allowed origins.
460
     *
461
     * @param array $origins
462
     *
463
     * @return self
464
     */
465 16
    public function setAllowedOrigins(array $origins): self
466
    {
467 16
        $this->allowedOrigins = [];
468
469 16
        foreach ($origins as $origin) {
470 16
            $this->allowedOrigins[strtolower($origin)] = true;
471
        }
472
473 16
        return $this->disableAllOriginsAllowed();
474
    }
475
476
    /**
477
     * @inheritdoc
478
     */
479 4
    public function isRequestMethodSupported(string $method): bool
480
    {
481 4
        return $this->areAllMethodsAllowed === true || isset($this->allowedLcMethods[strtolower($method)]) === true;
482
    }
483
484
    /**
485
     * Enable all methods allowed.
486
     *
487
     * @return self
488
     */
489 16
    public function enableAllMethodsAllowed(): self
490
    {
491 16
        $this->areAllMethodsAllowed = true;
492
493 16
        return $this;
494
    }
495
496
    /**
497
     * Disable all methods allowed.
498
     *
499
     * @return self
500
     */
501 16
    public function disableAllMethodsAllowed(): self
502
    {
503 16
        $this->areAllMethodsAllowed = false;
504
505 16
        return $this;
506
    }
507
508
    /**
509
     * Set allowed methods.
510
     *
511
     * Security Note: you have to remember CORS is not access control system and you should not expect all
512
     * cross-origin requests will have pre-flights. For so-called 'simple' methods with so-called 'simple'
513
     * headers request will be made without pre-flight. Thus you can not restrict such requests with CORS
514
     * and should use other means.
515
     * For example method 'GET' without any headers or with only 'simple' headers will not have pre-flight
516
     * request so disabling it will not restrict access to resource(s).
517
     * You can read more on 'simple' methods at http://www.w3.org/TR/cors/#simple-method
518
     *
519
     * @param array $methods
520
     *
521
     * @return self
522
     */
523 16
    public function setAllowedMethods(array $methods): self
524
    {
525 16
        $this->allowedMethodsList = implode(', ', $methods);
526
527 16
        $this->allowedLcMethods = [];
528 16
        foreach ($methods as $method) {
529 16
            $this->allowedLcMethods[strtolower($method)] = true;
530
        }
531
532 16
        return $this->disableAllMethodsAllowed();
533
    }
534
535
    /**
536
     * @inheritdoc
537
     */
538 3
    public function isRequestAllHeadersSupported(array $lcHeaders): bool
539
    {
540 3
        return $this->areAllHeadersAllowed === true ||
541 3
            count(array_intersect($this->allowedLcHeaders, $lcHeaders)) === count($lcHeaders);
542
    }
543
544
    /**
545
     * Enable all headers allowed.
546
     *
547
     * @return self
548
     */
549 16
    public function enableAllHeadersAllowed(): self
550
    {
551 16
        $this->areAllHeadersAllowed = true;
552
553 16
        return $this;
554
    }
555
556
    /**
557
     * Disable all headers allowed.
558
     *
559
     * @return self
560
     */
561 16
    public function disableAllHeadersAllowed(): self
562
    {
563 16
        $this->areAllHeadersAllowed = false;
564
565 16
        return $this;
566
    }
567
568
    /**
569
     * Set allowed headers.
570
     *
571
     * Security Note: you have to remember CORS is not access control system and you should not expect all
572
     * cross-origin requests will have pre-flights. For so-called 'simple' methods with so-called 'simple'
573
     * headers request will be made without pre-flight. Thus you can not restrict such requests with CORS
574
     * and should use other means.
575
     * For example method 'GET' without any headers or with only 'simple' headers will not have pre-flight
576
     * request so disabling it will not restrict access to resource(s).
577
     * You can read more on 'simple' headers at http://www.w3.org/TR/cors/#simple-header
578
     *
579
     * @param array $headers
580
     *
581
     * @return self
582
     */
583 16
    public function setAllowedHeaders(array $headers): self
584
    {
585 16
        $this->allowedHeadersList = implode(', ', $headers);
586
587 16
        $this->allowedLcHeaders = [];
588 16
        foreach ($headers as $header) {
589 16
            $this->allowedLcHeaders[] = strtolower($header);
590
        }
591
592 16
        return $this->disableAllHeadersAllowed();
593
    }
594
595
    /**
596
     * @inheritdoc
597
     */
598 2
    public function getRequestAllowedMethods(RequestInterface $request): string
599
    {
600 2
        return $this->allowedMethodsList;
601
    }
602
603
    /**
604
     * @inheritdoc
605
     */
606 2
    public function getRequestAllowedHeaders(RequestInterface $request): string
607
    {
608 2
        return $this->allowedHeadersList;
609
    }
610
611
    /**
612
     * @inheritdoc
613
     */
614 2
    public function getResponseExposedHeaders(RequestInterface $request): string
615
    {
616 2
        return $this->exposedHeadersList;
617
    }
618
619
    /**
620
     * Set headers other than the simple ones that might be exposed to user agent.
621
     *
622
     * @param array $headers
623
     *
624
     * @return Settings
625
     */
626 16
    public function setExposedHeaders(array $headers): self
627
    {
628
        // Optional: from #7.1.1 'simple response headers' will be available in any case so it does not
629
        // make sense to include those headers to exposed.
630 16
        $filtered = [];
631 16
        foreach ($headers as $header) {
632 16
            if (in_array(strtolower($header), static::SIMPLE_LC_RESPONSE_HEADERS) === false) {
633 16
                $filtered[] = $header;
634
            }
635
        }
636
637 16
        $this->exposedHeadersList = implode(', ', $filtered);
638
639 16
        return $this;
640
    }
641
642
    /**
643
     * @inheritdoc
644
     */
645 14
    public function isCheckHost(): bool
646
    {
647 14
        return $this->isCheckHost;
648
    }
649
650
    /**
651
     * If request 'Host' header should be checked against server's origin.
652
     * Check of Host header is strongly encouraged by #6.3 CORS.
653
     * Header 'Host' must present for all requests rfc2616 14.23
654
     *
655
     * @return self
656
     */
657 16
    public function enableCheckHost(): self
658
    {
659 16
        $this->isCheckHost = true;
660
661 16
        return $this;
662
    }
663
664
    /**
665
     * If request 'Host' header should be checked against server's origin.
666
     * Check of Host header is strongly encouraged by #6.3 CORS.
667
     * Header 'Host' must present for all requests rfc2616 14.23
668
     *
669
     * @return self
670
     */
671 16
    public function disableCheckHost(): self
672
    {
673 16
        $this->isCheckHost = false;
674
675 16
        return $this;
676
    }
677
}
678