Issue #2795965 by Wim Leers, neclimdul: REST requests with invalid X-CSRF-Token header get "missing " mesage

8.3.x
Alex Pott 2016-09-22 14:50:25 +01:00
parent 7f43b7ba67
commit 676f38d8c7
5 changed files with 48 additions and 21 deletions

View File

@ -97,12 +97,15 @@ class CsrfRequestHeaderAccessCheck implements AccessCheckInterface {
&& $account->isAuthenticated()
&& $this->sessionConfiguration->hasSession($request)
) {
if (!$request->headers->has('X-CSRF-Token')) {
return AccessResult::forbidden()->setReason('X-CSRF-Token request header is missing')->setCacheMaxAge(0);
}
$csrf_token = $request->headers->get('X-CSRF-Token');
// @todo Remove validate call using 'rest' in 8.3.
// Kept here for sessions active during update.
if (!$this->csrfToken->validate($csrf_token, self::TOKEN_KEY)
&& !$this->csrfToken->validate($csrf_token, 'rest')) {
return AccessResult::forbidden()->setReason('X-CSRF-Token request header is missing')->setCacheMaxAge(0);
return AccessResult::forbidden()->setReason('X-CSRF-Token request header is invalid')->setCacheMaxAge(0);
}
}
// Let other access checkers decide if the request is legit.

View File

