diff --git a/core/modules/rest/lib/Drupal/rest/Access/CSRFAccessCheck.php b/core/modules/rest/lib/Drupal/rest/Access/CSRFAccessCheck.php index c53bcea..396b3a1 100644 --- a/core/modules/rest/lib/Drupal/rest/Access/CSRFAccessCheck.php +++ b/core/modules/rest/lib/Drupal/rest/Access/CSRFAccessCheck.php @@ -45,7 +45,8 @@ public function applies(Route $route) { */ public function access(Route $route, Request $request, AccountInterface $account) { $method = $request->getMethod(); - $cookie = $request->cookies->get(session_name(), FALSE); + $cookie = $request->attributes->get('_authentication_provider') == 'cookie'; + // This check only applies if // 1. this is a write operation // 2. the user was successfully authenticated and diff --git a/core/modules/rest/lib/Drupal/rest/Tests/CreateTest.php b/core/modules/rest/lib/Drupal/rest/Tests/CreateTest.php index 184e811..809622a 100644 --- a/core/modules/rest/lib/Drupal/rest/Tests/CreateTest.php +++ b/core/modules/rest/lib/Drupal/rest/Tests/CreateTest.php @@ -101,19 +101,6 @@ public function testCreate() { $this->httpRequest('entity/' . $entity_type, 'POST', NULL, $this->defaultMimeType); $this->assertResponse(400); - // Try to create an entity without the CSRF token. - $this->curlExec(array( - CURLOPT_HTTPGET => FALSE, - CURLOPT_POST => TRUE, - CURLOPT_CUSTOMREQUEST => 'POST', - CURLOPT_POSTFIELDS => $serialized, - CURLOPT_URL => url('entity/' . $entity_type, array('absolute' => TRUE)), - CURLOPT_NOBODY => FALSE, - CURLOPT_HTTPHEADER => array('Content-Type: ' . $this->defaultMimeType), - )); - $this->assertResponse(403); - $this->assertFalse(entity_load_multiple($entity_type, NULL, TRUE), 'No entity has been created in the database.'); - // Try to send invalid data to trigger the entity validation constraints. // Send a UUID that is too long. $entity->set('uuid', $this->randomName(129)); diff --git a/core/modules/rest/lib/Drupal/rest/Tests/CsrfTest.php b/core/modules/rest/lib/Drupal/rest/Tests/CsrfTest.php new file mode 100644 index 0000000..c6d4499 --- /dev/null +++ b/core/modules/rest/lib/Drupal/rest/Tests/CsrfTest.php @@ -0,0 +1,128 @@ + 'CSRF access', + 'description' => 'Tests the CSRF protection.', + 'group' => 'REST', + ); + } + + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + + $this->enableService('entity:' . $this->testEntityType, 'POST', 'hal_json', array('basic_auth', 'cookie')); + + // Create a user account that has the required permissions to create + // resources via the REST API. + $permissions = $this->entityPermissions($this->testEntityType, 'create'); + $permissions[] = 'restful post entity:' . $this->testEntityType; + $this->account = $this->drupalCreateUser($permissions); + + // Serialize an entity to a string to use in the content body of the POST + // request. + $serializer = $this->container->get('serializer'); + $entity_values = $this->entityValues($this->testEntityType); + $entity = entity_create($this->testEntityType, $entity_values); + $this->serialized = $serializer->serialize($entity, $this->defaultFormat); + } + + /** + * Tests that CSRF check is not triggered for Basic Auth requests. + */ + public function testBasicAuth() { + // Login so the session cookie is sent in addition to the basic auth header. + $this->drupalLogin($this->account); + + $curl_options = $this->getCurlOptions(); + $curl_options[CURLOPT_HTTPAUTH] = CURLAUTH_BASIC; + $curl_options[CURLOPT_USERPWD] = $this->account->getUsername() . ':' . $this->account->pass_raw; + $this->curlExec($curl_options); + $this->assertResponse(201); + // Ensure that the entity was created. + $loaded_entity = $this->loadEntityFromLocationHeader($this->drupalGetHeader('location')); + $this->assertTrue($loaded_entity, 'An entity was created in the database'); + } + + /** + * Tests that CSRF check is triggered for Cookie Auth requests. + */ + public function testCookieAuth() { + $this->drupalLogin($this->account); + + $curl_options = $this->getCurlOptions(); + + // Try to create an entity without the CSRF token. + $this->curlExec($curl_options); + $this->assertResponse(403); + // Ensure that the entity was not created. + $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'); + $curl_options[CURLOPT_HTTPHEADER][] = "X-CSRF-Token: $token"; + $this->curlExec($curl_options); + $this->assertResponse(201); + // Ensure that the entity was created. + $loaded_entity = $this->loadEntityFromLocationHeader($this->drupalGetHeader('location')); + $this->assertTrue($loaded_entity, 'An entity was created in the database'); + } + + /** + * Gets the cURL options to create an entity with POST. + * + * @return array + * The array of cURL options. + */ + protected function getCurlOptions() { + return array( + CURLOPT_HTTPGET => FALSE, + CURLOPT_POST => TRUE, + CURLOPT_POSTFIELDS => $this->serialized, + CURLOPT_URL => url('entity/' . $this->testEntityType, array('absolute' => TRUE)), + CURLOPT_NOBODY => FALSE, + CURLOPT_HTTPHEADER => array( + "Content-Type: {$this->defaultMimeType}", + ), + ); + } +} diff --git a/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php b/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php index d6fc9f9..d1ae9c9 100644 --- a/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php +++ b/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php @@ -29,6 +29,13 @@ */ protected $defaultMimeType; + /** + * The entity type to use for testing. + * + * @var string + */ + protected $testEntityType = 'entity_test'; + protected function setUp() { parent::setUp(); $this->defaultFormat = 'hal_json'; @@ -295,4 +302,19 @@ protected function entityPermissions($entity_type, $operation) { } } } + + /** + * Loads an entity based on the location URL returned in the location header. + * + * @param string $location_url + * The URL returned in the Location header. + * + * @return \Drupal\Core\Entity\Entity|FALSE. + * The entity or FALSE if there is no matching entity. + */ + protected function loadEntityFromLocationHeader($location_url) { + $url_parts = explode('/', $location_url); + $id = end($url_parts); + return entity_load($this->testEntityType, $id); + } }