Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 namespace Drupal\Tests\user\Traits;
|
Chris@0
|
4
|
Chris@17
|
5 use Drupal\Component\Render\FormattableMarkup;
|
Chris@18
|
6 use Drupal\Core\Database\DatabaseExceptionWrapper;
|
Chris@18
|
7 use Drupal\Core\Database\SchemaObjectExistsException;
|
Chris@18
|
8 use Drupal\Core\Entity\EntityStorageException;
|
Chris@0
|
9 use Drupal\Core\Session\AccountInterface;
|
Chris@18
|
10 use Drupal\KernelTests\KernelTestBase;
|
Chris@0
|
11 use Drupal\user\Entity\Role;
|
Chris@0
|
12 use Drupal\user\Entity\User;
|
Chris@0
|
13 use Drupal\user\RoleInterface;
|
Chris@0
|
14
|
Chris@0
|
15 /**
|
Chris@0
|
16 * Provides methods to create additional test users and switch the currently
|
Chris@0
|
17 * logged in one.
|
Chris@0
|
18 *
|
Chris@0
|
19 * This trait is meant to be used only by test classes.
|
Chris@0
|
20 */
|
Chris@0
|
21 trait UserCreationTrait {
|
Chris@0
|
22
|
Chris@0
|
23 /**
|
Chris@18
|
24 * Creates a random user account and sets it as current user.
|
Chris@18
|
25 *
|
Chris@18
|
26 * Unless explicitly specified by setting the user ID to 1, a regular user
|
Chris@18
|
27 * account will be created and set as current, after creating user account 1.
|
Chris@18
|
28 * Additionally, this will ensure that at least the anonymous user account
|
Chris@18
|
29 * exists regardless of the specified user ID.
|
Chris@18
|
30 *
|
Chris@18
|
31 * @param array $values
|
Chris@18
|
32 * (optional) An array of initial user field values.
|
Chris@18
|
33 * @param array $permissions
|
Chris@18
|
34 * (optional) Array of permission names to assign to user. Note that the
|
Chris@18
|
35 * user always has the default permissions derived from the "authenticated
|
Chris@18
|
36 * users" role.
|
Chris@18
|
37 * @param bool $admin
|
Chris@18
|
38 * (optional) Whether the user should be an administrator with all the
|
Chris@18
|
39 * available permissions.
|
Chris@18
|
40 *
|
Chris@18
|
41 * @return \Drupal\user\UserInterface
|
Chris@18
|
42 * A user account object.
|
Chris@18
|
43 *
|
Chris@18
|
44 * @throws \LogicException
|
Chris@18
|
45 * If attempting to assign additional roles to the anonymous user account.
|
Chris@18
|
46 * @throws \Drupal\Core\Entity\EntityStorageException
|
Chris@18
|
47 * If the user could not be saved.
|
Chris@18
|
48 */
|
Chris@18
|
49 protected function setUpCurrentUser(array $values = [], array $permissions = [], $admin = FALSE) {
|
Chris@18
|
50 $values += [
|
Chris@18
|
51 'name' => $this->randomMachineName(),
|
Chris@18
|
52 ];
|
Chris@18
|
53
|
Chris@18
|
54 // In many cases the anonymous user account is fine for testing purposes,
|
Chris@18
|
55 // however, if we need to create a user with a non-empty ID, we need also
|
Chris@18
|
56 // the "sequences" table.
|
Chris@18
|
57 if (!\Drupal::moduleHandler()->moduleExists('system')) {
|
Chris@18
|
58 $values['uid'] = 0;
|
Chris@18
|
59 }
|
Chris@18
|
60 if ($this instanceof KernelTestBase && (!isset($values['uid']) || $values['uid'])) {
|
Chris@18
|
61 try {
|
Chris@18
|
62 $this->installSchema('system', ['sequences']);
|
Chris@18
|
63 }
|
Chris@18
|
64 catch (SchemaObjectExistsException $e) {
|
Chris@18
|
65 }
|
Chris@18
|
66 }
|
Chris@18
|
67
|
Chris@18
|
68 // Creating an administrator or assigning custom permissions would result in
|
Chris@18
|
69 // creating and assigning a new role to the user. This is not possible with
|
Chris@18
|
70 // the anonymous user account.
|
Chris@18
|
71 if (($admin || $permissions) && isset($values['uid']) && is_numeric($values['uid']) && $values['uid'] == 0) {
|
Chris@18
|
72 throw new \LogicException('The anonymous user account cannot have additional roles.');
|
Chris@18
|
73 }
|
Chris@18
|
74
|
Chris@18
|
75 $original_permissions = $permissions;
|
Chris@18
|
76 $original_admin = $admin;
|
Chris@18
|
77 $original_values = $values;
|
Chris@18
|
78 $autocreate_user_1 = !isset($values['uid']) || $values['uid'] > 1;
|
Chris@18
|
79
|
Chris@18
|
80 // No need to create user account 1 if it already exists.
|
Chris@18
|
81 try {
|
Chris@18
|
82 $autocreate_user_1 = $autocreate_user_1 && !User::load(1);
|
Chris@18
|
83 }
|
Chris@18
|
84 catch (DatabaseExceptionWrapper $e) {
|
Chris@18
|
85 // Missing schema, it will be created later on.
|
Chris@18
|
86 }
|
Chris@18
|
87
|
Chris@18
|
88 // Save the user entity object and created its schema if needed.
|
Chris@18
|
89 try {
|
Chris@18
|
90 if ($autocreate_user_1) {
|
Chris@18
|
91 $permissions = [];
|
Chris@18
|
92 $admin = FALSE;
|
Chris@18
|
93 $values = [];
|
Chris@18
|
94 }
|
Chris@18
|
95 $user = $this->createUser($permissions, NULL, $admin, $values);
|
Chris@18
|
96 }
|
Chris@18
|
97 catch (EntityStorageException $e) {
|
Chris@18
|
98 if ($this instanceof KernelTestBase) {
|
Chris@18
|
99 $this->installEntitySchema('user');
|
Chris@18
|
100 $user = $this->createUser($permissions, NULL, $admin, $values);
|
Chris@18
|
101 }
|
Chris@18
|
102 else {
|
Chris@18
|
103 throw $e;
|
Chris@18
|
104 }
|
Chris@18
|
105 }
|
Chris@18
|
106
|
Chris@18
|
107 // Ensure the anonymous user account exists.
|
Chris@18
|
108 if (!User::load(0)) {
|
Chris@18
|
109 $values = [
|
Chris@18
|
110 'uid' => 0,
|
Chris@18
|
111 'status' => 0,
|
Chris@18
|
112 'name' => '',
|
Chris@18
|
113 ];
|
Chris@18
|
114 User::create($values)->save();
|
Chris@18
|
115 }
|
Chris@18
|
116
|
Chris@18
|
117 // If we automatically created user account 1, we need to create a regular
|
Chris@18
|
118 // user account before setting up the current user service to avoid
|
Chris@18
|
119 // potential false positives caused by access control bypass.
|
Chris@18
|
120 if ($autocreate_user_1) {
|
Chris@18
|
121 $user = $this->createUser($original_permissions, NULL, $original_admin, $original_values);
|
Chris@18
|
122 }
|
Chris@18
|
123
|
Chris@18
|
124 $this->setCurrentUser($user);
|
Chris@18
|
125
|
Chris@18
|
126 return $user;
|
Chris@18
|
127 }
|
Chris@18
|
128
|
Chris@18
|
129 /**
|
Chris@0
|
130 * Switch the current logged in user.
|
Chris@0
|
131 *
|
Chris@0
|
132 * @param \Drupal\Core\Session\AccountInterface $account
|
Chris@0
|
133 * The user account object.
|
Chris@0
|
134 */
|
Chris@0
|
135 protected function setCurrentUser(AccountInterface $account) {
|
Chris@0
|
136 \Drupal::currentUser()->setAccount($account);
|
Chris@0
|
137 }
|
Chris@0
|
138
|
Chris@0
|
139 /**
|
Chris@0
|
140 * Create a user with a given set of permissions.
|
Chris@0
|
141 *
|
Chris@0
|
142 * @param array $permissions
|
Chris@0
|
143 * Array of permission names to assign to user. Note that the user always
|
Chris@0
|
144 * has the default permissions derived from the "authenticated users" role.
|
Chris@0
|
145 * @param string $name
|
Chris@0
|
146 * The user name.
|
Chris@0
|
147 * @param bool $admin
|
Chris@0
|
148 * (optional) Whether the user should be an administrator
|
Chris@0
|
149 * with all the available permissions.
|
Chris@18
|
150 * @param array $values
|
Chris@18
|
151 * (optional) An array of initial user field values.
|
Chris@0
|
152 *
|
Chris@0
|
153 * @return \Drupal\user\Entity\User|false
|
Chris@0
|
154 * A fully loaded user object with pass_raw property, or FALSE if account
|
Chris@0
|
155 * creation fails.
|
Chris@18
|
156 *
|
Chris@18
|
157 * @throws \Drupal\Core\Entity\EntityStorageException
|
Chris@18
|
158 * If the user creation fails.
|
Chris@0
|
159 */
|
Chris@18
|
160 protected function createUser(array $permissions = [], $name = NULL, $admin = FALSE, array $values = []) {
|
Chris@0
|
161 // Create a role with the given permission set, if any.
|
Chris@0
|
162 $rid = FALSE;
|
Chris@0
|
163 if ($permissions) {
|
Chris@0
|
164 $rid = $this->createRole($permissions);
|
Chris@0
|
165 if (!$rid) {
|
Chris@0
|
166 return FALSE;
|
Chris@0
|
167 }
|
Chris@0
|
168 }
|
Chris@0
|
169
|
Chris@0
|
170 // Create a user assigned to that role.
|
Chris@18
|
171 $edit = $values;
|
Chris@18
|
172 if ($name) {
|
Chris@18
|
173 $edit['name'] = $name;
|
Chris@18
|
174 }
|
Chris@18
|
175 elseif (!isset($values['name'])) {
|
Chris@18
|
176 $edit['name'] = $this->randomMachineName();
|
Chris@18
|
177 }
|
Chris@18
|
178 $edit += [
|
Chris@18
|
179 'mail' => $edit['name'] . '@example.com',
|
Chris@18
|
180 'pass' => user_password(),
|
Chris@18
|
181 'status' => 1,
|
Chris@18
|
182 ];
|
Chris@0
|
183 if ($rid) {
|
Chris@0
|
184 $edit['roles'] = [$rid];
|
Chris@0
|
185 }
|
Chris@0
|
186
|
Chris@0
|
187 if ($admin) {
|
Chris@0
|
188 $edit['roles'][] = $this->createAdminRole();
|
Chris@0
|
189 }
|
Chris@0
|
190
|
Chris@0
|
191 $account = User::create($edit);
|
Chris@0
|
192 $account->save();
|
Chris@0
|
193
|
Chris@18
|
194 $valid_user = $account->id() !== NULL;
|
Chris@18
|
195 $this->assertTrue($valid_user, new FormattableMarkup('User created with name %name and pass %pass', ['%name' => $edit['name'], '%pass' => $edit['pass']]), 'User login');
|
Chris@18
|
196 if (!$valid_user) {
|
Chris@0
|
197 return FALSE;
|
Chris@0
|
198 }
|
Chris@0
|
199
|
Chris@0
|
200 // Add the raw password so that we can log in as this user.
|
Chris@0
|
201 $account->pass_raw = $edit['pass'];
|
Chris@0
|
202 // Support BrowserTestBase as well.
|
Chris@0
|
203 $account->passRaw = $account->pass_raw;
|
Chris@0
|
204 return $account;
|
Chris@0
|
205 }
|
Chris@0
|
206
|
Chris@0
|
207 /**
|
Chris@0
|
208 * Creates an administrative role.
|
Chris@0
|
209 *
|
Chris@0
|
210 * @param string $rid
|
Chris@0
|
211 * (optional) The role ID (machine name). Defaults to a random name.
|
Chris@0
|
212 * @param string $name
|
Chris@0
|
213 * (optional) The label for the role. Defaults to a random string.
|
Chris@0
|
214 * @param int $weight
|
Chris@0
|
215 * (optional) The weight for the role. Defaults NULL so that entity_create()
|
Chris@0
|
216 * sets the weight to maximum + 1.
|
Chris@0
|
217 *
|
Chris@0
|
218 * @return string
|
Chris@0
|
219 * Role ID of newly created role, or FALSE if role creation failed.
|
Chris@0
|
220 */
|
Chris@0
|
221 protected function createAdminRole($rid = NULL, $name = NULL, $weight = NULL) {
|
Chris@0
|
222 $rid = $this->createRole([], $rid, $name, $weight);
|
Chris@0
|
223 if ($rid) {
|
Chris@0
|
224 /** @var \Drupal\user\RoleInterface $role */
|
Chris@0
|
225 $role = Role::load($rid);
|
Chris@0
|
226 $role->setIsAdmin(TRUE);
|
Chris@0
|
227 $role->save();
|
Chris@0
|
228 }
|
Chris@0
|
229 return $rid;
|
Chris@0
|
230 }
|
Chris@0
|
231
|
Chris@0
|
232 /**
|
Chris@0
|
233 * Creates a role with specified permissions.
|
Chris@0
|
234 *
|
Chris@0
|
235 * @param array $permissions
|
Chris@0
|
236 * Array of permission names to assign to role.
|
Chris@0
|
237 * @param string $rid
|
Chris@0
|
238 * (optional) The role ID (machine name). Defaults to a random name.
|
Chris@0
|
239 * @param string $name
|
Chris@0
|
240 * (optional) The label for the role. Defaults to a random string.
|
Chris@0
|
241 * @param int $weight
|
Chris@0
|
242 * (optional) The weight for the role. Defaults NULL so that entity_create()
|
Chris@0
|
243 * sets the weight to maximum + 1.
|
Chris@0
|
244 *
|
Chris@0
|
245 * @return string
|
Chris@0
|
246 * Role ID of newly created role, or FALSE if role creation failed.
|
Chris@0
|
247 */
|
Chris@0
|
248 protected function createRole(array $permissions, $rid = NULL, $name = NULL, $weight = NULL) {
|
Chris@0
|
249 // Generate a random, lowercase machine name if none was passed.
|
Chris@0
|
250 if (!isset($rid)) {
|
Chris@0
|
251 $rid = strtolower($this->randomMachineName(8));
|
Chris@0
|
252 }
|
Chris@0
|
253 // Generate a random label.
|
Chris@0
|
254 if (!isset($name)) {
|
Chris@0
|
255 // In the role UI role names are trimmed and random string can start or
|
Chris@0
|
256 // end with a space.
|
Chris@0
|
257 $name = trim($this->randomString(8));
|
Chris@0
|
258 }
|
Chris@0
|
259
|
Chris@0
|
260 // Check the all the permissions strings are valid.
|
Chris@0
|
261 if (!$this->checkPermissions($permissions)) {
|
Chris@0
|
262 return FALSE;
|
Chris@0
|
263 }
|
Chris@0
|
264
|
Chris@0
|
265 // Create new role.
|
Chris@0
|
266 $role = Role::create([
|
Chris@0
|
267 'id' => $rid,
|
Chris@0
|
268 'label' => $name,
|
Chris@0
|
269 ]);
|
Chris@0
|
270 if (isset($weight)) {
|
Chris@0
|
271 $role->set('weight', $weight);
|
Chris@0
|
272 }
|
Chris@0
|
273 $result = $role->save();
|
Chris@0
|
274
|
Chris@17
|
275 $this->assertIdentical($result, SAVED_NEW, new FormattableMarkup('Created role ID @rid with name @name.', [
|
Chris@0
|
276 '@name' => var_export($role->label(), TRUE),
|
Chris@0
|
277 '@rid' => var_export($role->id(), TRUE),
|
Chris@0
|
278 ]), 'Role');
|
Chris@0
|
279
|
Chris@0
|
280 if ($result === SAVED_NEW) {
|
Chris@0
|
281 // Grant the specified permissions to the role, if any.
|
Chris@0
|
282 if (!empty($permissions)) {
|
Chris@0
|
283 $this->grantPermissions($role, $permissions);
|
Chris@0
|
284 $assigned_permissions = Role::load($role->id())->getPermissions();
|
Chris@0
|
285 $missing_permissions = array_diff($permissions, $assigned_permissions);
|
Chris@0
|
286 if (!$missing_permissions) {
|
Chris@17
|
287 $this->pass(new FormattableMarkup('Created permissions: @perms', ['@perms' => implode(', ', $permissions)]), 'Role');
|
Chris@0
|
288 }
|
Chris@0
|
289 else {
|
Chris@17
|
290 $this->fail(new FormattableMarkup('Failed to create permissions: @perms', ['@perms' => implode(', ', $missing_permissions)]), 'Role');
|
Chris@0
|
291 }
|
Chris@0
|
292 }
|
Chris@0
|
293 return $role->id();
|
Chris@0
|
294 }
|
Chris@0
|
295 else {
|
Chris@0
|
296 return FALSE;
|
Chris@0
|
297 }
|
Chris@0
|
298 }
|
Chris@0
|
299
|
Chris@0
|
300 /**
|
Chris@0
|
301 * Checks whether a given list of permission names is valid.
|
Chris@0
|
302 *
|
Chris@0
|
303 * @param array $permissions
|
Chris@0
|
304 * The permission names to check.
|
Chris@0
|
305 *
|
Chris@0
|
306 * @return bool
|
Chris@0
|
307 * TRUE if the permissions are valid, FALSE otherwise.
|
Chris@0
|
308 */
|
Chris@0
|
309 protected function checkPermissions(array $permissions) {
|
Chris@0
|
310 $available = array_keys(\Drupal::service('user.permissions')->getPermissions());
|
Chris@0
|
311 $valid = TRUE;
|
Chris@0
|
312 foreach ($permissions as $permission) {
|
Chris@0
|
313 if (!in_array($permission, $available)) {
|
Chris@17
|
314 $this->fail(new FormattableMarkup('Invalid permission %permission.', ['%permission' => $permission]), 'Role');
|
Chris@0
|
315 $valid = FALSE;
|
Chris@0
|
316 }
|
Chris@0
|
317 }
|
Chris@0
|
318 return $valid;
|
Chris@0
|
319 }
|
Chris@0
|
320
|
Chris@0
|
321 /**
|
Chris@0
|
322 * Grant permissions to a user role.
|
Chris@0
|
323 *
|
Chris@0
|
324 * @param \Drupal\user\RoleInterface $role
|
Chris@0
|
325 * The ID of a user role to alter.
|
Chris@0
|
326 * @param array $permissions
|
Chris@0
|
327 * (optional) A list of permission names to grant.
|
Chris@0
|
328 */
|
Chris@0
|
329 protected function grantPermissions(RoleInterface $role, array $permissions) {
|
Chris@0
|
330 foreach ($permissions as $permission) {
|
Chris@0
|
331 $role->grantPermission($permission);
|
Chris@0
|
332 }
|
Chris@0
|
333 $role->trustData()->save();
|
Chris@0
|
334 }
|
Chris@0
|
335
|
Chris@0
|
336 }
|