3 namespace Drupal\Tests\rest\Functional\EntityResource\User;
6 use Drupal\Tests\rest\Functional\BcTimestampNormalizerUnixTestTrait;
7 use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
8 use Drupal\user\Entity\User;
9 use GuzzleHttp\RequestOptions;
11 abstract class UserResourceTestBase extends EntityResourceTestBase {
13 use BcTimestampNormalizerUnixTestTrait;
18 public static $modules = ['user'];
23 protected static $entityTypeId = 'user';
28 protected static $patchProtectedFieldNames = [
33 * @var \Drupal\user\UserInterface
40 protected static $labelFieldName = 'name';
45 protected static $firstCreatedEntityId = 4;
50 protected static $secondCreatedEntityId = 5;
55 protected function setUpAuthorization($method) {
58 $this->grantPermissionsToTestedRole(['access user profiles']);
63 $this->grantPermissionsToTestedRole(['administer users']);
71 protected function createEntity() {
72 // Create a "Llama" user.
73 $user = User::create(['created' => 123456789]);
74 $user->setUsername('Llama')
75 ->setChangedTime(123456789)
85 protected function getExpectedNormalizedEntity() {
91 ['value' => $this->entity->uuid()],
104 $this->formatExpectedTimestampItemValues(123456789),
107 $this->formatExpectedTimestampItemValues($this->entity->getChangedTime()),
109 'default_langcode' => [
120 protected function getNormalizedPostEntity() {
124 'value' => 'Dramallama',
131 * Tests PATCHing security-sensitive base fields of the logged in account.
133 public function testPatchDxForSecuritySensitiveBaseFields() {
134 // The anonymous user is never allowed to modify itself.
135 if (!static::$auth) {
136 $this->markTestSkipped();
139 $this->initAuthentication();
140 $this->provisionEntityResource();
142 /** @var \Drupal\user\UserInterface $user */
143 $user = static::$auth ? $this->account : User::load(0);
144 // @todo Remove the array_diff_key() call in https://www.drupal.org/node/2821077.
145 $original_normalization = array_diff_key($this->serializer->normalize($user, static::$format), ['created' => TRUE, 'changed' => TRUE, 'name' => TRUE]);
147 // Since this test must be performed by the user that is being modified,
148 // we cannot use $this->getUrl().
149 $url = $user->toUrl()->setOption('query', ['_format' => static::$format]);
151 RequestOptions::HEADERS => ['Content-Type' => static::$mimeType],
153 $request_options = array_merge_recursive($request_options, $this->getAuthenticationRequestOptions('PATCH'));
155 // Test case 1: changing email.
156 $normalization = $original_normalization;
157 $normalization['mail'] = [['value' => 'new-email@example.com']];
158 $request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
160 // DX: 422 when changing email without providing the password.
161 $response = $this->request('PATCH', $url, $request_options);
162 $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nmail: Your current password is missing or incorrect; it's required to change the Email.\n", $response);
164 $normalization['pass'] = [['existing' => 'wrong']];
165 $request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
167 // DX: 422 when changing email while providing a wrong password.
168 $response = $this->request('PATCH', $url, $request_options);
169 $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nmail: Your current password is missing or incorrect; it's required to change the Email.\n", $response);
171 $normalization['pass'] = [['existing' => $this->account->passRaw]];
172 $request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
174 // 200 for well-formed request.
175 $response = $this->request('PATCH', $url, $request_options);
176 $this->assertResourceResponse(200, FALSE, $response);
178 // Test case 2: changing password.
179 $normalization = $original_normalization;
180 $new_password = $this->randomString();
181 $normalization['pass'] = [['value' => $new_password]];
182 $request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
184 // DX: 422 when changing password without providing the current password.
185 $response = $this->request('PATCH', $url, $request_options);
186 $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\npass: Your current password is missing or incorrect; it's required to change the Password.\n", $response);
188 $normalization['pass'][0]['existing'] = $this->account->pass_raw;
189 $request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
191 // 200 for well-formed request.
192 $response = $this->request('PATCH', $url, $request_options);
193 $this->assertResourceResponse(200, FALSE, $response);
195 // Verify that we can log in with the new password.
196 $this->assertRpcLogin($user->getAccountName(), $new_password);
198 // Update password in $this->account, prepare for future requests.
199 $this->account->passRaw = $new_password;
200 $this->initAuthentication();
202 RequestOptions::HEADERS => ['Content-Type' => static::$mimeType],
204 $request_options = array_merge_recursive($request_options, $this->getAuthenticationRequestOptions('PATCH'));
206 // Test case 3: changing name.
207 $normalization = $original_normalization;
208 $normalization['name'] = [['value' => 'Cooler Llama']];
209 $request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
211 // DX: 403 when modifying username without required permission.
212 $response = $this->request('PATCH', $url, $request_options);
213 $this->assertResourceErrorResponse(403, "Access denied on updating field 'name'.", $response);
215 $this->grantPermissionsToTestedRole(['change own username']);
217 // 200 for well-formed request.
218 $response = $this->request('PATCH', $url, $request_options);
219 $this->assertResourceResponse(200, FALSE, $response);
221 // Verify that we can log in with the new username.
222 $this->assertRpcLogin('Cooler Llama', $new_password);
226 * Verifies that logging in with the given username and password works.
228 * @param string $username
229 * The username to log in with.
230 * @param string $password
231 * The password to log in with.
233 protected function assertRpcLogin($username, $password) {
239 RequestOptions::HEADERS => [],
240 RequestOptions::BODY => $this->serializer->encode($request_body, 'json'),
242 $response = $this->request('POST', Url::fromRoute('user.login.http')->setRouteParameter('_format', 'json'), $request_options);
243 $this->assertSame(200, $response->getStatusCode());
249 protected function getExpectedUnauthorizedAccessMessage($method) {
250 if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) {
251 return parent::getExpectedUnauthorizedAccessMessage($method);
256 return "The 'access user profiles' permission is required and the user must be active.";
258 return "You are not authorized to update this user entity.";
260 return 'You are not authorized to delete this user entity.';
262 return parent::getExpectedUnauthorizedAccessMessage($method);