Issue #2795965 by Wim Leers, neclimdul: REST requests with invalid X-CSRF-Token header get "missing " mesage
parent
7f43b7ba67
commit
676f38d8c7
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue