annotate core/modules/rest/tests/src/Functional/CookieResourceTestTrait.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children 7a779792577d
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\Tests\rest\Functional;
Chris@0 4
Chris@0 5 use Drupal\Core\Url;
Chris@0 6 use GuzzleHttp\RequestOptions;
Chris@0 7 use Psr\Http\Message\ResponseInterface;
Chris@0 8
Chris@0 9 /**
Chris@0 10 * Trait for ResourceTestBase subclasses testing $auth=cookie.
Chris@0 11 *
Chris@0 12 * Characteristics:
Chris@0 13 * - After performing a valid "log in" request, the server responds with a 2xx
Chris@0 14 * status code and a 'Set-Cookie' response header. This cookie is what
Chris@0 15 * continues to identify the user in subsequent requests.
Chris@0 16 * - When accessing a URI that requires authentication without being
Chris@0 17 * authenticated, a standard 403 response must be sent.
Chris@0 18 * - Because of the reliance on cookies, and the fact that user agents send
Chris@0 19 * cookies with every request, this is vulnerable to CSRF attacks. To mitigate
Chris@0 20 * this, the response for the "log in" request contains a CSRF token that must
Chris@0 21 * be sent with every unsafe (POST/PATCH/DELETE) HTTP request.
Chris@0 22 */
Chris@0 23 trait CookieResourceTestTrait {
Chris@0 24
Chris@0 25 /**
Chris@0 26 * The session cookie.
Chris@0 27 *
Chris@0 28 * @see ::initAuthentication
Chris@0 29 *
Chris@0 30 * @var string
Chris@0 31 */
Chris@0 32 protected $sessionCookie;
Chris@0 33
Chris@0 34 /**
Chris@0 35 * The CSRF token.
Chris@0 36 *
Chris@0 37 * @see ::initAuthentication
Chris@0 38 *
Chris@0 39 * @var string
Chris@0 40 */
Chris@0 41 protected $csrfToken;
Chris@0 42
Chris@0 43 /**
Chris@0 44 * The logout token.
Chris@0 45 *
Chris@0 46 * @see ::initAuthentication
Chris@0 47 *
Chris@0 48 * @var string
Chris@0 49 */
Chris@0 50 protected $logoutToken;
Chris@0 51
Chris@0 52 /**
Chris@0 53 * {@inheritdoc}
Chris@0 54 */
Chris@0 55 protected function initAuthentication() {
Chris@0 56 $user_login_url = Url::fromRoute('user.login.http')
Chris@0 57 ->setRouteParameter('_format', static::$format);
Chris@0 58
Chris@0 59 $request_body = [
Chris@0 60 'name' => $this->account->name->value,
Chris@0 61 'pass' => $this->account->passRaw,
Chris@0 62 ];
Chris@0 63
Chris@0 64 $request_options[RequestOptions::BODY] = $this->serializer->encode($request_body, 'json');
Chris@0 65 $request_options[RequestOptions::HEADERS] = [
Chris@0 66 'Content-Type' => static::$mimeType,
Chris@0 67 ];
Chris@0 68 $response = $this->request('POST', $user_login_url, $request_options);
Chris@0 69
Chris@0 70 // Parse and store the session cookie.
Chris@0 71 $this->sessionCookie = explode(';', $response->getHeader('Set-Cookie')[0], 2)[0];
Chris@0 72
Chris@0 73 // Parse and store the CSRF token and logout token.
Chris@0 74 $data = $this->serializer->decode((string) $response->getBody(), static::$format);
Chris@0 75 $this->csrfToken = $data['csrf_token'];
Chris@0 76 $this->logoutToken = $data['logout_token'];
Chris@0 77 }
Chris@0 78
Chris@0 79 /**
Chris@0 80 * {@inheritdoc}
Chris@0 81 */
Chris@0 82 protected function getAuthenticationRequestOptions($method) {
Chris@0 83 $request_options[RequestOptions::HEADERS]['Cookie'] = $this->sessionCookie;
Chris@0 84 // @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
Chris@0 85 if (!in_array($method, ['HEAD', 'GET', 'OPTIONS', 'TRACE'])) {
Chris@0 86 $request_options[RequestOptions::HEADERS]['X-CSRF-Token'] = $this->csrfToken;
Chris@0 87 }
Chris@0 88 return $request_options;
Chris@0 89 }
Chris@0 90
Chris@0 91 /**
Chris@0 92 * {@inheritdoc}
Chris@0 93 */
Chris@0 94 protected function assertResponseWhenMissingAuthentication(ResponseInterface $response) {
Chris@0 95 // Requests needing cookie authentication but missing it results in a 403
Chris@0 96 // response. The cookie authentication mechanism sets no response message.
Chris@0 97 // @todo https://www.drupal.org/node/2847623
Chris@0 98 $this->assertResourceErrorResponse(403, FALSE, $response);
Chris@0 99 }
Chris@0 100
Chris@0 101 /**
Chris@0 102 * {@inheritdoc}
Chris@0 103 */
Chris@0 104 protected function assertAuthenticationEdgeCases($method, Url $url, array $request_options) {
Chris@0 105 // X-CSRF-Token request header is unnecessary for safe and side effect-free
Chris@0 106 // HTTP methods. No need for additional assertions.
Chris@0 107 // @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
Chris@0 108 if (in_array($method, ['HEAD', 'GET', 'OPTIONS', 'TRACE'])) {
Chris@0 109 return;
Chris@0 110 }
Chris@0 111
Chris@0 112
Chris@0 113 unset($request_options[RequestOptions::HEADERS]['X-CSRF-Token']);
Chris@0 114
Chris@0 115
Chris@0 116 // DX: 403 when missing X-CSRF-Token request header.
Chris@0 117 $response = $this->request($method, $url, $request_options);
Chris@0 118 $this->assertResourceErrorResponse(403, 'X-CSRF-Token request header is missing', $response);
Chris@0 119
Chris@0 120
Chris@0 121 $request_options[RequestOptions::HEADERS]['X-CSRF-Token'] = 'this-is-not-the-token-you-are-looking-for';
Chris@0 122
Chris@0 123
Chris@0 124 // DX: 403 when invalid X-CSRF-Token request header.
Chris@0 125 $response = $this->request($method, $url, $request_options);
Chris@0 126 $this->assertResourceErrorResponse(403, 'X-CSRF-Token request header is invalid', $response);
Chris@0 127
Chris@0 128
Chris@0 129 $request_options[RequestOptions::HEADERS]['X-CSRF-Token'] = $this->csrfToken;
Chris@0 130 }
Chris@0 131
Chris@0 132 }