Issue #3443037 by alexpott, catch, kristiaanvandeneynde, Taran2L, longwave: Super user access policy and the installer
parent
215c567f38
commit
a2af0aa123
|
@ -4,6 +4,7 @@ namespace Drupal\Core\Installer\Form;
|
|||
|
||||
use Drupal\Core\Datetime\TimeZoneFormHelper;
|
||||
use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Extension\ModuleInstallerInterface;
|
||||
use Drupal\Core\Form\ConfigFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
@ -67,29 +68,39 @@ class SiteConfigureForm extends ConfigFormBase {
|
|||
* The app root.
|
||||
* @param string $site_path
|
||||
* The site path.
|
||||
* @param \Drupal\user\UserStorageInterface $user_storage
|
||||
* The user storage.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface|\Drupal\user\UserStorageInterface $entityTypeManager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Core\Extension\ModuleInstallerInterface $module_installer
|
||||
* The module installer.
|
||||
* @param \Drupal\Core\Locale\CountryManagerInterface|\Drupal\user\UserNameValidator $userNameValidator
|
||||
* The user validator.
|
||||
* @param bool|null $superUserAccessPolicy
|
||||
* The value of the 'security.enable_super_user' container parameter.
|
||||
*/
|
||||
public function __construct(
|
||||
$root,
|
||||
$site_path,
|
||||
UserStorageInterface $user_storage,
|
||||
protected EntityTypeManagerInterface|UserStorageInterface $entityTypeManager,
|
||||
ModuleInstallerInterface $module_installer,
|
||||
protected CountryManagerInterface|UserNameValidator $userNameValidator,
|
||||
protected ?bool $superUserAccessPolicy = NULL,
|
||||
) {
|
||||
$this->root = $root;
|
||||
$this->sitePath = $site_path;
|
||||
$this->userStorage = $user_storage;
|
||||
if ($this->entityTypeManager instanceof UserStorageInterface) {
|
||||
@trigger_error('Calling ' . __METHOD__ . '() with the $entityTypeManager argument as UserStorageInterface is deprecated in drupal:10.3.0 and must be EntityTypeManagerInterface in drupal:11.0.0. See https://www.drupal.org/node/3443172', E_USER_DEPRECATED);
|
||||
$this->entityTypeManager = \Drupal::entityTypeManager();
|
||||
}
|
||||
$this->userStorage = $this->entityTypeManager->getStorage('user');
|
||||
$this->moduleInstaller = $module_installer;
|
||||
if ($userNameValidator instanceof CountryManagerInterface) {
|
||||
@trigger_error('Calling ' . __METHOD__ . '() with the $userNameValidator argument as CountryManagerInterface is deprecated in drupal:10.3.0 and must be UserNameValidator in drupal:11.0.0. See https://www.drupal.org/node/3431205', E_USER_DEPRECATED);
|
||||
$userNameValidator = \Drupal::service('user.name_validator');
|
||||
$this->userNameValidator = \Drupal::service('user.name_validator');
|
||||
}
|
||||
if ($this->superUserAccessPolicy === NULL) {
|
||||
@trigger_error('Calling ' . __METHOD__ . '() without the $superUserAccessPolicy argument is deprecated in drupal:10.3.0 and must be passed in drupal:11.0.0. See https://www.drupal.org/node/3443172', E_USER_DEPRECATED);
|
||||
$this->superUserAccessPolicy = \Drupal::getContainer()->getParameter('security.enable_super_user') ?? TRUE;
|
||||
}
|
||||
$this->userNameValidator = $userNameValidator;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -99,9 +110,13 @@ class SiteConfigureForm extends ConfigFormBase {
|
|||
return new static(
|
||||
$container->getParameter('app.root'),
|
||||
$container->getParameter('site.path'),
|
||||
$container->get('entity_type.manager')->getStorage('user'),
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('module_installer'),
|
||||
$container->get('user.name_validator'),
|
||||
// In order to disable the super user policy this must be set to FALSE. If
|
||||
// the container parameter is missing then the policy is enabled. See
|
||||
// \Drupal\Core\DependencyInjection\Compiler\SuperUserAccessPolicyPass.
|
||||
$container->getParameter('security.enable_super_user') ?? TRUE,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -177,9 +192,16 @@ class SiteConfigureForm extends ConfigFormBase {
|
|||
'#access' => empty($install_state['config_install_path']),
|
||||
];
|
||||
|
||||
if (count($this->getAdminRoles()) === 0 && $this->superUserAccessPolicy === FALSE) {
|
||||
$account_label = $this->t('Site account');
|
||||
}
|
||||
else {
|
||||
$account_label = $this->t('Site maintenance account');
|
||||
}
|
||||
|
||||
$form['admin_account'] = [
|
||||
'#type' => 'fieldgroup',
|
||||
'#title' => $this->t('Site maintenance account'),
|
||||
'#title' => $account_label,
|
||||
];
|
||||
$form['admin_account']['account']['name'] = [
|
||||
'#type' => 'textfield',
|
||||
|
@ -300,6 +322,7 @@ class SiteConfigureForm extends ConfigFormBase {
|
|||
}
|
||||
|
||||
// We created user 1 with placeholder values. Let's save the real values.
|
||||
/** @var \Drupal\user\UserInterface $account */
|
||||
$account = $this->userStorage->load(1);
|
||||
$account->init = $account->mail = $account_values['mail'];
|
||||
$account->roles = $account->getRoles();
|
||||
|
@ -307,7 +330,38 @@ class SiteConfigureForm extends ConfigFormBase {
|
|||
$account->timezone = $form_state->getValue('date_default_timezone');
|
||||
$account->pass = $account_values['pass'];
|
||||
$account->name = $account_values['name'];
|
||||
|
||||
// Ensure user 1 has an administrator role if one exists.
|
||||
/** @var \Drupal\user\RoleInterface[] $admin_roles */
|
||||
$admin_roles = $this->getAdminRoles();
|
||||
if (count(array_intersect($account->getRoles(), array_keys($admin_roles))) === 0) {
|
||||
if (count($admin_roles) > 0) {
|
||||
foreach ($admin_roles as $role) {
|
||||
$account->addRole($role->id());
|
||||
}
|
||||
}
|
||||
elseif ($this->superUserAccessPolicy === FALSE) {
|
||||
$this->messenger()->addWarning($this->t(
|
||||
'The user %username does not have administrator access. For more information, see the documentation on <a href="@secure-user-1-docs">securing the admin super user</a>.',
|
||||
[
|
||||
'%username' => $account->getDisplayName(),
|
||||
'@secure-user-1-docs' => 'https://www.drupal.org/docs/administering-a-drupal-site/security-in-drupal/securing-the-admin-super-user-1#s-disable-the-super-user-access-policy',
|
||||
]
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
$account->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of admin roles.
|
||||
*
|
||||
* @return \Drupal\user\RoleInterface[]
|
||||
* The list of admin roles.
|
||||
*/
|
||||
protected function getAdminRoles(): array {
|
||||
return $this->entityTypeManager->getStorage('user_role')->loadByProperties(['is_admin' => TRUE]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Installer;
|
||||
|
||||
use Drupal\Core\Session\AccessPolicyBase;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Session\CalculatedPermissionsItem;
|
||||
use Drupal\Core\Session\RefinableCalculatedPermissionsInterface;
|
||||
|
||||
/**
|
||||
* Grants user 1 an all access pass during install.
|
||||
*
|
||||
* @internal
|
||||
* The policy is only to be used by the installer.
|
||||
*/
|
||||
final class InstallerAccessPolicy extends AccessPolicyBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculatePermissions(AccountInterface $account, string $scope): RefinableCalculatedPermissionsInterface {
|
||||
$calculated_permissions = parent::calculatePermissions($account, $scope);
|
||||
|
||||
// Prevent the access policy from working when not in the installer.
|
||||
if (((int) $account->id()) !== 1 || !InstallerKernel::installationAttempted()) {
|
||||
return $calculated_permissions;
|
||||
}
|
||||
|
||||
return $calculated_permissions->addItem(new CalculatedPermissionsItem([], TRUE));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPersistentCacheContexts(): array {
|
||||
// Note that cache contexts in the installer are ignored because
|
||||
// \Drupal\Core\Installer\NormalInstallerServiceProvider::register() changes
|
||||
// everything to use a memory cache. If this was not the case, then this
|
||||
// should also return a cache context related to the return value of
|
||||
// \Drupal\Core\Installer\InstallerKernel::installationAttempted().
|
||||
return ['user.is_super_user'];
|
||||
}
|
||||
|
||||
}
|
|
@ -12,6 +12,7 @@ use Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass;
|
|||
use Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass;
|
||||
use Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass;
|
||||
use Symfony\Component\DependencyInjection\Compiler\ResolveHotPathPass;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
|
@ -33,6 +34,9 @@ class NormalInstallerServiceProvider implements ServiceProviderInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function register(ContainerBuilder $container) {
|
||||
// During the installer user 1 is a superuser.
|
||||
$container->setDefinition(InstallerAccessPolicy::class, (new Definition())->addTag('access_policy')->setPublic(FALSE));
|
||||
|
||||
// Replace cache services with in-memory implementations. The results in
|
||||
// less queries to set caches which will only be cleared on the next module
|
||||
// install.
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* Install, update and uninstall functions for the demo_umami installation profile.
|
||||
*/
|
||||
|
||||
use Drupal\user\Entity\User;
|
||||
use Drupal\shortcut\Entity\Shortcut;
|
||||
|
||||
/**
|
||||
|
@ -34,12 +33,6 @@ function demo_umami_requirements($phase) {
|
|||
* @see system_install()
|
||||
*/
|
||||
function demo_umami_install() {
|
||||
// Assign user 1 the "administrator" role.
|
||||
/** @var \Drupal\user\Entity\User $user */
|
||||
$user = User::load(1);
|
||||
$user->addRole('administrator');
|
||||
$user->save();
|
||||
|
||||
// We install some menu links, so we have to rebuild the router, to ensure the
|
||||
// menu links are valid.
|
||||
\Drupal::service('router.builder')->rebuildIfNeeded();
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* Install, update and uninstall functions for the standard installation profile.
|
||||
*/
|
||||
|
||||
use Drupal\user\Entity\User;
|
||||
use Drupal\shortcut\Entity\Shortcut;
|
||||
|
||||
/**
|
||||
|
@ -16,12 +15,6 @@ use Drupal\shortcut\Entity\Shortcut;
|
|||
* @see system_install()
|
||||
*/
|
||||
function standard_install() {
|
||||
// Assign user 1 the "administrator" role.
|
||||
/** @var \Drupal\user\Entity\User $user */
|
||||
$user = User::load(1);
|
||||
$user->addRole('administrator');
|
||||
$user->save();
|
||||
|
||||
// Populate the default shortcut set.
|
||||
$shortcut = Shortcut::create([
|
||||
'shortcut_set' => 'default',
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\FunctionalTests\Installer;
|
||||
|
||||
use Drupal\Core\Serialization\Yaml;
|
||||
use Drupal\user\Entity\User;
|
||||
|
||||
/**
|
||||
* Tests superuser access and the installer.
|
||||
*
|
||||
* @group Installer
|
||||
*/
|
||||
class SuperUserAccessInstallTest extends InstallerTestBase {
|
||||
|
||||
/**
|
||||
* Message when the logged-in user does not have admin access after install.
|
||||
*
|
||||
* @see \Drupal\Core\Installer\Form\SiteConfigureForm::submitForm())
|
||||
*/
|
||||
protected const NO_ACCESS_MESSAGE = 'The user %s does not have administrator access. For more information, see the documentation on securing the admin super user.';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $profile = 'superuser';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function prepareEnvironment() {
|
||||
parent::prepareEnvironment();
|
||||
$info = [
|
||||
'type' => 'profile',
|
||||
'core_version_requirement' => '*',
|
||||
'name' => 'Superuser testing profile',
|
||||
];
|
||||
// File API functions are not available yet.
|
||||
$path = $this->siteDirectory . '/profiles/superuser';
|
||||
mkdir($path, 0777, TRUE);
|
||||
file_put_contents("$path/superuser.info.yml", Yaml::encode($info));
|
||||
|
||||
file_put_contents("$path/superuser.install", $this->getProvidedData()['install_code']);
|
||||
|
||||
$services = Yaml::decode(file_get_contents(DRUPAL_ROOT . '/sites/default/default.services.yml'));
|
||||
$services['parameters']['security.enable_super_user'] = $this->getProvidedData()['super_user_policy'];
|
||||
file_put_contents(DRUPAL_ROOT . '/' . $this->siteDirectory . '/services.yml', Yaml::encode($services));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpSite() {
|
||||
if ($this->getProvidedData()['super_user_policy'] === FALSE && empty($this->getProvidedData()['expected_roles'])) {
|
||||
$this->assertSession()->pageTextContains('Site account');
|
||||
$this->assertSession()->pageTextNotContains('Site maintenance account');
|
||||
}
|
||||
else {
|
||||
$this->assertSession()->pageTextNotContains('Site account');
|
||||
$this->assertSession()->pageTextContains('Site maintenance account');
|
||||
}
|
||||
parent::setUpSite();
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that the installation succeeded.
|
||||
*
|
||||
* @dataProvider getInstallTests
|
||||
*/
|
||||
public function testInstalled(bool $expected_runtime_has_permission, bool $expected_no_access_message, array $expected_roles): void {
|
||||
$user = User::load(1);
|
||||
$this->assertSame($expected_runtime_has_permission, $user->hasPermission('administer software updates'));
|
||||
$this->assertTrue(\Drupal::state()->get('admin_permission_in_installer'));
|
||||
$message = sprintf(static::NO_ACCESS_MESSAGE, $this->rootUser->getDisplayName());
|
||||
if ($expected_no_access_message) {
|
||||
$this->assertSession()->pageTextContains($message);
|
||||
}
|
||||
else {
|
||||
$this->assertSession()->pageTextNotContains($message);
|
||||
}
|
||||
$this->assertSame($expected_roles, $user->getRoles(TRUE));
|
||||
}
|
||||
|
||||
public static function getInstallTests(): array {
|
||||
$test_cases = [];
|
||||
$test_cases['runtime super user policy enabled'] = [
|
||||
'expected_runtime_has_permission' => TRUE,
|
||||
'expected_no_access_message' => FALSE,
|
||||
'expected_roles' => [],
|
||||
'install_code' => <<<PHP
|
||||
<?php
|
||||
function superuser_install() {
|
||||
\$user = \Drupal\user\Entity\User::load(1);
|
||||
\Drupal::state()->set('admin_permission_in_installer', \$user->hasPermission('administer software updates'));
|
||||
}
|
||||
PHP,
|
||||
'super_user_policy' => TRUE,
|
||||
];
|
||||
|
||||
$test_cases['no super user policy enabled and no admin role'] = [
|
||||
'expected_runtime_has_permission' => FALSE,
|
||||
'expected_no_access_message' => TRUE,
|
||||
'expected_roles' => [],
|
||||
'install_code' => $test_cases['runtime super user policy enabled']['install_code'],
|
||||
'super_user_policy' => FALSE,
|
||||
];
|
||||
|
||||
$test_cases['no super user policy enabled and admin role'] = [
|
||||
'expected_runtime_has_permission' => TRUE,
|
||||
'expected_no_access_message' => FALSE,
|
||||
'expected_roles' => ['admin_role'],
|
||||
'install_code' => <<<PHP
|
||||
<?php
|
||||
function superuser_install() {
|
||||
\$user = \Drupal\user\Entity\User::load(1);
|
||||
\Drupal::state()->set('admin_permission_in_installer', \$user->hasPermission('administer software updates'));
|
||||
\Drupal\user\Entity\Role::create(['id' => 'admin_role', 'label' => 'Admin role'])->setIsAdmin(TRUE)->save();
|
||||
\Drupal\user\Entity\Role::create(['id' => 'another_role', 'label' => 'Another role'])->save();
|
||||
}
|
||||
PHP,
|
||||
'super_user_policy' => FALSE,
|
||||
];
|
||||
|
||||
$test_cases['no super user policy enabled and multiple admin role'] = [
|
||||
'expected_runtime_has_permission' => TRUE,
|
||||
'expected_no_access_message' => FALSE,
|
||||
'expected_roles' => ['admin_role', 'another_admin_role'],
|
||||
'install_code' => <<<PHP
|
||||
<?php
|
||||
function superuser_install() {
|
||||
\$user = \Drupal\user\Entity\User::load(1);
|
||||
\Drupal::state()->set('admin_permission_in_installer', \$user->hasPermission('administer software updates'));
|
||||
\Drupal\user\Entity\Role::create(['id' => 'admin_role', 'label' => 'Admin role'])->setIsAdmin(TRUE)->save();
|
||||
\Drupal\user\Entity\Role::create(['id' => 'another_admin_role', 'label' => 'Another admin role'])->setIsAdmin(TRUE)->save();
|
||||
\Drupal\user\Entity\Role::create(['id' => 'another_role', 'label' => 'Another role'])->save();
|
||||
}
|
||||
PHP,
|
||||
'super_user_policy' => FALSE,
|
||||
];
|
||||
|
||||
return $test_cases;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue