Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
CorsMiddleware
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 3
56
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 process
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
30
 isOriginAllowed
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace BO\Zmscitizenapi\Middleware;
6
7use BO\Zmscitizenapi\Localization\ErrorMessages;
8use BO\Zmscitizenapi\Services\Core\LoggerService;
9use Psr\Http\Message\ResponseInterface;
10use Psr\Http\Message\ServerRequestInterface;
11use Psr\Http\Server\MiddlewareInterface;
12use Psr\Http\Server\RequestHandlerInterface;
13
14class CorsMiddleware implements MiddlewareInterface
15{
16    private const ERROR_CORS = 'corsOriginNotAllowed';
17    private array $whitelist = [];
18    private LoggerService $logger;
19    public function __construct(LoggerService $logger)
20    {
21        $this->logger = $logger;
22        $this->whitelist = \App::getCorsAllowedOrigins();
23    }
24
25    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
26    {
27        try {
28            $origin = $request->getHeaderLine('Origin');
29// Allow requests without Origin header (direct browser access)
30            if (empty($origin)) {
31/*$this->logger->logInfo('Direct browser request - no Origin header', [
32                    'uri' => (string)$request->getUri(),
33                    'headers' => $request->getHeaders()
34                ]);*/
35                return $handler->handle($request);
36            }
37
38            if (!$this->isOriginAllowed($origin)) {
39                $this->logger->logInfo(sprintf('CORS blocked - Origin %s not allowed. URI: %s', $origin, $request->getUri()));
40                $response = \App::$slim->getResponseFactory()->createResponse();
41                $language = $request->getAttribute('language');
42                $response = $response->withStatus(ErrorMessages::get(self::ERROR_CORS, $language)['statusCode'])
43                    ->withHeader('Content-Type', 'application/json');
44                $response->getBody()->write(json_encode([
45                    'errors' => [ErrorMessages::get(self::ERROR_CORS, $language)]
46                ]));
47                return $response;
48            }
49
50            // Handle preflight OPTIONS requests
51            if ($request->getMethod() === 'OPTIONS') {
52                $response = \App::$slim->getResponseFactory()->createResponse(200);
53                return $response
54                    ->withHeader('Access-Control-Allow-Origin', $origin)
55                    ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
56                    ->withHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-CSRF-Token')
57                    ->withHeader('Access-Control-Allow-Credentials', 'true')
58                    ->withHeader('Access-Control-Max-Age', '86400');
59            }
60
61            $response = $handler->handle($request);
62            return $response
63                ->withHeader('Access-Control-Allow-Origin', $origin)
64                ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
65                ->withHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-CSRF-Token')
66                ->withHeader('Access-Control-Allow-Credentials', 'true')
67                ->withHeader('Access-Control-Max-Age', '86400');
68        } catch (\Throwable $e) {
69            $this->logger->logError($e, $request);
70            throw $e;
71        }
72    }
73
74    private function isOriginAllowed(string $origin): bool
75    {
76        return in_array($origin, $this->whitelist, true);
77    }
78}