mirror of
https://github.com/shlinkio/shlink.git
synced 2026-02-28 04:03:12 +08:00
Add new CORS configuration options
This commit is contained in:
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
'cors' => [
|
||||
'max_age' => 3600,
|
||||
],
|
||||
|
||||
];
|
||||
@@ -37,6 +37,7 @@ return [
|
||||
Config\Options\RabbitMqOptions::class => [Config\Options\RabbitMqOptions::class, 'fromEnv'],
|
||||
Config\Options\RobotsOptions::class => [Config\Options\RobotsOptions::class, 'fromEnv'],
|
||||
Config\Options\RealTimeUpdatesOptions::class => [Config\Options\RealTimeUpdatesOptions::class, 'fromEnv'],
|
||||
Config\Options\CorsOptions::class => [Config\Options\CorsOptions::class, 'fromEnv'],
|
||||
|
||||
RedirectRule\ShortUrlRedirectRuleService::class => ConfigAbstractFactory::class,
|
||||
RedirectRule\ShortUrlRedirectionResolver::class => ConfigAbstractFactory::class,
|
||||
|
||||
@@ -86,6 +86,9 @@ enum EnvVars: string
|
||||
case INITIAL_API_KEY = 'INITIAL_API_KEY';
|
||||
case SKIP_INITIAL_GEOLITE_DOWNLOAD = 'SKIP_INITIAL_GEOLITE_DOWNLOAD';
|
||||
case REAL_TIME_UPDATES_TOPICS = 'REAL_TIME_UPDATES_TOPICS';
|
||||
case CORS_ALLOW_ORIGIN = 'CORS_ALLOW_ORIGIN';
|
||||
case CORS_ALLOW_CREDENTIALS = 'CORS_ALLOW_CREDENTIALS';
|
||||
case CORS_MAX_AGE = 'CORS_MAX_AGE';
|
||||
|
||||
/** @deprecated Use REDIRECT_EXTRA_PATH */
|
||||
case REDIRECT_APPEND_EXTRA_PATH = 'REDIRECT_APPEND_EXTRA_PATH';
|
||||
@@ -187,6 +190,10 @@ enum EnvVars: string
|
||||
self::DISABLE_REFERRER_TRACKING,
|
||||
self::DISABLE_UA_TRACKING => false,
|
||||
|
||||
self::CORS_ALLOW_ORIGIN => '*',
|
||||
self::CORS_ALLOW_CREDENTIALS => false,
|
||||
self::CORS_MAX_AGE => 3600,
|
||||
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
58
module/Core/src/Config/Options/CorsOptions.php
Normal file
58
module/Core/src/Config/Options/CorsOptions.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Config\Options;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||
|
||||
use function Shlinkio\Shlink\Core\ArrayUtils\contains;
|
||||
use function Shlinkio\Shlink\Core\splitByComma;
|
||||
|
||||
final readonly class CorsOptions
|
||||
{
|
||||
private const string ORIGIN_PATTERN = '<origin>';
|
||||
|
||||
/** @var string[]|'*'|'<origin>' */
|
||||
public string|array $allowOrigins;
|
||||
|
||||
public function __construct(
|
||||
string $allowOrigins = '*',
|
||||
public bool $allowCredentials = false,
|
||||
public int $maxAge = 3600,
|
||||
) {
|
||||
$this->allowOrigins = $allowOrigins !== '*' && $allowOrigins !== self::ORIGIN_PATTERN
|
||||
? splitByComma($allowOrigins)
|
||||
: $allowOrigins;
|
||||
}
|
||||
|
||||
public static function fromEnv(): self
|
||||
{
|
||||
return new self(
|
||||
allowOrigins: EnvVars::CORS_ALLOW_ORIGIN->loadFromEnv(),
|
||||
allowCredentials: EnvVars::CORS_ALLOW_CREDENTIALS->loadFromEnv(),
|
||||
maxAge: EnvVars::CORS_MAX_AGE->loadFromEnv(),
|
||||
);
|
||||
}
|
||||
|
||||
public function responseWithAllowOrigin(RequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
if ($this->allowOrigins === '*') {
|
||||
return $response->withHeader('Access-Control-Allow-Origin', '*');
|
||||
}
|
||||
|
||||
$requestOrigin = $request->getHeader('Origin');
|
||||
if (
|
||||
// The special <origin> value means we should allow requests from the origin set in the request's Origin
|
||||
// header
|
||||
$this->allowOrigins === self::ORIGIN_PATTERN
|
||||
// If an array of allowed hosts was provided, set Access-Control-Allow-Origin header only if request's
|
||||
// Origin header matches one of them
|
||||
|| contains($requestOrigin, $this->allowOrigins)
|
||||
) {
|
||||
return $response->withHeader('Access-Control-Allow-Origin', $requestOrigin);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -115,7 +115,7 @@ return [
|
||||
RedirectRule\ShortUrlRedirectRuleService::class,
|
||||
],
|
||||
|
||||
Middleware\CrossDomainMiddleware::class => ['config.cors'],
|
||||
Middleware\CrossDomainMiddleware::class => [Config\Options\CorsOptions::class],
|
||||
Middleware\ShortUrl\DropDefaultDomainFromRequestMiddleware::class => [
|
||||
Config\Options\UrlShortenerOptions::class,
|
||||
],
|
||||
|
||||
@@ -10,12 +10,13 @@ use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Shlinkio\Shlink\Core\Config\Options\CorsOptions;
|
||||
|
||||
use function implode;
|
||||
|
||||
class CrossDomainMiddleware implements MiddlewareInterface, RequestMethodInterface
|
||||
readonly class CrossDomainMiddleware implements MiddlewareInterface, RequestMethodInterface
|
||||
{
|
||||
public function __construct(private array $config)
|
||||
public function __construct(private CorsOptions $options)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -27,7 +28,7 @@ class CrossDomainMiddleware implements MiddlewareInterface, RequestMethodInterfa
|
||||
}
|
||||
|
||||
// Add Allow-Origin header
|
||||
$response = $response->withHeader('Access-Control-Allow-Origin', '*');
|
||||
$response = $this->options->responseWithAllowOrigin($request, $response);
|
||||
if ($request->getMethod() !== self::METHOD_OPTIONS) {
|
||||
return $response;
|
||||
}
|
||||
@@ -40,7 +41,7 @@ class CrossDomainMiddleware implements MiddlewareInterface, RequestMethodInterfa
|
||||
$corsHeaders = [
|
||||
'Access-Control-Allow-Methods' => $this->resolveCorsAllowedMethods($response),
|
||||
'Access-Control-Allow-Headers' => $request->getHeaderLine('Access-Control-Request-Headers'),
|
||||
'Access-Control-Max-Age' => $this->config['max_age'],
|
||||
'Access-Control-Max-Age' => $this->options->maxAge,
|
||||
];
|
||||
|
||||
// Options requests should always be empty and have a 204 status code
|
||||
|
||||
@@ -11,6 +11,7 @@ use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Shlinkio\Shlink\Core\Config\Options\CorsOptions;
|
||||
use Shlinkio\Shlink\Rest\Middleware\CrossDomainMiddleware;
|
||||
|
||||
class CrossDomainMiddlewareTest extends TestCase
|
||||
@@ -20,7 +21,7 @@ class CrossDomainMiddlewareTest extends TestCase
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->middleware = new CrossDomainMiddleware(['max_age' => 1000]);
|
||||
$this->middleware = new CrossDomainMiddleware(new CorsOptions(maxAge: 1000));
|
||||
$this->handler = $this->createMock(RequestHandlerInterface::class);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user