3 namespace Drupal\Tests\rest\Functional\EntityResource\User;
6 use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
7 use Drupal\user\Entity\User;
8 use GuzzleHttp\RequestOptions;
10 abstract class UserResourceTestBase extends EntityResourceTestBase {
15 public static $modules = ['user'];
20 protected static $entityTypeId = 'user';
25 protected static $patchProtectedFieldNames = [
30 * @var \Drupal\user\UserInterface
37 protected static $labelFieldName = 'name';
42 protected static $firstCreatedEntityId = 4;
47 protected static $secondCreatedEntityId = 5;
52 protected function setUpAuthorization($method) {
55 $this->grantPermissionsToTestedRole(['access user profiles']);
60 $this->grantPermissionsToTestedRole(['administer users']);
68 protected function createEntity() {
69 // Create a "Llama" user.
70 $user = User::create(['created' => 123456789]);
71 $user->setUsername('Llama')
72 ->setChangedTime(123456789)
82 protected function getExpectedNormalizedEntity() {
88 ['value' => $this->entity->uuid()],
102 'value' => 123456789,
107 'value' => $this->entity->getChangedTime(),
110 'default_langcode' => [
121 protected function getNormalizedPostEntity() {
125 'value' => 'Dramallama ' . $this->randomMachineName(),
132 * Tests PATCHing security-sensitive base fields of the logged in account.
134 public function testPatchDxForSecuritySensitiveBaseFields() {
135 // The anonymous user is never allowed to modify itself.
136 if (!static::$auth) {
137 $this->markTestSkipped();
140 $this->initAuthentication();
141 $this->provisionEntityResource();
143 /** @var \Drupal\user\UserInterface $user */
144 $user = static::$auth ? $this->account : User::load(0);
145 // @todo Remove the array_diff_key() call in https://www.drupal.org/node/2821077.
146 $original_normalization = array_diff_key($this->serializer->normalize($user, static::$format), ['created' => TRUE, 'changed' => TRUE, 'name' => TRUE]);
149 // Since this test must be performed by the user that is being modified,
150 // we cannot use $this->getUrl().
151 $url = $user->toUrl()->setOption('query', ['_format' => static::$format]);
153 RequestOptions::HEADERS => ['Content-Type' => static::$mimeType],
155 $request_options = array_merge_recursive($request_options, $this->getAuthenticationRequestOptions('PATCH'));
158 // Test case 1: changing email.
159 $normalization = $original_normalization;
160 $normalization['mail'] = [['value' => 'new-email@example.com']];
161 $request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
164 // DX: 422 when changing email without providing the password.
165 $response = $this->request('PATCH', $url, $request_options);
166 $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nmail: Your current password is missing or incorrect; it's required to change the Email.\n", $response);
169 $normalization['pass'] = [['existing' => 'wrong']];
170 $request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
172 // DX: 422 when changing email while providing a wrong password.
173 $response = $this->request('PATCH', $url, $request_options);
174 $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nmail: Your current password is missing or incorrect; it's required to change the Email.\n", $response);
177 $normalization['pass'] = [['existing' => $this->account->passRaw]];
178 $request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
181 // 200 for well-formed request.
182 $response = $this->request('PATCH', $url, $request_options);
183 $this->assertResourceResponse(200, FALSE, $response);
186 // Test case 2: changing password.
187 $normalization = $original_normalization;
188 $new_password = $this->randomString();
189 $normalization['pass'] = [['value' => $new_password]];
190 $request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
193 // DX: 422 when changing password without providing the current password.
194 $response = $this->request('PATCH', $url, $request_options);
195 $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\npass: Your current password is missing or incorrect; it's required to change the Password.\n", $response);
198 $normalization['pass'][0]['existing'] = $this->account->pass_raw;
199 $request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
202 // 200 for well-formed request.
203 $response = $this->request('PATCH', $url, $request_options);
204 $this->assertResourceResponse(200, FALSE, $response);
207 // Verify that we can log in with the new password.
208 $this->assertRpcLogin($user->getAccountName(), $new_password);
211 // Update password in $this->account, prepare for future requests.
212 $this->account->passRaw = $new_password;
213 $this->initAuthentication();
215 RequestOptions::HEADERS => ['Content-Type' => static::$mimeType],
217 $request_options = array_merge_recursive($request_options, $this->getAuthenticationRequestOptions('PATCH'));
220 // Test case 3: changing name.
221 $normalization = $original_normalization;
222 $normalization['name'] = [['value' => 'Cooler Llama']];
223 $request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
226 // DX: 403 when modifying username without required permission.
227 $response = $this->request('PATCH', $url, $request_options);
228 $this->assertResourceErrorResponse(403, "Access denied on updating field 'name'.", $response);
231 $this->grantPermissionsToTestedRole(['change own username']);
234 // 200 for well-formed request.
235 $response = $this->request('PATCH', $url, $request_options);
236 $this->assertResourceResponse(200, FALSE, $response);
238 // Verify that we can log in with the new username.
239 $this->assertRpcLogin('Cooler Llama', $new_password);
243 * Verifies that logging in with the given username and password works.
245 * @param string $username
246 * The username to log in with.
247 * @param string $password
248 * The password to log in with.
250 protected function assertRpcLogin($username, $password) {
256 RequestOptions::HEADERS => [],
257 RequestOptions::BODY => $this->serializer->encode($request_body, 'json'),
259 $response = $this->request('POST', Url::fromRoute('user.login.http')->setRouteParameter('_format', 'json'), $request_options);
260 $this->assertSame(200, $response->getStatusCode());
266 protected function getExpectedUnauthorizedAccessMessage($method) {
267 if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) {
268 return parent::getExpectedUnauthorizedAccessMessage($method);
273 return "The 'access user profiles' permission is required and the user must be active.";
275 return "You are not authorized to update this user entity.";
277 return 'You are not authorized to delete this user entity.';
279 return parent::getExpectedUnauthorizedAccessMessage($method);