annotate core/modules/jsonapi/tests/src/Functional/UserTest.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
Chris@18 1 <?php
Chris@18 2
Chris@18 3 namespace Drupal\Tests\jsonapi\Functional;
Chris@18 4
Chris@18 5 use Drupal\Component\Serialization\Json;
Chris@18 6 use Drupal\Component\Utility\NestedArray;
Chris@18 7 use Drupal\Core\Cache\Cache;
Chris@18 8 use Drupal\Core\Url;
Chris@18 9 use Drupal\field\Entity\FieldConfig;
Chris@18 10 use Drupal\field\Entity\FieldStorageConfig;
Chris@18 11 use Drupal\node\Entity\Node;
Chris@18 12 use Drupal\user\Entity\User;
Chris@18 13 use GuzzleHttp\RequestOptions;
Chris@18 14
Chris@18 15 /**
Chris@18 16 * JSON:API integration test for the "User" content entity type.
Chris@18 17 *
Chris@18 18 * @group jsonapi
Chris@18 19 */
Chris@18 20 class UserTest extends ResourceTestBase {
Chris@18 21
Chris@18 22 /**
Chris@18 23 * {@inheritdoc}
Chris@18 24 */
Chris@18 25 public static $modules = ['user'];
Chris@18 26
Chris@18 27 /**
Chris@18 28 * {@inheritdoc}
Chris@18 29 */
Chris@18 30 protected static $entityTypeId = 'user';
Chris@18 31
Chris@18 32 /**
Chris@18 33 * {@inheritdoc}
Chris@18 34 */
Chris@18 35 protected static $resourceTypeName = 'user--user';
Chris@18 36
Chris@18 37 /**
Chris@18 38 * {@inheritdoc}
Chris@18 39 */
Chris@18 40 protected static $patchProtectedFieldNames = [
Chris@18 41 'changed' => NULL,
Chris@18 42 ];
Chris@18 43
Chris@18 44 /**
Chris@18 45 * {@inheritdoc}
Chris@18 46 */
Chris@18 47 protected static $anonymousUsersCanViewLabels = TRUE;
Chris@18 48
Chris@18 49 /**
Chris@18 50 * {@inheritdoc}
Chris@18 51 *
Chris@18 52 * @var \Drupal\taxonomy\TermInterface
Chris@18 53 */
Chris@18 54 protected $entity;
Chris@18 55
Chris@18 56 /**
Chris@18 57 * {@inheritdoc}
Chris@18 58 */
Chris@18 59 protected static $labelFieldName = 'name';
Chris@18 60
Chris@18 61 /**
Chris@18 62 * {@inheritdoc}
Chris@18 63 */
Chris@18 64 protected static $firstCreatedEntityId = 4;
Chris@18 65
Chris@18 66 /**
Chris@18 67 * {@inheritdoc}
Chris@18 68 */
Chris@18 69 protected static $secondCreatedEntityId = 5;
Chris@18 70
Chris@18 71 /**
Chris@18 72 * {@inheritdoc}
Chris@18 73 */
Chris@18 74 protected function setUpAuthorization($method) {
Chris@18 75 // @todo Remove this in
Chris@18 76 $this->grantPermissionsToTestedRole(['access content']);
Chris@18 77
Chris@18 78 switch ($method) {
Chris@18 79 case 'GET':
Chris@18 80 $this->grantPermissionsToTestedRole(['access user profiles']);
Chris@18 81 break;
Chris@18 82
Chris@18 83 case 'POST':
Chris@18 84 case 'PATCH':
Chris@18 85 case 'DELETE':
Chris@18 86 $this->grantPermissionsToTestedRole(['administer users']);
Chris@18 87 break;
Chris@18 88 }
Chris@18 89 }
Chris@18 90
Chris@18 91 /**
Chris@18 92 * {@inheritdoc}
Chris@18 93 */
Chris@18 94 protected function createEntity() {
Chris@18 95 // Create a "Llama" user.
Chris@18 96 $user = User::create(['created' => 123456789]);
Chris@18 97 $user->setUsername('Llama')
Chris@18 98 ->setChangedTime(123456789)
Chris@18 99 ->activate()
Chris@18 100 ->save();
Chris@18 101
Chris@18 102 return $user;
Chris@18 103 }
Chris@18 104
Chris@18 105 /**
Chris@18 106 * {@inheritdoc}
Chris@18 107 */
Chris@18 108 protected function createAnotherEntity($key) {
Chris@18 109 /** @var \Drupal\user\UserInterface $user */
Chris@18 110 $user = $this->getEntityDuplicate($this->entity, $key);
Chris@18 111 $user->setUsername($user->label() . '_' . $key);
Chris@18 112 $user->setEmail("$key@example.com");
Chris@18 113 $user->save();
Chris@18 114 return $user;
Chris@18 115 }
Chris@18 116
Chris@18 117 /**
Chris@18 118 * {@inheritdoc}
Chris@18 119 */
Chris@18 120 protected function getExpectedDocument() {
Chris@18 121 $self_url = Url::fromUri('base:/jsonapi/user/user/' . $this->entity->uuid())->setAbsolute()->toString(TRUE)->getGeneratedUrl();
Chris@18 122 return [
Chris@18 123 'jsonapi' => [
Chris@18 124 'meta' => [
Chris@18 125 'links' => [
Chris@18 126 'self' => ['href' => 'http://jsonapi.org/format/1.0/'],
Chris@18 127 ],
Chris@18 128 ],
Chris@18 129 'version' => '1.0',
Chris@18 130 ],
Chris@18 131 'links' => [
Chris@18 132 'self' => ['href' => $self_url],
Chris@18 133 ],
Chris@18 134 'data' => [
Chris@18 135 'id' => $this->entity->uuid(),
Chris@18 136 'type' => 'user--user',
Chris@18 137 'links' => [
Chris@18 138 'self' => ['href' => $self_url],
Chris@18 139 ],
Chris@18 140 'attributes' => [
Chris@18 141 'created' => '1973-11-29T21:33:09+00:00',
Chris@18 142 'changed' => (new \DateTime())->setTimestamp($this->entity->getChangedTime())->setTimezone(new \DateTimeZone('UTC'))->format(\DateTime::RFC3339),
Chris@18 143 'default_langcode' => TRUE,
Chris@18 144 'langcode' => 'en',
Chris@18 145 'name' => 'Llama',
Chris@18 146 'drupal_internal__uid' => 3,
Chris@18 147 ],
Chris@18 148 ],
Chris@18 149 ];
Chris@18 150 }
Chris@18 151
Chris@18 152 /**
Chris@18 153 * {@inheritdoc}
Chris@18 154 */
Chris@18 155 protected function getExpectedCacheContexts(array $sparse_fieldset = NULL) {
Chris@18 156 $cache_contexts = parent::getExpectedCacheContexts($sparse_fieldset);
Chris@18 157 if ($sparse_fieldset === NULL || in_array('mail', $sparse_fieldset)) {
Chris@18 158 $cache_contexts = Cache::mergeContexts($cache_contexts, ['user']);
Chris@18 159 }
Chris@18 160 return $cache_contexts;
Chris@18 161 }
Chris@18 162
Chris@18 163 /**
Chris@18 164 * {@inheritdoc}
Chris@18 165 */
Chris@18 166 protected function getPostDocument() {
Chris@18 167 return [
Chris@18 168 'data' => [
Chris@18 169 'type' => 'user--user',
Chris@18 170 'attributes' => [
Chris@18 171 'name' => 'Dramallama',
Chris@18 172 ],
Chris@18 173 ],
Chris@18 174 ];
Chris@18 175 }
Chris@18 176
Chris@18 177 /**
Chris@18 178 * {@inheritdoc}
Chris@18 179 */
Chris@18 180 protected function getExpectedUnauthorizedAccessMessage($method) {
Chris@18 181 switch ($method) {
Chris@18 182 case 'GET':
Chris@18 183 return "The 'access user profiles' permission is required and the user must be active.";
Chris@18 184
Chris@18 185 case 'PATCH':
Chris@18 186 return "Users can only update their own account, unless they have the 'administer users' permission.";
Chris@18 187
Chris@18 188 case 'DELETE':
Chris@18 189 return "The 'cancel account' permission is required.";
Chris@18 190
Chris@18 191 default:
Chris@18 192 return parent::getExpectedUnauthorizedAccessMessage($method);
Chris@18 193 }
Chris@18 194 }
Chris@18 195
Chris@18 196 /**
Chris@18 197 * Tests PATCHing security-sensitive base fields of the logged in account.
Chris@18 198 */
Chris@18 199 public function testPatchDxForSecuritySensitiveBaseFields() {
Chris@18 200 // @todo Remove line below in favor of commented line in https://www.drupal.org/project/jsonapi/issues/2878463.
Chris@18 201 $url = Url::fromRoute(sprintf('jsonapi.user--user.individual'), ['entity' => $this->account->uuid()]);
Chris@18 202 /* $url = $this->account->toUrl('jsonapi'); */
Chris@18 203
Chris@18 204 $original_normalization = $this->normalize($this->account, $url);
Chris@18 205 // @todo Remove the array_diff_key() call in https://www.drupal.org/node/2821077.
Chris@18 206 $original_normalization['data']['attributes'] = array_diff_key(
Chris@18 207 $original_normalization['data']['attributes'],
Chris@18 208 ['created' => TRUE, 'changed' => TRUE, 'name' => TRUE]
Chris@18 209 );
Chris@18 210
Chris@18 211 // Since this test must be performed by the user that is being modified,
Chris@18 212 // we must use $this->account, not $this->entity.
Chris@18 213 $request_options = [];
Chris@18 214 $request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json';
Chris@18 215 $request_options[RequestOptions::HEADERS]['Content-Type'] = 'application/vnd.api+json';
Chris@18 216 $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions());
Chris@18 217
Chris@18 218 // Test case 1: changing email.
Chris@18 219 $normalization = $original_normalization;
Chris@18 220 $normalization['data']['attributes']['mail'] = 'new-email@example.com';
Chris@18 221 $request_options[RequestOptions::BODY] = Json::encode($normalization);
Chris@18 222
Chris@18 223 // DX: 405 when read-only mode is enabled.
Chris@18 224 $response = $this->request('PATCH', $url, $request_options);
Chris@18 225 $this->assertResourceErrorResponse(405, sprintf("JSON:API is configured to accept only read operations. Site administrators can configure this at %s.", Url::fromUri('base:/admin/config/services/jsonapi')->setAbsolute()->toString(TRUE)->getGeneratedUrl()), $url, $response);
Chris@18 226 $this->assertSame(['GET'], $response->getHeader('Allow'));
Chris@18 227
Chris@18 228 $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE);
Chris@18 229
Chris@18 230 // DX: 422 when changing email without providing the password.
Chris@18 231 $response = $this->request('PATCH', $url, $request_options);
Chris@18 232 $this->assertResourceErrorResponse(422, 'mail: Your current password is missing or incorrect; it\'s required to change the Email.', NULL, $response, '/data/attributes/mail');
Chris@18 233
Chris@18 234 $normalization['data']['attributes']['pass']['existing'] = 'wrong';
Chris@18 235 $request_options[RequestOptions::BODY] = Json::encode($normalization);
Chris@18 236
Chris@18 237 // DX: 422 when changing email while providing a wrong password.
Chris@18 238 $response = $this->request('PATCH', $url, $request_options);
Chris@18 239 $this->assertResourceErrorResponse(422, 'mail: Your current password is missing or incorrect; it\'s required to change the Email.', NULL, $response, '/data/attributes/mail');
Chris@18 240
Chris@18 241 $normalization['data']['attributes']['pass']['existing'] = $this->account->passRaw;
Chris@18 242 $request_options[RequestOptions::BODY] = Json::encode($normalization);
Chris@18 243
Chris@18 244 // 200 for well-formed request.
Chris@18 245 $response = $this->request('PATCH', $url, $request_options);
Chris@18 246 $this->assertResourceResponse(200, FALSE, $response);
Chris@18 247
Chris@18 248 // Test case 2: changing password.
Chris@18 249 $normalization = $original_normalization;
Chris@18 250 $normalization['data']['attributes']['mail'] = 'new-email@example.com';
Chris@18 251 $new_password = $this->randomString();
Chris@18 252 $normalization['data']['attributes']['pass']['value'] = $new_password;
Chris@18 253 $request_options[RequestOptions::BODY] = Json::encode($normalization);
Chris@18 254
Chris@18 255 // DX: 422 when changing password without providing the current password.
Chris@18 256 $response = $this->request('PATCH', $url, $request_options);
Chris@18 257 $this->assertResourceErrorResponse(422, 'pass: Your current password is missing or incorrect; it\'s required to change the Password.', NULL, $response, '/data/attributes/pass');
Chris@18 258
Chris@18 259 $normalization['data']['attributes']['pass']['existing'] = $this->account->passRaw;
Chris@18 260 $request_options[RequestOptions::BODY] = Json::encode($normalization);
Chris@18 261
Chris@18 262 // 200 for well-formed request.
Chris@18 263 $response = $this->request('PATCH', $url, $request_options);
Chris@18 264 $this->assertResourceResponse(200, FALSE, $response);
Chris@18 265
Chris@18 266 // Verify that we can log in with the new password.
Chris@18 267 $this->assertRpcLogin($this->account->getAccountName(), $new_password);
Chris@18 268
Chris@18 269 // Update password in $this->account, prepare for future requests.
Chris@18 270 $this->account->passRaw = $new_password;
Chris@18 271 $request_options = [];
Chris@18 272 $request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json';
Chris@18 273 $request_options[RequestOptions::HEADERS]['Content-Type'] = 'application/vnd.api+json';
Chris@18 274 $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions());
Chris@18 275
Chris@18 276 // Test case 3: changing name.
Chris@18 277 $normalization = $original_normalization;
Chris@18 278 $normalization['data']['attributes']['mail'] = 'new-email@example.com';
Chris@18 279 $normalization['data']['attributes']['pass']['existing'] = $new_password;
Chris@18 280 $normalization['data']['attributes']['name'] = 'Cooler Llama';
Chris@18 281 $request_options[RequestOptions::BODY] = Json::encode($normalization);
Chris@18 282
Chris@18 283 // DX: 403 when modifying username without required permission.
Chris@18 284 $response = $this->request('PATCH', $url, $request_options);
Chris@18 285 $this->assertResourceErrorResponse(403, 'The current user is not allowed to PATCH the selected field (name).', $url, $response, '/data/attributes/name');
Chris@18 286
Chris@18 287 $this->grantPermissionsToTestedRole(['change own username']);
Chris@18 288
Chris@18 289 // 200 for well-formed request.
Chris@18 290 $response = $this->request('PATCH', $url, $request_options);
Chris@18 291 $this->assertResourceResponse(200, FALSE, $response);
Chris@18 292
Chris@18 293 // Verify that we can log in with the new username.
Chris@18 294 $this->assertRpcLogin('Cooler Llama', $new_password);
Chris@18 295 }
Chris@18 296
Chris@18 297 /**
Chris@18 298 * Verifies that logging in with the given username and password works.
Chris@18 299 *
Chris@18 300 * @param string $username
Chris@18 301 * The username to log in with.
Chris@18 302 * @param string $password
Chris@18 303 * The password to log in with.
Chris@18 304 */
Chris@18 305 protected function assertRpcLogin($username, $password) {
Chris@18 306 $request_body = [
Chris@18 307 'name' => $username,
Chris@18 308 'pass' => $password,
Chris@18 309 ];
Chris@18 310 $request_options = [
Chris@18 311 RequestOptions::HEADERS => [],
Chris@18 312 RequestOptions::BODY => Json::encode($request_body),
Chris@18 313 ];
Chris@18 314 $response = $this->request('POST', Url::fromRoute('user.login.http')->setRouteParameter('_format', 'json'), $request_options);
Chris@18 315 $this->assertSame(200, $response->getStatusCode());
Chris@18 316 }
Chris@18 317
Chris@18 318 /**
Chris@18 319 * Tests PATCHing security-sensitive base fields to change other users.
Chris@18 320 */
Chris@18 321 public function testPatchSecurityOtherUser() {
Chris@18 322 // @todo Remove line below in favor of commented line in https://www.drupal.org/project/jsonapi/issues/2878463.
Chris@18 323 $url = Url::fromRoute(sprintf('jsonapi.user--user.individual'), ['entity' => $this->account->uuid()]);
Chris@18 324 /* $url = $this->account->toUrl('jsonapi'); */
Chris@18 325
Chris@18 326 $original_normalization = $this->normalize($this->account, $url);
Chris@18 327
Chris@18 328 // Since this test must be performed by the user that is being modified,
Chris@18 329 // we must use $this->account, not $this->entity.
Chris@18 330 $request_options = [];
Chris@18 331 $request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json';
Chris@18 332 $request_options[RequestOptions::HEADERS]['Content-Type'] = 'application/vnd.api+json';
Chris@18 333 $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions());
Chris@18 334
Chris@18 335 $normalization = $original_normalization;
Chris@18 336 $normalization['data']['attributes']['mail'] = 'new-email@example.com';
Chris@18 337 $request_options[RequestOptions::BODY] = Json::encode($normalization);
Chris@18 338
Chris@18 339 // DX: 405 when read-only mode is enabled.
Chris@18 340 $response = $this->request('PATCH', $url, $request_options);
Chris@18 341 $this->assertResourceErrorResponse(405, sprintf("JSON:API is configured to accept only read operations. Site administrators can configure this at %s.", Url::fromUri('base:/admin/config/services/jsonapi')->setAbsolute()->toString(TRUE)->getGeneratedUrl()), $url, $response);
Chris@18 342 $this->assertSame(['GET'], $response->getHeader('Allow'));
Chris@18 343
Chris@18 344 $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE);
Chris@18 345
Chris@18 346 // Try changing user 1's email.
Chris@18 347 $user1 = $original_normalization;
Chris@18 348 $user1['data']['attributes']['mail'] = 'another_email_address@example.com';
Chris@18 349 $user1['data']['attributes']['uid'] = 1;
Chris@18 350 $user1['data']['attributes']['name'] = 'another_user_name';
Chris@18 351 $user1['data']['attributes']['pass']['existing'] = $this->account->passRaw;
Chris@18 352 $request_options[RequestOptions::BODY] = Json::encode($user1);
Chris@18 353 $response = $this->request('PATCH', $url, $request_options);
Chris@18 354 // Ensure the email address has not changed.
Chris@18 355 $this->assertEquals('admin@example.com', $this->entityStorage->loadUnchanged(1)->getEmail());
Chris@18 356 $this->assertResourceErrorResponse(403, 'The current user is not allowed to PATCH the selected field (uid). The entity ID cannot be changed.', $url, $response, '/data/attributes/uid');
Chris@18 357 }
Chris@18 358
Chris@18 359 /**
Chris@18 360 * Tests GETting privacy-sensitive base fields.
Chris@18 361 */
Chris@18 362 public function testGetMailFieldOnlyVisibleToOwner() {
Chris@18 363 // Create user B, with the same roles (and hence permissions) as user A.
Chris@18 364 $user_a = $this->account;
Chris@18 365 $pass = user_password();
Chris@18 366 $user_b = User::create([
Chris@18 367 'name' => 'sibling-of-' . $user_a->getAccountName(),
Chris@18 368 'mail' => 'sibling-of-' . $user_a->getAccountName() . '@example.com',
Chris@18 369 'pass' => $pass,
Chris@18 370 'status' => 1,
Chris@18 371 'roles' => $user_a->getRoles(),
Chris@18 372 ]);
Chris@18 373 $user_b->save();
Chris@18 374 $user_b->passRaw = $pass;
Chris@18 375
Chris@18 376 // Grant permission to role that both users use.
Chris@18 377 $this->grantPermissionsToTestedRole(['access user profiles']);
Chris@18 378
Chris@18 379 $collection_url = Url::fromRoute('jsonapi.user--user.collection', [], ['query' => ['sort' => 'drupal_internal__uid']]);
Chris@18 380 // @todo Remove line below in favor of commented line in https://www.drupal.org/project/jsonapi/issues/2878463.
Chris@18 381 $user_a_url = Url::fromRoute(sprintf('jsonapi.user--user.individual'), ['entity' => $user_a->uuid()]);
Chris@18 382 /* $user_a_url = $user_a->toUrl('jsonapi'); */
Chris@18 383 $request_options = [];
Chris@18 384 $request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json';
Chris@18 385 $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions());
Chris@18 386
Chris@18 387 // Viewing user A as user A: "mail" field is accessible.
Chris@18 388 $response = $this->request('GET', $user_a_url, $request_options);
Chris@18 389 $doc = Json::decode((string) $response->getBody());
Chris@18 390 $this->assertArrayHasKey('mail', $doc['data']['attributes']);
Chris@18 391 // Also when looking at the collection.
Chris@18 392 $response = $this->request('GET', $collection_url, $request_options);
Chris@18 393 $doc = Json::decode((string) $response->getBody());
Chris@18 394 $this->assertSame($user_a->uuid(), $doc['data']['2']['id']);
Chris@18 395 $this->assertArrayHasKey('mail', $doc['data'][2]['attributes'], "Own user--user resource's 'mail' field is visible.");
Chris@18 396 $this->assertSame($user_b->uuid(), $doc['data'][count($doc['data']) - 1]['id']);
Chris@18 397 $this->assertArrayNotHasKey('mail', $doc['data'][count($doc['data']) - 1]['attributes']);
Chris@18 398
Chris@18 399 // Now request the same URLs, but as user B (same roles/permissions).
Chris@18 400 $this->account = $user_b;
Chris@18 401 $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions());
Chris@18 402 // Viewing user A as user B: "mail" field should be inaccessible.
Chris@18 403 $response = $this->request('GET', $user_a_url, $request_options);
Chris@18 404 $doc = Json::decode((string) $response->getBody());
Chris@18 405 $this->assertArrayNotHasKey('mail', $doc['data']['attributes']);
Chris@18 406 // Also when looking at the collection.
Chris@18 407 $response = $this->request('GET', $collection_url, $request_options);
Chris@18 408 $doc = Json::decode((string) $response->getBody());
Chris@18 409 $this->assertSame($user_a->uuid(), $doc['data']['2']['id']);
Chris@18 410 $this->assertArrayNotHasKey('mail', $doc['data'][2]['attributes']);
Chris@18 411 $this->assertSame($user_b->uuid(), $doc['data'][count($doc['data']) - 1]['id']);
Chris@18 412 $this->assertArrayHasKey('mail', $doc['data'][count($doc['data']) - 1]['attributes']);
Chris@18 413 }
Chris@18 414
Chris@18 415 /**
Chris@18 416 * Test good error DX when trying to filter users by role.
Chris@18 417 */
Chris@18 418 public function testQueryInvolvingRoles() {
Chris@18 419 $this->setUpAuthorization('GET');
Chris@18 420
Chris@18 421 $collection_url = Url::fromRoute('jsonapi.user--user.collection', [], ['query' => ['filter[roles.id][value]' => 'e9b1de3f-9517-4c27-bef0-0301229de792']]);
Chris@18 422 $request_options = [];
Chris@18 423 $request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json';
Chris@18 424 $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions());
Chris@18 425
Chris@18 426 // The 'administer users' permission is required to filter by role entities.
Chris@18 427 $this->grantPermissionsToTestedRole(['administer users']);
Chris@18 428
Chris@18 429 $response = $this->request('GET', $collection_url, $request_options);
Chris@18 430 $expected_cache_contexts = ['url.path', 'url.query_args:filter', 'url.site'];
Chris@18 431 $this->assertResourceErrorResponse(400, "Filtering on config entities is not supported by Drupal's entity API. You tried to filter on a Role config entity.", $collection_url, $response, FALSE, ['4xx-response', 'http_response'], $expected_cache_contexts, FALSE, 'MISS');
Chris@18 432 }
Chris@18 433
Chris@18 434 /**
Chris@18 435 * Tests that the collection contains the anonymous user.
Chris@18 436 */
Chris@18 437 public function testCollectionContainsAnonymousUser() {
Chris@18 438 $url = Url::fromRoute('jsonapi.user--user.collection', [], ['query' => ['sort' => 'drupal_internal__uid']]);
Chris@18 439 $request_options = [];
Chris@18 440 $request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json';
Chris@18 441 $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions());
Chris@18 442
Chris@18 443 $response = $this->request('GET', $url, $request_options);
Chris@18 444 $doc = Json::decode((string) $response->getBody());
Chris@18 445
Chris@18 446 $this->assertCount(4, $doc['data']);
Chris@18 447 $this->assertSame(User::load(0)->uuid(), $doc['data'][0]['id']);
Chris@18 448 $this->assertSame('Anonymous', $doc['data'][0]['attributes']['name']);
Chris@18 449 }
Chris@18 450
Chris@18 451 /**
Chris@18 452 * {@inheritdoc}
Chris@18 453 */
Chris@18 454 public function testCollectionFilterAccess() {
Chris@18 455 // Set up data model.
Chris@18 456 $this->assertTrue($this->container->get('module_installer')->install(['node'], TRUE), 'Installed modules.');
Chris@18 457 FieldStorageConfig::create([
Chris@18 458 'entity_type' => static::$entityTypeId,
Chris@18 459 'field_name' => 'field_favorite_animal',
Chris@18 460 'type' => 'string',
Chris@18 461 ])
Chris@18 462 ->setCardinality(1)
Chris@18 463 ->save();
Chris@18 464 FieldConfig::create([
Chris@18 465 'entity_type' => static::$entityTypeId,
Chris@18 466 'field_name' => 'field_favorite_animal',
Chris@18 467 'bundle' => 'user',
Chris@18 468 ])
Chris@18 469 ->setLabel('Test field')
Chris@18 470 ->setTranslatable(FALSE)
Chris@18 471 ->save();
Chris@18 472 $this->drupalCreateContentType(['type' => 'x']);
Chris@18 473 $this->rebuildAll();
Chris@18 474 $this->grantPermissionsToTestedRole(['access content']);
Chris@18 475
Chris@18 476 // Create data.
Chris@18 477 $user_a = User::create([])->setUsername('A')->activate();
Chris@18 478 $user_a->save();
Chris@18 479 $user_b = User::create([])->setUsername('B')->set('field_favorite_animal', 'stegosaurus')->block();
Chris@18 480 $user_b->save();
Chris@18 481 $node_a = Node::create(['type' => 'x'])->setTitle('Owned by A')->setOwner($user_a);
Chris@18 482 $node_a->save();
Chris@18 483 $node_b = Node::create(['type' => 'x'])->setTitle('Owned by B')->setOwner($user_b);
Chris@18 484 $node_b->save();
Chris@18 485 $node_anon_1 = Node::create(['type' => 'x'])->setTitle('Owned by anon #1')->setOwnerId(0);
Chris@18 486 $node_anon_1->save();
Chris@18 487 $node_anon_2 = Node::create(['type' => 'x'])->setTitle('Owned by anon #2')->setOwnerId(0);
Chris@18 488 $node_anon_2->save();
Chris@18 489 $node_auth_1 = Node::create(['type' => 'x'])->setTitle('Owned by auth #1')->setOwner($this->account);
Chris@18 490 $node_auth_1->save();
Chris@18 491
Chris@18 492 $favorite_animal_test_url = Url::fromRoute('jsonapi.user--user.collection')->setOption('query', ['filter[field_favorite_animal]' => 'stegosaurus']);
Chris@18 493
Chris@18 494 // Test.
Chris@18 495 $collection_url = Url::fromRoute('jsonapi.node--x.collection');
Chris@18 496 $request_options = [];
Chris@18 497 $request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json';
Chris@18 498 $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions());
Chris@18 499 // ?filter[uid.id]=OWN_UUID requires no permissions: 1 result.
Chris@18 500 $response = $this->request('GET', $collection_url->setOption('query', ['filter[uid.id]' => $this->account->uuid()]), $request_options);
Chris@18 501 $this->assertSession()->responseHeaderContains('X-Drupal-Cache-Contexts', 'user.permissions');
Chris@18 502 $doc = Json::decode((string) $response->getBody());
Chris@18 503 $this->assertCount(1, $doc['data']);
Chris@18 504 $this->assertSame($node_auth_1->uuid(), $doc['data'][0]['id']);
Chris@18 505 // ?filter[uid.id]=ANONYMOUS_UUID: 0 results.
Chris@18 506 $response = $this->request('GET', $collection_url->setOption('query', ['filter[uid.id]' => User::load(0)->uuid()]), $request_options);
Chris@18 507 $this->assertSession()->responseHeaderContains('X-Drupal-Cache-Contexts', 'user.permissions');
Chris@18 508 $doc = Json::decode((string) $response->getBody());
Chris@18 509 $this->assertCount(0, $doc['data']);
Chris@18 510 // ?filter[uid.name]=A: 0 results.
Chris@18 511 $response = $this->request('GET', $collection_url->setOption('query', ['filter[uid.name]' => 'A']), $request_options);
Chris@18 512 $doc = Json::decode((string) $response->getBody());
Chris@18 513 $this->assertCount(0, $doc['data']);
Chris@18 514 // /jsonapi/user/user?filter[field_favorite_animal]: 0 results.
Chris@18 515 $response = $this->request('GET', $favorite_animal_test_url, $request_options);
Chris@18 516 $this->assertSame(200, $response->getStatusCode());
Chris@18 517 $doc = Json::decode((string) $response->getBody());
Chris@18 518 $this->assertCount(0, $doc['data']);
Chris@18 519 // Grant "view" permission.
Chris@18 520 $this->grantPermissionsToTestedRole(['access user profiles']);
Chris@18 521 // ?filter[uid.id]=ANONYMOUS_UUID: 0 results.
Chris@18 522 $response = $this->request('GET', $collection_url->setOption('query', ['filter[uid.id]' => User::load(0)->uuid()]), $request_options);
Chris@18 523 $this->assertSession()->responseHeaderContains('X-Drupal-Cache-Contexts', 'user.permissions');
Chris@18 524 $doc = Json::decode((string) $response->getBody());
Chris@18 525 $this->assertCount(0, $doc['data']);
Chris@18 526 // ?filter[uid.name]=A: 1 result since user A is active.
Chris@18 527 $response = $this->request('GET', $collection_url->setOption('query', ['filter[uid.name]' => 'A']), $request_options);
Chris@18 528 $this->assertSession()->responseHeaderContains('X-Drupal-Cache-Contexts', 'user.permissions');
Chris@18 529 $doc = Json::decode((string) $response->getBody());
Chris@18 530 $this->assertCount(1, $doc['data']);
Chris@18 531 $this->assertSame($node_a->uuid(), $doc['data'][0]['id']);
Chris@18 532 // ?filter[uid.name]=B: 0 results since user B is blocked.
Chris@18 533 $response = $this->request('GET', $collection_url->setOption('query', ['filter[uid.name]' => 'B']), $request_options);
Chris@18 534 $this->assertSession()->responseHeaderContains('X-Drupal-Cache-Contexts', 'user.permissions');
Chris@18 535 $doc = Json::decode((string) $response->getBody());
Chris@18 536 $this->assertCount(0, $doc['data']);
Chris@18 537 // /jsonapi/user/user?filter[field_favorite_animal]: 0 results.
Chris@18 538 $response = $this->request('GET', $favorite_animal_test_url, $request_options);
Chris@18 539 $this->assertSame(200, $response->getStatusCode());
Chris@18 540 $doc = Json::decode((string) $response->getBody());
Chris@18 541 $this->assertCount(0, $doc['data']);
Chris@18 542 // Grant "admin" permission.
Chris@18 543 $this->grantPermissionsToTestedRole(['administer users']);
Chris@18 544 // ?filter[uid.name]=B: 1 result.
Chris@18 545 $response = $this->request('GET', $collection_url->setOption('query', ['filter[uid.name]' => 'B']), $request_options);
Chris@18 546 $this->assertSession()->responseHeaderContains('X-Drupal-Cache-Contexts', 'user.permissions');
Chris@18 547 $doc = Json::decode((string) $response->getBody());
Chris@18 548 $this->assertCount(1, $doc['data']);
Chris@18 549 $this->assertSame($node_b->uuid(), $doc['data'][0]['id']);
Chris@18 550 // /jsonapi/user/user?filter[field_favorite_animal]: 1 result.
Chris@18 551 $response = $this->request('GET', $favorite_animal_test_url, $request_options);
Chris@18 552 $this->assertSame(200, $response->getStatusCode());
Chris@18 553 $doc = Json::decode((string) $response->getBody());
Chris@18 554 $this->assertCount(1, $doc['data']);
Chris@18 555 $this->assertSame($user_b->uuid(), $doc['data'][0]['id']);
Chris@18 556 }
Chris@18 557
Chris@18 558 }