Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 52 |
|
0.00% |
0 / 7 |
CRAP | |
0.00% |
0 / 1 |
CsrfMiddleware | |
0.00% |
0 / 52 |
|
0.00% |
0 / 7 |
272 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
process | |
0.00% |
0 / 32 |
|
0.00% |
0 / 1 |
30 | |||
validateToken | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
20 | |||
ensureTokenExists | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
generateNewToken | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
getStoredToken | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getToken | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | declare(strict_types=1); |
4 | |
5 | namespace BO\Zmscitizenapi\Middleware; |
6 | |
7 | use BO\Zmscitizenapi\Localization\ErrorMessages; |
8 | use BO\Zmscitizenapi\Services\Core\LoggerService; |
9 | use Psr\Http\Message\ResponseInterface; |
10 | use Psr\Http\Message\ServerRequestInterface; |
11 | use Psr\Http\Server\MiddlewareInterface; |
12 | use Psr\Http\Server\RequestHandlerInterface; |
13 | |
14 | class CsrfMiddleware implements MiddlewareInterface |
15 | { |
16 | private const ERROR_TOKEN_MISSING = 'csrfTokenMissing'; |
17 | private const ERROR_TOKEN_INVALID = 'csrfTokenInvalid'; |
18 | private const SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS', 'POST', 'PUT', 'DELETE']; |
19 | //Remove POST DELETE and PUT when in use |
20 | |
21 | private int $tokenLength; |
22 | private string $sessionKey; |
23 | private LoggerService $logger; |
24 | public function __construct(LoggerService $logger) |
25 | { |
26 | $this->logger = $logger; |
27 | $config = \App::getCsrfConfig(); |
28 | $this->tokenLength = $config['tokenLength']; |
29 | $this->sessionKey = $config['sessionKey']; |
30 | if (session_status() === PHP_SESSION_NONE) { |
31 | session_start(); |
32 | } |
33 | } |
34 | |
35 | public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface |
36 | { |
37 | try { |
38 | if (in_array($request->getMethod(), self::SAFE_METHODS, true)) { |
39 | $this->ensureTokenExists(); |
40 | return $handler->handle($request); |
41 | } |
42 | |
43 | $token = $request->getHeaderLine('X-CSRF-Token'); |
44 | if (empty($token)) { |
45 | $this->logger->logInfo('CSRF token missing', [ |
46 | 'uri' => (string)$request->getUri() |
47 | ]); |
48 | $response = \App::$slim->getResponseFactory()->createResponse(); |
49 | $language = $request->getAttribute('language'); |
50 | $response = $response->withStatus(ErrorMessages::get(self::ERROR_TOKEN_MISSING, $language)['statusCode']) |
51 | ->withHeader('Content-Type', 'application/json'); |
52 | $response->getBody()->write(json_encode([ |
53 | 'errors' => [ErrorMessages::get(self::ERROR_TOKEN_MISSING, $language)] |
54 | ])); |
55 | return $response; |
56 | } |
57 | |
58 | if (!$this->validateToken($token)) { |
59 | $this->logger->logInfo('Invalid CSRF token', [ |
60 | 'uri' => (string)$request->getUri() |
61 | ]); |
62 | $response = \App::$slim->getResponseFactory()->createResponse(); |
63 | $language = $request->getAttribute('language'); |
64 | $response = $response->withStatus(ErrorMessages::get(self::ERROR_TOKEN_INVALID, $language)['statusCode']) |
65 | ->withHeader('Content-Type', 'application/json'); |
66 | $response->getBody()->write(json_encode([ |
67 | 'errors' => [ErrorMessages::get(self::ERROR_TOKEN_INVALID, $language)] |
68 | ])); |
69 | return $response; |
70 | } |
71 | |
72 | return $handler->handle($request); |
73 | } catch (\Throwable $e) { |
74 | $this->logger->logError($e, $request); |
75 | throw $e; |
76 | } |
77 | } |
78 | |
79 | private function validateToken(string $token): bool |
80 | { |
81 | if (strlen($token) !== $this->tokenLength || !ctype_xdigit($token)) { |
82 | return false; |
83 | } |
84 | |
85 | $storedToken = $this->getStoredToken(); |
86 | if (empty($storedToken)) { |
87 | return false; |
88 | } |
89 | |
90 | return hash_equals($storedToken, $token); |
91 | } |
92 | |
93 | private function ensureTokenExists(): void |
94 | { |
95 | if (empty($this->getStoredToken())) { |
96 | $this->generateNewToken(); |
97 | } |
98 | } |
99 | |
100 | private function generateNewToken(): string |
101 | { |
102 | $token = bin2hex(random_bytes($this->tokenLength / 2)); |
103 | $_SESSION[$this->sessionKey] = $token; |
104 | return $token; |
105 | } |
106 | |
107 | private function getStoredToken(): string |
108 | { |
109 | return $_SESSION[$this->sessionKey] ?? ''; |
110 | } |
111 | |
112 | public function getToken(): string |
113 | { |
114 | $this->ensureTokenExists(); |
115 | return $this->getStoredToken(); |
116 | } |
117 | } |