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