Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
SecurityHeadersMiddleware
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
2 / 2
6
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 process
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2
3declare(strict_types=1);
4
5namespace BO\Slim\Middleware;
6
7use BO\Slim\LoggerService;
8use Psr\Http\Message\ResponseInterface;
9use Psr\Http\Message\ServerRequestInterface;
10use Psr\Http\Server\MiddlewareInterface;
11use Psr\Http\Server\RequestHandlerInterface;
12
13class SecurityHeadersMiddleware implements MiddlewareInterface
14{
15    private const DEFAULT_SECURITY_HEADERS = [
16        'X-Frame-Options' => 'DENY',
17        'X-Content-Type-Options' => 'nosniff',
18        'X-XSS-Protection' => '1; mode=block',
19        'Strict-Transport-Security' => 'max-age=31536000; includeSubDomains',
20        'Content-Security-Policy' => "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self'",
21        'Referrer-Policy' => 'strict-origin-when-cross-origin',
22        'Permissions-Policy' => 'geolocation=(), microphone=(), camera=()',
23        'X-Permitted-Cross-Domain-Policies' => 'none',
24    ];
25
26    /** @var array<string, string> */
27    private array $securityHeaders;
28
29    private LoggerService $logger;
30
31    /** @var callable(\Throwable, ServerRequestInterface): ResponseInterface|null */
32    private $errorResponseBuilder;
33
34    /**
35     * @param array<string, string>|null $securityHeaders
36     * @param callable(\Throwable, ServerRequestInterface): ResponseInterface|null $errorResponseBuilder
37     */
38    public function __construct(
39        LoggerService $logger,
40        ?array $securityHeaders = null,
41        $errorResponseBuilder = null
42    ) {
43        $this->logger = $logger;
44        $this->securityHeaders = $securityHeaders ?? self::DEFAULT_SECURITY_HEADERS;
45        $this->errorResponseBuilder = $errorResponseBuilder;
46    }
47
48    #[\Override]
49    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
50    {
51        try {
52            $response = $handler->handle($request);
53            foreach ($this->securityHeaders as $header => $value) {
54                $response = $response->withHeader($header, $value);
55            }
56
57            return $response;
58        } catch (\Throwable $e) {
59            $this->logger->logError($e, $request);
60            if ($this->errorResponseBuilder !== null) {
61                $response = ($this->errorResponseBuilder)($e, $request);
62                if ($response !== null) {
63                    return $response;
64                }
65            }
66            throw $e;
67        }
68    }
69}