Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
13 / 13 |
|
100.00% |
2 / 2 |
CRAP | |
100.00% |
1 / 1 |
| CsrfAuditMiddleware | |
100.00% |
13 / 13 |
|
100.00% |
2 / 2 |
8 | |
100.00% |
1 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| process | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
7 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace App\Shared\Security; |
| 6 | |
| 7 | use App\Shared\Logging\CentralLogger; |
| 8 | use Psr\Http\Message\ResponseInterface; |
| 9 | use Psr\Http\Message\ServerRequestInterface; |
| 10 | use Psr\Http\Server\MiddlewareInterface; |
| 11 | use Psr\Http\Server\RequestHandlerInterface; |
| 12 | use Yiisoft\Csrf\CsrfTokenInterface; |
| 13 | |
| 14 | class CsrfAuditMiddleware implements MiddlewareInterface |
| 15 | { |
| 16 | public function __construct(private CsrfTokenInterface $csrfToken) {} |
| 17 | |
| 18 | public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface |
| 19 | { |
| 20 | $response = $handler->handle($request); |
| 21 | |
| 22 | if (in_array($response->getStatusCode(), [422, 403], true) && in_array($request->getMethod(), ['POST', 'PUT', 'DELETE', 'PATCH'], true)) { |
| 23 | /** @var array<string, mixed> $parsedBody */ |
| 24 | $parsedBody = (array) ($request->getParsedBody() ?? []); |
| 25 | $csrfInBody = isset($parsedBody['_csrf']) && is_string($parsedBody['_csrf']) ? $parsedBody['_csrf'] : 'NONE'; |
| 26 | $providedToken = $request->getHeaderLine('X-CSRF-Token') ?: $csrfInBody; |
| 27 | |
| 28 | CentralLogger::logSecurity('CSRF_TOKEN_MISMATCH', [ |
| 29 | 'from_url' => $request->getHeaderLine('Referer') ?: 'DIRECT/UNKNOWN', |
| 30 | 'to_url' => (string) $request->getUri(), |
| 31 | 'provided_token' => $providedToken, |
| 32 | 'current_session_token' => $this->csrfToken->getValue(), |
| 33 | ]); |
| 34 | } |
| 35 | |
| 36 | return $response; |
| 37 | } |
| 38 | } |