.../Block/BlockHalJsonCookieTest.php | 40 +++++++++ .../Comment/CommentHalJsonCookieTest.php | 19 +++++ .../ConfigTest/ConfigTestHalJsonCookieTest.php | 40 +++++++++ .../EntityTest/EntityTestHalJsonCookieTest.php | 19 +++++ .../EntityResource/Node/NodeHalJsonCookieTest.php | 19 +++++ .../EntityResource/Role/RoleHalJsonCookieTest.php | 40 +++++++++ .../EntityResource/Term/TermHalJsonCookieTest.php | 18 ++++ .../EntityResource/User/UserHalJsonCookieTest.php | 19 +++++ .../Vocabulary/VocabularyHalJsonCookieTest.php | 40 +++++++++ .../tests/src/Functional/AnonResourceTestTrait.php | 6 ++ .../src/Functional/BasicAuthResourceTestTrait.php | 8 +- .../src/Functional/CookieResourceTestTrait.php | 98 ++++++++++++++++++++++ .../EntityResource/Block/BlockJsonCookieTest.php | 34 ++++++++ .../Comment/CommentJsonCookieTest.php | 34 ++++++++ .../Comment/CommentResourceTestBase.php | 3 +- .../ConfigTest/ConfigTestJsonCookieTest.php | 34 ++++++++ .../EntityResource/EntityResourceTestBase.php | 32 ++----- .../EntityTest/EntityTestJsonCookieTest.php | 34 ++++++++ .../EntityResource/Node/NodeJsonCookieTest.php | 34 ++++++++ .../EntityResource/Role/RoleJsonCookieTest.php | 36 ++++++++ .../EntityResource/Term/TermJsonCookieTest.php | 34 ++++++++ .../EntityResource/User/UserJsonCookieTest.php | 34 ++++++++ .../Vocabulary/VocabularyJsonCookieTest.php | 34 ++++++++ .../rest/tests/src/Functional/ResourceTestBase.php | 28 ++++++- 24 files changed, 711 insertions(+), 26 deletions(-) diff --git a/core/modules/hal/tests/src/Functional/EntityResource/Block/BlockHalJsonCookieTest.php b/core/modules/hal/tests/src/Functional/EntityResource/Block/BlockHalJsonCookieTest.php new file mode 100644 index 0000000..1403ebe --- /dev/null +++ b/core/modules/hal/tests/src/Functional/EntityResource/Block/BlockHalJsonCookieTest.php @@ -0,0 +1,40 @@ + [ 'Authorization' => 'Basic ' . base64_encode($this->account->name->value . ':' . $this->account->passRaw), @@ -27,4 +28,9 @@ protected function verifyResponseWhenMissingAuthentication(ResponseInterface $re $this->assertResourceErrorResponse(401, 'No authentication credentials provided.', $response); } + /** + * {@inheritdoc} + */ + protected function assertAuthenticationEdgeCases($method, Url $url, array $request_options) {} + } diff --git a/core/modules/rest/tests/src/Functional/CookieResourceTestTrait.php b/core/modules/rest/tests/src/Functional/CookieResourceTestTrait.php new file mode 100644 index 0000000..500cfe2 --- /dev/null +++ b/core/modules/rest/tests/src/Functional/CookieResourceTestTrait.php @@ -0,0 +1,98 @@ +setRouteParameter('_format', 'json'); + + $request_body = [ + 'name' => $this->account->name->value, + 'pass' => $this->account->passRaw, + ]; + + $request_options[RequestOptions::BODY] = $this->serializer->encode($request_body, 'json'); + $request_options[RequestOptions::HEADERS]['Accept'] = 'application/json'; + $response = $this->request('POST', $user_login_url, $request_options); + + // Parse and store the session cookie. + $this->sessionCookie = explode(';', $response->getHeader('Set-Cookie')[0], 2)[0]; + + // Parse and store the CSRF token and logout token. + $data = $this->serializer->decode($response->getBody()->getContents(), static::$format); + $this->csrfToken = $data['csrf_token']; + $this->logoutToken = $data['logout_token']; + } + + /** + * {@inheritdoc} + */ + protected function getAuthenticationRequestOptions($method) { + $request_options[RequestOptions::HEADERS]['Cookie'] = $this->sessionCookie; + if (!in_array($method, ['HEAD', 'GET'])) { + $request_options[RequestOptions::HEADERS]['X-CSRF-Token'] = $this->csrfToken; + } + return $request_options; + } + + // @todo XCSRF token missing/invalid edge cases!!!!!!!!!!! + + /** + * {@inheritdoc} + */ + protected function verifyResponseWhenMissingAuthentication(ResponseInterface $response) { + $this->assertResourceErrorResponse(403, '', $response); + } + + + /** + * {@inheritdoc} + */ + protected function assertAuthenticationEdgeCases($method, Url $url, array $request_options) { + // X-CSRF-Token request header is unnecessary for safe HTTP methods. No need + // for additional assertions. + if (in_array($method, ['HEAD', 'GET'])) { + return; + } + + + unset($request_options[RequestOptions::HEADERS]['X-CSRF-Token']); + + + // DX: 403 when missing X-CSRF-Token request header. + $response = $this->request($method, $url, $request_options); + $this->assertResourceErrorResponse(403, 'X-CSRF-Token request header is missing', $response); + + + $request_options[RequestOptions::HEADERS]['X-CSRF-Token'] = 'this-is-not-the-token-you-are-looking-for'; + + + // DX: 403 when invalid X-CSRF-Token request header. + $response = $this->request($method, $url, $request_options); + $this->assertResourceErrorResponse(403, 'X-CSRF-Token request header is invalid', $response); + + + $request_options[RequestOptions::HEADERS]['X-CSRF-Token'] = $this->csrfToken; + } + +} diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Block/BlockJsonCookieTest.php b/core/modules/rest/tests/src/Functional/EntityResource/Block/BlockJsonCookieTest.php new file mode 100644 index 0000000..f609448 --- /dev/null +++ b/core/modules/rest/tests/src/Functional/EntityResource/Block/BlockJsonCookieTest.php @@ -0,0 +1,34 @@ +initAuthentication(); $this->provisionEntityResource(); $this->setUpAuthorization('POST'); @@ -215,7 +216,7 @@ public function testPostDxWithoutCriticalBaseFields() { $request_options = []; $request_options[RequestOptions::HEADERS]['Accept'] = static::$mimeType; $request_options[RequestOptions::HEADERS]['Content-Type'] = static::$mimeType; - $request_options = array_merge_recursive($request_options, $this->getAuthenticationRequestOptions()); + $request_options = array_merge_recursive($request_options, $this->getAuthenticationRequestOptions('POST')); // DX: 422 when missing 'entity_type' field. $request_options[RequestOptions::BODY] = $this->serializer->encode(array_diff_key($this->getNormalizedEntityToCreate(), ['entity_type' => TRUE]), static::$format); diff --git a/core/modules/rest/tests/src/Functional/EntityResource/ConfigTest/ConfigTestJsonCookieTest.php b/core/modules/rest/tests/src/Functional/EntityResource/ConfigTest/ConfigTestJsonCookieTest.php new file mode 100644 index 0000000..c243bde --- /dev/null +++ b/core/modules/rest/tests/src/Functional/EntityResource/ConfigTest/ConfigTestJsonCookieTest.php @@ -0,0 +1,34 @@ +initAuthentication(); $has_canonical_url = $this->entity->hasLinkTemplate('canonical'); // The URL and Guzzle request options that will be used in this test. The @@ -189,7 +190,7 @@ public function testGet() { // DX: 406 when ?_format is missing, except when requesting a canonical HTML // route. $response = $this->request('GET', $url, $request_options); - if ($has_canonical_url && !static::$auth) { + if ($has_canonical_url && (!static::$auth || static::$auth === 'cookie')) { $this->assertSame(403, $response->getStatusCode()); } else { @@ -207,7 +208,7 @@ public function testGet() { $this->verifyResponseWhenMissingAuthentication($response); } - $request_options = array_merge_recursive($request_options, $this->getAuthenticationRequestOptions()); + $request_options = array_merge_recursive($request_options, $this->getAuthenticationRequestOptions('GET')); // DX: 403 when unauthorized. @@ -327,6 +328,7 @@ public function testPost() { return; } + $this->initAuthentication(); $has_canonical_url = $this->entity->hasLinkTemplate('canonical'); // Try with all of the following request bodies. @@ -424,7 +426,7 @@ public function testPost() { } - $request_options = array_merge_recursive($request_options, $this->getAuthenticationRequestOptions()); + $request_options = array_merge_recursive($request_options, $this->getAuthenticationRequestOptions('POST')); // DX: 403 when unauthorized. @@ -470,6 +472,10 @@ public function testPost() { $request_options[RequestOptions::BODY] = $parseable_valid_request_body; + // Before sending a well-formed request, allow the authentication provider's + // edge cases to also be tested. + $this->assertAuthenticationEdgeCases('POST', $url, $request_options); + // 201 for well-formed request. $response = $this->request('POST', $url, $request_options); $this->assertResourceResponse(201, FALSE, $response); @@ -549,24 +555,4 @@ protected function assert406Response(ResponseInterface $response) { } } - /** - * Simulate common developer mistake when performing an unsafe operation: - * - forget to specify the X-CSRF-Token request header - * - specify in invalid X-CSRF-Token request header value - * - * In either case, the REST module must provide meaningful feedback for DX. - */ - protected function performUnsafeOperation($method) { - // Try without CSRF token - // …request - $this->assertSame(403, $this->getSession()->getStatusCode()); - $this->assertSession()->responseContains('X-CSRF-Token request header is missing'); - // Try with invalid CSRF token - // …request - $this->assertSame(403, $this->getSession()->getStatusCode()); - $this->assertSession()->responseContains('X-CSRF-Token request header is invalid'); - // Try with valid CSRF token - // …request - } - } diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestJsonCookieTest.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestJsonCookieTest.php new file mode 100644 index 0000000..8407922 --- /dev/null +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestJsonCookieTest.php @@ -0,0 +1,34 @@ +