Chris@0: $this->randomMachineName(), Chris@18: ]; Chris@18: Chris@18: // In many cases the anonymous user account is fine for testing purposes, Chris@18: // however, if we need to create a user with a non-empty ID, we need also Chris@18: // the "sequences" table. Chris@18: if (!\Drupal::moduleHandler()->moduleExists('system')) { Chris@18: $values['uid'] = 0; Chris@18: } Chris@18: if ($this instanceof KernelTestBase && (!isset($values['uid']) || $values['uid'])) { Chris@18: try { Chris@18: $this->installSchema('system', ['sequences']); Chris@18: } Chris@18: catch (SchemaObjectExistsException $e) { Chris@18: } Chris@18: } Chris@18: Chris@18: // Creating an administrator or assigning custom permissions would result in Chris@18: // creating and assigning a new role to the user. This is not possible with Chris@18: // the anonymous user account. Chris@18: if (($admin || $permissions) && isset($values['uid']) && is_numeric($values['uid']) && $values['uid'] == 0) { Chris@18: throw new \LogicException('The anonymous user account cannot have additional roles.'); Chris@18: } Chris@18: Chris@18: $original_permissions = $permissions; Chris@18: $original_admin = $admin; Chris@18: $original_values = $values; Chris@18: $autocreate_user_1 = !isset($values['uid']) || $values['uid'] > 1; Chris@18: Chris@18: // No need to create user account 1 if it already exists. Chris@18: try { Chris@18: $autocreate_user_1 = $autocreate_user_1 && !User::load(1); Chris@18: } Chris@18: catch (DatabaseExceptionWrapper $e) { Chris@18: // Missing schema, it will be created later on. Chris@18: } Chris@18: Chris@18: // Save the user entity object and created its schema if needed. Chris@18: try { Chris@18: if ($autocreate_user_1) { Chris@18: $permissions = []; Chris@18: $admin = FALSE; Chris@18: $values = []; Chris@18: } Chris@18: $user = $this->createUser($permissions, NULL, $admin, $values); Chris@18: } Chris@18: catch (EntityStorageException $e) { Chris@18: if ($this instanceof KernelTestBase) { Chris@18: $this->installEntitySchema('user'); Chris@18: $user = $this->createUser($permissions, NULL, $admin, $values); Chris@18: } Chris@18: else { Chris@18: throw $e; Chris@18: } Chris@18: } Chris@18: Chris@18: // Ensure the anonymous user account exists. Chris@18: if (!User::load(0)) { Chris@18: $values = [ Chris@18: 'uid' => 0, Chris@18: 'status' => 0, Chris@18: 'name' => '', Chris@18: ]; Chris@18: User::create($values)->save(); Chris@18: } Chris@18: Chris@18: // If we automatically created user account 1, we need to create a regular Chris@18: // user account before setting up the current user service to avoid Chris@18: // potential false positives caused by access control bypass. Chris@18: if ($autocreate_user_1) { Chris@18: $user = $this->createUser($original_permissions, NULL, $original_admin, $original_values); Chris@18: } Chris@18: Chris@18: $this->setCurrentUser($user); Chris@18: Chris@18: return $user; Chris@18: } Chris@18: Chris@18: /** Chris@0: * Switch the current logged in user. Chris@0: * Chris@0: * @param \Drupal\Core\Session\AccountInterface $account Chris@0: * The user account object. Chris@0: */ Chris@0: protected function setCurrentUser(AccountInterface $account) { Chris@0: \Drupal::currentUser()->setAccount($account); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Create a user with a given set of permissions. Chris@0: * Chris@0: * @param array $permissions Chris@0: * Array of permission names to assign to user. Note that the user always Chris@0: * has the default permissions derived from the "authenticated users" role. Chris@0: * @param string $name Chris@0: * The user name. Chris@0: * @param bool $admin Chris@0: * (optional) Whether the user should be an administrator Chris@0: * with all the available permissions. Chris@18: * @param array $values Chris@18: * (optional) An array of initial user field values. Chris@0: * Chris@0: * @return \Drupal\user\Entity\User|false Chris@0: * A fully loaded user object with pass_raw property, or FALSE if account Chris@0: * creation fails. Chris@18: * Chris@18: * @throws \Drupal\Core\Entity\EntityStorageException Chris@18: * If the user creation fails. Chris@0: */ Chris@18: protected function createUser(array $permissions = [], $name = NULL, $admin = FALSE, array $values = []) { Chris@0: // Create a role with the given permission set, if any. Chris@0: $rid = FALSE; Chris@0: if ($permissions) { Chris@0: $rid = $this->createRole($permissions); Chris@0: if (!$rid) { Chris@0: return FALSE; Chris@0: } Chris@0: } Chris@0: Chris@0: // Create a user assigned to that role. Chris@18: $edit = $values; Chris@18: if ($name) { Chris@18: $edit['name'] = $name; Chris@18: } Chris@18: elseif (!isset($values['name'])) { Chris@18: $edit['name'] = $this->randomMachineName(); Chris@18: } Chris@18: $edit += [ Chris@18: 'mail' => $edit['name'] . '@example.com', Chris@18: 'pass' => user_password(), Chris@18: 'status' => 1, Chris@18: ]; Chris@0: if ($rid) { Chris@0: $edit['roles'] = [$rid]; Chris@0: } Chris@0: Chris@0: if ($admin) { Chris@0: $edit['roles'][] = $this->createAdminRole(); Chris@0: } Chris@0: Chris@0: $account = User::create($edit); Chris@0: $account->save(); Chris@0: Chris@18: $valid_user = $account->id() !== NULL; Chris@18: $this->assertTrue($valid_user, new FormattableMarkup('User created with name %name and pass %pass', ['%name' => $edit['name'], '%pass' => $edit['pass']]), 'User login'); Chris@18: if (!$valid_user) { Chris@0: return FALSE; Chris@0: } Chris@0: Chris@0: // Add the raw password so that we can log in as this user. Chris@0: $account->pass_raw = $edit['pass']; Chris@0: // Support BrowserTestBase as well. Chris@0: $account->passRaw = $account->pass_raw; Chris@0: return $account; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Creates an administrative role. Chris@0: * Chris@0: * @param string $rid Chris@0: * (optional) The role ID (machine name). Defaults to a random name. Chris@0: * @param string $name Chris@0: * (optional) The label for the role. Defaults to a random string. Chris@0: * @param int $weight Chris@0: * (optional) The weight for the role. Defaults NULL so that entity_create() Chris@0: * sets the weight to maximum + 1. Chris@0: * Chris@0: * @return string Chris@0: * Role ID of newly created role, or FALSE if role creation failed. Chris@0: */ Chris@0: protected function createAdminRole($rid = NULL, $name = NULL, $weight = NULL) { Chris@0: $rid = $this->createRole([], $rid, $name, $weight); Chris@0: if ($rid) { Chris@0: /** @var \Drupal\user\RoleInterface $role */ Chris@0: $role = Role::load($rid); Chris@0: $role->setIsAdmin(TRUE); Chris@0: $role->save(); Chris@0: } Chris@0: return $rid; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Creates a role with specified permissions. Chris@0: * Chris@0: * @param array $permissions Chris@0: * Array of permission names to assign to role. Chris@0: * @param string $rid Chris@0: * (optional) The role ID (machine name). Defaults to a random name. Chris@0: * @param string $name Chris@0: * (optional) The label for the role. Defaults to a random string. Chris@0: * @param int $weight Chris@0: * (optional) The weight for the role. Defaults NULL so that entity_create() Chris@0: * sets the weight to maximum + 1. Chris@0: * Chris@0: * @return string Chris@0: * Role ID of newly created role, or FALSE if role creation failed. Chris@0: */ Chris@0: protected function createRole(array $permissions, $rid = NULL, $name = NULL, $weight = NULL) { Chris@0: // Generate a random, lowercase machine name if none was passed. Chris@0: if (!isset($rid)) { Chris@0: $rid = strtolower($this->randomMachineName(8)); Chris@0: } Chris@0: // Generate a random label. Chris@0: if (!isset($name)) { Chris@0: // In the role UI role names are trimmed and random string can start or Chris@0: // end with a space. Chris@0: $name = trim($this->randomString(8)); Chris@0: } Chris@0: Chris@0: // Check the all the permissions strings are valid. Chris@0: if (!$this->checkPermissions($permissions)) { Chris@0: return FALSE; Chris@0: } Chris@0: Chris@0: // Create new role. Chris@0: $role = Role::create([ Chris@0: 'id' => $rid, Chris@0: 'label' => $name, Chris@0: ]); Chris@0: if (isset($weight)) { Chris@0: $role->set('weight', $weight); Chris@0: } Chris@0: $result = $role->save(); Chris@0: Chris@17: $this->assertIdentical($result, SAVED_NEW, new FormattableMarkup('Created role ID @rid with name @name.', [ Chris@0: '@name' => var_export($role->label(), TRUE), Chris@0: '@rid' => var_export($role->id(), TRUE), Chris@0: ]), 'Role'); Chris@0: Chris@0: if ($result === SAVED_NEW) { Chris@0: // Grant the specified permissions to the role, if any. Chris@0: if (!empty($permissions)) { Chris@0: $this->grantPermissions($role, $permissions); Chris@0: $assigned_permissions = Role::load($role->id())->getPermissions(); Chris@0: $missing_permissions = array_diff($permissions, $assigned_permissions); Chris@0: if (!$missing_permissions) { Chris@17: $this->pass(new FormattableMarkup('Created permissions: @perms', ['@perms' => implode(', ', $permissions)]), 'Role'); Chris@0: } Chris@0: else { Chris@17: $this->fail(new FormattableMarkup('Failed to create permissions: @perms', ['@perms' => implode(', ', $missing_permissions)]), 'Role'); Chris@0: } Chris@0: } Chris@0: return $role->id(); Chris@0: } Chris@0: else { Chris@0: return FALSE; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Checks whether a given list of permission names is valid. Chris@0: * Chris@0: * @param array $permissions Chris@0: * The permission names to check. Chris@0: * Chris@0: * @return bool Chris@0: * TRUE if the permissions are valid, FALSE otherwise. Chris@0: */ Chris@0: protected function checkPermissions(array $permissions) { Chris@0: $available = array_keys(\Drupal::service('user.permissions')->getPermissions()); Chris@0: $valid = TRUE; Chris@0: foreach ($permissions as $permission) { Chris@0: if (!in_array($permission, $available)) { Chris@17: $this->fail(new FormattableMarkup('Invalid permission %permission.', ['%permission' => $permission]), 'Role'); Chris@0: $valid = FALSE; Chris@0: } Chris@0: } Chris@0: return $valid; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Grant permissions to a user role. Chris@0: * Chris@0: * @param \Drupal\user\RoleInterface $role Chris@0: * The ID of a user role to alter. Chris@0: * @param array $permissions Chris@0: * (optional) A list of permission names to grant. Chris@0: */ Chris@0: protected function grantPermissions(RoleInterface $role, array $permissions) { Chris@0: foreach ($permissions as $permission) { Chris@0: $role->grantPermission($permission); Chris@0: } Chris@0: $role->trustData()->save(); Chris@0: } Chris@0: Chris@0: }