mirror of
https://github.com/shlinkio/shlink.git
synced 2026-02-28 04:03:12 +08:00
Merge pull request #2489 from acelaya-forks/feature/cors-credentials-fix
Make sure Access-Control-Allow-Credentials is always set if configured
This commit is contained in:
@@ -37,7 +37,19 @@ final readonly class CorsOptions
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function responseWithAllowOrigin(RequestInterface $request, ResponseInterface $response): ResponseInterface
|
/**
|
||||||
|
* Creates a new response which contains the CORS headers that apply to provided request
|
||||||
|
*/
|
||||||
|
public function responseWithCorsHeaders(RequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||||
|
{
|
||||||
|
$response = $this->responseWithAllowOrigin($request, $response);
|
||||||
|
return $this->allowCredentials ? $response->withHeader('Access-Control-Allow-Credentials', 'true') : $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If applicable, a new response with the appropriate Access-Control-Allow-Origin header is returned
|
||||||
|
*/
|
||||||
|
private function responseWithAllowOrigin(RequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||||
{
|
{
|
||||||
if ($this->allowOrigins === '*') {
|
if ($this->allowOrigins === '*') {
|
||||||
return $response->withHeader('Access-Control-Allow-Origin', '*');
|
return $response->withHeader('Access-Control-Allow-Origin', '*');
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use Laminas\Diactoros\ServerRequestFactory;
|
|||||||
use PHPUnit\Framework\Attributes\Test;
|
use PHPUnit\Framework\Attributes\Test;
|
||||||
use PHPUnit\Framework\Attributes\TestWith;
|
use PHPUnit\Framework\Attributes\TestWith;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Shlinkio\Shlink\Core\Config\Options\CorsOptions;
|
use Shlinkio\Shlink\Core\Config\Options\CorsOptions;
|
||||||
|
|
||||||
class CorsOptionsTest extends TestCase
|
class CorsOptionsTest extends TestCase
|
||||||
@@ -28,10 +29,30 @@ class CorsOptionsTest extends TestCase
|
|||||||
self::assertEquals($expectedAllowOrigins, $options->allowOrigins);
|
self::assertEquals($expectedAllowOrigins, $options->allowOrigins);
|
||||||
self::assertEquals(
|
self::assertEquals(
|
||||||
$expectedAllowOriginsHeader,
|
$expectedAllowOriginsHeader,
|
||||||
$options->responseWithAllowOrigin(
|
$this->responseFromOptions($options)->getHeaderLine('Access-Control-Allow-Origin'),
|
||||||
ServerRequestFactory::fromGlobals()->withHeader('Origin', 'https://example.com'),
|
);
|
||||||
new Response(),
|
}
|
||||||
)->getHeaderLine('Access-Control-Allow-Origin'),
|
|
||||||
|
#[Test]
|
||||||
|
#[TestWith([true])]
|
||||||
|
#[TestWith([false])]
|
||||||
|
public function expectedAccessControlAllowCredentialsIsSet(bool $allowCredentials): void
|
||||||
|
{
|
||||||
|
$options = new CorsOptions(allowCredentials: $allowCredentials);
|
||||||
|
$resp = $this->responseFromOptions($options);
|
||||||
|
|
||||||
|
if ($allowCredentials) {
|
||||||
|
self::assertEquals('true', $resp->getHeaderLine('Access-Control-Allow-Credentials'));
|
||||||
|
} else {
|
||||||
|
self::assertFalse($resp->hasHeader('Access-Control-Allow-Credentials'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function responseFromOptions(CorsOptions $options): ResponseInterface
|
||||||
|
{
|
||||||
|
return $options->responseWithCorsHeaders(
|
||||||
|
ServerRequestFactory::fromGlobals()->withHeader('Origin', 'https://example.com'),
|
||||||
|
new Response(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ readonly class CrossDomainMiddleware implements MiddlewareInterface, RequestMeth
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add Allow-Origin header
|
// Add Allow-Origin header
|
||||||
$response = $this->options->responseWithAllowOrigin($request, $response);
|
$response = $this->options->responseWithCorsHeaders($request, $response);
|
||||||
if ($request->getMethod() !== self::METHOD_OPTIONS) {
|
if ($request->getMethod() !== self::METHOD_OPTIONS) {
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
@@ -38,18 +38,13 @@ readonly class CrossDomainMiddleware implements MiddlewareInterface, RequestMeth
|
|||||||
|
|
||||||
private function addOptionsHeaders(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
private function addOptionsHeaders(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||||
{
|
{
|
||||||
$corsHeaders = [
|
// Options requests should always be empty and have a 204 status code
|
||||||
|
return EmptyResponse::withHeaders([
|
||||||
|
...$response->getHeaders(),
|
||||||
'Access-Control-Allow-Methods' => $this->resolveCorsAllowedMethods($response),
|
'Access-Control-Allow-Methods' => $this->resolveCorsAllowedMethods($response),
|
||||||
'Access-Control-Allow-Headers' => $request->getHeaderLine('Access-Control-Request-Headers'),
|
'Access-Control-Allow-Headers' => $request->getHeaderLine('Access-Control-Request-Headers'),
|
||||||
'Access-Control-Max-Age' => $this->options->maxAge,
|
'Access-Control-Max-Age' => $this->options->maxAge,
|
||||||
];
|
]);
|
||||||
|
|
||||||
if ($this->options->allowCredentials) {
|
|
||||||
$corsHeaders['Access-Control-Allow-Credentials'] = 'true';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Options requests should always be empty and have a 204 status code
|
|
||||||
return EmptyResponse::withHeaders([...$response->getHeaders(), ...$corsHeaders]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function resolveCorsAllowedMethods(ResponseInterface $response): string
|
private function resolveCorsAllowedMethods(ResponseInterface $response): string
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class CorsTest extends ApiTestCase
|
|||||||
self::assertFalse($resp->hasHeader('Access-Control-Allow-Methods'));
|
self::assertFalse($resp->hasHeader('Access-Control-Allow-Methods'));
|
||||||
self::assertFalse($resp->hasHeader('Access-Control-Max-Age'));
|
self::assertFalse($resp->hasHeader('Access-Control-Max-Age'));
|
||||||
self::assertFalse($resp->hasHeader('Access-Control-Allow-Headers'));
|
self::assertFalse($resp->hasHeader('Access-Control-Allow-Headers'));
|
||||||
|
self::assertFalse($resp->hasHeader('Access-Control-Allow-Credentials'));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Test, DataProvider('provideOrigins')]
|
#[Test, DataProvider('provideOrigins')]
|
||||||
@@ -38,6 +39,7 @@ class CorsTest extends ApiTestCase
|
|||||||
self::assertFalse($resp->hasHeader('Access-Control-Allow-Methods'));
|
self::assertFalse($resp->hasHeader('Access-Control-Allow-Methods'));
|
||||||
self::assertFalse($resp->hasHeader('Access-Control-Max-Age'));
|
self::assertFalse($resp->hasHeader('Access-Control-Max-Age'));
|
||||||
self::assertFalse($resp->hasHeader('Access-Control-Allow-Headers'));
|
self::assertFalse($resp->hasHeader('Access-Control-Allow-Headers'));
|
||||||
|
self::assertFalse($resp->hasHeader('Access-Control-Allow-Credentials'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function provideOrigins(): iterable
|
public static function provideOrigins(): iterable
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace ShlinkioTest\Shlink\Rest\Middleware;
|
namespace ShlinkioTest\Shlink\Rest\Middleware;
|
||||||
|
|
||||||
|
use Fig\Http\Message\RequestMethodInterface;
|
||||||
use Laminas\Diactoros\Response;
|
use Laminas\Diactoros\Response;
|
||||||
use Laminas\Diactoros\ServerRequest;
|
use Laminas\Diactoros\ServerRequest;
|
||||||
use PHPUnit\Framework\Attributes\DataProvider;
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
@@ -142,13 +143,17 @@ class CrossDomainMiddlewareTest extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[Test]
|
#[Test]
|
||||||
#[TestWith([true])]
|
#[TestWith([true, RequestMethodInterface::METHOD_OPTIONS])]
|
||||||
#[TestWith([false])]
|
#[TestWith([false, RequestMethodInterface::METHOD_OPTIONS])]
|
||||||
public function credentialsAreAllowedIfConfiguredSo(bool $allowCredentials): void
|
#[TestWith([true, RequestMethodInterface::METHOD_GET])]
|
||||||
|
#[TestWith([false, RequestMethodInterface::METHOD_GET])]
|
||||||
|
#[TestWith([true, RequestMethodInterface::METHOD_POST])]
|
||||||
|
#[TestWith([false, RequestMethodInterface::METHOD_POST])]
|
||||||
|
public function credentialsAreAllowedIfConfiguredSo(bool $allowCredentials, string $method): void
|
||||||
{
|
{
|
||||||
$originalResponse = new Response();
|
$originalResponse = new Response();
|
||||||
$request = (new ServerRequest())
|
$request = (new ServerRequest())
|
||||||
->withMethod('OPTIONS')
|
->withMethod($method)
|
||||||
->withHeader('Origin', 'local');
|
->withHeader('Origin', 'local');
|
||||||
$this->handler->method('handle')->willReturn($originalResponse);
|
$this->handler->method('handle')->willReturn($originalResponse);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user