diff --git a/composer.lock b/composer.lock index 63573e0..15ca50d 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": { @@ -1061,7 +1104,7 @@ }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/1.0.0", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", "reference": "1.0.0", "shasum": "" }, @@ -2714,24 +2757,24 @@ "packages-dev": [ { "name": "behat/mink", - "version": "v1.7.1", + "version": "v1.7.0", "source": { "type": "git", "url": "https://github.com/minkphp/Mink.git", - "reference": "e6930b9c74693dff7f4e58577e1b1743399f3ff9" + "reference": "6c129030ec2cc029905cf969a56ca8f087b2dfdf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/minkphp/Mink/zipball/e6930b9c74693dff7f4e58577e1b1743399f3ff9", - "reference": "e6930b9c74693dff7f4e58577e1b1743399f3ff9", + "url": "https://api.github.com/repos/minkphp/Mink/zipball/6c129030ec2cc029905cf969a56ca8f087b2dfdf", + "reference": "6c129030ec2cc029905cf969a56ca8f087b2dfdf", "shasum": "" }, "require": { "php": ">=5.3.1", - "symfony/css-selector": "~2.1|~3.0" + "symfony/css-selector": "~2.1" }, "require-dev": { - "symfony/phpunit-bridge": "~2.7|~3.0" + "symfony/phpunit-bridge": "~2.7" }, "suggest": { "behat/mink-browserkit-driver": "extremely fast headless driver for Symfony\\Kernel-based apps (Sf2, Silex)", @@ -2768,31 +2811,31 @@ "testing", "web" ], - "time": "2016-03-05 08:26:18" + "time": "2015-09-20 20:24:03" }, { "name": "behat/mink-browserkit-driver", - "version": "v1.3.2", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/minkphp/MinkBrowserKitDriver.git", - "reference": "10e67fb4a295efcd62ea0bf16025a85ea19534fb" + "reference": "da47df1593dac132f04d24e7277ef40d33d9f201" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/minkphp/MinkBrowserKitDriver/zipball/10e67fb4a295efcd62ea0bf16025a85ea19534fb", - "reference": "10e67fb4a295efcd62ea0bf16025a85ea19534fb", + "url": "https://api.github.com/repos/minkphp/MinkBrowserKitDriver/zipball/da47df1593dac132f04d24e7277ef40d33d9f201", + "reference": "da47df1593dac132f04d24e7277ef40d33d9f201", "shasum": "" }, "require": { - "behat/mink": "^1.7.1@dev", + "behat/mink": "~1.7@dev", "php": ">=5.3.6", - "symfony/browser-kit": "~2.3|~3.0", - "symfony/dom-crawler": "~2.3|~3.0" + "symfony/browser-kit": "~2.3", + "symfony/dom-crawler": "~2.3" }, "require-dev": { "silex/silex": "~1.2", - "symfony/phpunit-bridge": "~2.7|~3.0" + "symfony/phpunit-bridge": "~2.7" }, "type": "mink-driver", "extra": { @@ -2824,20 +2867,20 @@ "browser", "testing" ], - "time": "2016-03-05 08:59:47" + "time": "2015-09-21 20:56:13" }, { "name": "behat/mink-goutte-driver", - "version": "v1.2.1", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/minkphp/MinkGoutteDriver.git", - "reference": "8b9ad6d2d95bc70b840d15323365f52fcdaea6ca" + "reference": "c8e254f127d6f2242b994afd4339fb62d471df3f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/minkphp/MinkGoutteDriver/zipball/8b9ad6d2d95bc70b840d15323365f52fcdaea6ca", - "reference": "8b9ad6d2d95bc70b840d15323365f52fcdaea6ca", + "url": "https://api.github.com/repos/minkphp/MinkGoutteDriver/zipball/c8e254f127d6f2242b994afd4339fb62d471df3f", + "reference": "c8e254f127d6f2242b994afd4339fb62d471df3f", "shasum": "" }, "require": { @@ -2847,7 +2890,7 @@ "php": ">=5.3.1" }, "require-dev": { - "symfony/phpunit-bridge": "~2.7|~3.0" + "symfony/phpunit-bridge": "~2.7" }, "type": "mink-driver", "extra": { @@ -2879,7 +2922,7 @@ "headless", "testing" ], - "time": "2016-03-05 09:04:22" + "time": "2015-09-21 21:31:11" }, { "name": "doctrine/instantiator", @@ -2937,24 +2980,24 @@ }, { "name": "fabpot/goutte", - "version": "v3.1.2", + "version": "v3.1.1", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/Goutte.git", - "reference": "3cbc6ed222422a28400e470050f14928a153207e" + "reference": "751a3dc5c4d86ec3e97c9f27133ef9694d9243cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/3cbc6ed222422a28400e470050f14928a153207e", - "reference": "3cbc6ed222422a28400e470050f14928a153207e", + "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/751a3dc5c4d86ec3e97c9f27133ef9694d9243cc", + "reference": "751a3dc5c4d86ec3e97c9f27133ef9694d9243cc", "shasum": "" }, "require": { "guzzlehttp/guzzle": "^6.0", "php": ">=5.5.0", - "symfony/browser-kit": "~2.1|~3.0", - "symfony/css-selector": "~2.1|~3.0", - "symfony/dom-crawler": "~2.1|~3.0" + "symfony/browser-kit": "~2.1", + "symfony/css-selector": "~2.1", + "symfony/dom-crawler": "~2.1" }, "type": "application", "extra": { @@ -2982,7 +3025,7 @@ "keywords": [ "scraper" ], - "time": "2015-11-05 12:58:44" + "time": "2015-08-29 16:16:56" }, { "name": "jcalderonzumba/gastonjs", diff --git a/core/composer.json b/core/composer.json index 9e68eaa..0a5656e 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 6049d3b..c64df18 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: @@ -700,6 +708,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 51b1c51..eb11790 100644 --- a/core/lib/Drupal/Core/CoreServiceProvider.php +++ b/core/lib/Drupal/Core/CoreServiceProvider.php @@ -5,6 +5,7 @@ use Drupal\Core\Cache\Context\CacheContextsPass; use Drupal\Core\Cache\ListCacheBinsPass; 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; @@ -63,6 +64,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