diff --git a/README.md b/README.md index b0a0d11..f223cd8 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ This package can be used as a library. You can use it in your framework using: - [Stack middleware](http://stackphp.com/): https://github.com/asm89/stack-cors - [Laravel](https://laravel.com): https://github.com/fruitcake/laravel-cors - + ### Options @@ -38,6 +38,7 @@ This package can be used as a library. You can use it in your framework using: | exposedHeaders | Sets the Access-Control-Expose-Headers response header. | `[]` | | maxAge | Sets the Access-Control-Max-Age response header. | `0` | | supportsCredentials | Sets the Access-Control-Allow-Credentials header. | `false` | +| allowPrivateNetwork | Sets the Access-Control-Allow-Private-Network header. | `false` | The _allowedMethods_ and _allowedHeaders_ options are case-insensitive. @@ -62,6 +63,7 @@ $cors = new CorsService([ 'exposedHeaders' => ['Content-Encoding'], 'maxAge' => 0, 'supportsCredentials' => false, + 'allowPrivateNetwork' => false, ]); $cors->addActualRequestHeaders(Response $response, $origin); diff --git a/src/CorsService.php b/src/CorsService.php index 35a3f73..2fde8c5 100644 --- a/src/CorsService.php +++ b/src/CorsService.php @@ -21,6 +21,7 @@ * 'allowedOrigins'?: string[], * 'allowedOriginsPatterns'?: string[], * 'supportsCredentials'?: bool, + * 'allowPrivateNetwork'? : bool, * 'allowedHeaders'?: string[], * 'allowedMethods'?: string[], * 'exposedHeaders'?: string[]|false, @@ -28,6 +29,7 @@ * 'allowed_origins'?: string[], * 'allowed_origins_patterns'?: string[], * 'supports_credentials'?: bool, + * 'allow_private_network'? : bool, * 'allowed_headers'?: string[], * 'allowed_methods'?: string[], * 'exposed_headers'?: string[]|false, @@ -48,6 +50,7 @@ class CorsService /** @var string[] */ private array $exposedHeaders = []; private bool $supportsCredentials = false; + private bool $allowPrivateNetwork = false; private ?int $maxAge = 0; private bool $allowAllOrigins = false; @@ -76,6 +79,8 @@ public function setOptions(array $options): void $this->allowedHeaders = $options['allowedHeaders'] ?? $options['allowed_headers'] ?? $this->allowedHeaders; $this->supportsCredentials = $options['supportsCredentials'] ?? $options['supports_credentials'] ?? $this->supportsCredentials; + $this->allowPrivateNetwork = + $options['allowPrivateNetwork'] ?? $options['allow_private_network'] ?? $this->allowPrivateNetwork; $maxAge = $this->maxAge; if (array_key_exists('maxAge', $options)) { @@ -162,6 +167,8 @@ public function addPreflightRequestHeaders(Response $response, Request $request) $this->configureAllowedHeaders($response, $request); $this->configureMaxAge($response, $request); + + $this->configurePrivateNetwork($response, $request); } return $response; @@ -258,6 +265,13 @@ private function configureAllowCredentials(Response $response, Request $request) } } + private function configurePrivateNetwork(Response $response, Request $request): void + { + if ($request->headers->get('Access-Control-Request-Private-Network') === 'true' && $this->allowPrivateNetwork) { + $response->headers->set('Access-Control-Allow-Private-Network', 'true'); + } + } + private function configureExposedHeaders(Response $response, Request $request): void { if ($this->exposedHeaders) { diff --git a/tests/CorsServiceTest.php b/tests/CorsServiceTest.php index ee6957a..c771b6b 100644 --- a/tests/CorsServiceTest.php +++ b/tests/CorsServiceTest.php @@ -21,6 +21,7 @@ * 'allowedOrigins': string[], * 'allowedOriginsPatterns': string[], * 'supportsCredentials': bool, + * 'allowPrivateNetwork' : bool, * 'allowedHeaders': string[], * 'allowedMethods': string[], * 'exposedHeaders': string[], @@ -44,6 +45,7 @@ public function itCanHaveOptions(): void 'allowedMethods' => ['PUT'], 'maxAge' => 684, 'supportsCredentials' => true, + 'allowPrivateNetwork' => true, 'exposedHeaders' => ['x-custom-2'], ]; @@ -59,6 +61,7 @@ public function itCanHaveOptions(): void $this->assertEquals($options['allowedMethods'], $normalized['allowedMethods']); $this->assertEquals($options['maxAge'], $normalized['maxAge']); $this->assertEquals($options['supportsCredentials'], $normalized['supportsCredentials']); + $this->assertEquals($options['allowPrivateNetwork'], $normalized['allowPrivateNetwork']); $this->assertEquals($options['exposedHeaders'], $normalized['exposedHeaders']); } @@ -80,6 +83,7 @@ public function itCanSetOptions(): void 'allowedMethods' => ['PUT'], 'maxAge' => 684, 'supportsCredentials' => true, + 'allowPrivateNetwork' => true, 'exposedHeaders' => ['x-custom-2'], ]; @@ -93,6 +97,7 @@ public function itCanSetOptions(): void $this->assertEquals($options['allowedMethods'], $normalized['allowedMethods']); $this->assertEquals($options['maxAge'], $normalized['maxAge']); $this->assertEquals($options['supportsCredentials'], $normalized['supportsCredentials']); + $this->assertEquals($options['allowPrivateNetwork'], $normalized['allowPrivateNetwork']); $this->assertEquals($options['exposedHeaders'], $normalized['exposedHeaders']); } @@ -115,6 +120,7 @@ public function itCanOverwriteSetOptions(): void 'allowedMethods' => ['PUT'], 'maxAge' => 684, 'supportsCredentials' => true, + 'allowPrivateNetwork' => true, 'exposedHeaders' => ['x-custom-2'], ]; @@ -128,6 +134,7 @@ public function itCanOverwriteSetOptions(): void $this->assertEquals($options['allowedMethods'], $normalized['allowedMethods']); $this->assertEquals($options['maxAge'], $normalized['maxAge']); $this->assertEquals($options['supportsCredentials'], $normalized['supportsCredentials']); + $this->assertEquals($options['allowPrivateNetwork'], $normalized['allowPrivateNetwork']); $this->assertEquals($options['exposedHeaders'], $normalized['exposedHeaders']); } @@ -148,6 +155,7 @@ public function itCanHaveNoOptions(): void $this->assertEquals([], $normalized['exposedHeaders']); $this->assertEquals(0, $normalized['maxAge']); $this->assertEquals(false, $normalized['supportsCredentials']); + $this->assertEquals(false, $normalized['allowPrivateNetwork']); } /** @@ -167,6 +175,7 @@ public function itCanHaveEmptyOptions(): void $this->assertEquals([], $normalized['exposedHeaders']); $this->assertEquals(0, $normalized['maxAge']); $this->assertEquals(false, $normalized['supportsCredentials']); + $this->assertEquals(false, $normalized['allowPrivateNetwork']); } /** @@ -275,6 +284,7 @@ public function itNormalizesUnderscoreOptions(): void 'allowed_methods' => ['PUT'], 'max_age' => 684, 'supports_credentials' => true, + 'allow_private_network' => true, 'exposed_headers' => ['x-custom-2'], ]; @@ -294,6 +304,10 @@ public function itNormalizesUnderscoreOptions(): void $options['supports_credentials'], $this->getOptionsFromService($service)['supportsCredentials'] ); + $this->assertEquals( + $options['allow_private_network'], + $this->getOptionsFromService($service)['allowPrivateNetwork'] + ); } /** diff --git a/tests/CorsTest.php b/tests/CorsTest.php index 8838a32..a8486fa 100644 --- a/tests/CorsTest.php +++ b/tests/CorsTest.php @@ -535,6 +535,44 @@ public function itDoesntSetAccessControlAllowOriginWithoutOrigin(): void $this->assertFalse($response->headers->has('Access-Control-Allow-Origin')); } + /** + * @test + */ + public function itSetsAllowPrivateNetworkWhenAllowed(): void + { + $app = $this->createStackedApp(array('allowPrivateNetwork' => true)); + $request = $this->createValidPreflightRequest(); + $request->headers->set('Access-Control-Request-Private-Network', 'true'); + + $response = $app->handle($request); + $this->assertTrue($response->headers->has('Access-Control-Allow-Private-Network')); + } + + /** + * @test + */ + public function itDoesntSetAllowPrivateNetworkWhenNotAllowed(): void + { + $app = $this->createStackedApp(array('allowPrivateNetwork' => false)); + $request = $this->createValidPreflightRequest(); + $request->headers->set('Access-Control-Request-Private-Network', 'true'); + + $response = $app->handle($request); + $this->assertFalse($response->headers->has('Access-Control-Allow-Private-Network')); + } + + /** + * @test + */ + public function itDoesntSetAllowPrivateNetworkWhenNotRequested(): void + { + $app = $this->createStackedApp(array('allowPrivateNetwork' => true)); + $request = $this->createValidPreflightRequest(); + + $response = $app->handle($request); + $this->assertFalse($response->headers->has('Access-Control-Allow-Private-Network')); + } + private function createValidActualRequest(): Request { $request = new Request();