diff --git a/core/lib/Drupal/Core/Access/CsrfRequestHeaderAccessCheck.php b/core/lib/Drupal/Core/Access/CsrfRequestHeaderAccessCheck.php index 065efc2..d9cab5b 100644 --- a/core/lib/Drupal/Core/Access/CsrfRequestHeaderAccessCheck.php +++ b/core/lib/Drupal/Core/Access/CsrfRequestHeaderAccessCheck.php @@ -36,6 +36,7 @@ public function applies(Route $route) { $requirements = $route->getRequirements(); // Check for current requirement _csrf_request_header_token and deprecated // REST requirement. + // @todo Remove _access_rest_csrf in Drupal 9.0.0. $applicable_requirements = ['_csrf_request_header_token', '_access_rest_csrf']; $requirement_keys = array_keys($requirements); diff --git a/core/modules/rest/rest.routing.yml b/core/modules/rest/rest.routing.yml index 843dee9..2fdb1d7 100644 --- a/core/modules/rest/rest.routing.yml +++ b/core/modules/rest/rest.routing.yml @@ -1,6 +1,9 @@ +# @deprecated This path is deprectated use the system.csrftoken route from the +# system module instead. +# @todo Remove this route in Drupal 9.0.0. rest.csrftoken: path: '/rest/session/token' defaults: - _controller: '\Drupal\rest\RequestHandler::csrfToken' + _controller: '\Drupal\system\Controller\CSRFTokenController::csrfToken' requirements: _access: 'TRUE' diff --git a/core/modules/rest/rest.services.yml b/core/modules/rest/rest.services.yml index a3c8d58..594c76c 100644 --- a/core/modules/rest/rest.services.yml +++ b/core/modules/rest/rest.services.yml @@ -8,6 +8,9 @@ services: - { name: cache.bin } factory: cache_factory:get arguments: [rest] + # @todo Remove this service in Drupal 9.0.0. + access_check.rest.csrf: + alias: access_check.header.csrf rest.link_manager: class: Drupal\rest\LinkManager\LinkManager arguments: ['@rest.link_manager.type', '@rest.link_manager.relation'] diff --git a/core/modules/rest/src/RequestHandler.php b/core/modules/rest/src/RequestHandler.php index 228135f..df4c566 100644 --- a/core/modules/rest/src/RequestHandler.php +++ b/core/modules/rest/src/RequestHandler.php @@ -137,16 +137,6 @@ public function handle(RouteMatchInterface $route_match, Request $request) { } /** - * Generates a CSRF protecting session token. - * - * @return \Symfony\Component\HttpFoundation\Response - * The response object. - */ - public function csrfToken() { - return new Response(\Drupal::csrfToken()->get('header'), 200, array('Content-Type' => 'text/plain')); - } - - /** * Renders a resource response. * * Serialization can invoke rendering (e.g., generating URLs), but the diff --git a/core/modules/rest/src/Tests/CsrfTest.php b/core/modules/rest/src/Tests/CsrfTest.php index f98abad..d9d6f0c 100644 --- a/core/modules/rest/src/Tests/CsrfTest.php +++ b/core/modules/rest/src/Tests/CsrfTest.php @@ -87,7 +87,7 @@ public function testCookieAuth() { $this->assertFalse(entity_load_multiple($this->testEntityType, NULL, TRUE), 'No entity has been created in the database.'); // Create an entity with the CSRF token. - $token = $this->drupalGet('rest/session/token'); + $token = $this->drupalGet('session/token'); $curl_options[CURLOPT_HTTPHEADER][] = "X-CSRF-Token: $token"; $this->curlExec($curl_options); $this->assertResponse(201); diff --git a/core/modules/rest/src/Tests/RESTTestBase.php b/core/modules/rest/src/Tests/RESTTestBase.php index 4779b6b..f7cf3dd 100644 --- a/core/modules/rest/src/Tests/RESTTestBase.php +++ b/core/modules/rest/src/Tests/RESTTestBase.php @@ -95,7 +95,7 @@ protected function httpRequest($url, $method, $body = NULL, $mime_type = NULL) { } if (!in_array($method, array('GET', 'HEAD', 'OPTIONS', 'TRACE'))) { // GET the CSRF token first for writing requests. - $token = $this->drupalGet('rest/session/token'); + $token = $this->drupalGet('session/token'); } $url = $this->buildUrl($url); diff --git a/core/modules/system/src/Controller/CSRFTokenController.php b/core/modules/system/src/Controller/CSRFTokenController.php new file mode 100644 index 0000000..66eda7b --- /dev/null +++ b/core/modules/system/src/Controller/CSRFTokenController.php @@ -0,0 +1,51 @@ +tokenGenerator = $token_generator; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('csrf_token') + ); + } + + /** + * Returns a CSRF protecting session token. + * + * @return \Symfony\Component\HttpFoundation\Response + * The response object. + */ + public function csrfToken() { + return new Response($this->tokenGenerator->get('header'), 200, array('Content-Type' => 'text/plain')); + } + +} diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml index 547b083..9449847 100644 --- a/core/modules/system/system.routing.yml +++ b/core/modules/system/system.routing.yml @@ -492,3 +492,10 @@ system.entity_autocomplete: _controller: '\Drupal\system\Controller\EntityAutocompleteController::handleAutocomplete' requirements: _access: 'TRUE' + +system.csrftoken: + path: '/session/token' + defaults: + _controller: '\Drupal\system\Controller\CSRFTokenController::csrfToken' + requirements: + _access: 'TRUE' diff --git a/core/modules/system/tests/modules/csrf_test/csrf_test.info.yml b/core/modules/system/tests/modules/csrf_test/csrf_test.info.yml new file mode 100644 index 0000000..c830f58 --- /dev/null +++ b/core/modules/system/tests/modules/csrf_test/csrf_test.info.yml @@ -0,0 +1,6 @@ +name: CSRF test +type: module +description: 'Support testing protecting routes with CSRF token.' +package: Testing +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/csrf_test/csrf_test.routing.yml b/core/modules/system/tests/modules/csrf_test/csrf_test.routing.yml new file mode 100644 index 0000000..4a4cf1e --- /dev/null +++ b/core/modules/system/tests/modules/csrf_test/csrf_test.routing.yml @@ -0,0 +1,18 @@ +# Tests CSRF Header Token protection. +csrf_test.protected: + path: csrf/protected + defaults: + _controller: '\Drupal\csrf_test\Controller\TestController::testMethod' + requirements: + _csrf_request_header_token: 'TRUE' + _method: 'POST' +# Tests deprecated _access_rest_csrf protection. +# This originally was in the REST module but now is supported in Core/lib. +# @todo Remove this test route in Drupal 9.0.0. +csrf_test.deprecated.protected: + path: csrf/deprecated/protected + defaults: + _controller: '\Drupal\csrf_test\Controller\TestController::testMethod' + #methods: [POST] + requirements: + _access_rest_csrf: 'TRUE' diff --git a/core/modules/system/tests/modules/csrf_test/src/Controller/TestController.php b/core/modules/system/tests/modules/csrf_test/src/Controller/TestController.php new file mode 100644 index 0000000..d722b3a --- /dev/null +++ b/core/modules/system/tests/modules/csrf_test/src/Controller/TestController.php @@ -0,0 +1,22 @@ +drupalCreateUser(); + $this->drupalLogin($user); + + $csrf_token = $this->drupalGet('session/token'); + $url = Url::fromRoute($route_name) + ->setAbsolute(TRUE) + ->toString(); + $domain = parse_url($url, PHP_URL_HOST); + + $session_id = $this->getSession()->getCookie($this->getSessionName()); + /** @var \GuzzleHttp\Cookie\CookieJar $cookies */ + $cookies = CookieJar::fromArray([$this->getSessionName() => $session_id], $domain); + $post_options = [ + 'headers' => ['Accept' => 'text/plain'], + 'http_errors' => FALSE, + ]; + + // Test that access is allowed for anonymous user with no token in header. + $result = $client->post($url, $post_options); + $this->assertEquals(200, $result->getStatusCode()); + + // Add cookies to post options so that all other requests are for the + // authenticated user. + $post_options['cookies'] = $cookies; + + // Test that access is denied with no token in header. + $result = $client->post($url, $post_options); + $this->assertEquals(403, $result->getStatusCode()); + + // Test that access is allowed with correct token in header. + $post_options['headers']['X-CSRF-Token'] = $csrf_token; + $result = $client->post($url, $post_options); + $this->assertEquals(200, $result->getStatusCode()); + + // Test that access is denied with incorrect token in header. + $post_options['headers']['X-CSRF-Token'] = 'this-is-not-the-token-you-are-looking-for'; + $result = $client->post($url, $post_options); + $this->assertEquals(403, $result->getStatusCode()); + } + } + +}