diff --git a/composer.lock b/composer.lock index 132fba5..e78a4c5 100644 --- a/composer.lock +++ b/composer.lock @@ -8,6 +8,49 @@ "content-hash": "60f7057617c6d995bf9946d0b12f0b5d", "packages": [ { + "name": "asm89/stack-cors", + "version": "0.2.1", + "source": { + "type": "git", + "url": "https://github.com/asm89/stack-cors.git", + "reference": "2d77e77251a434e4527315313a672f5801b29fa2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/asm89/stack-cors/zipball/2d77e77251a434e4527315313a672f5801b29fa2", + "reference": "2d77e77251a434e4527315313a672f5801b29fa2", + "shasum": "" + }, + "require": { + "php": ">=5.3.2", + "symfony/http-foundation": "~2.1", + "symfony/http-kernel": "~2.1" + }, + "type": "library", + "autoload": { + "psr-0": { + "Asm89\\Stack": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alexander", + "email": "iam.asm89@gmail.com" + } + ], + "description": "Cross-origin resource sharing library and stack middleware", + "homepage": "https://github.com/asm89/stack-cors", + "keywords": [ + "cors", + "stack" + ], + "time": "2014-07-28 07:22:35" + }, + { "name": "composer/installers", "version": "v1.0.21", "source": { diff --git a/core/composer.json b/core/composer.json index 3109649..7ae7f4f 100644 --- a/core/composer.json +++ b/core/composer.json @@ -31,7 +31,8 @@ "symfony/psr-http-message-bridge": "v0.2", "zendframework/zend-diactoros": "~1.1", "composer/semver": "~1.0", - "paragonie/random_compat": "~1.0" + "paragonie/random_compat": "~1.0", + "asm89/stack-cors": "~0.2.1" }, "require-dev": { "behat/mink": "~1.7", diff --git a/core/core.services.yml b/core/core.services.yml index d90aeba..6310c21 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -33,6 +33,14 @@ parameters: - sftp - webcal - rtsp + cors.config: + enabled: false + allowedHeaders: [] + allowedMethods: [] + allowedOrigins: ['*'] + exposedHeaders: false + maxAge: false + supportsCredentials: false services: # Simple cache contexts, directly derived from the request context. cache_context.ip: @@ -706,6 +714,11 @@ services: - { name: http_middleware, priority: 50 } calls: - [setContainer, ['@service_container']] + http_middleware.cors: + class: Asm89\Stack\Cors + arguments: ['%cors.config%'] + tags: + - { name: http_middleware } psr7.http_foundation_factory: class: Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory psr7.http_message_factory: diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php index 013dec1..8a22bc5 100644 --- a/core/lib/Drupal/Core/CoreServiceProvider.php +++ b/core/lib/Drupal/Core/CoreServiceProvider.php @@ -6,6 +6,7 @@ use Drupal\Core\Cache\ListCacheBinsPass; use Drupal\Core\DependencyInjection\Compiler\AuthenticationProviderPass; use Drupal\Core\DependencyInjection\Compiler\BackendCompilerPass; +use Drupal\Core\DependencyInjection\Compiler\CorsCompilerPass; use Drupal\Core\DependencyInjection\Compiler\GuzzleMiddlewarePass; use Drupal\Core\DependencyInjection\Compiler\ContextProvidersPass; use Drupal\Core\DependencyInjection\Compiler\ProxyServicesPass; @@ -64,6 +65,8 @@ public function register(ContainerBuilder $container) { $container->addCompilerPass(new BackendCompilerPass()); + $container->addCompilerPass(new CorsCompilerPass()); + $container->addCompilerPass(new StackedKernelPass()); $container->addCompilerPass(new StackedSessionHandlerPass()); diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/CorsCompilerPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/CorsCompilerPass.php new file mode 100644 index 0000000..207e094 --- /dev/null +++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/CorsCompilerPass.php @@ -0,0 +1,31 @@ +getParameter('cors.config')) { + $enabled = !empty($cors_config['enabled']); + } + + // Remove the CORS middleware completly in case it was not enabled. + if (!$enabled) { + $container->removeDefinition('http_middleware.cors'); + } + } + +} diff --git a/core/tests/Drupal/KernelTests/Core/HttpKernel/CorsIntegrationTest.php b/core/tests/Drupal/KernelTests/Core/HttpKernel/CorsIntegrationTest.php new file mode 100644 index 0000000..e73efa3 --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/HttpKernel/CorsIntegrationTest.php @@ -0,0 +1,84 @@ +installSchema('system', 'router'); + \Drupal::service('router.builder')->rebuild(); + } + + public function testCrossSiteRequest() { + + // Test default parameters. + $cors_config = $this->container->getParameter('cors.config'); + $this->assertSame(FALSE, $cors_config['enabled']); + $this->assertSame([], $cors_config['allowedHeaders']); + $this->assertSame([], $cors_config['allowedMethods']); + $this->assertSame(['*'], $cors_config['allowedOrigins']); + + $this->assertSame(FALSE, $cors_config['exposedHeaders']); + $this->assertSame(FALSE, $cors_config['maxAge']); + $this->assertSame(FALSE, $cors_config['supportsCredentials']); + + // Configure the CORS stack to allow a specific set of origins, but don't + // specify an origin header. + $request = Request::create('/test-page'); + $request->headers->set('Origin', ''); + $cors_config['enabled'] = TRUE; + $cors_config['allowedOrigins'] = ['http://example.com']; + + $this->corsConfig = $cors_config; + $this->container->get('kernel')->rebuildContainer(); + + /** @var \Symfony\Component\HttpFoundation\Response $response */ + $response = $this->container->get('http_kernel')->handle($request); + $this->assertEquals(Response::HTTP_FORBIDDEN, $response->getStatusCode()); + $this->assertEquals('Not allowed.', $response->getContent()); + + // Specify a valid origin. + $request->headers->set('Origin', 'http://example.com'); + $response = $this->container->get('http_kernel')->handle($request); + $this->assertEquals(Response::HTTP_OK, $response->getStatusCode()); + } + + /** + * {@inheritdoc} + */ + public function alter(ContainerBuilder $container) { + if (isset($this->corsConfig)) { + $container->setParameter('cors.config', $this->corsConfig); + } + } + +} diff --git a/sites/default/default.services.yml b/sites/default/default.services.yml index 23f6483..e1bbbc7 100644 --- a/sites/default/default.services.yml +++ b/sites/default/default.services.yml @@ -153,3 +153,22 @@ parameters: - sftp - webcal - rtsp + + # Configure Cross-Site HTTP requests (CORS). + # Read https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS + # for more information about the topic in general. + # Note: By default the configuration is disabled. + cors.config: + enabled: false + # Specify allowed headers, like 'x-allowed-header'. + allowedHeaders: [] + # Specify allowed request methods, specify ['*'] to allow all possible ones. + allowedMethods: [] + # Configure requests allowed from specific origins. + allowedOrigins: ['*'] + # Sets the Access-Control-Expose-Headers header. + exposedHeaders: false + # Sets the Access-Control-Max-Age header. + maxAge: false + # Sets the Access-Control-Allow-Credentials header. + supportsCredentials: false