@ -366,10 +366,16 @@ class CreateTest extends RESTTestBase {
// Note: this will fail with PHP 5.6 when always_populate_raw_post_data is
// set to something other than -1. See https://www.drupal.org/node/2456025.
// Try first without the CSRF token, which should fail.
$this->httpRequest('entity/' . $entity_type, 'POST', $serialized, $this->defaultMimeType, TRUE);
$this->assertResponse(403, 'X-CSRF-Token request header is missing');
$url = Url::fromUri('internal:/entity/' . $entity_type)->setOption('query', ['_format' => $this->defaultFormat]);
$this->httpRequest($url, 'POST', $serialized, $this->defaultMimeType, FALSE);
$this->assertResponse(403);
$this->assertRaw('X-CSRF-Token request header is missing');
// Then try with an invalid CSRF token.
$this->httpRequest($url, 'POST', $serialized, $this->defaultMimeType, 'invalid-csrf-token');
$this->assertResponse(403);
$this->assertRaw('X-CSRF-Token request header is invalid');
// Then try with the CSRF token.
$response = $this->httpRequest('entity/' . $entity_type, 'POST', $serialized, $this->defaultMimeType);
$response = $this->httpRequest($url, 'POST', $serialized, $this->defaultMimeType);
$this->assertResponse(201);
// Make sure that the response includes an entity in the body and check the

View File

@ -38,10 +38,17 @@ class DeleteTest extends RESTTestBase {
$entity = $this->entityCreate($entity_type);
$entity->save();
// Try first to delete over REST API without the CSRF token.
$this->httpRequest($entity->urlInfo(), 'DELETE', NULL, NULL, TRUE);
$this->assertResponse(403, 'X-CSRF-Token request header is missing');
$url = $entity->toUrl()->setRouteParameter('_format', $this->defaultFormat);
$this->httpRequest($url, 'DELETE', NULL, 'application/hal+json', FALSE);
$this->assertResponse(403);
$this->assertRaw('X-CSRF-Token request header is missing');
// Then try with an invalid CSRF token.
$this->httpRequest($url, 'DELETE', NULL, 'application/hal+json', 'invalid-csrf-token');
$this->assertResponse(403);
$this->assertRaw('X-CSRF-Token request header is invalid');
// Delete it over the REST API.
$response = $this->httpRequest($entity->urlInfo(), 'DELETE');
$response = $this->httpRequest($url, 'DELETE');
$this->assertResponse(204);
// Clear the static cache with entity_load(), otherwise we won't see the
// update.
$storage = $this->container->get('entity_type.manager')

View File

@ -85,19 +85,22 @@ abstract class RESTTestBase extends WebTestBase {
* The body for POST and PUT.
* @param string $mime_type
* The MIME type of the transmitted content.
* @param bool $forget_xcsrf_token
* If TRUE, the CSRF token won't be included in request.
* @param bool $csrf_token
* If NULL, a CSRF token will be retrieved and used. If FALSE, omit the
* X-CSRF-Token request header (to simulate developer error). Otherwise, the
* passed in value will be used as the value for the X-CSRF-Token request
* header (to simulate developer error, by sending an invalid CSRF token).
*
* @return string
* The content returned from the request.
*/
protected function httpRequest($url, $method, $body = NULL, $mime_type = NULL, $forget_xcsrf_token = FALSE) {
protected function httpRequest($url, $method, $body = NULL, $mime_type = NULL, $csrf_token = NULL) {
if (!isset($mime_type)) {
$mime_type = $this->defaultMimeType;
}
if (!in_array($method, array('GET', 'HEAD', 'OPTIONS', 'TRACE'))) {
// GET the CSRF token first for writing requests.
$token = $this->drupalGet('session/token');
$requested_token = $this->drupalGet('session/token');
}
$url = $this->buildUrl($url);
@ -132,9 +135,9 @@ abstract class RESTTestBase extends WebTestBase {
CURLOPT_POSTFIELDS => $body,
CURLOPT_URL => $url,
CURLOPT_NOBODY => FALSE,
CURLOPT_HTTPHEADER => !$forget_xcsrf_token ? array(
CURLOPT_HTTPHEADER => $csrf_token !== FALSE ? array(
'Content-Type: ' . $mime_type,
'X-CSRF-Token: ' . $token,
'X-CSRF-Token: ' . ($csrf_token === NULL ? $requested_token : $csrf_token),
) : array(
'Content-Type: ' . $mime_type,
),
@ -148,9 +151,9 @@ abstract class RESTTestBase extends WebTestBase {
CURLOPT_POSTFIELDS => $body,
CURLOPT_URL => $url,
CURLOPT_NOBODY => FALSE,
CURLOPT_HTTPHEADER => !$forget_xcsrf_token ? array(
CURLOPT_HTTPHEADER => $csrf_token !== FALSE ? array(
'Content-Type: ' . $mime_type,
'X-CSRF-Token: ' . $token,
'X-CSRF-Token: ' . ($csrf_token === NULL ? $requested_token : $csrf_token),
) : array(
'Content-Type: ' . $mime_type,
),
@ -164,9 +167,9 @@ abstract class RESTTestBase extends WebTestBase {
CURLOPT_POSTFIELDS => $body,
CURLOPT_URL => $url,
CURLOPT_NOBODY => FALSE,
CURLOPT_HTTPHEADER => !$forget_xcsrf_token ? array(
CURLOPT_HTTPHEADER => $csrf_token !== FALSE ? array(
'Content-Type: ' . $mime_type,
'X-CSRF-Token: ' . $token,
'X-CSRF-Token: ' . ($csrf_token === NULL ? $requested_token : $csrf_token),
) : array(
'Content-Type: ' . $mime_type,
),
@ -179,7 +182,9 @@ abstract class RESTTestBase extends WebTestBase {
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_URL => $url,
CURLOPT_NOBODY => FALSE,
CURLOPT_HTTPHEADER => !$forget_xcsrf_token ? array('X-CSRF-Token: ' . $token) : array(),
CURLOPT_HTTPHEADER => $csrf_token !== FALSE ? array(
'X-CSRF-Token: ' . ($csrf_token === NULL ? $requested_token : $csrf_token),
) : array(),
);
break;
}
@ -197,6 +202,8 @@ abstract class RESTTestBase extends WebTestBase {
$this->verbose($method . ' request to: ' . $url .
'<hr />Code: ' . curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE) .
(isset($curl_options[CURLOPT_HTTPHEADER]) ? '<hr />Request headers: ' . nl2br(print_r($curl_options[CURLOPT_HTTPHEADER], TRUE)) : '' ) .
(isset($curl_options[CURLOPT_POSTFIELDS]) ? '<hr />Request body: ' . nl2br(print_r($curl_options[CURLOPT_POSTFIELDS], TRUE)) : '' ) .
'<hr />Response headers: ' . nl2br(print_r($headers, TRUE)) .
'<hr />Response body: ' . $this->responseBody);

View File

@ -381,9 +381,13 @@ class UpdateTest extends RESTTestBase {
$serialized = $serializer->serialize($normalized, $format, $context);
// Try first without CSRF token which should fail.
$this->httpRequest($url, 'PATCH', $serialized, $mime_type, TRUE);
$this->assertResponse(403, 'X-CSRF-Token request header is missing');
$this->httpRequest($url, 'PATCH', $serialized, $mime_type, FALSE);
$this->assertResponse(403);
$this->assertRaw('X-CSRF-Token request header is missing');
// Then try with an invalid CSRF token.
$this->httpRequest($url, 'PATCH', $serialized, $mime_type, 'invalid-csrf-token');
$this->assertResponse(403);
$this->assertRaw('X-CSRF-Token request header is invalid');
// Then try with CSRF token.
$this->httpRequest($url, 'PATCH', $serialized, $mime_type);
$this->assertResponse(200);