Test Failed
Push — develop ( 2def8e...aad200 )
by nguereza
02:54
created

CsrfMiddleware::unauthorizedResponse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 8
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 15
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 Lang $lang
104
     * @param Config $config
105
     * @param Session $session
106
     */
107
    public function __construct(
108
        LoggerInterface $logger,
109
        Lang $lang,
110
        Config $config,
111
        Session $session
112
    ) {
113
        $this->config = $config;
114
        $this->session = $session;
115
        $this->lang = $lang;
116
        $this->logger = $logger;
117
    }
118
119
    /**
120
     * {@inheritdoc}
121
     */
122
    public function process(
123
        ServerRequestInterface $request,
124
        RequestHandlerInterface $handler
125
    ): ResponseInterface {
126
        //If no route has been match no need check for CSRF
127
        /** @var ?Route $route */
128
        $route = $request->getAttribute(Route::class);
129
        if (!$route) {
130
            return $handler->handle($request);
131
        }
132
133
        $methods = $this->config->get('app.csrf.http_methods', []);
134
135
        if (!in_array($request->getMethod(), $methods)) {
136
            return $handler->handle($request);
137
        }
138
139
        //check if is url whitelist
140
        $urls = $this->config->get('app.csrf.url_whitelist', []);
141
        foreach ($urls as $url) {
142
            /*
143
             * Route: /users/login, url: /users/login
144
             * Route: /users/detail/{id}, url: /users/detail/
145
             */
146
            if (preg_match('~^' . $url . '~', $route->getPattern())) {
147
                return $handler->handle($request);
148
            }
149
        }
150
151
        $this->request = $request;
152
153
        if (!$this->isValid()) {
154
            return $this->unauthorizedResponse();
155
        }
156
157
        return $handler->handle($request);
158
    }
159
160
    /**
161
     * Check if the request if valid
162
     * @return bool
163
     */
164
    protected function isValid(): bool
165
    {
166
        $param = new RequestData($this->request);
167
        $key = $this->config->get('app.csrf.key', '');
168
169
        $sessionExpire = $this->session->get('csrf_data.expire');
170
        $sessionValue = $this->session->get('csrf_data.value');
171
172
        if (
173
            $sessionExpire === null
174
            || $sessionValue === null
175
            || $sessionExpire <= time()
176
        ) {
177
            $this->logger->warning('The CSRF session data is not valide');
178
179
            return false;
180
        }
181
182
183
        $token = $param->post($key, '');
184
        if ($token !== $sessionValue) {
185
            $this->logger->warning(sprintf(
186
                'The CSRF token [%s] is not valide may be attacker do his job',
187
                $token
188
            ));
189
190
            return false;
191
        }
192
193
        $this->session->remove('csrf_data');
194
195
        return true;
196
    }
197
198
    /**
199
     * Return the unauthorized responses
200
     * @return ResponseInterface
201
     */
202
    protected function unauthorizedResponse(): ResponseInterface
203
    {
204
        $this->logger->error(
205
            'Unauthorized Request, CSRF validation failed for {method}:{url}',
206
            [
207
                'method' => (string) $this->request->getMethod(),
208
                'url' => (string) $this->request->getUri(),
209
            ]
210
        );
211
        $response = new Response(401);
212
213
        $message = $this->lang->tr('Unauthorized Request');
214
        $response->getBody()->write($message);
215
216
        return $response;
217
    }
218
}
219