diff --git a/composer.json b/composer.json index b9750927be0af97b59258261b029cfae8447ddad..1e4a9ed879eb2cab0265b7effcc6dc4b700f93ba 100644 --- a/composer.json +++ b/composer.json @@ -89,7 +89,7 @@ "drupal/address": "1.1", "drupal/addtocalendar": "3.1", "drupal/admin_toolbar": "2.2", - "drupal/administerusersbyrole": "2.0-beta1", + "drupal/administerusersbyrole": "3.0", "drupal/allowed_formats": "1.2", "drupal/anchor_link": "1.6", "drupal/better_exposed_filters": "3.0-alpha6", @@ -321,4 +321,4 @@ "php": "7.0.8" } } -} +} \ No newline at end of file diff --git a/composer.lock b/composer.lock index 71b264212e3b42e74e00ee9fd4f128bf8d0ac2cc..903eebe490ef86ff5a5372616161580f4bb9c9ed 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b9b913a1bd52f92ead96c185a7473fd2", + "content-hash": "a5b57e165ccb78ef450729502b4e2962", "packages": [ { "name": "alchemy/zippy", @@ -2194,32 +2194,29 @@ }, { "name": "drupal/administerusersbyrole", - "version": "2.0.0-beta1", + "version": "3.0.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/administerusersbyrole.git", - "reference": "8.x-2.0-beta1" + "reference": "8.x-3.0" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/administerusersbyrole-8.x-2.0-beta1.zip", - "reference": "8.x-2.0-beta1", - "shasum": "735162d6d1a5f393598bf97a420de354abef3218" + "url": "https://ftp.drupal.org/files/projects/administerusersbyrole-8.x-3.0.zip", + "reference": "8.x-3.0", + "shasum": "decf16981abe616f675812c040db2b52332f0a66" }, "require": { - "drupal/core": "~8.0" + "drupal/core": "^8 || ^9" }, "type": "drupal-module", "extra": { - "branch-alias": { - "dev-2.x": "2.x-dev" - }, "drupal": { - "version": "8.x-2.0-beta1", - "datestamp": "1553362685", + "version": "8.x-3.0", + "datestamp": "1586962918", "security-coverage": { - "status": "not-covered", - "message": "Beta releases are not covered by Drupal security advisories." + "status": "covered", + "message": "Covered by Drupal's security advisory policy" } } }, @@ -2241,7 +2238,7 @@ "homepage": "https://www.drupal.org/user/161913" } ], - "description": "Allows users with 'administer users' permission and a role (specified in 'Permissions') to edit/delete other users with a specified role. Also provides control over user creation.", + "description": "Allows site builders to set up fine-grained permissions for allowing \"sub-admin\" users to edit and delete other users.", "homepage": "https://www.drupal.org/project/administerusersbyrole", "support": { "source": "https://git.drupalcode.org/project/administerusersbyrole" diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index 1fa1e2131c6d3c086417387a3ad22dd11451f845..eff327b679263d89e92e528ab0198fc50691b96d 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -2261,33 +2261,30 @@ }, { "name": "drupal/administerusersbyrole", - "version": "2.0.0-beta1", - "version_normalized": "2.0.0.0-beta1", + "version": "3.0.0", + "version_normalized": "3.0.0.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/administerusersbyrole.git", - "reference": "8.x-2.0-beta1" + "reference": "8.x-3.0" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/administerusersbyrole-8.x-2.0-beta1.zip", - "reference": "8.x-2.0-beta1", - "shasum": "735162d6d1a5f393598bf97a420de354abef3218" + "url": "https://ftp.drupal.org/files/projects/administerusersbyrole-8.x-3.0.zip", + "reference": "8.x-3.0", + "shasum": "decf16981abe616f675812c040db2b52332f0a66" }, "require": { - "drupal/core": "~8.0" + "drupal/core": "^8 || ^9" }, "type": "drupal-module", "extra": { - "branch-alias": { - "dev-2.x": "2.x-dev" - }, "drupal": { - "version": "8.x-2.0-beta1", - "datestamp": "1553362685", + "version": "8.x-3.0", + "datestamp": "1586962918", "security-coverage": { - "status": "not-covered", - "message": "Beta releases are not covered by Drupal security advisories." + "status": "covered", + "message": "Covered by Drupal's security advisory policy" } } }, @@ -2310,7 +2307,7 @@ "homepage": "https://www.drupal.org/user/161913" } ], - "description": "Allows users with 'administer users' permission and a role (specified in 'Permissions') to edit/delete other users with a specified role. Also provides control over user creation.", + "description": "Allows site builders to set up fine-grained permissions for allowing \"sub-admin\" users to edit and delete other users.", "homepage": "https://www.drupal.org/project/administerusersbyrole", "support": { "source": "https://git.drupalcode.org/project/administerusersbyrole" diff --git a/web/modules/administerusersbyrole/README.md b/web/modules/administerusersbyrole/README.md index 479b2a368fe1cdb99b4800db6422793995078ef7..7dfe12b6cec24811d073f3c97d45d9ea490df701 100644 --- a/web/modules/administerusersbyrole/README.md +++ b/web/modules/administerusersbyrole/README.md @@ -1,48 +1,66 @@ # Administer Users by Role -## SUMMARY -This module allows site builders to set up fine-grained permissions for -allowing "sub-admin" users to edit and delete other users — more specific -than Drupal Core's all-or-nothing 'administer users' permission. It also -provides and enforces a 'create users' permission. +## Contents of this file -## CORE PERMISSIONS +* Introduction +* Requirements +* Installation +* Configuration +* Maintainers -### Administer users -DO NOT set this for sub-admins. This permission bypasses all of the -permissions in "Administer Users by Role". +## Introduction -### View user information -Your sub-admins should probably have this permission. (Most things work -without it, but for example with a View showing users, the user name -will only become a link if this permission is set.) +Administer Users by Role allows site builders to set up fine-grained +permissions for allowing "sub-admin" users to manage other users based on the +target user's role. -### Select method for cancelling account -If you set this for sub-admins, then the sub-admin can choose a cancellation -method when cancelling an account. If not, then the sum-admin will always -use the default cancellation method. +The module defines new permissions to control access to edit/delete users and +assign roles - more specific than Drupal Core's all-or-nothing 'Administer +users' and 'Administer permissions'. It also provides a 'Create new users' +permission and fine-grained permissions for viewing users. -## NEW PERMISSIONS +* For a full description of the module, visit the project page: +[https://www.drupal.org/project/administerusersbyrole](https://www.drupal.org/project/administerusersbyrole) -### Access the users overview page -See the list of users at admin/people. Only users that can be edited are shown. +* To submit bug reports and feature suggestions, or to track changes: +[https://www.drupal.org/project/issues/administerusersbyrole](https://www.drupal.org/project/issues/administerusersbyrole) -### Create new users -Create users, at admin/people/create. +## Requirements -### Allow empty user mail when managing users -Create and manage users that have no email address. +This module requires no modules outside of Drupal core. -### Edit users with no custom roles -Allows editing of any authenticated user that has no custom roles set. +## Installation -### Edit users with role XXX -Allows editing of any authenticated user with the specified role. -To edit a user with multiple roles, the sub-admin must have permission to -edit ALL of those roles. ("Edit users with no custom roles" is NOT needed.) +Install the Administer Users by Role module as you would normally install a +contributed Drupal module. Visit +[https://www.drupal.org/node/1897420](https://www.drupal.org/node/1897420) for +further information. -### Cancel -The permission for cancel work exactly the same as those for edit. +## Configuration -## GOOGLE CODE-IN -Drupal 8 port done with assistance from the student gvso as a Google Code-In (GCI) 2014 task. Google Code-in is a contest for pre-university students (e.g., high school and secondary school students ages 13-17) with the goal of encouraging young people to participate in open source. More info about GCI [https://developers.google.com/open-source/gci/](https://developers.google.com/open-source/gci/) +Use the configuration settings (Administration > People > Administer Users) to +classify each role. + +* Safe - Grants sub-admins the ability to manage users with that role if they + have the related permission such as 'Edit users with safe roles'. +* Unsafe - Means sub-admins cannot manage users with that role. For example, + the "admin" role is always unsafe. +* Custom - Allows for more selective access determined by extra permissions for + that role. + +The sub-admin can access a target user provided they have access to all of +that user's roles. + +## Maintainers + +* Adam Shepherd (AdamPS) - +[https://www.drupal.org/u/adamps](https://www.drupal.org/u/adamps) +* Steve Mokris (smokris) - +[https://www.drupal.org/u/smokris](https://www.drupal.org/u/smokris) +* Tom Kirkpatrick (mrfelton) - +[https://www.drupal.org/u/mrfelton](https://www.drupal.org/u/mrfelton) + +Supporting organizations: + +* AlbanyWeb - +[https://www.drupal.org/albanyweb](https://www.drupal.org/albanyweb) diff --git a/web/modules/administerusersbyrole/administerusersbyrole.info.yml b/web/modules/administerusersbyrole/administerusersbyrole.info.yml index a8f53cd41310032ee7ad21a964af89efd6042203..54d016db2dc38922faa9297a68334bdf77a52992 100644 --- a/web/modules/administerusersbyrole/administerusersbyrole.info.yml +++ b/web/modules/administerusersbyrole/administerusersbyrole.info.yml @@ -1,10 +1,10 @@ name: 'Administer Users by Role' -description: "Allows users with 'administer users' permission and a role (specified in 'Permissions') to edit/delete other users with a specified role. Also provides control over user creation." -# core: 8.x +description: 'Allows site builders to set up fine-grained permissions for allowing "sub-admin" users to edit and delete other users.' +core_version_requirement: ^8 || ^9 type: module +configure: administerusersbyrole.settings -# Information added by Drupal.org packaging script on 2019-03-23 -version: '8.x-2.0-beta1' -core: '8.x' +# Information added by Drupal.org packaging script on 2020-04-15 +version: '8.x-3.0' project: 'administerusersbyrole' -datestamp: 1553362692 +datestamp: 1586962922 diff --git a/web/modules/administerusersbyrole/administerusersbyrole.install b/web/modules/administerusersbyrole/administerusersbyrole.install deleted file mode 100644 index b3d9bbc7f3711e882119cd6b3af051245d859d04..0000000000000000000000000000000000000000 --- a/web/modules/administerusersbyrole/administerusersbyrole.install +++ /dev/null @@ -1 +0,0 @@ -<?php diff --git a/web/modules/administerusersbyrole/administerusersbyrole.links.menu.yml b/web/modules/administerusersbyrole/administerusersbyrole.links.menu.yml new file mode 100644 index 0000000000000000000000000000000000000000..3af4bbb7f8495eacc152e63ef774d6bfe7897411 --- /dev/null +++ b/web/modules/administerusersbyrole/administerusersbyrole.links.menu.yml @@ -0,0 +1,5 @@ +administerusersbyrole.settings: + title: Administer Users by Role + description: Manage Administer Users by Role settings. + route_name: administerusersbyrole.settings + parent: user.admin_index diff --git a/web/modules/administerusersbyrole/administerusersbyrole.module b/web/modules/administerusersbyrole/administerusersbyrole.module index 5b72d18575e7607c8a820773cac34d3593aa6acd..8eadb2e35c8ca438b8906d2273e33e81456d22ad 100644 --- a/web/modules/administerusersbyrole/administerusersbyrole.module +++ b/web/modules/administerusersbyrole/administerusersbyrole.module @@ -1,32 +1,35 @@ <?php +/** + * @file Administer Users by Role main module file. + */ + +use Drupal\administerusersbyrole\Plugin\Action\AddRoleUser; +use Drupal\administerusersbyrole\Plugin\Action\RemoveRoleUser; use Drupal\Core\Url; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Access\AccessResult; use Drupal\Core\Session\AccountInterface; -use Drupal\User\UserInterface; -use Drupal\User\RoleInterface; +use Drupal\user\UserInterface; +use Drupal\user\RoleInterface; use Drupal\Core\Field\FieldDefinitionInterface; -use Drupal\Core\Field\FieldItemList; +use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Database\Query\AlterableInterface; -/** - * Generates a permission string for a given a role name. - */ -function _administerusersbyrole_build_perm_string($role_id, $op = 'edit') { - $perm = "$op users with role $role_id"; - return $perm; -} - /** * Implements hook_ENTITY_TYPE_access() for entity type "user_role". * - * @param \Drupal\User\RoleInterface $role + * @param \Drupal\user\RoleInterface $role * The role object to check access for. * - * @param string $operation: The operation that is to be performed on $entity. + * @param string $operation + * The operation that is to be performed on $role. * - * @param \Drupal\Core\Session\AccountInterface $account: The account trying to access the entity. + * @param \Drupal\Core\Session\AccountInterface $account + * The account trying to access the entity. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. hook_entity_access() has detailed documentation. */ function administerusersbyrole_user_role_access(RoleInterface $role, $operation, AccountInterface $account) { // Allow users without the permission "administer permissions" to view the @@ -41,12 +44,17 @@ function administerusersbyrole_user_role_access(RoleInterface $role, $operation, /** * Implements hook_ENTITY_TYPE_access() for entity type "user". * - * @param \Drupal\User\UserInterface $user + * @param \Drupal\user\UserInterface $user * The user object to check access for. * - * @param string $operation: The operation that is to be performed on $entity. + * @param string $operation + * The operation that is to be performed on $entity. + * + * @param \Drupal\Core\Session\AccountInterface $account + * The account trying to access the entity. * - * @param \Drupal\Core\Session\AccountInterface $account: The account trying to access the entity. + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. hook_entity_access() has detailed documentation. */ function administerusersbyrole_user_access(UserInterface $user, $operation, AccountInterface $account) { // Never allow uid 0 (anonymous) or 1 (master admin). @@ -56,27 +64,37 @@ function administerusersbyrole_user_access(UserInterface $user, $operation, Acco // Grant access to view blocked users if we can update them. if ($user->isBlocked() && ($operation == 'view')) { - return administerusersbyrole_user_access($user, 'update', $account); + $operation = 'update'; } - $convert = array('delete' => 'cancel', 'update' => 'edit'); - if (!isset($convert[$operation])) { - return AccessResult::neutral(); - } - - $roles = $user->getRoles(); - foreach ($roles as $rid) { - // If there is only AUTHENTICATED_ROLE, then we must test for it, otherwise skip it. - if (($rid == AccountInterface::AUTHENTICATED_ROLE) && (count($roles) > 1)) { - continue; - } - - if (!$account->hasPermission(_administerusersbyrole_build_perm_string($rid, $convert[$operation]))) { - return AccessResult::neutral(); - } - } + $result = \Drupal::service('administerusersbyrole.access')->access($user->getRoles(TRUE), $operation, $account); + return $result->cachePerPermissions()->addCacheableDependency($user); +} - return AccessResult::allowed()->cachePerPermissions()->addCacheableDependency($user); +/** + * Check for permission to assign roles to a user. + * + * @param \Drupal\user\UserInterface $user + * The user object to check access for. + * + * @param \Drupal\Core\Session\AccountInterface $account + * The account trying to access the entity. + * + * @param array $rids + * Array of role ids to add/remove. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. hook_entity_access() has detailed documentation. + */ +function administerusersbyrole_user_assign_role(UserInterface $user, AccountInterface $account, array $rids) { + // Allow access if + // 1a) The sub-admin can edit the user OR + // 1b) The sub-admin can assign all the roles the user already has AND + // 2) The sub-admin can assign all the roles that are being changed. + $oneA = administerusersbyrole_user_access($user, 'update', $account); + $oneB = administerusersbyrole_user_access($user, 'role-assign', $account); + $two = \Drupal::service('administerusersbyrole.access')->access($rids, 'role-assign', $account); + return $oneA->orIf($oneB)->andIf($two); } /** @@ -93,12 +111,12 @@ function administerusersbyrole_entity_create_access(AccountInterface $account, a /** * Implements hook_entity_field_access(). */ -function administerusersbyrole_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemList $items = NULL) { +function administerusersbyrole_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) { if ($field_definition->getTargetEntityTypeId() != 'user') { return AccessResult::neutral(); } - $fields = array('name', 'status', 'mail'); + $fields = ['name', 'status', 'mail']; if ($operation == 'view') { array_push($fields, 'roles', 'access'); } @@ -116,6 +134,7 @@ function administerusersbyrole_entity_field_access($operation, FieldDefinitionIn return AccessResult::neutral(); } + // Grant access to read/update extra fields to a sub-admin with permission to update the user. return administerusersbyrole_user_access($items->getEntity(), 'update', $account); } @@ -142,40 +161,62 @@ function administerusersbyrole_query_administerusersbyrole_edit_access_alter(Alt // Exclude the root user. $query->condition('users_field_data.uid', 1, '<>'); - $roles = user_roles(TRUE); - foreach ($roles as $rid => $role) { - if (!$account->hasPermission(_administerusersbyrole_build_perm_string($rid, 'edit'))) { - $exclude[$rid] = $rid; - } - } + // Hide any user accounts that the sub-admin can't edit or assign roles to. + $access_service = \Drupal::service('administerusersbyrole.access'); + $roles = array_merge($access_service->listRoles('edit', $account), $access_service->listRoles('role-assign', $account)); - // Exclude accounts with no roles if the user does not have permission - // to edit them. - if (isset($exclude[RoleInterface::AUTHENTICATED_ID])) { - $query->Join('user__roles', 'ur', 'ur.entity_id=users_field_data.uid'); - unset($exclude[RoleInterface::AUTHENTICATED_ID]); - } - - // Hide any user accounts that the user does not have permission to edit. - // If an account has multiple roles, we make sure the current user has - // permission to edit all assigned roles. - if (!empty($exclude)) { + if ($roles) { // This code was changed from D7 to workaround D8 core bug https://www.drupal.org/node/2744069. - // Get a list of uids with roles that the user does not have permission // to edit. $subquery = \Drupal::database()->select('user__roles', 'ur2'); - $subquery->fields('ur2', array('entity_id')); - $subquery->condition('ur2.roles_target_id', $exclude, 'IN'); + $subquery->fields('ur2', ['entity_id']); + + $subquery->condition('ur2.roles_target_id', $roles, 'NOT IN'); // Exclude those uids from the result list. $query->condition('users_field_data.uid', $subquery, 'NOT IN'); } + else { + // Exclude all users. + $query->condition('users_field_data.uid', NULL); + } } } +/** + * Implements hook_action_info_alter(). + */ +function administerusersbyrole_action_info_alter(array &$definitions) { + $definitions['user_add_role_action']['class'] = AddRoleUser::class; + $definitions['user_remove_role_action']['class'] = RemoveRoleUser::class; +} + +/** + * Implements hook_ENTITY_TYPE_insert() for user_role. + */ +function administerusersbyrole_user_role_insert(RoleInterface $role) { + \Drupal::service('administerusersbyrole.access')->rolesChanged(); +} + +/** + * Implements hook_ENTITY_TYPE_update() for user_role. + */ +function administerusersbyrole_user_role_update(RoleInterface $role) { + \Drupal::service('administerusersbyrole.access')->rolesChanged(); +} + +/** + * Implements hook_ENTITY_TYPE_delete() for user_role. + */ +function administerusersbyrole_user_role_delete(RoleInterface $role) { + \Drupal::service('administerusersbyrole.access')->rolesChanged(); +} + /** * Implements hook_form_FORM_ID_alter(). + * + * Enable roles if required. */ function administerusersbyrole_form_user_form_alter(&$form, &$form_state) { $user = $form_state->getFormObject()->getEntity(); @@ -183,9 +224,16 @@ function administerusersbyrole_form_user_form_alter(&$form, &$form_state) { // Allow empty email. // @todo Remove when https://www.drupal.org/node/2992848 is fixed. - if (!$user->getEmail() && $account->hasPermission('allow empty user mail')) { + if (isset($form['account']['mail']) && !$user->getEmail() && \Drupal::currentUser()->hasPermission('allow empty user mail')) { $form['account']['mail']['#required'] = FALSE; } + + if (isset($form['account']['roles']) && administerusersbyrole_user_access($user, 'update', $account)->isAllowed()) { + $allowed = \Drupal::service('administerusersbyrole.access')->listRoles('role-assign', $account); + $options = array_intersect_key($form['account']['roles']['#options'], array_flip($allowed)); + $form['account']['roles']['#options'] = $options; + $form['account']['roles']['#access'] = !empty($options); + } } /** @@ -210,30 +258,81 @@ function administerusersbyrole_help($route_name, RouteMatchInterface $route_matc case 'help.page.administerusersbyrole': $output = ''; $output .= '<h3>' . t('About') . '</h3>'; - $output .= '<p>' . t('Administer Users by Role allows site builders to set up fine-grained permissions for allowing "sub-admin" users to edit and delete other users - more specific than Drupal Core\'s all-or-nothing \'administer users\' permission. It also provides and enforces a \'create users\' permission') . '</p>'; + $output .= '<p>' . t('Administer Users by Role allows site builders to set up fine‐grained permissions for allowing <i>sub‐admin</i> users to manage other users based on the target user\'s role.'); + $output .= ' ' . t('The module defines new permissions to control access to edit/delete users and assign roles - more specific than Drupal Core\'s all‐or‐nothing “Administer users” and “Administer permissions”.'); + $output .= ' ' . t('It also provides a “Create new users” permission and fine‐grained permissions for viewing users.') . '</p>'; + $output .= '<h3>' . t('Configuration') . '</h3>'; + $output .= '<p>' . t('Use the <a href=":config">configuration settings</a> to classify each role.', [':config' => Url::fromRoute('administerusersbyrole.settings')->toString()]) . '</p>'; + $output .= administerusersbyrole_help_role_options(); $output .= '<h3>' . t('Core permissions') . '</h3>'; $output .= '<dl>'; - $output .= '<dt>' . t('Administer users') . '</dt>'; - $output .= '<dd>' . t('<em>Do not</em> set this for sub-admins. This permission bypasses all of the permissions in "Administer Users by Role".') . '</dd>'; + $output .= '<dt>' . t('Administer users') . '/' . t('Administer permissions') . '</dt>'; + $output .= '<dd>' . t('<em>Do not</em> set these for sub‐admins. These permissions bypass all of the permissions in “Administer Users by Role".') . '</dd>'; $output .= '<dt>' . t('View user profiles') . '</dt>'; - $output .= '<dd>' . t('Your sub-admins should probably have this permission. (Most things work without it, but for example with a View showing users, the user name will only become a link if this permission is set.)') . '</dd>'; + $output .= '<dd>' . t('Don\'t set this if you wish to use the fine-grained permissions for viewing users.') . '</dd>'; $output .= '<dt>' . t('Select method for cancelling account') . '</dt>'; - $output .= '<dd>' . t('If you set this for sub-admins, then the sub-admin can choose a cancellation method when cancelling an account. If not, then the sum-admin will always use the default cancellation method.') . '</dd>'; + $output .= '<dd>' . t('If you set this for sub‐admins, then the sub‐admin can choose a cancellation method when cancelling an account. If not, then the sub‐admin will always use the default cancellation method.') . '</dd>'; $output .= '</dl>'; $output .= '<h3>' . t('New permissions') . '</h3>'; $output .= '<dl>'; $output .= '<dt>' . t('Access the users overview page') . '</dt>'; - $output .= '<dd>' . t('Grants access to <a href=":people">manage users page</a>. Only users that can be edited are shown.', [':people' => \Drupal::url('entity.user.collection')]) . '</dd>'; + $output .= '<dd>' . t('Grants access to <a href=":people">manage users page</a>. Only users that can be edited are shown.', [':people' => Url::fromRoute('entity.user.collection')->toString()]) . '</dd>'; $output .= '<dt>' . t('Create new users') . '</dt>'; - $output .= '<dd>' . t('Grants access to <a href=":create">create users</a>.', [':create' => \Drupal::url('user.admin_create')]) . '</dd>'; + $output .= '<dd>' . t('Grants access to <a href=":create">create users</a>.', [':create' => Url::fromRoute('user.admin_create')->toString()]) . '</dd>'; $output .= '<dt>' . t('Allow empty user mail when managing users') . '</dt>'; $output .= '<dd>' . t('Create and manage users that have no email address.') . '</dd>'; - $output .= '<dt>' . t('Edit users with no custom roles') . '</dt>'; - $output .= '<dd>' . t('Allows editing of any authenticated user that has no custom roles set.') . '</dd>'; - $output .= '<dt>' . t('Edit users with role XXX') . '</dt>'; - $output .= '<dd>' . t('Allows editing of any authenticated user with the specified role. To edit a user with multiple roles, the sub-admin must have permission to edit ALL of those roles. (\'Edit users with no custom roles\' is NOT needed.)') . '</dd>'; + $output .= '<dt>' . t('Assign allowed roles') . '</dt>'; + $output .= '<dd>' . t('Allows assigning of any roles that have been configured as <i>allowed</i>.') . '</dd>'; + $output .= '<dt>' . t('Edit users with allowed roles') . '</dt>'; + $output .= '<dd>' . t('Allows editing of any user with <i>allowed</i> roles.') . '</dd>'; + $output .= '<dt>' . t('Cancel users with allowed roles') . '</dt>'; + $output .= '<dd>' . t('Allows cancelling of any user with <i>allowed</i> roles.') . '</dd>'; + $output .= '<dt>' . t('View users with allowed roles') . '</dt>'; + $output .= '<dd>' . t('Allows viewing of any user with <i>allowed</i> roles. Note that this permission only controls direct viewing of a single user, it does not affect Views.') . '</dd>'; $output .= '</dl>'; - $output .= '<p>' . t('The permission for cancel work exactly the same as those for edit.') . '</p>'; + $output .= '<p>' . t('There will be 4 extra permissions (assign/edit/cancel/view) for each role configured as <i>custom</i>.') . '</p>'; + $output .= '<h3>' . t('Assign role without permission to edit users') . '</h3>'; + $output .= '<p>' . t('A sub-admin without access to edit users can assign roles using actions in the <a href=":people">manage users page</a>.', [':people' => Url::fromRoute('entity.user.collection')->toString()]); + $output .= t('The sub-admin can assign roles to a user if EITHER the sub-admin can edit the user OR the sub-admin can assign all the roles the user already has.') . '</p>'; + $output .= '<h3>' . t('Example') . '</h3>'; + $output .= '<p>' . t('You have an organisation website with the following roles:') . '</p>'; + $output .= '<ol>'; + $output .= '<li>' . t('Overall Administrator - all permissions') . '</li>'; + $output .= '<li>' . t('Content Editors - are not members and are managed by the administrator') . '</li>'; + $output .= '<li>' . t('Membership secretary - needs to view and edit membership information') . '</li>'; + $output .= '<li>' . t('Ordinary Members - have basic authenticated user rights') . '</li>'; + $output .= '</ol>'; + $output .= '<p>' . t('What is wanted is for the membership secretary to be able to view and edit members, but not other roles, therefore the roles are configured as follows:') . '</p>'; + $output .= '<ol>'; + $output .= '<li>' . t('Administrator (Forbidden by default)') . '</li>'; + $output .= '<li>' . t('Content editors - Forbidden') . '</li>'; + $output .= '<li>' . t('Membership Secretary - Allowed') . '</li>'; + $output .= '<li>' . t('Ordinary Members - Allowed') . '</li>'; + $output .= '</ol>'; + return $output; + + case 'administerusersbyrole.settings': + $perms_link = Url::fromRoute('user.admin_permissions', [], ['fragment' => 'module-administerusersbyrole'])->toString(); + $output = '<p>' . t('Use this page to classify each role before you <a href=:perms>assign permissions</a>.', [':perms' => $perms_link]) . '</p>'; + $output .= administerusersbyrole_help_role_options(); + $output .= t('See the <a href=":help">module help</a> for information.', [':help' => Url::fromRoute('help.page', ['name' => 'administerusersbyrole'])->toString()]); return $output; } } + +/** + * Returns a fragment of help text describing the configuration options for + * roles. + */ +function administerusersbyrole_help_role_options() { + $options = '<dl>'; + $options .= '<dt>' . t('Allowed') . '</dt>'; + $options .= '<dd>' . t('Grants sub‐admins the ability to manage users with that role if they have the related permission such as “Edit users with allowed roles”') . '</dd>'; + $options .= '<dt>' . t('Forbidden') . '</dt>'; + $options .= '<dd>' . t('Means sub‐admins cannot manage users with that role. For example, the ‘admin’ role is always <i>forbidden</i>.') . '</dd>'; + $options .= '<dt>' . t('Custom') . '</dt>'; + $options .= '<dd>' . t('Allows for more selective access determined by extra permissions for that role.') . '</dd>'; + $options .= '</ul>'; + $options .= '<p>' . t('The sub‐admin can access a target user provided they have access to all of that user\'s roles.') . '</p>'; + return $options; +} diff --git a/web/modules/administerusersbyrole/administerusersbyrole.routing.yml b/web/modules/administerusersbyrole/administerusersbyrole.routing.yml new file mode 100644 index 0000000000000000000000000000000000000000..fa11e873e605303794de5b3eb3753c0149064fae --- /dev/null +++ b/web/modules/administerusersbyrole/administerusersbyrole.routing.yml @@ -0,0 +1,7 @@ +administerusersbyrole.settings: + path: '/admin/config/people/administerusersbyrole' + defaults: + _form: '\Drupal\administerusersbyrole\Form\SettingsForm' + _title: 'Administer Users by Role Settings' + requirements: + _permission: 'administer permissions' diff --git a/web/modules/administerusersbyrole/administerusersbyrole.services.yml b/web/modules/administerusersbyrole/administerusersbyrole.services.yml index 622b68eeabe43dd9e1340634932ebd9bc6cf8d64..1cb2e4fed7168bdcbac5cae00ca17c4844386212 100644 --- a/web/modules/administerusersbyrole/administerusersbyrole.services.yml +++ b/web/modules/administerusersbyrole/administerusersbyrole.services.yml @@ -3,3 +3,6 @@ services: class: Drupal\administerusersbyrole\Routing\RouteSubscriber tags: - { name: event_subscriber } + administerusersbyrole.access: + class: Drupal\administerusersbyrole\Services\AccessManager + arguments: ['@config.factory'] diff --git a/web/modules/administerusersbyrole/config/install/administerusersbyrole.settings.yml b/web/modules/administerusersbyrole/config/install/administerusersbyrole.settings.yml new file mode 100644 index 0000000000000000000000000000000000000000..3f65442a01008a089ca16f89d6ae0e8fc9429022 --- /dev/null +++ b/web/modules/administerusersbyrole/config/install/administerusersbyrole.settings.yml @@ -0,0 +1 @@ +roles: { } diff --git a/web/modules/administerusersbyrole/config/schema/administerusersbyrole.schema.yml b/web/modules/administerusersbyrole/config/schema/administerusersbyrole.schema.yml new file mode 100644 index 0000000000000000000000000000000000000000..cd0a55157a323132a1fea3670ffaa25b4bf14238 --- /dev/null +++ b/web/modules/administerusersbyrole/config/schema/administerusersbyrole.schema.yml @@ -0,0 +1,10 @@ +administerusersbyrole.settings: + type: config_object + label: 'Administer Users by Role settings' + mapping: + roles: + type: sequence + label: 'Roles' + sequence: + type: string + label: 'Role' diff --git a/web/modules/administerusersbyrole/drupalci.yml b/web/modules/administerusersbyrole/drupalci.yml new file mode 100644 index 0000000000000000000000000000000000000000..d20f47308ede3d48a4cabe2d1b20d3712dcfa58a --- /dev/null +++ b/web/modules/administerusersbyrole/drupalci.yml @@ -0,0 +1,19 @@ +build: + assessment: + validate_codebase: + phplint: + container_composer: + csslint: + eslint: + phpcs: + testing: + run_tests.standard: + types: 'Simpletest,PHPUnit-Unit,PHPUnit-Kernel,PHPUnit-Functional' + # Test for Drupal 9 compatibility + suppress-deprecations: false + run_tests.js: + concurrency: 1 + types: 'PHPUnit-FunctionalJavascript' + # Test for Drupal 9 compatibility + suppress-deprecations: false + nightwatchjs: diff --git a/web/modules/administerusersbyrole/src/AdministerusersbyrolePermissions.php b/web/modules/administerusersbyrole/src/AdministerusersbyrolePermissions.php index 8a631138befe2de7169a1b146b2f863f023f5fee..b135e2a5df6c2f74d54e89d8b2b554edda11bab9 100644 --- a/web/modules/administerusersbyrole/src/AdministerusersbyrolePermissions.php +++ b/web/modules/administerusersbyrole/src/AdministerusersbyrolePermissions.php @@ -3,22 +3,36 @@ namespace Drupal\administerusersbyrole; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; -use Drupal\Core\StringTranslation\StringTranslationTrait; use Symfony\Component\DependencyInjection\ContainerInterface; -use Drupal\Core\Session\AccountInterface; +use Drupal\administerusersbyrole\Services\AccessManagerInterface; /** * Provides dynamic permissions of the administerusersbyrole module. */ class AdministerusersbyrolePermissions implements ContainerInjectionInterface { - use StringTranslationTrait; + /** + * The access manager. + * + * @var \Drupal\administerusersbyrole\Services\AccessManagerInterface + */ + protected $accessManager; + + /** + * Constructs a new AdministerusersbyrolePermissions instance. + * + * @param \Drupal\administerusersbyrole\Services\AccessManagerInterface $access_manager + * The entity manager. + */ + public function __construct(AccessManagerInterface $access_manager) { + $this->accessManager = $access_manager; + } /** * {@inheritdoc} */ public static function create(ContainerInterface $container) { - return new static(); + return new static($container->get('administerusersbyrole.access')); } /** @@ -27,34 +41,7 @@ public static function create(ContainerInterface $container) { * @return array */ public function permissions() { - $roles = user_roles(TRUE); - $perms = []; - $ops = array('edit' => t('Edit'), 'cancel' => t('Cancel')); - - foreach ($roles as $rid => $role) { - if ($role->isAdmin()) { - // Exclude the admin role. Once you can edit an admin, you can set their password, log in and do anything, - // which defeats the point of using this module. - continue; - } - - foreach ($ops as $op => $operation) { - $perm_string = _administerusersbyrole_build_perm_string($rid, $op); - if ($rid == AccountInterface::AUTHENTICATED_ROLE) { - $perm_title = $this->t("@operation users with no custom roles", array( - '@operation' => $operation, - )); - } - else { - $perm_title = $this->t("@operation users with role %role", array( - '@operation' => $operation, - '%role' => $role->label(), - )); - } - $perms[$perm_string] = array('title' => $perm_title); - } - } - - return $perms; + return $this->accessManager->permissions(); } + } diff --git a/web/modules/administerusersbyrole/src/Constraint/OverrideUserMailRequiredValidator.php b/web/modules/administerusersbyrole/src/Constraint/OverrideUserMailRequiredValidator.php index 069e26eb8a4dcaf1df2ee3a2010a9d0bc4a533bf..2f35feb9eacd916dbdb1b9fa3356eb3ed0ae62d9 100644 --- a/web/modules/administerusersbyrole/src/Constraint/OverrideUserMailRequiredValidator.php +++ b/web/modules/administerusersbyrole/src/Constraint/OverrideUserMailRequiredValidator.php @@ -23,7 +23,7 @@ public function validate($items, Constraint $constraint) { $account = $items->getEntity(); $existing_value = NULL; if ($account->id()) { - $account_unchanged = \Drupal::entityManager() + $account_unchanged = \Drupal::entityTypeManager() ->getStorage('user') ->loadUnchanged($account->id()); $existing_value = $account_unchanged->getEmail(); diff --git a/web/modules/administerusersbyrole/src/Form/SettingsForm.php b/web/modules/administerusersbyrole/src/Form/SettingsForm.php new file mode 100644 index 0000000000000000000000000000000000000000..12a2bd994fe3b2f9f6d5fac155347e71bdadc40b --- /dev/null +++ b/web/modules/administerusersbyrole/src/Form/SettingsForm.php @@ -0,0 +1,100 @@ +<?php + +namespace Drupal\administerusersbyrole\Form; + +use Drupal\Component\Utility\Html; +use Drupal\Core\Form\ConfigFormBase; +use Drupal\Core\Form\FormStateInterface; +use Drupal\administerusersbyrole\Services\AccessManagerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Configure AlbanyWeb settings for this site. + */ +class SettingsForm extends ConfigFormBase { + + /** + * The access manager. + * + * @var \Drupal\administerusersbyrole\Services\AccessManagerInterface + */ + protected $accessManager; + + /** + * Constructs a new AdministerusersbyrolePermissions instance. + * + * @param \Drupal\administerusersbyrole\Services\AccessManagerInterface $access_manager + * The entity manager. + */ + public function __construct(AccessManagerInterface $access_manager) { + $this->accessManager = $access_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static($container->get('administerusersbyrole.access')); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'administerusersbyrole_settings'; + } + + /** + * {@inheritdoc} + */ + protected function getEditableConfigNames() { + return [ + 'administerusersbyrole.settings', + ]; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $config = $this->config('administerusersbyrole.settings'); + + $form['roles'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Roles'), + ]; + + $options = [ + AccessManagerInterface::SAFE => $this->t('Allowed'), + AccessManagerInterface::UNSAFE => $this->t('Forbidden'), + AccessManagerInterface::PERM => $this->t('Custom'), + ]; + + foreach ($this->accessManager->managedRoles() as $rid => $role) { + $form['roles'][$rid] = [ + '#type' => 'select', + '#title' => Html::escape($role->label()), + '#default_value' => $config->get("roles.$rid"), + '#options' => $options, + '#required' => TRUE, + ]; + } + + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $config = $this->config('administerusersbyrole.settings'); + $values = $form_state->cleanValues()->getValues(); + foreach ($values as $rid => $value) { + $config->set("roles.$rid", $value); + } + $config->save(); + + parent::submitForm($form, $form_state); + } + +} diff --git a/web/modules/administerusersbyrole/src/Plugin/Action/AddRoleUser.php b/web/modules/administerusersbyrole/src/Plugin/Action/AddRoleUser.php new file mode 100644 index 0000000000000000000000000000000000000000..a42e5c743856d3a161ee18c26df914a39bf4e116 --- /dev/null +++ b/web/modules/administerusersbyrole/src/Plugin/Action/AddRoleUser.php @@ -0,0 +1,14 @@ +<?php + +namespace Drupal\administerusersbyrole\Plugin\Action; + +use Drupal\user\Plugin\Action\AddRoleUser as AddRoleUserBase; + +/** + * Alternative implementation for Action id = "user_add_role_action". + */ +class AddRoleUser extends AddRoleUserBase { + + use ChangeUserRoleTrait; + +} diff --git a/web/modules/administerusersbyrole/src/Plugin/Action/ChangeUserRoleTrait.php b/web/modules/administerusersbyrole/src/Plugin/Action/ChangeUserRoleTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..7d86db2cfd0676d8116cb892b3e713f7497574dc --- /dev/null +++ b/web/modules/administerusersbyrole/src/Plugin/Action/ChangeUserRoleTrait.php @@ -0,0 +1,33 @@ +<?php + +namespace Drupal\administerusersbyrole\Plugin\Action; + +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Session\AccountInterface; + +/** + * Common overrides for AddRoleUser and RemoveRoleUser. + */ +trait ChangeUserRoleTrait { + + /** + * {@inheritdoc} + */ + public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) { + /** @var \Drupal\user\UserInterface $object */ + $access = parent::access($object, $account, TRUE) + ->orIf(administerusersbyrole_user_assign_role($object, $account, [$this->configuration['rid']])); + return $return_as_object ? $access : $access->isAllowed(); + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $form = parent::buildConfigurationForm($form, $form_state); + $allowed = \Drupal::service('administerusersbyrole.access')->listRoles('role-assign', \Drupal::currentUser()); + $form['rid']['#options'] = array_intersect_key($form['rid']['#options'], array_flip($allowed)); + return $form; + } + +} diff --git a/web/modules/administerusersbyrole/src/Plugin/Action/RemoveRoleUser.php b/web/modules/administerusersbyrole/src/Plugin/Action/RemoveRoleUser.php new file mode 100644 index 0000000000000000000000000000000000000000..f3eb5f30ec090aadb75f1b36080254e4b22edb24 --- /dev/null +++ b/web/modules/administerusersbyrole/src/Plugin/Action/RemoveRoleUser.php @@ -0,0 +1,14 @@ +<?php + +namespace Drupal\administerusersbyrole\Plugin\Action; + +use Drupal\user\Plugin\Action\RemoveRoleUser as RemoveRoleUserBase; + +/** + * Alternative implementation for Action id = "user_remove_role_action". + */ +class RemoveRoleUser extends RemoveRoleUserBase { + + use ChangeUserRoleTrait; + +} diff --git a/web/modules/administerusersbyrole/src/Routing/RouteSubscriber.php b/web/modules/administerusersbyrole/src/Routing/RouteSubscriber.php index 5cafd3bb609ebb4d2bf85244f87136d6adcad30b..2ec928e1e7744a23e7a75c51ab878252aeb520c9 100644 --- a/web/modules/administerusersbyrole/src/Routing/RouteSubscriber.php +++ b/web/modules/administerusersbyrole/src/Routing/RouteSubscriber.php @@ -15,7 +15,6 @@ class RouteSubscriber extends RouteSubscriberBase { */ public function alterRoutes(RouteCollection $collection) { // Provide additional access according to our permissions. - if ($route = $collection->get('entity.user.collection')) { $perm = $route->getRequirement('_permission') . '+access users overview'; $route->setRequirement('_permission', $perm); diff --git a/web/modules/administerusersbyrole/src/Services/AccessManager.php b/web/modules/administerusersbyrole/src/Services/AccessManager.php new file mode 100644 index 0000000000000000000000000000000000000000..18734fda9410caa63a5485e2feaa25b112c2d19f --- /dev/null +++ b/web/modules/administerusersbyrole/src/Services/AccessManager.php @@ -0,0 +1,214 @@ +<?php + +namespace Drupal\administerusersbyrole\Services; + +use Drupal\Core\Access\AccessResult; +use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\Core\Session\AccountInterface; +use Drupal\Core\Config\ConfigFactoryInterface; + +/** + * Access Manager. + */ +class AccessManager implements AccessManagerInterface { + + use StringTranslationTrait; + + /** + * The configuration factory. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + + /** + * @var \Drupal\Core\Config\ImmutableConfig + */ + + protected $config; + + const CONVERT_OP = [ + 'cancel' => 'cancel', + 'delete' => 'cancel', + 'edit' => 'edit', + 'update' => 'edit', + 'view' => 'view', + 'role-assign' => 'role-assign', + ]; + + /** + * Constructs a new AccessManager object. + */ + public function __construct(ConfigFactoryInterface $config_factory) { + $this->configFactory = $config_factory; + $this->config = $config_factory->get('administerusersbyrole.settings'); + } + + /** + * {@inheritdoc} + */ + public function rolesChanged() { + $role_config = []; + foreach (array_keys($this->managedRoles()) as $rid) { + $role_config[$rid] = $this->config->get("roles.$rid") ?: self::UNSAFE; + } + + $this->configFactory->getEditable('administerusersbyrole.settings')->set('roles', $role_config)->save(); + } + + /** + * {@inheritdoc} + */ + public function permissions() { + // Base permissions. + $op_titles = [ + 'edit' => $this->t('Edit users with allowed roles'), + 'cancel' => $this->t('Cancel users with allowed roles'), + 'view' => $this->t('View users with allowed roles'), + 'role-assign' => $this->t('Assign allowed roles'), + ]; + + foreach ($op_titles as $op => $title) { + $perm_string = $this->buildPermString($op); + $perms[$perm_string] = ['title' => $title]; + } + + // Per role permissions. + $role_config = $this->config->get('roles') ?: []; + $role_config = array_filter($role_config, function ($s) { + return $s == self::PERM; + }); + $roles = array_intersect_key($this->managedRoles(), $role_config); + + foreach ($roles as $rid => $role) { + // Use a non-breaking space here to adjust the order so that these come + // after the base permission. + $op_role_titles = [ + 'edit' => $this->t("Edit users\u{00A0}includes role %role", ['%role' => $role->label()]), + 'cancel' => $this->t("Cancel users\u{00A0}includes role %role", ['%role' => $role->label()]), + 'view' => $this->t("View users\u{00A0}includes role %role", ['%role' => $role->label()]), + 'role-assign' => $this->t("Assign roles includes role %role", ['%role' => $role->label()]), + ]; + + + foreach ($op_role_titles as $op => $title) { + $perm_string = $this->buildPermString($op, $rid); + $description = $this->t('This permission only works when combined with %base', ['%base' => $op_titles[$op]]); + $perms[$perm_string] = ['title' => $title, 'description' => $description]; + } + } + + return $perms; + } + + /** + * {@inheritdoc} + */ + public function access(array $roles, $operation, AccountInterface $account) { + if (!$this->preAccess($operation, $account)) { + return AccessResult::neutral(); + } + + foreach ($roles as $rid) { + if (!$this->roleAccess($operation, $account, $rid)) { + return AccessResult::neutral(); + } + } + + return AccessResult::allowed(); + } + + /** + * {@inheritdoc} + */ + public function listRoles($operation, AccountInterface $account) { + if (!$this->preAccess($operation, $account)) { + return []; + } + + $roles = [AccountInterface::AUTHENTICATED_ROLE]; + foreach (array_keys($this->managedRoles()) as $rid) { + if ($this->roleAccess($operation, $account, $rid)) { + $roles[] = $rid; + } + } + + return $roles; + } + + /** + * {@inheritdoc} + */ + public function managedRoles() { + $roles = array_filter(user_roles(TRUE), function ($role) { + return !$role->hasPermission('administer users'); + }); + unset($roles[AccountInterface::AUTHENTICATED_ROLE]); + return $roles; + } + + /** + * Initial access check for an operation to test if access might be granted for some roles. + * + * @param string $operation + * The operation that is to be performed on the user. + * Value is updated to match the canonical value used in this module. + * + * @param \Drupal\Core\Session\AccountInterface $account + * The account trying to access the entity. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. hook_entity_access() has detailed documentation. + */ + protected function preAccess(&$operation, AccountInterface $account) { + // Full admins already have permissions so we are wasting our time to continue. + if ($account->hasPermission('administer users')) { + return FALSE; + } + + // Ignore unrecognised operation. + if (!array_key_exists($operation, self::CONVERT_OP)) { + return FALSE; + } + + // Check the base permission. + $operation = self::CONVERT_OP[$operation]; + return $this->hasPerm($operation, $account); + } + + /** + * Checks access for a given role. + */ + protected function roleAccess($operation, AccountInterface $account, $rid) { + if ($rid == AccountInterface::AUTHENTICATED_ROLE) { + return self::SAFE; + } + + $setting = $this->config->get("roles.$rid") ?: self::UNSAFE; + switch ($setting) { + case self::SAFE: + return TRUE; + + case self::UNSAFE: + return FALSE; + + case self::PERM: + return $this->hasPerm($operation, $account, $rid); + } + } + + /** + * Checks access to a permission for a given role. + */ + protected function hasPerm($operation, AccountInterface $account, $rid = NULL) { + return $account->hasPermission($this->buildPermString($operation, $rid)); + } + + /** + * Generates a permission string for a given role. + */ + public function buildPermString($operation, $rid = NULL) { + return $rid ? "$operation users with role $rid" : "$operation users by role"; + } + +} diff --git a/web/modules/administerusersbyrole/src/Services/AccessManagerInterface.php b/web/modules/administerusersbyrole/src/Services/AccessManagerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..8caa2a779b912987370b5ca63dd93926b0688dae --- /dev/null +++ b/web/modules/administerusersbyrole/src/Services/AccessManagerInterface.php @@ -0,0 +1,67 @@ +<?php + +namespace Drupal\administerusersbyrole\Services; + +use Drupal\Core\Session\AccountInterface; + +/** + * Interface for access manager service. + */ +interface AccessManagerInterface { + + const SAFE = 'safe'; + const UNSAFE = 'unsafe'; + const PERM = 'perm'; + + /** + * Check access for the specified roles. + * + * @param array $roles + * Roles of the user object to check access for. + * + * @param string $operation + * The operation that is to be performed on the user. + * + * @param \Drupal\Core\Session\AccountInterface $account + * The account trying to access the entity. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. hook_entity_access() has detailed documentation. + */ + public function access(array $roles, $operation, AccountInterface $account); + + /** + * List all accessible roles for the specified operation. + * + * @param string $operation + * The operation that is to be performed. + * + * @param \Drupal\Core\Session\AccountInterface $account + * The account trying to access the entity. + * + * @return array of role IDs. + */ + public function listRoles($operation, AccountInterface $account); + + /** + * Acts on changes to configured roles. + */ + public function rolesChanged(); + + /** + * Return permissions to add. + * + * @return array of permissions. + */ + public function permissions(); + + /** + * Returns a list of all roles that are available to be managed by this module. + * + * @return \Drupal\user\RoleInterface[] + * An associative array with the role id as the key and the role object as + * value. + */ + public function managedRoles(); + +} diff --git a/web/modules/administerusersbyrole/tests/src/Functional/AdministerusersbyroleTest.php b/web/modules/administerusersbyrole/tests/src/Functional/AdministerusersbyroleTest.php index c4e836f90feb809affb9f3b8809ec779a7b68d61..a47f9573027d40d4671622ecb3ee62317094e475 100644 --- a/web/modules/administerusersbyrole/tests/src/Functional/AdministerusersbyroleTest.php +++ b/web/modules/administerusersbyrole/tests/src/Functional/AdministerusersbyroleTest.php @@ -3,218 +3,250 @@ namespace Drupal\Tests\administerusersbyrole\Functional; use Drupal\Tests\BrowserTestBase; -use Drupal\Component\Utility\SafeMarkup; -use Drupal\Core\Session\AccountInterface; /** - * Testing for administerusersbyrole module + * Testing for administerusersbyrole module. * * @group administerusersbyrole */ class AdministerusersbyroleTest extends BrowserTestBase { - public static $modules = array('administerusersbyrole', 'user'); + /** + * Modules to install. + * + * @var array + */ + public static $modules = ['administerusersbyrole', 'user']; - protected $roles = array(); - protected $users = array(); + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; - public function setUp() { + protected $roles = []; + protected $users = []; + + /** + * The access manager. + * + * @var \Drupal\administerusersbyrole\Services\AccessManagerInterface + */ + protected $accessManager; + + /** + * Editable module configuration. + */ + protected $config; + + /** + * {@inheritdoc} + */ + protected function setUp() { parent::setUp(); + $this->accessManager = \Drupal::service('administerusersbyrole.access'); + $this->config = \Drupal::service('config.factory')->getEditable('administerusersbyrole.settings'); - $this->createUserWithRole('noroles', array()); + $this->createUserWithRole('noroles', []); $this->createRolesAndUsers('alpha', FALSE); $this->createRolesAndUsers('beta', TRUE); - $this->createUserWithRole('alphabeta', array('alpha', 'beta')); + $this->createUserWithRole('alphabeta', ['alpha', 'beta']); - // alphabeta_ed - $perms = array( + // alphabeta_ed. + $perms = [ 'access content', - _administerusersbyrole_build_perm_string($this->roles['alpha'], 'edit'), - _administerusersbyrole_build_perm_string($this->roles['beta'], 'edit'), - ); - $this->roles['alphabeta_ed'] = $this->drupalCreateRole($perms, 'alphabeta_ed'); - $this->createUserWithRole('alphabeta_ed', array('alphabeta_ed')); + $this->accessManager->buildPermString('edit'), + $this->accessManager->buildPermString('edit', 'alpha'), + $this->accessManager->buildPermString('edit', 'beta'), + ]; + $this->drupalCreateRole($perms, 'alphabeta_ed'); + $this->config->set("roles.alphabeta_ed", 'perm')->save(); + $this->createUserWithRole('alphabeta_ed', ['alphabeta_ed']); - // all_editor - $perms = array( + // all_editor. + $perms = [ 'access content', - _administerusersbyrole_build_perm_string(AccountInterface::AUTHENTICATED_ROLE, 'edit'), - ); - foreach ($this->roles as $roleName => $roleID) { - $perms[] = _administerusersbyrole_build_perm_string($this->roles[$roleName], 'edit'); + $this->accessManager->buildPermString('edit'), + ]; + foreach (array_keys($this->accessManager->managedRoles()) as $roleName) { + $perms[] = $this->accessManager->buildPermString('edit', $roleName); } - $this->roles['all_editor'] = $this->drupalCreateRole($perms, 'all_editor'); - $this->createUserWithRole('all_editor', array('all_editor')); + $this->drupalCreateRole($perms, 'all_editor'); + $this->config->set("roles.all_editor", 'perm')->save(); + $this->createUserWithRole('all_editor', ['all_editor']); - // all_deletor - $perms = array( + // all_deletor. + $perms = [ 'access content', - _administerusersbyrole_build_perm_string(AccountInterface::AUTHENTICATED_ROLE, 'cancel'), - ); - foreach ($this->roles as $roleName => $roleID) { - $perms[] = _administerusersbyrole_build_perm_string($roleID, 'cancel'); + $this->accessManager->buildPermString('cancel'), + ]; + foreach (array_keys($this->accessManager->managedRoles()) as $roleName) { + $perms[] = $this->accessManager->buildPermString('cancel', $roleName); } - $this->roles['all_deletor'] = $this->drupalCreateRole($perms, 'all_deletor'); - $this->createUserWithRole('all_deletor', array('all_deletor')); + $this->drupalCreateRole($perms, 'all_deletor'); + $this->createUserWithRole('all_deletor', ['all_deletor']); - // creator - $perms = array( + // Creator. + $perms = [ 'access content', 'create users', - ); - $this->roles['creator'] = $this->drupalCreateRole($perms, 'creator'); - $this->createUserWithRole('creator', array('creator')); + ]; + $this->drupalCreateRole($perms, 'creator'); + $this->createUserWithRole('creator', ['creator']); } + /** + * Tests basic permissions. + */ public function testPermissions() { - $expectations = array( + $expectations = [ // When I'm logged in as... - 'nobody' => array( + 'nobody' => [ // ...I can perform these actions on this other user... - 'noroles' => array('edit' => FALSE, 'cancel' => FALSE), - 'alpha' => array('edit' => FALSE, 'cancel' => FALSE), - 'alpha_editor' => array('edit' => FALSE, 'cancel' => FALSE), - 'beta' => array('edit' => FALSE, 'cancel' => FALSE), - 'beta_editor' => array('edit' => FALSE, 'cancel' => FALSE), - 'alphabeta' => array('edit' => FALSE, 'cancel' => FALSE), - 'alphabeta_ed' => array('edit' => FALSE, 'cancel' => FALSE), - 'creator' => array('edit' => FALSE, 'cancel' => FALSE), - 'all_editor' => array('edit' => FALSE, 'cancel' => FALSE), - 'all_deletor' => array('edit' => FALSE, 'cancel' => FALSE), + 'noroles' => ['edit' => FALSE, 'cancel' => FALSE], + 'alpha' => ['edit' => FALSE, 'cancel' => FALSE], + 'alpha_editor' => ['edit' => FALSE, 'cancel' => FALSE], + 'beta' => ['edit' => FALSE, 'cancel' => FALSE], + 'beta_editor' => ['edit' => FALSE, 'cancel' => FALSE], + 'alphabeta' => ['edit' => FALSE, 'cancel' => FALSE], + 'alphabeta_ed' => ['edit' => FALSE, 'cancel' => FALSE], + 'creator' => ['edit' => FALSE, 'cancel' => FALSE], + 'all_editor' => ['edit' => FALSE, 'cancel' => FALSE], + 'all_deletor' => ['edit' => FALSE, 'cancel' => FALSE], 'create users' => FALSE, - ), - 'noroles' => array( - 'noroles' => array('edit' => TRUE, 'cancel' => FALSE), - 'alpha' => array('edit' => FALSE, 'cancel' => FALSE), - 'alpha_editor' => array('edit' => FALSE, 'cancel' => FALSE), - 'beta' => array('edit' => FALSE, 'cancel' => FALSE), - 'beta_editor' => array('edit' => FALSE, 'cancel' => FALSE), - 'alphabeta' => array('edit' => FALSE, 'cancel' => FALSE), - 'alphabeta_ed' => array('edit' => FALSE, 'cancel' => FALSE), - 'creator' => array('edit' => FALSE, 'cancel' => FALSE), - 'all_editor' => array('edit' => FALSE, 'cancel' => FALSE), - 'all_deletor' => array('edit' => FALSE, 'cancel' => FALSE), + ], + 'noroles' => [ + 'noroles' => ['edit' => TRUE, 'cancel' => FALSE], + 'alpha' => ['edit' => FALSE, 'cancel' => FALSE], + 'alpha_editor' => ['edit' => FALSE, 'cancel' => FALSE], + 'beta' => ['edit' => FALSE, 'cancel' => FALSE], + 'beta_editor' => ['edit' => FALSE, 'cancel' => FALSE], + 'alphabeta' => ['edit' => FALSE, 'cancel' => FALSE], + 'alphabeta_ed' => ['edit' => FALSE, 'cancel' => FALSE], + 'creator' => ['edit' => FALSE, 'cancel' => FALSE], + 'all_editor' => ['edit' => FALSE, 'cancel' => FALSE], + 'all_deletor' => ['edit' => FALSE, 'cancel' => FALSE], 'create users' => FALSE, - ), - 'alpha' => array( - 'noroles' => array('edit' => FALSE, 'cancel' => FALSE), - 'alpha' => array('edit' => TRUE, 'cancel' => FALSE), - 'alpha_editor' => array('edit' => FALSE, 'cancel' => FALSE), - 'beta' => array('edit' => FALSE, 'cancel' => FALSE), - 'beta_editor' => array('edit' => FALSE, 'cancel' => FALSE), - 'alphabeta' => array('edit' => FALSE, 'cancel' => FALSE), - 'alphabeta_ed' => array('edit' => FALSE, 'cancel' => FALSE), - 'creator' => array('edit' => FALSE, 'cancel' => FALSE), - 'all_editor' => array('edit' => FALSE, 'cancel' => FALSE), - 'all_deletor' => array('edit' => FALSE, 'cancel' => FALSE), + ], + 'alpha' => [ + 'noroles' => ['edit' => FALSE, 'cancel' => FALSE], + 'alpha' => ['edit' => TRUE, 'cancel' => FALSE], + 'alpha_editor' => ['edit' => FALSE, 'cancel' => FALSE], + 'beta' => ['edit' => FALSE, 'cancel' => FALSE], + 'beta_editor' => ['edit' => FALSE, 'cancel' => FALSE], + 'alphabeta' => ['edit' => FALSE, 'cancel' => FALSE], + 'alphabeta_ed' => ['edit' => FALSE, 'cancel' => FALSE], + 'creator' => ['edit' => FALSE, 'cancel' => FALSE], + 'all_editor' => ['edit' => FALSE, 'cancel' => FALSE], + 'all_deletor' => ['edit' => FALSE, 'cancel' => FALSE], 'create users' => FALSE, - ), - 'alpha_editor' => array( - 'noroles' => array('edit' => TRUE, 'cancel' => FALSE), - 'alpha' => array('edit' => TRUE, 'cancel' => FALSE), - 'alpha_editor' => array('edit' => TRUE, 'cancel' => FALSE), - 'beta' => array('edit' => FALSE, 'cancel' => FALSE), - 'beta_editor' => array('edit' => FALSE, 'cancel' => FALSE), - 'alphabeta' => array('edit' => FALSE, 'cancel' => FALSE), - 'alphabeta_ed' => array('edit' => FALSE, 'cancel' => FALSE), - 'creator' => array('edit' => FALSE, 'cancel' => FALSE), - 'all_editor' => array('edit' => FALSE, 'cancel' => FALSE), - 'all_deletor' => array('edit' => FALSE, 'cancel' => FALSE), + ], + 'alpha_editor' => [ + 'noroles' => ['edit' => TRUE, 'cancel' => FALSE], + 'alpha' => ['edit' => TRUE, 'cancel' => FALSE], + 'alpha_editor' => ['edit' => TRUE, 'cancel' => FALSE], + 'beta' => ['edit' => FALSE, 'cancel' => FALSE], + 'beta_editor' => ['edit' => FALSE, 'cancel' => FALSE], + 'alphabeta' => ['edit' => FALSE, 'cancel' => FALSE], + 'alphabeta_ed' => ['edit' => FALSE, 'cancel' => FALSE], + 'creator' => ['edit' => FALSE, 'cancel' => FALSE], + 'all_editor' => ['edit' => FALSE, 'cancel' => FALSE], + 'all_deletor' => ['edit' => FALSE, 'cancel' => FALSE], 'create users' => FALSE, - ), - 'beta' => array( - 'noroles' => array('edit' => FALSE, 'cancel' => FALSE), - 'alpha' => array('edit' => FALSE, 'cancel' => FALSE), - 'alpha_editor' => array('edit' => FALSE, 'cancel' => FALSE), - 'beta' => array('edit' => TRUE, 'cancel' => FALSE), - 'beta_editor' => array('edit' => FALSE, 'cancel' => FALSE), - 'alphabeta' => array('edit' => FALSE, 'cancel' => FALSE), - 'alphabeta_ed' => array('edit' => FALSE, 'cancel' => FALSE), - 'creator' => array('edit' => FALSE, 'cancel' => FALSE), - 'all_editor' => array('edit' => FALSE, 'cancel' => FALSE), - 'all_deletor' => array('edit' => FALSE, 'cancel' => FALSE), + ], + 'beta' => [ + 'noroles' => ['edit' => FALSE, 'cancel' => FALSE], + 'alpha' => ['edit' => FALSE, 'cancel' => FALSE], + 'alpha_editor' => ['edit' => FALSE, 'cancel' => FALSE], + 'beta' => ['edit' => TRUE, 'cancel' => FALSE], + 'beta_editor' => ['edit' => FALSE, 'cancel' => FALSE], + 'alphabeta' => ['edit' => FALSE, 'cancel' => FALSE], + 'alphabeta_ed' => ['edit' => FALSE, 'cancel' => FALSE], + 'creator' => ['edit' => FALSE, 'cancel' => FALSE], + 'all_editor' => ['edit' => FALSE, 'cancel' => FALSE], + 'all_deletor' => ['edit' => FALSE, 'cancel' => FALSE], 'create users' => FALSE, - ), - 'beta_editor' => array( - 'noroles' => array('edit' => TRUE, 'cancel' => FALSE), - 'alpha' => array('edit' => FALSE, 'cancel' => FALSE), - 'alpha_editor' => array('edit' => FALSE, 'cancel' => FALSE), - 'beta' => array('edit' => TRUE, 'cancel' => TRUE), - 'beta_editor' => array('edit' => TRUE, 'cancel' => FALSE), - 'alphabeta' => array('edit' => FALSE, 'cancel' => FALSE), - 'alphabeta_ed' => array('edit' => FALSE, 'cancel' => FALSE), - 'creator' => array('edit' => FALSE, 'cancel' => FALSE), - 'all_editor' => array('edit' => FALSE, 'cancel' => FALSE), - 'all_deletor' => array('edit' => FALSE, 'cancel' => FALSE), + ], + 'beta_editor' => [ + 'noroles' => ['edit' => TRUE, 'cancel' => TRUE], + 'alpha' => ['edit' => FALSE, 'cancel' => FALSE], + 'alpha_editor' => ['edit' => FALSE, 'cancel' => FALSE], + 'beta' => ['edit' => TRUE, 'cancel' => TRUE], + 'beta_editor' => ['edit' => TRUE, 'cancel' => FALSE], + 'alphabeta' => ['edit' => FALSE, 'cancel' => FALSE], + 'alphabeta_ed' => ['edit' => FALSE, 'cancel' => FALSE], + 'creator' => ['edit' => FALSE, 'cancel' => FALSE], + 'all_editor' => ['edit' => FALSE, 'cancel' => FALSE], + 'all_deletor' => ['edit' => FALSE, 'cancel' => FALSE], 'create users' => FALSE, - ), - 'alphabeta' => array( - 'noroles' => array('edit' => FALSE, 'cancel' => FALSE), - 'alpha' => array('edit' => FALSE, 'cancel' => FALSE), - 'alpha_editor' => array('edit' => FALSE, 'cancel' => FALSE), - 'beta' => array('edit' => FALSE, 'cancel' => FALSE), - 'beta_editor' => array('edit' => FALSE, 'cancel' => FALSE), - 'alphabeta' => array('edit' => TRUE, 'cancel' => FALSE), - 'alphabeta_ed' => array('edit' => FALSE, 'cancel' => FALSE), - 'creator' => array('edit' => FALSE, 'cancel' => FALSE), - 'all_editor' => array('edit' => FALSE, 'cancel' => FALSE), - 'all_deletor' => array('edit' => FALSE, 'cancel' => FALSE), + ], + 'alphabeta' => [ + 'noroles' => ['edit' => FALSE, 'cancel' => FALSE], + 'alpha' => ['edit' => FALSE, 'cancel' => FALSE], + 'alpha_editor' => ['edit' => FALSE, 'cancel' => FALSE], + 'beta' => ['edit' => FALSE, 'cancel' => FALSE], + 'beta_editor' => ['edit' => FALSE, 'cancel' => FALSE], + 'alphabeta' => ['edit' => TRUE, 'cancel' => FALSE], + 'alphabeta_ed' => ['edit' => FALSE, 'cancel' => FALSE], + 'creator' => ['edit' => FALSE, 'cancel' => FALSE], + 'all_editor' => ['edit' => FALSE, 'cancel' => FALSE], + 'all_deletor' => ['edit' => FALSE, 'cancel' => FALSE], 'create users' => FALSE, - ), - 'alphabeta_ed' => array( - 'noroles' => array('edit' => FALSE, 'cancel' => FALSE), - 'alpha' => array('edit' => TRUE, 'cancel' => FALSE), - 'alpha_editor' => array('edit' => FALSE, 'cancel' => FALSE), - 'beta' => array('edit' => TRUE, 'cancel' => FALSE), - 'beta_editor' => array('edit' => FALSE, 'cancel' => FALSE), - 'alphabeta' => array('edit' => TRUE, 'cancel' => FALSE), - 'alphabeta_ed' => array('edit' => TRUE, 'cancel' => FALSE), - 'creator' => array('edit' => FALSE, 'cancel' => FALSE), - 'all_editor' => array('edit' => FALSE, 'cancel' => FALSE), - 'all_deletor' => array('edit' => FALSE, 'cancel' => FALSE), + ], + 'alphabeta_ed' => [ + 'noroles' => ['edit' => TRUE, 'cancel' => FALSE], + 'alpha' => ['edit' => TRUE, 'cancel' => FALSE], + 'alpha_editor' => ['edit' => FALSE, 'cancel' => FALSE], + 'beta' => ['edit' => TRUE, 'cancel' => FALSE], + 'beta_editor' => ['edit' => FALSE, 'cancel' => FALSE], + 'alphabeta' => ['edit' => TRUE, 'cancel' => FALSE], + 'alphabeta_ed' => ['edit' => TRUE, 'cancel' => FALSE], + 'creator' => ['edit' => FALSE, 'cancel' => FALSE], + 'all_editor' => ['edit' => FALSE, 'cancel' => FALSE], + 'all_deletor' => ['edit' => FALSE, 'cancel' => FALSE], 'create users' => FALSE, - ), - 'all_editor' => array( - 'noroles' => array('edit' => TRUE, 'cancel' => FALSE), - 'alpha' => array('edit' => TRUE, 'cancel' => FALSE), - 'alpha_editor' => array('edit' => TRUE, 'cancel' => FALSE), - 'beta' => array('edit' => TRUE, 'cancel' => FALSE), - 'beta_editor' => array('edit' => TRUE, 'cancel' => FALSE), - 'alphabeta' => array('edit' => TRUE, 'cancel' => FALSE), - 'alphabeta_ed' => array('edit' => TRUE, 'cancel' => FALSE), - 'creator' => array('edit' => FALSE, 'cancel' => FALSE), - 'all_editor' => array('edit' => TRUE, 'cancel' => FALSE), - 'all_deletor' => array('edit' => FALSE, 'cancel' => FALSE), + ], + 'all_editor' => [ + 'noroles' => ['edit' => TRUE, 'cancel' => FALSE], + 'alpha' => ['edit' => TRUE, 'cancel' => FALSE], + 'alpha_editor' => ['edit' => TRUE, 'cancel' => FALSE], + 'beta' => ['edit' => TRUE, 'cancel' => FALSE], + 'beta_editor' => ['edit' => TRUE, 'cancel' => FALSE], + 'alphabeta' => ['edit' => TRUE, 'cancel' => FALSE], + 'alphabeta_ed' => ['edit' => TRUE, 'cancel' => FALSE], + 'creator' => ['edit' => FALSE, 'cancel' => FALSE], + 'all_editor' => ['edit' => TRUE, 'cancel' => FALSE], + 'all_deletor' => ['edit' => FALSE, 'cancel' => FALSE], 'create users' => FALSE, - ), - 'all_deletor' => array( - 'noroles' => array('edit' => FALSE, 'cancel' => TRUE), - 'alpha' => array('edit' => FALSE, 'cancel' => TRUE), - 'alpha_editor' => array('edit' => FALSE, 'cancel' => TRUE), - 'beta' => array('edit' => FALSE, 'cancel' => TRUE), - 'beta_editor' => array('edit' => FALSE, 'cancel' => TRUE), - 'alphabeta' => array('edit' => FALSE, 'cancel' => TRUE), - 'alphabeta_ed' => array('edit' => FALSE, 'cancel' => TRUE), - 'creator' => array('edit' => FALSE, 'cancel' => FALSE), - 'all_editor' => array('edit' => FALSE, 'cancel' => TRUE), - 'all_deletor' => array('edit' => TRUE, 'cancel' => FALSE), + ], + 'all_deletor' => [ + 'noroles' => ['edit' => FALSE, 'cancel' => TRUE], + 'alpha' => ['edit' => FALSE, 'cancel' => TRUE], + 'alpha_editor' => ['edit' => FALSE, 'cancel' => TRUE], + 'beta' => ['edit' => FALSE, 'cancel' => TRUE], + 'beta_editor' => ['edit' => FALSE, 'cancel' => TRUE], + 'alphabeta' => ['edit' => FALSE, 'cancel' => TRUE], + 'alphabeta_ed' => ['edit' => FALSE, 'cancel' => TRUE], + 'creator' => ['edit' => FALSE, 'cancel' => FALSE], + 'all_editor' => ['edit' => FALSE, 'cancel' => TRUE], + 'all_deletor' => ['edit' => TRUE, 'cancel' => FALSE], 'create users' => FALSE, - ), - 'creator' => array( - 'noroles' => array('edit' => FALSE, 'cancel' => FALSE), - 'alpha' => array('edit' => FALSE, 'cancel' => FALSE), - 'alpha_editor' => array('edit' => FALSE, 'cancel' => FALSE), - 'beta' => array('edit' => FALSE, 'cancel' => FALSE), - 'beta_editor' => array('edit' => FALSE, 'cancel' => FALSE), - 'alphabeta' => array('edit' => FALSE, 'cancel' => FALSE), - 'alphabeta_ed' => array('edit' => FALSE, 'cancel' => FALSE), - 'creator' => array('edit' => TRUE, 'cancel' => FALSE), - 'all_editor' => array('edit' => FALSE, 'cancel' => FALSE), - 'all_deletor' => array('edit' => FALSE, 'cancel' => FALSE), + ], + 'creator' => [ + 'noroles' => ['edit' => FALSE, 'cancel' => FALSE], + 'alpha' => ['edit' => FALSE, 'cancel' => FALSE], + 'alpha_editor' => ['edit' => FALSE, 'cancel' => FALSE], + 'beta' => ['edit' => FALSE, 'cancel' => FALSE], + 'beta_editor' => ['edit' => FALSE, 'cancel' => FALSE], + 'alphabeta' => ['edit' => FALSE, 'cancel' => FALSE], + 'alphabeta_ed' => ['edit' => FALSE, 'cancel' => FALSE], + 'creator' => ['edit' => TRUE, 'cancel' => FALSE], + 'all_editor' => ['edit' => FALSE, 'cancel' => FALSE], + 'all_deletor' => ['edit' => FALSE, 'cancel' => FALSE], 'create users' => TRUE, - ), - ); + ], + ]; + $assert = $this->assertSession(); foreach ($expectations as $loginUsername => $editUsernames) { if ($loginUsername !== 'nobody') { $this->drupalLogin($this->users[$loginUsername]); @@ -237,20 +269,19 @@ public function testPermissions() { $editUid = $this->users[$editUsername]->id(); foreach ($operations as $operation => $expectedResult) { $this->drupalGet("user/$editUid/$operation"); - // $loginUsername perform $operation on $editUsername + if ($expectedResult) { if ($operation === 'edit') { - $this->assertRaw("All emails from the system will be sent to this address."); + $assert->responseContains("All emails from the system will be sent to this address."); } elseif ($operation === 'cancel') { - $this->assertRaw("Are you sure you want to cancel the account <em class=\"placeholder\">$editUsername</em>?"); + $assert->responseContains("Are you sure you want to cancel the account <em class=\"placeholder\">$editUsername</em>?"); } } else { - $this->assertTrue( - strstr($this->getRawContent(), "You do not have permission to $operation <em class=\"placeholder\">$editUsername</em>.") - || strstr($this->getRawContent(), 'Access denied'), - "My expectation is that $loginUsername shouldn't be able to $operation $editUsername, but it can."); + $content = $this->getSession()->getPage()->getContent(); + $denied = strstr($content, "You do not have permission to $operation <em class=\"placeholder\">$editUsername</em>.") || strstr($content, 'Access denied'); + $this->assertTrue($denied, "My expectation is that $loginUsername shouldn't be able to $operation $editUsername, but it can."); } } } @@ -262,9 +293,12 @@ public function testPermissions() { } } + /** + * Creates a user with the specified name and roles. + */ protected function createUserWithRole($userName, $roleNames) { $user = $this->drupalCreateUser([], $userName); - $this->assertTrue($user, "Unable to create user $userName."); + $this->assertNotEmpty($user, "Unable to create user $userName."); foreach ($roleNames as $role) { $user->addRole($role); } @@ -272,23 +306,29 @@ protected function createUserWithRole($userName, $roleNames) { $this->users[$userName] = $user; } + /** + * Creates and role, a user with that roles and a user that can edit the + * role. + */ protected function createRolesAndUsers($roleName, $allowEditorToCancel) { - // create basic role - $this->roles[$roleName] = $this->drupalCreateRole(array('access content'), $roleName); - $this->createUserWithRole($roleName, array($roleName)); + // Create basic role. + $this->drupalCreateRole(['access content'], $roleName); + $this->config->set("roles.$roleName", 'perm')->save(); + $this->createUserWithRole($roleName, [$roleName]); - // create role to edit above role and also anyone with no custom roles. - $perms = array( + // Create role to edit above role and also anyone with no custom roles. + $perms = [ 'access content', - _administerusersbyrole_build_perm_string(AccountInterface::AUTHENTICATED_ROLE, 'edit'), - _administerusersbyrole_build_perm_string($this->roles[$roleName], 'edit'), - ); + $this->accessManager->buildPermString('edit'), + $this->accessManager->buildPermString('edit', $roleName), + ]; if ($allowEditorToCancel) { - // Don't add in "no custom roles" this time, to give better variety of testing. - $perms[] = _administerusersbyrole_build_perm_string($this->roles[$roleName], 'cancel'); + $perms[] = $this->accessManager->buildPermString('cancel'); + $perms[] = $this->accessManager->buildPermString('cancel', $roleName); } - $this->roles["{$roleName}_editor"] = $this->drupalCreateRole($perms, "{$roleName}_editor"); - $this->createUserWithRole("{$roleName}_editor", array("{$roleName}_editor")); + $this->drupalCreateRole($perms, "{$roleName}_editor"); + $this->config->set("roles.{$roleName}_editor", 'perm')->save(); + $this->createUserWithRole("{$roleName}_editor", ["{$roleName}_editor"]); } }