From dfa2307b3c9b6dbae0fa9f056c3a4414857a947a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20B=C3=B6sing?= <2189546+boesing@users.noreply.github.com> Date: Fri, 8 Dec 2023 18:16:30 +0100 Subject: [PATCH 1/3] qa: add failing test for `CorsMiddleware` running into `InvalidOriginValueException` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maximilian Bösing <2189546+boesing@users.noreply.github.com> --- test/Middleware/CorsMiddlewareTest.php | 33 ++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/Middleware/CorsMiddlewareTest.php b/test/Middleware/CorsMiddlewareTest.php index 57113ac..a7926ba 100644 --- a/test/Middleware/CorsMiddlewareTest.php +++ b/test/Middleware/CorsMiddlewareTest.php @@ -5,8 +5,10 @@ namespace Mezzio\CorsTest\Middleware; use Fig\Http\Message\RequestMethodInterface; +use InvalidArgumentException; use Mezzio\Cors\Configuration\ConfigurationInterface; use Mezzio\Cors\Configuration\RouteConfigurationInterface; +use Mezzio\Cors\Exception\InvalidOriginValueException; use Mezzio\Cors\Middleware\CorsMiddleware; use Mezzio\Cors\Middleware\Exception\InvalidConfigurationException; use Mezzio\Cors\Service\ConfigurationLocatorInterface; @@ -486,4 +488,35 @@ public function testWillDelegateUnknownRouteForRequestToRequestHandler(): void $this->middleware->process($request, $handler); } + + public function testWillHandleRequestsWithInvalidOriginAsUnauthorized(): void + { + $request = $this->createMock(ServerRequestInterface::class); + $request + ->method('getHeaderLine') + ->willReturnMap([['Origin', 'foobarbaz://example.org']]); + + $this->cors + ->expects(self::once()) + ->method('isCorsRequest') + ->with($request) + ->willThrowException( + InvalidOriginValueException::fromThrowable( + 'foobarbaz://example.org', + new InvalidArgumentException('Some exception from PSR-17 factory.') + ), + ); + + $handler = $this->createMock(RequestHandlerInterface::class); + $handler + ->expects(self::never()) + ->method('handle'); + + $this->responseFactoryInterface + ->expects(self::once()) + ->method('unauthorized') + ->with('foobarbaz://example.org'); + + $this->middleware->process($request, $handler); + } } From b7b50db0e14f633dc3179aff7fc2fe36f0871518 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20B=C3=B6sing?= <2189546+boesing@users.noreply.github.com> Date: Fri, 8 Dec 2023 18:16:59 +0100 Subject: [PATCH 2/3] feature: introduce public readonly `origin` property for `InvalidOriginValueException` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maximilian Bösing <2189546+boesing@users.noreply.github.com> --- src/Exception/InvalidOriginValueException.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Exception/InvalidOriginValueException.php b/src/Exception/InvalidOriginValueException.php index fa59a3d..82f7a9a 100644 --- a/src/Exception/InvalidOriginValueException.php +++ b/src/Exception/InvalidOriginValueException.php @@ -11,13 +11,16 @@ final class InvalidOriginValueException extends RuntimeException implements ExceptionInterface { - private function __construct(string $message, ?Throwable $previous = null) - { + private function __construct( + string $message, + public readonly string $origin, + ?Throwable $previous = null + ) { parent::__construct($message, 0, $previous); } public static function fromThrowable(string $origin, Throwable $throwable): self { - return new self(sprintf('Provided Origin "%s" is invalid.', $origin), $throwable); + return new self(sprintf('Provided Origin "%s" is invalid.', $origin), $origin, $throwable); } } From b08de20d006002bb8b5b07bb8b3ab7ce5536b4c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20B=C3=B6sing?= <2189546+boesing@users.noreply.github.com> Date: Fri, 8 Dec 2023 18:18:49 +0100 Subject: [PATCH 3/3] bugfix: handle unhandled `InvalidOriginValueException` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For origins resulting in `InvalidOriginValueException`, we can assume that these are actual CORS requests. If these are made from unsupported origins, we should treat these as unauthorized requests. Signed-off-by: Maximilian Bösing <2189546+boesing@users.noreply.github.com> --- src/Middleware/CorsMiddleware.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Middleware/CorsMiddleware.php b/src/Middleware/CorsMiddleware.php index aa4f93e..63fdf0c 100644 --- a/src/Middleware/CorsMiddleware.php +++ b/src/Middleware/CorsMiddleware.php @@ -4,6 +4,7 @@ namespace Mezzio\Cors\Middleware; +use Mezzio\Cors\Exception\InvalidOriginValueException; use Mezzio\Cors\Middleware\Exception\InvalidConfigurationException; use Mezzio\Cors\Service\ConfigurationLocatorInterface; use Mezzio\Cors\Service\CorsInterface; @@ -46,11 +47,18 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface throw InvalidConfigurationException::fromInvalidPipelineConfiguration(); } - if (! $this->cors->isCorsRequest($request)) { + try { + $isCorsRequest = $this->cors->isCorsRequest($request); + } catch (InvalidOriginValueException $exception) { + return $this->responseFactory->unauthorized($exception->origin); + } + + if (! $isCorsRequest) { return $this->vary($handler->handle($request)); } $metadata = $this->cors->metadata($request); + if ($this->cors->isPreflightRequest($request)) { return $this->preflight($metadata) ?? $handler->handle($request); }