Policy::addNonceForDirective()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Spatie\Csp\Policies;
4
5
use Illuminate\Http\Request;
6
use Illuminate\Support\Arr;
7
use Illuminate\Support\Str;
8
use ReflectionClass;
9
use Spatie\Csp\Directive;
10
use Spatie\Csp\Exceptions\InvalidDirective;
11
use Spatie\Csp\Exceptions\InvalidValueSet;
12
use Spatie\Csp\Keyword;
13
use Spatie\Csp\Value;
14
use Symfony\Component\HttpFoundation\Response;
15
16
abstract class Policy
17
{
18
    protected $directives = [];
19
20
    protected $reportOnly = false;
21
22
    abstract public function configure();
23
24
    /**
25
     * @param string $directive
26
     * @param string|array|bool $values
27
     *
28
     * @return \Spatie\Csp\Policies\Policy
29
     *
30
     * @throws \Spatie\Csp\Exceptions\InvalidDirective
31
     * @throws \Spatie\Csp\Exceptions\InvalidValueSet
32
     */
33
    public function addDirective(string $directive, $values): self
34
    {
35
        $this->guardAgainstInvalidDirectives($directive);
36
        $this->guardAgainstInvalidValues(Arr::wrap($values));
37
38
        if ($values === Value::NO_VALUE) {
39
            $this->directives[$directive][] = Value::NO_VALUE;
40
41
            return $this;
42
        }
43
44
        $values = array_filter(Arr::flatten(array_map(function ($value) {
0 ignored issues
show
Documentation introduced by
array_map(function ($val...ort\Arr::wrap($values)) is of type array, but the function expects a object<Illuminate\Support\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
45
            return explode(' ', $value);
46
        }, Arr::wrap($values))));
47
48
        if (in_array(Keyword::NONE, $values, true)) {
49
            $this->directives[$directive] = [$this->sanitizeValue(Keyword::NONE)];
50
51
            return $this;
52
        }
53
54
        $this->directives[$directive] = array_filter($this->directives[$directive] ?? [], function ($value) {
55
            return $value !== $this->sanitizeValue(Keyword::NONE);
56
        });
57
58
        foreach ($values as $value) {
59
            $sanitizedValue = $this->sanitizeValue($value);
60
61
            if (! in_array($sanitizedValue, $this->directives[$directive] ?? [])) {
62
                $this->directives[$directive][] = $sanitizedValue;
63
            }
64
        }
65
66
        return $this;
67
    }
68
69
    public function reportOnly(): self
70
    {
71
        $this->reportOnly = true;
72
73
        return $this;
74
    }
75
76
    public function enforce(): self
77
    {
78
        $this->reportOnly = false;
79
80
        return $this;
81
    }
82
83
    public function reportTo(string $uri): self
84
    {
85
        $this->directives['report-uri'] = [$uri];
86
87
        return $this;
88
    }
89
90
    public function shouldBeApplied(Request $request, Response $response): bool
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $response is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
91
    {
92
        return config('csp.enabled');
93
    }
94
95
    public function addNonceForDirective(string $directive): self
96
    {
97
        return $this->addDirective($directive, "'nonce-".app('csp-nonce')."'");
98
    }
99
100
    public function applyTo(Response $response)
101
    {
102
        $this->configure();
103
104
        $headerName = $this->reportOnly
105
            ? 'Content-Security-Policy-Report-Only'
106
            : 'Content-Security-Policy';
107
108
        if ($response->headers->has($headerName)) {
109
            return;
110
        }
111
112
        $response->headers->set($headerName, (string) $this);
113
    }
114
115
    public function __toString()
116
    {
117
        return collect($this->directives)
118
            ->map(function (array $values, string $directive) {
119
                $valueString = implode(' ', $values);
120
121
                return empty($valueString) ? "{$directive}" : "{$directive} {$valueString}";
122
            })
123
            ->implode(';');
124
    }
125
126
    protected function guardAgainstInvalidDirectives(string $directive)
127
    {
128
        if (! Directive::isValid($directive)) {
129
            throw InvalidDirective::notSupported($directive);
130
        }
131
    }
132
133
    protected function guardAgainstInvalidValues(array $values)
134
    {
135
        if (in_array(Keyword::NONE, $values, true) && count($values) > 1) {
136
            throw InvalidValueSet::noneMustBeOnlyValue();
137
        }
138
    }
139
140
    protected function isHash(string $value): bool
141
    {
142
        $acceptableHashingAlgorithms = [
143
            'sha256-',
144
            'sha384-',
145
            'sha512-',
146
        ];
147
148
        return Str::startsWith($value, $acceptableHashingAlgorithms);
149
    }
150
151
    protected function isKeyword(string $value): bool
152
    {
153
        $keywords = (new ReflectionClass(Keyword::class))->getConstants();
154
155
        return in_array($value, $keywords);
156
    }
157
158
    protected function sanitizeValue(string $value): string
159
    {
160
        if (
161
            $this->isKeyword($value)
162
            || $this->isHash($value)
163
        ) {
164
            return "'{$value}'";
165
        }
166
167
        return $value;
168
    }
169
}
170