Issues (536)

src/Middlewares/VerifyCsrfToken.php (1 issue)

Labels
Severity
1
<?php
2
3
/**
4
 * This file is part of Blitz PHP framework.
5
 *
6
 * (c) 2022 Dimitri Sitchet Tomkeu <[email protected]>
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE file that was distributed with this source code.
10
 */
11
12
namespace BlitzPHP\Middlewares;
13
14
use BlitzPHP\Contracts\Http\ResponsableInterface;
15
use BlitzPHP\Contracts\Security\EncrypterInterface;
16
use BlitzPHP\Exceptions\EncryptionException;
17
use BlitzPHP\Exceptions\TokenMismatchException;
18
use BlitzPHP\Http\Request;
19
use BlitzPHP\Http\Response;
20
use BlitzPHP\Session\Cookie\Cookie;
21
use BlitzPHP\Session\Cookie\CookieValuePrefix;
22
use BlitzPHP\Traits\Support\InteractsWithTime;
23
use Psr\Http\Message\ResponseInterface;
24
use Psr\Http\Message\ServerRequestInterface;
25
use Psr\Http\Server\MiddlewareInterface;
26
use Psr\Http\Server\RequestHandlerInterface;
27
28
class VerifyCsrfToken implements MiddlewareInterface
29
{
30
    use InteractsWithTime;
31
32
    /**
33
     * Les URI qui doivent être exclus de la vérification CSRF.
34
     */
35
    protected array $except = [];
36
37
    /**
38
     * Indique si le cookie XSRF-TOKEN doit être défini dans la réponse.
39
     */
40
    protected bool $addHttpCookie = true;
41
42
    /**
43
     * Constructeur
44
     */
45
    public function __construct(protected EncrypterInterface $encrypter)
46
    {
47
    }
48
49
    /**
50
     * {@inheritDoc}
51
     *
52
     * @param Request $request
53
     */
54
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
55
    {
56
        if ($this->isReading($request) || $this->runningUnitTests() || $this->inExceptArray($request) || $this->tokensMatch($request)) {
57
            return tap($handler->handle($request), function ($response) use ($request) {
58
                if ($this->shouldAddXsrfTokenCookie()) {
59
                    $this->addCookieToResponse($request, $response);
60
                }
61
            });
62
        }
63
64
        throw new TokenMismatchException('Erreur de jeton CSRF.');
65
    }
66
67
    /**
68
     * Détermine si la requête HTTP utilise un verbe « read ».
69
     */
70
    protected function isReading(Request $request): bool
71
    {
72
        return in_array($request->method(), ['HEAD', 'GET', 'OPTIONS'], true);
73
    }
74
75
    /**
76
     * Détermine si l'application exécute des tests unitaires.
77
     */
78
    protected function runningUnitTests(): bool
79
    {
80
        return is_cli() && on_test();
81
    }
82
83
    /**
84
     * Détermine si la requête comporte un URI qui doit faire l'objet d'une vérification CSRF.
85
     */
86
    protected function inExceptArray(Request $request): bool
87
    {
88
        foreach ($this->except as $except) {
89
            if ($except !== '/') {
90
                $except = trim($except, '/');
91
            }
92
93
            if ($request->fullUrlIs($except) || $request->pathIs($except)) {
94
                return true;
95
            }
96
        }
97
98
        return false;
99
    }
100
101
    /**
102
     * Détermine si les jetons CSRF de session et d'entrée correspondent.
103
     */
104
    protected function tokensMatch(Request $request): bool
105
    {
106
        $token = $this->getTokenFromRequest($request);
107
108
        return is_string($request->session()->token())
109
               && is_string($token)
110
               && hash_equals($request->session()->token(), $token);
111
    }
112
113
    /**
114
     * Récupère le jeton CSRF de la requête.
115
     */
116
    protected function getTokenFromRequest(Request $request): ?string
117
    {
118
        $token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');
119
120
        if (! $token && $header = $request->header('X-XSRF-TOKEN')) {
121
            try {
122
                $token = CookieValuePrefix::remove($this->encrypter->decrypt($header));
123
            } catch (EncryptionException) {
124
                $token = '';
125
            }
126
        }
127
128
        return $token;
129
    }
130
131
    /**
132
     * Détermine si le cookie doit être ajouté à la réponse.
133
     */
134
    public function shouldAddXsrfTokenCookie(): bool
135
    {
136
        return $this->addHttpCookie;
137
    }
138
139
    /**
140
     * Ajoute le jeton CSRF aux cookies de la réponse.
141
     *
142
     * @param Response $response
143
     */
144
    protected function addCookieToResponse(Request $request, $response): ResponseInterface
145
    {
146
        if ($response instanceof ResponsableInterface) {
147
            $response = $response->toResponse($request);
148
        }
149
150
        if (! ($response instanceof Response)) {
151
            return $response;
152
        }
153
154
        $config = config('cookie');
155
156
        return $response->withCookie(Cookie::create('XSRF-TOKEN', $request->session()->token(), [
157
            'expires'  => $this->availableAt(config('session.expiration')),
0 ignored issues
show
It seems like config('session.expiration') can also be of type null; however, parameter $delay of BlitzPHP\Middlewares\Ver...srfToken::availableAt() does only seem to accept DateInterval|DateTimeInterface|integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

157
            'expires'  => $this->availableAt(/** @scrutinizer ignore-type */ config('session.expiration')),
Loading history...
158
            'path'     => $config['path'],
159
            'domain'   => $config['domain'],
160
            'secure'   => $config['secure'],
161
            'httponly' => false,
162
            'samesite' => $config['samesite'] ?? null,
163
        ]));
164
    }
165
166
    /**
167
     * Détermine si le contenu du cookie doit être sérialisé.
168
     */
169
    public static function serialized(): bool
170
    {
171
        return EncryptCookies::serialized('XSRF-TOKEN');
172
    }
173
}
174