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"]);
   }
 
 }