From 501d573b1914690064eda2b52c45dfe4b5d313f5 Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Sat, 26 Aug 2023 12:11:50 -0400 Subject: [PATCH 1/6] Add an Account Admin role with permission to administer users and assign managed roles. --- CHANGELOG.md | 1 + composer.json | 1 + farm.profile | 1 + .../install/user.role.farm_account_admin.yml | 24 +++++++ .../farm_role_account_admin.info.yml | 9 +++ ...account_admin.managed_role_permissions.yml | 3 + .../src/AccountAdminPermissions.php | 66 +++++++++++++++++ .../Kernel/AccountAdminPermissionsTest.php | 72 +++++++++++++++++++ 8 files changed, 177 insertions(+) create mode 100644 modules/core/role/modules/account_admin/config/install/user.role.farm_account_admin.yml create mode 100644 modules/core/role/modules/account_admin/farm_role_account_admin.info.yml create mode 100644 modules/core/role/modules/account_admin/farm_role_account_admin.managed_role_permissions.yml create mode 100644 modules/core/role/modules/account_admin/src/AccountAdminPermissions.php create mode 100644 modules/core/role/modules/account_admin/tests/src/Kernel/AccountAdminPermissionsTest.php 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..959d6face --- /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: true + create all: true + update all: true + delete all: true 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.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/src/AccountAdminPermissions.php b/modules/core/role/modules/account_admin/src/AccountAdminPermissions.php new file mode 100644 index 000000000..26bfe23d1 --- /dev/null +++ b/modules/core/role/modules/account_admin/src/AccountAdminPermissions.php @@ -0,0 +1,66 @@ +managedRolePermissionsManager = $managed_role_permissions_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('plugin.manager.managed_role_permissions'), + ); + } + + /** + * 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') { + + // Grant the ability to assign managed farmOS roles. + $roles = $this->managedRolePermissionsManager->getMangedRoles(); + foreach ($roles as $role) { + $perms[] = 'assign ' . $role->id() . ' role'; + } + } + + return $perms; + } + +} 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..fb23f59c3 --- /dev/null +++ b/modules/core/role/modules/account_admin/tests/src/Kernel/AccountAdminPermissionsTest.php @@ -0,0 +1,72 @@ +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_account_admin role', + '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)); + } + } + +} From 05a3b374bbb4ac808f784c772a700c6aa401b3d2 Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Sat, 26 Aug 2023 15:58:12 -0400 Subject: [PATCH 2/6] Restrict access to user 1 so that Account Admins cannot edit it. --- .../farm_role_account_admin.module | 22 +++++++++++ .../tests/src/Functional/UserAccessTest.php | 39 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 modules/core/role/modules/account_admin/farm_role_account_admin.module create mode 100644 modules/core/role/modules/account_admin/tests/src/Functional/UserAccessTest.php 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/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); + } + +} From e4b656af745b9e2b299d0a0550e7a3ebdd0f58ca Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Fri, 1 Sep 2023 11:32:51 -0400 Subject: [PATCH 3/6] Add a setting for allowing Account Admins to assign Account Admin role. --- .../schema/farm_role_account_admin.schema.yml | 7 +++ .../farm_role_account_admin.links.task.yml | 5 ++ .../farm_role_account_admin.routing.yml | 7 +++ .../src/AccountAdminPermissions.php | 25 +++++++- .../src/Form/AccountAdminSettingsForm.php | 63 +++++++++++++++++++ .../Kernel/AccountAdminPermissionsTest.php | 18 +++++- 6 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 modules/core/role/modules/account_admin/config/schema/farm_role_account_admin.schema.yml create mode 100644 modules/core/role/modules/account_admin/farm_role_account_admin.links.task.yml create mode 100644 modules/core/role/modules/account_admin/farm_role_account_admin.routing.yml create mode 100644 modules/core/role/modules/account_admin/src/Form/AccountAdminSettingsForm.php 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.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.routing.yml b/modules/core/role/modules/account_admin/farm_role_account_admin.routing.yml new file mode 100644 index 000000000..760ab0126 --- /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: 'administer farm settings' diff --git a/modules/core/role/modules/account_admin/src/AccountAdminPermissions.php b/modules/core/role/modules/account_admin/src/AccountAdminPermissions.php index 26bfe23d1..e2da8574b 100644 --- a/modules/core/role/modules/account_admin/src/AccountAdminPermissions.php +++ b/modules/core/role/modules/account_admin/src/AccountAdminPermissions.php @@ -2,6 +2,7 @@ namespace Drupal\farm_role_account_admin; +use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\farm_role\ManagedRolePermissionsManagerInterface; use Drupal\user\RoleInterface; @@ -19,14 +20,24 @@ class AccountAdminPermissions implements ContainerInjectionInterface { */ protected $managedRolePermissionsManager; + /** + * The config factory service. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + /** * Constructs an AccountAdminPermissions object. * * @param \Drupal\farm_role\ManagedRolePermissionsManagerInterface $managed_role_permissions_manager * The managed role permissions manager. + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The config factory service. */ - public function __construct(ManagedRolePermissionsManagerInterface $managed_role_permissions_manager) { + public function __construct(ManagedRolePermissionsManagerInterface $managed_role_permissions_manager, ConfigFactoryInterface $config_factory) { $this->managedRolePermissionsManager = $managed_role_permissions_manager; + $this->configFactory = $config_factory; } /** @@ -35,6 +46,7 @@ class AccountAdminPermissions implements ContainerInjectionInterface { public static function create(ContainerInterface $container) { return new static( $container->get('plugin.manager.managed_role_permissions'), + $container->get('config.factory'), ); } @@ -53,9 +65,20 @@ class AccountAdminPermissions implements ContainerInjectionInterface { // 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'; } } 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..0519862e8 --- /dev/null +++ b/modules/core/role/modules/account_admin/src/Form/AccountAdminSettingsForm.php @@ -0,0 +1,63 @@ +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(); + + parent::submitForm($form, $form_state); + } + +} 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 index fb23f59c3..ebad83452 100644 --- a/modules/core/role/modules/account_admin/tests/src/Kernel/AccountAdminPermissionsTest.php +++ b/modules/core/role/modules/account_admin/tests/src/Kernel/AccountAdminPermissionsTest.php @@ -49,7 +49,6 @@ class AccountAdminPermissionsTest extends KernelTestBase { $account_admin_permissions = [ 'administer farm settings', 'administer users', - 'assign farm_account_admin role', 'assign farm_manager role', 'assign farm_worker role', 'assign farm_viewer role', @@ -67,6 +66,23 @@ class AccountAdminPermissionsTest extends KernelTestBase { 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')); } } From 978281c212a58aa9b3ce8bc717e36e5133dc3028 Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Fri, 1 Sep 2023 13:14:38 -0400 Subject: [PATCH 4/6] Add a separate permission for configuring the Account Admin role module settings. --- .../account_admin/farm_role_account_admin.permissions.yml | 2 ++ .../modules/account_admin/farm_role_account_admin.routing.yml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 modules/core/role/modules/account_admin/farm_role_account_admin.permissions.yml 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 index 760ab0126..8ec2b2e76 100644 --- 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 @@ -4,4 +4,4 @@ farm_role_account_admin.settings: _form: '\Drupal\farm_role_account_admin\Form\AccountAdminSettingsForm' _title: 'Account Admin Role settings' requirements: - _permission: 'administer farm settings' + _permission: 'configure account admin role' From c5b7d25d29af91efda831b9cf2349130f7c47ff7 Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Fri, 1 Sep 2023 15:22:53 -0400 Subject: [PATCH 5/6] Invalidate the user_role:farm_account_admin cache tag when the account admin settings form is submitted. --- .../src/Form/AccountAdminSettingsForm.php | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/modules/core/role/modules/account_admin/src/Form/AccountAdminSettingsForm.php b/modules/core/role/modules/account_admin/src/Form/AccountAdminSettingsForm.php index 0519862e8..4e5313457 100644 --- a/modules/core/role/modules/account_admin/src/Form/AccountAdminSettingsForm.php +++ b/modules/core/role/modules/account_admin/src/Form/AccountAdminSettingsForm.php @@ -2,8 +2,11 @@ namespace Drupal\farm_role_account_admin\Form; +use Drupal\Core\Cache\CacheTagsInvalidatorInterface; +use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Provides a settings form for the Account Admin Role module. @@ -17,6 +20,36 @@ class AccountAdminSettingsForm extends ConfigFormbase { */ const SETTINGS = 'farm_role_account_admin.settings'; + /** + * The cache tags invalidator. + * + * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface + */ + protected $cacheTagsInvalidator; + + /** + * Constructs a \Drupal\system\ConfigFormBase object. + * + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The factory for configuration objects. + * @param \Drupal\Core\Cache\CacheTagsInvalidatorInterface $cache_tags_invalidator + * The cache tags invalidator. + */ + public function __construct(ConfigFactoryInterface $config_factory, CacheTagsInvalidatorInterface $cache_tags_invalidator) { + $this->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} */ @@ -57,6 +90,9 @@ class AccountAdminSettingsForm extends ConfigFormbase { ->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); } From 9b4700dced8b6ab8510d32566c669b9572d9aee0 Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Wed, 6 Sep 2023 14:27:06 -0400 Subject: [PATCH 6/6] Limit permissions of Account Admin role. --- .../config/install/user.role.farm_account_admin.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 index 959d6face..aad8266a6 100644 --- 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 @@ -18,7 +18,7 @@ third_party_settings: access: config: true entity: - view all: true - create all: true - update all: true - delete all: true + view all: false + create all: false + update all: false + delete all: false