Verified Commit e4e14c71 authored by Alex Pott's avatar Alex Pott
Browse files

Issue #3442024 by phenaproxima, alexpott, larowlan, amateescu: Account...

Issue #3442024 by phenaproxima, alexpott, larowlan, amateescu: Account switching to user 1 is now fraught

(cherry picked from commit d20f0ea528f0bdce66f7fabd8f840468757b59ae)
parent e069efcf
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -20,7 +20,7 @@ variables:
  MYSQL_PASSWORD: drupaltestbotpw
  # Note if you add anything to the lists below you will need to change the root
  # phpunit.xml.dist file.
  TEST_DIRECTORIES: "core/tests/Drupal/Tests/Core/Recipe core/tests/Drupal/KernelTests/Core/Recipe core/tests/Drupal/FunctionalTests/Core/Recipe core/tests/Drupal/KernelTests/Core/Config/Action core/tests/Drupal/KernelTests/Core/Config/Storage/Checkpoint core/tests/Drupal/Tests/Core/Config/Checkpoint core/tests/Drupal/Tests/Core/Config/Action core/modules/content_moderation/tests/src/Kernel/ConfigAction core/modules/ckeditor5/tests/src/Kernel/ConfigAction core/tests/Drupal/Tests/Core/DefaultContent core/tests/Drupal/FunctionalTests/DefaultContent"
  TEST_DIRECTORIES: "core/tests/Drupal/Tests/Core/Recipe core/tests/Drupal/KernelTests/Core/Recipe core/tests/Drupal/FunctionalTests/Core/Recipe core/tests/Drupal/KernelTests/Core/Config/Action core/tests/Drupal/KernelTests/Core/Config/Storage/Checkpoint core/tests/Drupal/Tests/Core/Config/Checkpoint core/tests/Drupal/Tests/Core/Config/Action core/modules/content_moderation/tests/src/Kernel/ConfigAction core/modules/ckeditor5/tests/src/Kernel/ConfigAction core/tests/Drupal/Tests/Core/DefaultContent core/tests/Drupal/KernelTests/Core/DefaultContent core/tests/Drupal/FunctionalTests/DefaultContent"
  CODE_DIRECTORIES: "core/lib/Drupal/Core/Recipe core/lib/Drupal/Core/Config/Action core/modules/config/tests/config_action_duplicate_test core/tests/fixtures/recipes core/lib/Drupal/Core/Config/Checkpoint core/modules/content_moderation/src/Plugin/ConfigAction core/modules/ckeditor5/src/Plugin/ConfigAction core/lib/Drupal/Core/DefaultContent"
  ALL_DIRECTORIES: "${CODE_DIRECTORIES} ${TEST_DIRECTORIES}"

+5 −0
Original line number Diff line number Diff line
@@ -73,6 +73,11 @@ services:
    arguments: ['@config.manager', '@config.storage', '@config.typed', '@config.factory']
  Drupal\Core\DefaultContent\Importer:
    autowire: true
  Drupal\Core\DefaultContent\AdminAccountSwitcher:
    arguments:
      $isSuperUserAccessEnabled: '%security.enable_super_user%'
    autowire: true
    public: false
  # Simple cache contexts, directly derived from the request context.
  cache_context.ip:
    class: Drupal\Core\Cache\Context\IpCacheContext
+83 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Core\DefaultContent;

use Drupal\Core\Access\AccessException;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\AccountSwitcherInterface;

/**
 * @internal
 *   This API is experimental.
 */
final class AdminAccountSwitcher implements AccountSwitcherInterface {

  public function __construct(
    private readonly AccountSwitcherInterface $decorated,
    private readonly EntityTypeManagerInterface $entityTypeManager,
    private readonly bool $isSuperUserAccessEnabled,
  ) {}

