Passed
Push — develop ( 36c2b4...1d782e )
by nguereza
03:02
created

CsrfMiddleware::process()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 6
c 1
b 0
f 0
nc 3
nop 2
dl 0
loc 16
rs 10
1
<?php
2
3
/**
4
 * Platine Framework
5
 *
6
 * Platine Framework is a lightweight, high-performance, simple and elegant PHP
7
 * Web framework
8
 *
9
 * This content is released under the MIT License (MIT)
10
 *
11
 * Copyright (c) 2020 Platine Framework
12
 *
13
 * Permission is hereby granted, free of charge, to any person obtaining a copy
14
 * of this software and associated documentation files (the "Software"), to deal
15
 * in the Software without restriction, including without limitation the rights
16
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
 * copies of the Software, and to permit persons to whom the Software is
18
 * furnished to do so, subject to the following conditions:
19
 *
20
 * The above copyright notice and this permission notice shall be included in all
21
 * copies or substantial portions of the Software.
22
 *
23
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
 * SOFTWARE.
30
 */
31
32
/**
33
 *  @file CsrfMiddleware.php
34
 *
35
 *  The CSRF middleware class is used to check validation of each data send using
36
 *  HTTP method not safe like PUT, POST
37
 *
38
 *  @package    Platine\Framework\Http\Middleware
39
 *  @author Platine Developers Team
40
 *  @copyright  Copyright (c) 2020
41
 *  @license    http://opensource.org/licenses/MIT  MIT License
42
 *  @link   http://www.iacademy.cf
43
 *  @version 1.0.0
44
 *  @filesource
45
 */
46
47
declare(strict_types=1);
48
49
namespace Platine\Framework\Http\Middleware;
50
51
use Platine\Config\Config;
52
use Platine\Framework\Http\RequestData;
53
use Platine\Http\Handler\MiddlewareInterface;
54
use Platine\Http\Handler\RequestHandlerInterface;
55
use Platine\Http\Response;
56
use Platine\Http\ResponseInterface;
57
use Platine\Http\ServerRequestInterface;
58
use Platine\Lang\Lang;
59
use Platine\Logger\LoggerInterface;
60
use Platine\Route\Route;
61
use Platine\Session\Session;
62
63
/**
64
 * @class CsrfMiddleware
65
 * @package Platine\Framework\Http\Middleware
66
 * @template T
67
 */
68
class CsrfMiddleware implements MiddlewareInterface
69
{
70
71
    /**
72
     * The configuration instance
73
     * @var Config<T>
74
     */
75
    protected Config $config;
76
77
    /**
78
     * The session instance
79
     * @var Session
80
     */
81
    protected Session $session;
82
83
    /**
84
     * The translator instance
85
     * @var Lang
86
     */
87
    protected Lang $lang;
88
89
    /**
90
     * The request instance to use
91
     * @var ServerRequestInterface
92
     */
93
    protected ServerRequestInterface $request;
94
95
    /**
96
     * The logger instance
97
     * @var LoggerInterface
98
     */
99
    protected LoggerInterface $logger;
100
101
    /**
102
     * Create new instance
103
     * @param LoggerInterface $logger
104
     * @param Lang $lang
105
     * @param Config<T> $config
106
     * @param Session $session
107
     */
108
    public function __construct(
109
        LoggerInterface $logger,
110
        Lang $lang,
111
        Config $config,
112
        Session $session
113
    ) {
114
        $this->config = $config;
115
        $this->session = $session;
116
        $this->lang = $lang;
117
        $this->logger = $logger;
118
    }
119
120
    /**
121
     * {@inheritdoc}
122
     */
123
    public function process(
124
        ServerRequestInterface $request,
125
        RequestHandlerInterface $handler
126
    ): ResponseInterface {
127
128
        if (!$this->shouldBeProcessed($request)) {
129
            return $handler->handle($request);
130
        }
131
132
        $this->request = $request;
133
134
        if (!$this->isValid()) {
135
            return $this->unauthorizedResponse();
136
        }
137
138
        return $handler->handle($request);
139
    }
140
141
    /**
142
     * Check if the request if valid
143
     * @return bool
144
     */
145
    protected function isValid(): bool
146
    {
147
        $param = new RequestData($this->request);
148
        $key = $this->config->get('security.csrf.key', '');
149
150
        $sessionExpire = $this->session->get('csrf_data.expire');
151
        $sessionValue = $this->session->get('csrf_data.value');
152
153
        if (
154
            $sessionExpire === null
155
            || $sessionValue === null
156
            || $sessionExpire <= time()
157
        ) {
158
            $this->logger->warning('The CSRF session data is not valide');
159
160
            return false;
161
        }
162
163
164
        $token = $param->post($key, '');
165
        if ($token !== $sessionValue) {
166
            $this->logger->warning(sprintf(
167
                'The CSRF token [%s] is not valide may be attacker do his job',
168
                $token
169
            ));
170
171
            return false;
172
        }
173
174
        $this->session->remove('csrf_data');
175
176
        return true;
177
    }
178
179
    /**
180
     * Whether we can process this request
181
     * @param ServerRequestInterface $request
182
     * @return bool
183
     */
184
    protected function shouldBeProcessed(ServerRequestInterface $request): bool
185
    {
186
       //If no route has been match no need check for CSRF
187
        /** @var ?Route $route */
188
        $route = $request->getAttribute(Route::class);
189
        if (!$route) {
190
            return false;
191
        }
192
193
        $methods = $this->config->get('security.csrf.http_methods', []);
194
195
        if (!in_array($request->getMethod(), $methods)) {
196
            return false;
197
        }
198
199
        //check if is url whitelist
200
        $urls = $this->config->get('security.csrf.url_whitelist', []);
201
        foreach ($urls as $url) {
202
            /*
203
             * Route: /users/login, url: /users/login
204
             * Route: /users/detail/{id}, url: /users/detail/
205
             */
206
            if (preg_match('~^' . $url . '~', $route->getPattern())) {
207
                return false;
208
            }
209
        }
210
211
        return true;
212
    }
213
214
    /**
215
     * Return the unauthorized response
216
     * @return ResponseInterface
217
     */
218
    protected function unauthorizedResponse(): ResponseInterface
219
    {
220
        $this->logger->error(
221
            'Unauthorized Request, CSRF validation failed for {method}:{url}',
222
            [
223
                'method' => $this->request->getMethod(),
224
                'url' => (string) $this->request->getUri(),
225
            ]
226
        );
227
        $response = new Response(401);
228
229
        $message = $this->lang->tr('Unauthorized Request');
230
        $response->getBody()->write($message);
231
232
        return $response;
233
    }
234
}
235