Commit 8421e649 authored by Zeeshaan khaan's avatar Zeeshaan khaan
Browse files

Merge branch '185492-privilege-escalation-fix' into '1.0.x'

Require administer permissions and filter assignable roles

See merge request security/185492-bulk_update_user_roles-security!1
parents a9b6356a 64fcea71
Loading
Loading
Loading
Loading
Loading
+1 −1
Changes for bulk_update_user_roles.routing.yml: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -4,4 +4,4 @@ bulk_update_user_roles.settings:
    _form: '\Drupal\bulk_update_user_roles\Form\BulkUpdateUserForm'
    _title: 'Bulk Update User Roles'
  requirements:
    _permission: 'administer users'
    _permission: 'administer permissions'
+55 −14
Changes for src/Form/BulkUpdateUserForm.php: 55 added lines, 14 removed lines.
Original line number Diff line number Diff line
@@ -8,7 +8,8 @@ use Drupal\bulk_update_user_roles\Batch\BulkUpdateUserRoles;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\user\RoleStorageInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\user\RoleInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
@@ -24,23 +25,23 @@ class BulkUpdateUserForm extends FormBase {
  protected $entityTypeManager;

  /**
   * The role storage.
   * The current user.
   *
   * @var \Drupal\user\RoleStorageInterface
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected $roleStorage;
  protected $currentUserAccount;

  /**
   * Constructs a new BulkUpdateUserForm.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\user\RoleStorageInterface $role_storage
   *   The role storage.
   * @param \Drupal\Core\Session\AccountInterface $current_user
   *   The current user.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager, RoleStorageInterface $role_storage) {
  public function __construct(EntityTypeManagerInterface $entity_type_manager, AccountInterface $current_user) {
    $this->entityTypeManager = $entity_type_manager;
    $this->roleStorage = $role_storage;
    $this->currentUserAccount = $current_user;
  }

  /**
@@ -49,7 +50,7 @@ class BulkUpdateUserForm extends FormBase {
  public static function create(ContainerInterface $container): static {
    return new static(
      $container->get('entity_type.manager'),
      $container->get('entity_type.manager')->getStorage('user_role')
      $container->get('current_user')
    );
  }

@@ -69,13 +70,10 @@ class BulkUpdateUserForm extends FormBase {
      'remove' => $this->t('Remove roles'),
    ];

    $roles = $this->roleStorage->loadMultiple();
    $role_options = [];
    foreach ($roles as $role) {
      if (!in_array($role->id(), ['anonymous', 'authenticated'])) {
    foreach ($this->getAssignableRoles() as $role) {
      $role_options[$role->id()] = $role->label();
    }
    }

    $user_storage = $this->entityTypeManager->getStorage('user');
    $query = $user_storage->getQuery()
@@ -159,11 +157,54 @@ class BulkUpdateUserForm extends FormBase {
    return $options;
  }

  /**
   * Returns the roles the current user is allowed to bulk assign/remove.
   *
   * Mirrors \Drupal\user\AccountForm, which only exposes role assignment at
   * all to users holding 'administer permissions'. The route already
   * requires that permission, but the role list is filtered again here
   * (and re-checked in submitForm()) so that a mis-restricted route, a
   * stale cached page, or a directly-crafted POST can never grant a role
   * the current user was not actually authorized to hand out -- an
   * account must not be able to assign an admin role it does not already
   * hold itself. User 1 is exempt from that check: as the site's
   * superuser it typically holds no roles at all (its permissions bypass
   * comes from being uid 1, not from role membership), so without this
   * exemption user 1 could never assign an admin role through this form.
   *
   * @return \Drupal\user\RoleInterface[]
   *   The assignable roles, keyed by role ID.
   */
  protected function getAssignableRoles(): array {
    if (!$this->currentUserAccount->hasPermission('administer permissions')) {
      return [];
    }

    $assignable = [];
    foreach ($this->entityTypeManager->getStorage('user_role')->loadMultiple() as $role) {
      if (in_array($role->id(), [RoleInterface::ANONYMOUS_ID, RoleInterface::AUTHENTICATED_ID], TRUE)) {
        continue;
      }
      if ($role->isAdmin() && (int) $this->currentUserAccount->id() !== 1 && !in_array($role->id(), $this->currentUserAccount->getRoles(), TRUE)) {
        continue;
      }
      $assignable[$role->id()] = $role;
    }
    return $assignable;
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state): void {
    $roles_selected = $form_state->getValue('buur_user_roles');
    $assignable_role_ids = array_keys($this->getAssignableRoles());
    $roles_selected = array_intersect((array) $form_state->getValue('buur_user_roles'), $assignable_role_ids);

    if (empty($roles_selected)) {
      $this->messenger()->addError($this->t('You are not authorized to assign or remove the selected role(s).'));
      return;
    }

    $select_all_users = $form_state->getValue('select_all_users');
    $user_storage = $this->entityTypeManager->getStorage('user');
    $query = $user_storage->getQuery()