  /**
   * Switches to an administrative account.
   *
   * This will switch to the first available account with a role that has the
   * `is_admin` flag. If there are no such roles, or no such users, this will
   * try to switch to user 1 if superuser access is enabled.
   *
   * @return \Drupal\Core\Session\AccountInterface
   *   The account that was switched to.
   *
   * @throws \Drupal\Core\Access\AccessException
   *   Thrown if there are no users with administrative roles.
   */
  public function switchToAdministrator(): AccountInterface {
    $admin_roles = $this->entityTypeManager->getStorage('user_role')
      ->getQuery()
      ->condition('is_admin', TRUE)
      ->execute();

    $user_storage = $this->entityTypeManager->getStorage('user');

    if ($admin_roles) {
      $accounts = $user_storage->getQuery()
        ->accessCheck(FALSE)
        ->condition('roles', $admin_roles, 'IN')
        ->condition('status', 1)
        ->sort('uid')
        ->range(0, 1)
        ->execute();
    }
    else {
      $accounts = [];
    }
    $account = $user_storage->load(reset($accounts) ?: 1);
    assert($account instanceof AccountInterface);

    if (array_intersect($account->getRoles(), $admin_roles) || ((int) $account->id() === 1 && $this->isSuperUserAccessEnabled)) {
      $this->switchTo($account);
      return $account;
    }
    throw new AccessException("There are no user accounts with administrative roles.");
  }

  /**
   * {@inheritdoc}
   */
  public function switchTo(AccountInterface $account): AccountSwitcherInterface {
    $this->decorated->switchTo($account);
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function switchBack(): AccountSwitcherInterface {
    $this->decorated->switchBack();
    return $this;
  }

}
+37 −36
Original line number Diff line number Diff line
@@ -13,7 +13,6 @@
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Installer\InstallerKernel;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Session\AccountSwitcherInterface;
use Drupal\file\FileInterface;
use Drupal\link\Plugin\Field\FieldType\LinkItem;
use Drupal\user\EntityOwnerInterface;
@@ -42,7 +41,7 @@ final class Importer implements LoggerAwareInterface {

  public function __construct(
    private readonly EntityTypeManagerInterface $entityTypeManager,
    private readonly AccountSwitcherInterface $accountSwitcher,
    private readonly AdminAccountSwitcher $accountSwitcher,
    private readonly FileSystemInterface $fileSystem,
    private readonly LanguageManagerInterface $languageManager,
    private readonly EntityRepositoryInterface $entityRepository,
@@ -71,10 +70,9 @@ public function importContent(Finder $content, Existing $existing = Existing::Er
      return;
    }

    /** @var \Drupal\user\UserInterface $root_user */
    $root_user = $this->entityTypeManager->getStorage('user')->load(1);
    $this->accountSwitcher->switchTo($root_user);
    $account = $this->accountSwitcher->switchToAdministrator();

    try {
      /** @var array{_meta: array<mixed>} $decoded */
      foreach ($content->data as $decoded) {
        ['uuid' => $uuid, 'entity_type' => $entity_type_id, 'path' => $path] = $decoded['_meta'];
@@ -102,7 +100,7 @@ public function importContent(Finder $content, Existing $existing = Existing::Er

        // Ensure that the entity is not owned by the anonymous user.
        if ($entity instanceof EntityOwnerInterface && empty($entity->getOwnerId())) {
        $entity->setOwner($root_user);
          $entity->setOwnerId($account->id());
        }

        // If a file exists in the same folder, copy it to the designated
@@ -112,8 +110,11 @@ public function importContent(Finder $content, Existing $existing = Existing::Er
        }
        $entity->save();
      }
    }
    finally {
      $this->accountSwitcher->switchBack();
    }
  }

  private function copyFileAssociatedWithEntity(string $path, FileInterface $entity): void {
    $destination = $entity->getFileUri();
+1 −0
Original line number Diff line number Diff line
@@ -64,6 +64,7 @@ public function providerApplyRecipe(): iterable {
   * @dataProvider providerApplyRecipe
   */
  public function testApplyRecipe(string $path): void {
    $this->setUpCurrentUser(admin: TRUE);
    $this->applyRecipe($path);
  }

Loading