annotate core/modules/user/tests/src/Traits/UserCreationTrait.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@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 }