diff --git a/composer.lock b/composer.lock index a406a5d..43a645b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,10 +4,53 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "dac77f10c1f7585fd1f7344c6a376338", - "content-hash": "73cbcb262208c5d802cb528279f2a95c", + "hash": "a6d0d7551e183076eaf3b6e2d9e217c3", + "content-hash": "8a9fb36031925e970c3c84f9a6c8ec7c", "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 1eabf49..e6f40c3 100644 --- a/core/composer.json +++ b/core/composer.json @@ -17,6 +17,7 @@ "symfony/validator": "2.7.*", "symfony/process": "2.7.*", "symfony/yaml": "2.7.*", + "asm89/stack-cors": "0.2.*", "twig/twig": "^1.23.1", "doctrine/common": "2.5.*", "doctrine/annotations": "1.2.*", diff --git a/core/core.services.yml b/core/core.services.yml index afccb31..219e5a7 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -4,6 +4,7 @@ parameters: gc_divisor: 100 gc_maxlifetime: 200000 cookie_lifetime: 2000000 + cors.config: {} twig.config: debug: false auto_reload: null @@ -689,6 +690,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 82e8bd5..1de0542 100644 --- a/core/lib/Drupal/Core/CoreServiceProvider.php +++ b/core/lib/Drupal/Core/CoreServiceProvider.php @@ -31,6 +31,7 @@ use Drupal\Core\Render\MainContent\MainContentRenderersPass; use Drupal\Core\Site\Settings; use Symfony\Component\DependencyInjection\Compiler\PassConfig; +use Drupal\Core\DependencyInjection\Compiler\CorsCompilerPass; /** * ServiceProvider class for mandatory core services. @@ -68,6 +69,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..3c66ad6 --- /dev/null +++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/CorsCompilerPass.php @@ -0,0 +1,36 @@ +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/modules/rest/rest.install b/core/modules/rest/rest.install index 4bca69b..9e118c4 100644 --- a/core/modules/rest/rest.install +++ b/core/modules/rest/rest.install @@ -10,14 +10,14 @@ */ function rest_requirements($phase) { $requirements = array(); - - if (version_compare(PHP_VERSION, '5.6.0', '>=') && version_compare(PHP_VERSION, '7', '<') && ini_get('always_populate_raw_post_data') != -1) { - $requirements['always_populate_raw_post_data'] = array( - 'title' => t('always_populate_raw_post_data PHP setting'), - 'value' => t('Not set to -1.'), - 'severity' => REQUIREMENT_ERROR, - 'description' => t('The always_populate_raw_post_data PHP setting should be set to -1 in PHP version 5.6. Please check the PHP manual for information on how to correct this.'), - ); - } - return $requirements; +// +// if (version_compare(PHP_VERSION, '5.6.0', '>=') && version_compare(PHP_VERSION, '7', '<') && ini_get('always_populate_raw_post_data') != -1) { +// $requirements['always_populate_raw_post_data'] = array( +// 'title' => t('always_populate_raw_post_data PHP setting'), +// 'value' => t('Not set to -1.'), +// 'severity' => REQUIREMENT_ERROR, +// 'description' => t('The always_populate_raw_post_data PHP setting should be set to -1 in PHP version 5.6. Please check the PHP manual for information on how to correct this.'), +// ); +// } +// return $requirements; } diff --git a/core/modules/system/src/Tests/HttpKernel/CorsIntegrationTest.php b/core/modules/system/src/Tests/HttpKernel/CorsIntegrationTest.php new file mode 100644 index 0000000..2b500a7 --- /dev/null +++ b/core/modules/system/src/Tests/HttpKernel/CorsIntegrationTest.php @@ -0,0 +1,56 @@ +container->getParameter('cors.config'); + $this->assertIdentical(FALSE, $cors_config['enabled']); + $this->assertIdentical([], $cors_config['allowedHeaders']); + $this->assertIdentical([], $cors_config['allowedMethods']); + $this->assertIdentical(['*'], $cors_config['allowedOrigins']); + + $this->assertIdentical(FALSE, $cors_config['exposedHeaders']); + $this->assertIdentical(FALSE, $cors_config['maxAge']); + $this->assertIdentical(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('/'); + $request->headers->set('Origin', ''); + $cors_config['enabled'] = TRUE; + $cors_config['allowedOrigins'] = array('http://example.com'); + $this->setContainerParameter('cors.config', $cors_config); + $this->rebuildContainer(); + + $response = $this->container->get('http_kernel')->handle($request); + $this->assertEqual(Response::HTTP_FORBIDDEN, $response->getStatusCode()); + $this->assertEqual('Not allowed.', $response->getContent()); + + // Specify a valid origin. + $request->headers->set('Origin', 'http://example.com'); + $response = $this->container->get('http_kernel')->handle($request); + $this->assertEqual(Response::HTTP_OK, $response->getStatusCode()); + } + +} diff --git a/sites/default/default.services.yml b/sites/default/default.services.yml index 23f6483..5a70555 100644 --- a/sites/default/default.services.yml +++ b/sites/default/default.services.yml @@ -153,3 +153,18 @@ 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: ['*'] + exposedHeaders: false + maxAge: false + supportsCredentials: false diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php old mode 100644 new mode 100755 diff --git a/vendor/asm89/stack-cors/.travis.yml b/vendor/asm89/stack-cors/.travis.yml new file mode 100755 index 0000000..ddb1058 --- /dev/null +++ b/vendor/asm89/stack-cors/.travis.yml @@ -0,0 +1,10 @@ +language: php + +php: + - 5.3 + - 5.4 + - 5.5 + +before_script: composer install --dev + +script: phpunit diff --git a/vendor/asm89/stack-cors/README.md b/vendor/asm89/stack-cors/README.md new file mode 100755 index 0000000..dea18a8 --- /dev/null +++ b/vendor/asm89/stack-cors/README.md @@ -0,0 +1,59 @@ +# Stack/Cors + +Library and middleware enabling cross-origin resource sharing for your +http-{foundation,kernel} using application. It attempts to implement the +[W3C Candidate Recommendation] for cross-origin resource sharing. + +[W3C Candidate Recommendation]: http://www.w3.org/TR/cors/ + +Master [![Build Status](https://secure.travis-ci.org/asm89/stack-cors.png?branch=master)](http://travis-ci.org/asm89/stack-cors) +Develop [![Build Status](https://secure.travis-ci.org/asm89/stack-cors.png?branch=develop)](http://travis-ci.org/asm89/stack-cors) + +## Installation + +Require `asm89/stack-cors` using composer. + +## Usage + +Stack middleware: + +```php + array('x-allowed-header', 'x-other-allowed-header'), + // you can use array('*') to allow any methods + 'allowedMethods' => array('DELETE', 'GET', 'POST', 'PUT'), + // you can use array('*') to allow requests from any origin + 'allowedOrigins' => array('localhost'), + 'exposedHeaders' => false, + 'maxAge' => false, + 'supportsCredentials' => false, +)); +``` + +Or use the library: + +```php + array('x-allowed-header', 'x-other-allowed-header'), + 'allowedMethods' => array('DELETE', 'GET', 'POST', 'PUT'), + 'allowedOrigins' => array('localhost'), + 'exposedHeaders' => false, + 'maxAge' => false, + 'supportsCredentials' => false, +)); + +$cors->addActualRequestHeaders(Response $response, $origin); +$cors->handlePreflightRequest(Request $request); +$cors->isActualRequestAllowed(Request $request); +$cors->isCorsRequest(Request $request); +$cors->isPreflightRequest(Request $request); +``` diff --git a/vendor/asm89/stack-cors/composer.json b/vendor/asm89/stack-cors/composer.json new file mode 100755 index 0000000..f8be507 --- /dev/null +++ b/vendor/asm89/stack-cors/composer.json @@ -0,0 +1,22 @@ +{ + "name": "asm89/stack-cors", + "description": "Cross-origin resource sharing library and stack middleware", + "keywords": ["stack", "cors"], + "homepage": "https://github.com/asm89/stack-cors", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Alexander", + "email": "iam.asm89@gmail.com" + } + ], + "require": { + "php": ">=5.3.2", + "symfony/http-foundation": "~2.1", + "symfony/http-kernel": "~2.1" + }, + "autoload": { + "psr-0": { "Asm89\\Stack": "src/" } + } +} diff --git a/vendor/asm89/stack-cors/phpunit.xml.dist b/vendor/asm89/stack-cors/phpunit.xml.dist new file mode 100755 index 0000000..1959e53 --- /dev/null +++ b/vendor/asm89/stack-cors/phpunit.xml.dist @@ -0,0 +1,26 @@ + + + + + + ./test/Asm89/ + + + + + + ./src/ + + + + diff --git a/vendor/asm89/stack-cors/src/Asm89/Stack/Cors.php b/vendor/asm89/stack-cors/src/Asm89/Stack/Cors.php new file mode 100755 index 0000000..a5e81ce --- /dev/null +++ b/vendor/asm89/stack-cors/src/Asm89/Stack/Cors.php @@ -0,0 +1,55 @@ + array(), + 'allowedMethods' => array(), + 'allowedOrigins' => array(), + 'exposedHeaders' => false, + 'maxAge' => false, + 'supportsCredentials' => false, + ); + + public function __construct(HttpKernelInterface $app, array $options = array()) + { + $this->app = $app; + $this->cors = new CorsService(array_merge($this->defaultOptions, $options)); + + } + + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + if ( ! $this->cors->isCorsRequest($request)) { + return $this->app->handle($request, $type, $catch); + } + + if ($this->cors->isPreflightRequest($request)) { + return $this->cors->handlePreflightRequest($request); + } + + if ( ! $this->cors->isActualRequestAllowed($request)) { + return new Response('Not allowed.', 403); + } + + $response = $this->app->handle($request, $type, $catch); + + return $this->cors->addActualRequestHeaders($response, $request); + } +} diff --git a/vendor/asm89/stack-cors/src/Asm89/Stack/CorsService.php b/vendor/asm89/stack-cors/src/Asm89/Stack/CorsService.php new file mode 100755 index 0000000..339256b --- /dev/null +++ b/vendor/asm89/stack-cors/src/Asm89/Stack/CorsService.php @@ -0,0 +1,178 @@ +options = $this->normalizeOptions($options); + } + + private function normalizeOptions(array $options = array()) + { + + $options += array( + 'allowedOrigins' => array(), + 'supportsCredentials' => false, + 'allowedHeaders' => array(), + 'exposedHeaders' => array(), + 'allowedMethods' => array(), + 'maxAge' => 0, + ); + + // normalize array('*') to true + if (in_array('*', $options['allowedOrigins'])) { + $options['allowedOrigins'] = true; + } + if (in_array('*', $options['allowedHeaders'])) { + $options['allowedHeaders'] = true; + } else { + $options['allowedHeaders'] = array_map('strtolower', $options['allowedHeaders']); + } + + if (in_array('*', $options['allowedMethods'])) { + $options['allowedMethods'] = true; + } else { + $options['allowedMethods'] = array_map('strtoupper', $options['allowedMethods']); + } + + return $options; + } + + public function isActualRequestAllowed(Request $request) + { + return $this->checkOrigin($request); + } + + public function isCorsRequest(Request $request) + { + return $request->headers->has('Origin'); + } + + public function isPreflightRequest(Request $request) + { + return $this->isCorsRequest($request) + &&$request->getMethod() === 'OPTIONS' + && $request->headers->has('Access-Control-Request-Method'); + } + + public function addActualRequestHeaders(Response $response, Request $request) + { + if ( ! $this->checkOrigin($request)) { + return $response; + } + + $response->headers->set('Access-Control-Allow-Origin', $request->headers->get('Origin')); + + if ( ! $response->headers->has('Vary')) { + $response->headers->set('Vary', 'Origin'); + } else { + $response->headers->set('Vary', $response->headers->get('Vary') . ', Origin'); + } + + if ($this->options['supportsCredentials']) { + $response->headers->set('Access-Control-Allow-Credentials', 'true'); + } + + if ($this->options['exposedHeaders']) { + $response->headers->set('Access-Control-Expose-Headers', implode(', ', $this->options['exposedHeaders'])); + } + + return $response; + } + + public function handlePreflightRequest(Request $request) + { + if (true !== $check = $this->checkPreflightRequestConditions($request)) { + return $check; + } + + return $this->buildPreflightCheckResponse($request); + } + + private function buildPreflightCheckResponse(Request $request) + { + $response = new Response(); + + if ($this->options['supportsCredentials']) { + $response->headers->set('Access-Control-Allow-Credentials', 'true'); + } + + $response->headers->set('Access-Control-Allow-Origin', $request->headers->get('Origin')); + + if ($this->options['maxAge']) { + $response->headers->set('Access-Control-Max-Age', $this->options['maxAge']); + } + + $allowMethods = $this->options['allowedMethods'] === true + ? strtoupper($request->headers->get('Access-Control-Request-Method')) + : implode(', ', $this->options['allowedMethods']); + $response->headers->set('Access-Control-Allow-Methods', $allowMethods); + + $allowHeaders = $this->options['allowedHeaders'] === true + ? strtoupper($request->headers->get('Access-Control-Request-Headers')) + : implode(', ', $this->options['allowedHeaders']); + $response->headers->set('Access-Control-Allow-Headers', $allowHeaders); + + return $response; + } + + private function checkPreflightRequestConditions(Request $request) + { + if ( ! $this->checkOrigin($request)) { + return $this->createBadRequestResponse(403, 'Origin not allowed'); + } + + if ( ! $this->checkMethod($request)) { + return $this->createBadRequestResponse(405, 'Method not allowed'); + } + + $requestHeaders = array(); + // if allowedHeaders has been set to true ('*' allow all flag) just skip this check + if ($this->options['allowedHeaders'] !== true && $request->headers->has('Access-Control-Request-Headers')) { + $headers = strtolower($request->headers->get('Access-Control-Request-Headers')); + $requestHeaders = explode(',', $headers); + + foreach ($requestHeaders as $header) { + if ( ! in_array(trim($header), $this->options['allowedHeaders'])) { + return $this->createBadRequestResponse(403, 'Header not allowed'); + } + } + } + + return true; + } + + private function createBadRequestResponse($code, $reason = '') + { + return new Response($reason, $code); + } + + private function checkOrigin(Request $request) { + if ($this->options['allowedOrigins'] === true) { + // allow all '*' flag + return true; + } + $origin = $request->headers->get('Origin'); + + return in_array($origin, $this->options['allowedOrigins']); + } + + private function checkMethod(Request $request) { + if ($this->options['allowedMethods'] === true) { + // allow all '*' flag + return true; + } + + $requestMethod = strtoupper($request->headers->get('Access-Control-Request-Method')); + return in_array($requestMethod, $this->options['allowedMethods']); + } + +} diff --git a/vendor/asm89/stack-cors/test/Asm89/Stack/CorsTest.php b/vendor/asm89/stack-cors/test/Asm89/Stack/CorsTest.php new file mode 100755 index 0000000..1493c46 --- /dev/null +++ b/vendor/asm89/stack-cors/test/Asm89/Stack/CorsTest.php @@ -0,0 +1,395 @@ +createStackedApp(); + $unmodifiedResponse = new Response(); + + $response = $app->handle(new Request()); + + $this->assertEquals($unmodifiedResponse->headers, $response->headers); + } + + /** + * @test + */ + public function it_returns_403_on_valid_actual_request_with_origin_not_allowed() + { + $app = $this->createStackedApp(array('allowedOrigins' => array('notlocalhost'))); + $request = $this->createValidActualRequest(); + + $response = $app->handle($request); + + $this->assertEquals(403, $response->getStatusCode()); + } + + /** + * @test + */ + public function it_returns_allow_origin_header_on_valid_actual_request() + { + $app = $this->createStackedApp(); + $request = $this->createValidActualRequest(); + + $response = $app->handle($request); + + $this->assertTrue($response->headers->has('Access-Control-Allow-Origin')); + $this->assertEquals('localhost', $response->headers->get('Access-Control-Allow-Origin')); + } + + /** + * @test + */ + public function it_returns_allow_origin_header_on_allow_all_origin_request() + { + $app = $this->createStackedApp(array('allowedOrigins' => array('*'))); + $request = new Request(); + $request->headers->set('Origin', 'http://localhost'); + + $response = $app->handle($request); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertTrue($response->headers->has('Access-Control-Allow-Origin')); + $this->assertEquals('http://localhost', $response->headers->get('Access-Control-Allow-Origin')); + } + + /** + * @test + */ + public function it_returns_allow_headers_header_on_allow_all_headers_request() + { + $app = $this->createStackedApp(array('allowedHeaders' => array('*'))); + $request = $this->createValidPreflightRequest(); + $request->headers->set('Access-Control-Request-Headers', 'Foo, BAR'); + + $response = $app->handle($request); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('FOO, BAR', $response->headers->get('Access-Control-Allow-Headers')); + } + + /** + * @test + */ + public function it_does_not_return_allow_origin_header_on_valid_actual_request_with_origin_not_allowed() + { + $app = $this->createStackedApp(array('allowedOrigins' => array('notlocalhost'))); + $request = $this->createValidActualRequest(); + + $response = $app->handle($request); + + $this->assertFalse($response->headers->has('Access-Control-Allow-Origin')); + } + + /** + * @test + */ + public function it_sets_allow_credentials_header_when_flag_is_set_on_valid_actual_request() + { + $app = $this->createStackedApp(array('supportsCredentials' => true)); + $request = $this->createValidActualRequest(); + + $response = $app->handle($request); + + $this->assertTrue($response->headers->has('Access-Control-Allow-Credentials')); + $this->assertEquals('true', $response->headers->get('Access-Control-Allow-Credentials')); + } + + /** + * @test + */ + public function it_does_not_set_allow_credentials_header_when_flag_is_not_set_on_valid_actual_request() + { + $app = $this->createStackedApp(); + $request = $this->createValidActualRequest(); + + $response = $app->handle($request); + + $this->assertFalse($response->headers->has('Access-Control-Allow-Credentials')); + } + + /** + * @test + */ + public function it_sets_exposed_headers_when_configured_on_actual_request() + { + $app = $this->createStackedApp(array('exposedHeaders' => array('x-exposed-header', 'x-another-exposed-header'))); + $request = $this->createValidActualRequest(); + + $response = $app->handle($request); + + $this->assertTrue($response->headers->has('Access-Control-Expose-Headers')); + $this->assertEquals('x-exposed-header, x-another-exposed-header', $response->headers->get('Access-Control-Expose-Headers')); + } + + /** + * @test + * @see http://www.w3.org/TR/cors/index.html#resource-implementation + */ + public function it_adds_a_vary_header() + { + $app = $this->createStackedApp(); + $request = $this->createValidActualRequest(); + + $response = $app->handle($request); + + $this->assertTrue($response->headers->has('Vary')); + $this->assertEquals('Origin', $response->headers->get('Vary')); + } + + /** + * @test + * @see http://www.w3.org/TR/cors/index.html#resource-implementation + */ + public function it_appends_an_existing_vary_header() + { + $app = $this->createStackedApp(array(), array('Vary' => 'Content-Type')); + $request = $this->createValidActualRequest(); + + $response = $app->handle($request); + + $this->assertTrue($response->headers->has('Vary')); + $this->assertEquals('Content-Type, Origin', $response->headers->get('Vary')); + } + + /** + * @test + */ + public function it_returns_access_control_headers_on_cors_request() + { + $app = $this->createStackedApp(); + $request = new Request(); + $request->headers->set('Origin', 'localhost'); + + $response = $app->handle($request); + + $this->assertTrue($response->headers->has('Access-Control-Allow-Origin')); + $this->assertEquals('localhost', $response->headers->get('Access-Control-Allow-Origin')); + } + + /** + * @test + */ + public function it_returns_access_control_headers_on_valid_preflight_request() + { + $app = $this->createStackedApp(); + $request = $this->createValidPreflightRequest(); + + $response = $app->handle($request); + + $this->assertTrue($response->headers->has('Access-Control-Allow-Origin')); + $this->assertEquals('localhost', $response->headers->get('Access-Control-Allow-Origin')); + } + + /** + * @test + */ + public function it_returns_403_on_valid_preflight_request_with_origin_not_allowed() + { + $app = $this->createStackedApp(array('allowedOrigins' => array('notlocalhost'))); + $request = $this->createValidPreflightRequest(); + + $response = $app->handle($request); + + $this->assertEquals(403, $response->getStatusCode()); + } + + /** + * @test + */ + public function it_does_not_modify_request_with_origin_not_allowed() + { + $passedOptions = array( + 'allowedOrigins' => array('notlocalhost'), + ); + + $service = new CorsService($passedOptions); + $request = $this->createValidActualRequest(); + $response = new Response(); + $service->addActualRequestHeaders($response, $request); + + $this->assertEquals($response, new Response()); + } + + /** + * @test + */ + public function it_returns_405_on_valid_preflight_request_with_method_not_allowed() + { + $app = $this->createStackedApp(array('allowedMethods' => array('put'))); + $request = $this->createValidPreflightRequest(); + + $response = $app->handle($request); + + $this->assertEquals(405, $response->getStatusCode()); + } + + /** + * @test + */ + public function it_allow_methods_on_valid_preflight_request() + { + $app = $this->createStackedApp(array('allowedMethods' => array('get', 'put'))); + $request = $this->createValidPreflightRequest(); + + $response = $app->handle($request); + + $this->assertTrue($response->headers->has('Access-Control-Allow-Methods')); + // it will uppercase the methods + $this->assertEquals('GET, PUT', $response->headers->get('Access-Control-Allow-Methods')); + } + + /** + * @test + */ + public function it_returns_valid_preflight_request_with_allow_methods_all() + { + $app = $this->createStackedApp(array('allowedMethods' => array('*'))); + $request = $this->createValidPreflightRequest(); + + $response = $app->handle($request); + + $this->assertTrue($response->headers->has('Access-Control-Allow-Methods')); + // it will return the Access-Control-Request-Method pass in the request + $this->assertEquals('GET', $response->headers->get('Access-Control-Allow-Methods')); + } + + /** + * @test + */ + public function it_returns_403_on_valid_preflight_request_with_one_of_the_requested_headers_not_allowed() + { + $app = $this->createStackedApp(); + $request = $this->createValidPreflightRequest(); + $request->headers->set('Access-Control-Request-Headers', 'x-not-allowed-header'); + + $response = $app->handle($request); + + $this->assertEquals(403, $response->getStatusCode()); + } + + /** + * @test + */ + public function it_returns_ok_on_valid_preflight_request_with_requested_headers_allowed() + { + $app = $this->createStackedApp(); + $requestHeaders = 'X-Allowed-Header, x-other-allowed-header'; + $request = $this->createValidPreflightRequest(); + $request->headers->set('Access-Control-Request-Headers', $requestHeaders); + + $response = $app->handle($request); + + $this->assertEquals(200, $response->getStatusCode()); + + $this->assertTrue($response->headers->has('Access-Control-Allow-Headers')); + // the response will have the "allowedHeaders" value passed to Cors rather than the request one + $this->assertEquals('x-allowed-header, x-other-allowed-header', $response->headers->get('Access-Control-Allow-Headers')); + } + + /** + * @test + */ + public function it_sets_allow_credentials_header_when_flag_is_set_on_valid_preflight_request() + { + $app = $this->createStackedApp(array('supportsCredentials' => true)); + $request = $this->createValidPreflightRequest(); + + $response = $app->handle($request); + + $this->assertTrue($response->headers->has('Access-Control-Allow-Credentials')); + $this->assertEquals('true', $response->headers->get('Access-Control-Allow-Credentials')); + } + + /** + * @test + */ + public function it_does_not_set_allow_credentials_header_when_flag_is_not_set_on_valid_preflight_request() + { + $app = $this->createStackedApp(); + $request = $this->createValidPreflightRequest(); + + $response = $app->handle($request); + + $this->assertFalse($response->headers->has('Access-Control-Allow-Credentials')); + } + + /** + * @test + */ + public function it_sets_max_age_when_set() + { + $app = $this->createStackedApp(array('maxAge' => 42)); + $request = $this->createValidPreflightRequest(); + + $response = $app->handle($request); + + $this->assertTrue($response->headers->has('Access-Control-Max-Age')); + $this->assertEquals(42, $response->headers->get('Access-Control-Max-Age')); + } + + private function createValidActualRequest() + { + $request = new Request(); + $request->headers->set('Origin', 'localhost'); + + return $request; + } + + private function createValidPreflightRequest() + { + $request = new Request(); + $request->headers->set('Origin', 'localhost'); + $request->headers->set('Access-Control-Request-Method', 'get'); + $request->setMethod('OPTIONS'); + + return $request; + } + + private function createStackedApp(array $options = array(), array $responseHeaders = array()) + { + $passedOptions = array_merge(array( + 'allowedHeaders' => array('x-allowed-header', 'x-other-allowed-header'), + 'allowedMethods' => array('delete', 'get', 'post', 'put'), + 'allowedOrigins' => array('localhost'), + 'exposedHeaders' => false, + 'maxAge' => false, + 'supportsCredentials' => false, + ), + $options + ); + + return new Cors(new MockApp($responseHeaders), $passedOptions); + } +} + +class MockApp implements HttpKernelInterface +{ + private $responseHeaders; + + public function __construct(array $responseHeaders) + { + $this->responseHeaders = $responseHeaders; + } + + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + $response = new Response(); + + $response->headers->add($this->responseHeaders); + + return $response; + } +} diff --git a/vendor/asm89/stack-cors/test/bootstrap.php b/vendor/asm89/stack-cors/test/bootstrap.php new file mode 100755 index 0000000..deb617f --- /dev/null +++ b/vendor/asm89/stack-cors/test/bootstrap.php @@ -0,0 +1,10 @@ +add('Asm89\Stack', __DIR__); + $loader->add('Asm89\Stack', __DIR__ . '/../src'); +} else { + throw new RuntimeException('Install dependencies to run test suite.'); +} + diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php index bb43043..5df820a 100644 --- a/vendor/composer/autoload_namespaces.php +++ b/vendor/composer/autoload_namespaces.php @@ -21,4 +21,5 @@ 'Doctrine\\Common\\Annotations\\' => array($vendorDir . '/doctrine/annotations/lib'), 'Doctrine\\Common\\' => array($vendorDir . '/doctrine/common/lib'), 'Composer\\Installers\\' => array($vendorDir . '/composer/installers/src'), + 'Asm89\\Stack' => array($vendorDir . '/asm89/stack-cors/src'), ); diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index e2b7991..87fec7e 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -3857,5 +3857,50 @@ "phantomjs", "testing" ] + }, + { + "name": "asm89/stack-cors", + "version": "0.2.1", + "version_normalized": "0.2.1.0", + "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" + }, + "time": "2014-07-28 07:22:35", + "type": "library", + "installation-source": "dist", + "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" + ] } ]