diff --git a/CHANGELOG.md b/CHANGELOG.md index 67de33fd3..34bae4154 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [Add a Group membership assignment quick form #723](https://github.com/farmOS/farmOS/pull/723) - [farmOS Setup Menu #706](https://github.com/farmOS/farmOS/pull/706) - [Issue #3354935: Configurable quick forms](https://www.drupal.org/project/farm/issues/3354935) +- [Add an Account Admin role with permission to administer users and assign managed roles #714](https://github.com/farmOS/farmOS/pull/714) ### Changed diff --git a/composer.json b/composer.json index 6504e3b4c..631195c20 100644 --- a/composer.json +++ b/composer.json @@ -41,6 +41,7 @@ "drupal/migrate_source_csv": "^3.5", "drupal/migrate_source_ui": "^1.0", "drupal/migrate_tools": "^6.0.2", + "drupal/role_delegation": "^1.2", "drupal/simple_oauth": "5.2.3", "drupal/state_machine": "^1.0", "drupal/subrequests": "^3.0.3", diff --git a/farm.profile b/farm.profile index 375a10566..dcafb2f15 100644 --- a/farm.profile +++ b/farm.profile @@ -60,6 +60,7 @@ function farm_modules() { 'farm_import_kml' => t('KML asset importer'), 'farm_fieldkit' => t('Field Kit integration'), 'farm_l10n' => t('Translation/localization features'), + 'farm_role_account_admin' => t('Account Admin role'), ], ]; } diff --git a/modules/core/role/modules/account_admin/config/install/user.role.farm_account_admin.yml b/modules/core/role/modules/account_admin/config/install/user.role.farm_account_admin.yml new file mode 100644 index 000000000..aad8266a6 --- /dev/null +++ b/modules/core/role/modules/account_admin/config/install/user.role.farm_account_admin.yml @@ -0,0 +1,24 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - farm_role_account_admin + module: + - farm_role +id: farm_account_admin +label: 'Account Admin' +weight: 1 +is_admin: false +permissions: + - 'administer farm settings' + - 'administer users' +third_party_settings: + farm_role: + access: + config: true + entity: + view all: false + create all: false + update all: false + delete all: false diff --git a/modules/core/role/modules/account_admin/config/schema/farm_role_account_admin.schema.yml b/modules/core/role/modules/account_admin/config/schema/farm_role_account_admin.schema.yml new file mode 100644 index 000000000..e802eccf0 --- /dev/null +++ b/modules/core/role/modules/account_admin/config/schema/farm_role_account_admin.schema.yml @@ -0,0 +1,7 @@ +farm_role_account_admin.settings: + type: config_object + label: 'farmOS Account Admin Role settings' + mapping: + allow_peer_role_assignment: + type: boolean + label: 'Allow users with the Account Admin role to assign/revoke the Account Admin role.' diff --git a/modules/core/role/modules/account_admin/farm_role_account_admin.info.yml b/modules/core/role/modules/account_admin/farm_role_account_admin.info.yml new file mode 100644 index 000000000..cae0451c7 --- /dev/null +++ b/modules/core/role/modules/account_admin/farm_role_account_admin.info.yml @@ -0,0 +1,9 @@ +name: farmOS Account Admin Role +description: Provides an Account Admin role for managing users. +type: module +package: farmOS +core_version_requirement: ^9 +dependencies: + - farm:farm_role + - farm:farm_settings + - role_delegation:role_delegation diff --git a/modules/core/role/modules/account_admin/farm_role_account_admin.links.task.yml b/modules/core/role/modules/account_admin/farm_role_account_admin.links.task.yml new file mode 100644 index 000000000..9cd209159 --- /dev/null +++ b/modules/core/role/modules/account_admin/farm_role_account_admin.links.task.yml @@ -0,0 +1,5 @@ +farm_role_account_admin.settings: + base_route: farm_settings.settings_page + route_name: farm_role_account_admin.settings + title: 'Account Admin' + weight: 5 diff --git a/modules/core/role/modules/account_admin/farm_role_account_admin.managed_role_permissions.yml b/modules/core/role/modules/account_admin/farm_role_account_admin.managed_role_permissions.yml new file mode 100644 index 000000000..340e0e38a --- /dev/null +++ b/modules/core/role/modules/account_admin/farm_role_account_admin.managed_role_permissions.yml @@ -0,0 +1,3 @@ +farm_role_account_admin: + permission_callbacks: + - Drupal\farm_role_account_admin\AccountAdminPermissions::permissions diff --git a/modules/core/role/modules/account_admin/farm_role_account_admin.module b/modules/core/role/modules/account_admin/farm_role_account_admin.module new file mode 100644 index 000000000..d56f3e58d --- /dev/null +++ b/modules/core/role/modules/account_admin/farm_role_account_admin.module @@ -0,0 +1,22 @@ +id() == 1 && $account->id() != 1) { + return AccessResult::forbidden(); + } + return AccessResult::neutral(); +} diff --git a/modules/core/role/modules/account_admin/farm_role_account_admin.permissions.yml b/modules/core/role/modules/account_admin/farm_role_account_admin.permissions.yml new file mode 100644 index 000000000..e7c669b26 --- /dev/null +++ b/modules/core/role/modules/account_admin/farm_role_account_admin.permissions.yml @@ -0,0 +1,2 @@ +configure account admin role: + title: 'Configure Account Admin role' diff --git a/modules/core/role/modules/account_admin/farm_role_account_admin.routing.yml b/modules/core/role/modules/account_admin/farm_role_account_admin.routing.yml new file mode 100644 index 000000000..8ec2b2e76 --- /dev/null +++ b/modules/core/role/modules/account_admin/farm_role_account_admin.routing.yml @@ -0,0 +1,7 @@ +farm_role_account_admin.settings: + path: 'farm/settings/account-admin' + defaults: + _form: '\Drupal\farm_role_account_admin\Form\AccountAdminSettingsForm' + _title: 'Account Admin Role settings' + requirements: + _permission: 'configure account admin role' diff --git a/modules/core/role/modules/account_admin/src/AccountAdminPermissions.php b/modules/core/role/modules/account_admin/src/AccountAdminPermissions.php new file mode 100644 index 000000000..e2da8574b --- /dev/null +++ b/modules/core/role/modules/account_admin/src/AccountAdminPermissions.php @@ -0,0 +1,89 @@ +managedRolePermissionsManager = $managed_role_permissions_manager; + $this->configFactory = $config_factory; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('plugin.manager.managed_role_permissions'), + $container->get('config.factory'), + ); + } + + /** + * Add permissions to default farmOS roles. + * + * @param \Drupal\user\RoleInterface $role + * The role to add permissions to. + * + * @return array + * An array of permission strings. + */ + public function permissions(RoleInterface $role) { + $perms = []; + + // Add permissions to the farm_account_admin role. + if ($role->id() == 'farm_account_admin') { + + // Load the module settings. + $settings = $this->configFactory->get('farm_role_account_admin.settings'); + + // Grant the ability to assign managed farmOS roles. + $roles = $this->managedRolePermissionsManager->getMangedRoles(); + foreach ($roles as $role) { + + // Do not allow assigning the "Account Admin" role if + // allow_peer_role_assignment is disabled. + if ($role->id() == 'farm_account_admin' && !$settings->get('allow_peer_role_assignment', FALSE)) { + continue; + } + + // Add permission to assign the role. + $perms[] = 'assign ' . $role->id() . ' role'; + } + } + + return $perms; + } + +} diff --git a/modules/core/role/modules/account_admin/src/Form/AccountAdminSettingsForm.php b/modules/core/role/modules/account_admin/src/Form/AccountAdminSettingsForm.php new file mode 100644 index 000000000..4e5313457 --- /dev/null +++ b/modules/core/role/modules/account_admin/src/Form/AccountAdminSettingsForm.php @@ -0,0 +1,99 @@ +setConfigFactory($config_factory); + $this->cacheTagsInvalidator = $cache_tags_invalidator; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('config.factory'), + $container->get('cache_tags.invalidator'), + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'farm_role_account_admin_settings'; + } + + /** + * {@inheritdoc} + */ + protected function getEditableConfigNames() { + return [ + static::SETTINGS, + ]; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateinterface $form_state) { + $config = $this->config(static::SETTINGS); + + $form['allow_peer_role_assignment'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Allow peer role assignment'), + '#description' => $this->t('Allow users with the Account Admin role to assign/revoke the Account Admin role.'), + '#default_value' => $config->get('allow_peer_role_assignment'), + ]; + + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $this->configFactory->getEditable(static::SETTINGS) + ->set('allow_peer_role_assignment', $form_state->getValue('allow_peer_role_assignment')) + ->save(); + + // Invalidate the user_role:farm_account_admin cache tag. + $this->cacheTagsInvalidator->invalidateTags(['user_role:farm_account_admin']); + + parent::submitForm($form, $form_state); + } + +} diff --git a/modules/core/role/modules/account_admin/tests/src/Functional/UserAccessTest.php b/modules/core/role/modules/account_admin/tests/src/Functional/UserAccessTest.php new file mode 100644 index 000000000..c2d6f68bc --- /dev/null +++ b/modules/core/role/modules/account_admin/tests/src/Functional/UserAccessTest.php @@ -0,0 +1,39 @@ +createUser(); + $user->addRole('farm_account_admin'); + $user->save(); + $this->drupalLogin($user); + + // Confirm that the user cannot access user 1. + $this->drupalGet('user/1'); + $this->assertSession()->statusCodeEquals(403); + $this->drupalGet('user/1/edit'); + $this->assertSession()->statusCodeEquals(403); + } + +} diff --git a/modules/core/role/modules/account_admin/tests/src/Kernel/AccountAdminPermissionsTest.php b/modules/core/role/modules/account_admin/tests/src/Kernel/AccountAdminPermissionsTest.php new file mode 100644 index 000000000..ebad83452 --- /dev/null +++ b/modules/core/role/modules/account_admin/tests/src/Kernel/AccountAdminPermissionsTest.php @@ -0,0 +1,88 @@ +installEntitySchema('user'); + $this->installSchema('system', ['sequences']); + $this->installConfig(['farm_role_account_admin', 'farm_role_roles']); + } + + /** + * Test that the Account Admin role gets appropriate permissions. + */ + public function testAccountAdminPermissions() { + + // Create a user. + $user = $this->setUpCurrentUser([], [], FALSE); + + // List Account Admin permissions. + $account_admin_permissions = [ + 'administer farm settings', + 'administer users', + 'assign farm_manager role', + 'assign farm_worker role', + 'assign farm_viewer role', + ]; + + // Ensure the user does not have permissions. + foreach ($account_admin_permissions as $permission) { + $this->assertFalse($user->hasPermission($permission)); + } + + // Add Account Admin role. + $user->addRole('farm_account_admin'); + + // Ensure the user has permissions. + foreach ($account_admin_permissions as $permission) { + $this->assertTrue($user->hasPermission($permission)); + } + + // Ensure the user does not have the "assign farm_account_admin role" + // permission. + $this->assertFalse($user->hasPermission('assign farm_account_admin role')); + + // Enable the allow_peer_role_assignment setting. + $settings = \Drupal::configFactory()->getEditable('farm_role_account_admin.settings'); + $settings->set('allow_peer_role_assignment', TRUE); + $settings->save(); + + // Rebuild the container so the configuration change takes effect. + $kernel = \Drupal::service('kernel'); + $kernel->invalidateContainer(); + $kernel->rebuildContainer(); + + // Ensure the user has the "assign farm_account_admin role" permission. + $this->assertTrue($user->hasPermission('assign farm_account_admin role')); + } + +}