From 8002c1010bd51020e02997c0afa876ebd1097001 Mon Sep 17 00:00:00 2001 From: Samuel Georges Date: Tue, 11 Jul 2017 19:17:44 +1000 Subject: [PATCH] Create migrate script to split permissions out Permissions have been moved from Groups to a new Role model Refs #2367 --- modules/backend/controllers/UserGroups.php | 26 ---- modules/backend/controllers/UserRoles.php | 60 ++++++++ .../backend/controllers/usergroups/create.htm | 2 +- .../backend/controllers/usergroups/update.htm | 2 +- .../controllers/userroles/_list_toolbar.htm | 8 + .../controllers/userroles/config_form.yaml | 16 ++ .../controllers/userroles/config_list.yaml | 15 ++ .../backend/controllers/userroles/create.htm | 44 ++++++ .../backend/controllers/userroles/index.htm | 8 + .../backend/controllers/userroles/update.htm | 53 +++++++ .../controllers/users/_list_toolbar.htm | 3 + .../2013_10_01_000001_Db_Backend_Users.php | 1 + ...13_10_01_000002_Db_Backend_User_Groups.php | 1 - ...017_10_01_000010_Db_Backend_User_Roles.php | 143 ++++++++++++++++++ modules/backend/lang/en/lang.php | 20 ++- modules/backend/models/User.php | 17 +++ modules/backend/models/UserRole.php | 37 +++++ modules/backend/models/user/fields.yaml | 8 +- modules/backend/models/usergroup/fields.yaml | 3 - modules/backend/models/userrole/columns.yaml | 19 +++ modules/backend/models/userrole/fields.yaml | 22 +++ 21 files changed, 473 insertions(+), 35 deletions(-) create mode 100644 modules/backend/controllers/UserRoles.php create mode 100644 modules/backend/controllers/userroles/_list_toolbar.htm create mode 100644 modules/backend/controllers/userroles/config_form.yaml create mode 100644 modules/backend/controllers/userroles/config_list.yaml create mode 100644 modules/backend/controllers/userroles/create.htm create mode 100644 modules/backend/controllers/userroles/index.htm create mode 100644 modules/backend/controllers/userroles/update.htm create mode 100644 modules/backend/database/migrations/2017_10_01_000010_Db_Backend_User_Roles.php create mode 100644 modules/backend/models/UserRole.php create mode 100644 modules/backend/models/userrole/columns.yaml create mode 100644 modules/backend/models/userrole/fields.yaml diff --git a/modules/backend/controllers/UserGroups.php b/modules/backend/controllers/UserGroups.php index 3db4d78ee..97b55e399 100644 --- a/modules/backend/controllers/UserGroups.php +++ b/modules/backend/controllers/UserGroups.php @@ -31,30 +31,4 @@ class UserGroups extends Controller BackendMenu::setContext('October.System', 'system', 'users'); SettingsManager::setContext('October.System', 'administrators'); } - - /** - * Add available permission fields to the Group form. - */ - public function formExtendFields($form) - { - /* - * Add permissions tab - */ - $form->addTabFields($this->generatePermissionsField()); - } - - /** - * Adds the permissions editor widget to the form. - * @return array - */ - protected function generatePermissionsField() - { - return [ - 'permissions' => [ - 'tab' => 'backend::lang.user.permissions', - 'type' => 'Backend\FormWidgets\PermissionEditor', - 'mode' => 'checkbox' - ] - ]; - } } diff --git a/modules/backend/controllers/UserRoles.php b/modules/backend/controllers/UserRoles.php new file mode 100644 index 000000000..e6b9600f9 --- /dev/null +++ b/modules/backend/controllers/UserRoles.php @@ -0,0 +1,60 @@ +addTabFields($this->generatePermissionsField()); + } + + /** + * Adds the permissions editor widget to the form. + * @return array + */ + protected function generatePermissionsField() + { + return [ + 'permissions' => [ + 'tab' => 'backend::lang.user.permissions', + 'type' => 'Backend\FormWidgets\PermissionEditor', + 'mode' => 'checkbox' + ] + ]; + } +} diff --git a/modules/backend/controllers/usergroups/create.htm b/modules/backend/controllers/usergroups/create.htm index fcc511965..7e8cf3665 100644 --- a/modules/backend/controllers/usergroups/create.htm +++ b/modules/backend/controllers/usergroups/create.htm @@ -41,4 +41,4 @@

fatalError)) ?>

- \ No newline at end of file + diff --git a/modules/backend/controllers/usergroups/update.htm b/modules/backend/controllers/usergroups/update.htm index 3b19df71b..a262441d7 100644 --- a/modules/backend/controllers/usergroups/update.htm +++ b/modules/backend/controllers/usergroups/update.htm @@ -50,4 +50,4 @@

fatalError)) ?>

- \ No newline at end of file + diff --git a/modules/backend/controllers/userroles/_list_toolbar.htm b/modules/backend/controllers/userroles/_list_toolbar.htm new file mode 100644 index 000000000..29e576249 --- /dev/null +++ b/modules/backend/controllers/userroles/_list_toolbar.htm @@ -0,0 +1,8 @@ +
+ + + + + + +
diff --git a/modules/backend/controllers/userroles/config_form.yaml b/modules/backend/controllers/userroles/config_form.yaml new file mode 100644 index 000000000..68b4063b7 --- /dev/null +++ b/modules/backend/controllers/userroles/config_form.yaml @@ -0,0 +1,16 @@ +# =================================== +# Form Behavior Config +# =================================== + +name: backend::lang.user.role.name +form: ~/modules/backend/models/userrole/fields.yaml +modelClass: Backend\Models\UserRole +defaultRedirect: backend/userroles + +create: + redirect: backend/userroles/update/:id + redirectClose: backend/userroles + +update: + redirect: backend/userroles + redirectClose: backend/userroles diff --git a/modules/backend/controllers/userroles/config_list.yaml b/modules/backend/controllers/userroles/config_list.yaml new file mode 100644 index 000000000..5871e58fd --- /dev/null +++ b/modules/backend/controllers/userroles/config_list.yaml @@ -0,0 +1,15 @@ +# =================================== +# List Behavior Config +# =================================== + +title: backend::lang.user.role.list_title +list: ~/modules/backend/models/userrole/columns.yaml +modelClass: Backend\Models\UserRole +recordUrl: backend/userroles/update/:id +noRecordsMessage: backend::lang.list.no_records +recordsPerPage: 5 + +toolbar: + buttons: list_toolbar + search: + prompt: backend::lang.list.search_prompt diff --git a/modules/backend/controllers/userroles/create.htm b/modules/backend/controllers/userroles/create.htm new file mode 100644 index 000000000..07f55b3b1 --- /dev/null +++ b/modules/backend/controllers/userroles/create.htm @@ -0,0 +1,44 @@ + + + + +fatalError): ?> + + 'layout']) ?> + +
+ formRender() ?> +
+ +
+
+ + +
+
+ + + + +

fatalError)) ?>

+

+ diff --git a/modules/backend/controllers/userroles/index.htm b/modules/backend/controllers/userroles/index.htm new file mode 100644 index 000000000..ee75b8527 --- /dev/null +++ b/modules/backend/controllers/userroles/index.htm @@ -0,0 +1,8 @@ + + + + +listRender() ?> diff --git a/modules/backend/controllers/userroles/update.htm b/modules/backend/controllers/userroles/update.htm new file mode 100644 index 000000000..7f1b97736 --- /dev/null +++ b/modules/backend/controllers/userroles/update.htm @@ -0,0 +1,53 @@ + + + + +fatalError): ?> + + 'layout']) ?> + +
+ formRender() ?> +
+ +
+
+ + + +
+
+ + + + +

fatalError)) ?>

+

+ diff --git a/modules/backend/controllers/users/_list_toolbar.htm b/modules/backend/controllers/users/_list_toolbar.htm index 69ff34f5e..096c4d68c 100644 --- a/modules/backend/controllers/users/_list_toolbar.htm +++ b/modules/backend/controllers/users/_list_toolbar.htm @@ -2,6 +2,9 @@ + + + diff --git a/modules/backend/database/migrations/2013_10_01_000001_Db_Backend_Users.php b/modules/backend/database/migrations/2013_10_01_000001_Db_Backend_Users.php index 15069a180..caa90669e 100644 --- a/modules/backend/database/migrations/2013_10_01_000001_Db_Backend_Users.php +++ b/modules/backend/database/migrations/2013_10_01_000001_Db_Backend_Users.php @@ -20,6 +20,7 @@ class DbBackendUsers extends Migration $table->string('reset_password_code')->nullable()->index('reset_code_index'); $table->text('permissions')->nullable(); $table->boolean('is_activated')->default(0); + $table->integer('role_id')->unsigned()->nullable()->index('admin_role_index'); $table->timestamp('activated_at')->nullable(); $table->timestamp('last_login')->nullable(); $table->timestamps(); diff --git a/modules/backend/database/migrations/2013_10_01_000002_Db_Backend_User_Groups.php b/modules/backend/database/migrations/2013_10_01_000002_Db_Backend_User_Groups.php index f656ab296..599707c46 100644 --- a/modules/backend/database/migrations/2013_10_01_000002_Db_Backend_User_Groups.php +++ b/modules/backend/database/migrations/2013_10_01_000002_Db_Backend_User_Groups.php @@ -11,7 +11,6 @@ class DbBackendUserGroups extends Migration $table->engine = 'InnoDB'; $table->increments('id'); $table->string('name')->unique('name_unique'); - $table->text('permissions')->nullable(); $table->timestamps(); }); } diff --git a/modules/backend/database/migrations/2017_10_01_000010_Db_Backend_User_Roles.php b/modules/backend/database/migrations/2017_10_01_000010_Db_Backend_User_Roles.php new file mode 100644 index 000000000..4c49c3dd7 --- /dev/null +++ b/modules/backend/database/migrations/2017_10_01_000010_Db_Backend_User_Roles.php @@ -0,0 +1,143 @@ +engine = 'InnoDB'; + $table->increments('id'); + $table->string('name')->unique('role_unique'); + $table->string('code')->nullable()->index('role_code_index'); + $table->text('description')->nullable(); + $table->text('permissions')->nullable(); + $table->timestamps(); + }); + + // This detects older builds and performs a migration to include + // the new role system. This column will exist for new installs + // so this heavy migration process does not need to execute. + $this->migratePreviousBuild(); + } + + public function down() + { + Schema::dropIfExists('backend_user_roles'); + } + + protected function migratePreviousBuild() + { + // Role not found in the users table, perform a complete migration. + // Merging group permissions with the user and assigning the user + // with the first available role. + if (Schema::hasColumn('backend_users', 'role_id')) { + Schema::table('backend_users', function (Blueprint $table) { + $table->integer('role_id')->unsigned()->nullable()->index('admin_role_index'); + }); + + $this->migratePermissionsFromGroupsToRoles(); + } + + // Drop permissions column on groups table as it is no longer needed. + if (Schema::hasColumn('backend_user_groups', 'permissions')) { + Schema::table('backend_user_groups', function (Blueprint $table) { + $table->dropColumn('permissions'); + }); + } + } + + protected function migratePermissionsFromGroupsToRoles() + { + $groups = Db::table('backend_user_groups')->get(); + $roles = []; + $permissions = []; + + /* + * Carbon copy groups to roles + */ + foreach ($groups as $group) { + if (!isset($group->name) || !$group->name) { + continue; + } + + try { + $roles[$group->id] = Db::table('backend_user_roles')->insertGetId([ + 'name' => $group->name, + 'permissions' => $group->permissions ?? null + ]); + } + catch (Exception $ex) {} + + $permissions[$group->id] = $group->permissions ?? null; + } + + /* + * Assign a user with the first available role + */ + $found = []; + $joins = Db::table('backend_users_groups')->get(); + + foreach ($joins as $join) { + if (!$roleId = array_get($roles, $join->user_group_id)) { + continue; + } + + $userId = $join->user_id; + + if (!isset($found[$userId])) { + Db::table('backend_users')->where('id', $userId)->update([ + 'role_id' => $roleId + ]); + } + + $found[$userId][] = $join->user_group_id; + } + + /* + * Merge group permissions in to user + */ + foreach ($found as $userId => $groups) { + $userPerms = []; + + foreach ($groups as $groupId) { + if (!$permString = array_get($permissions, $groupId)) { + continue; + } + + try { + $perms = json_decode($permString, true); + $userPerms = array_merge($userPerms, $perms); + } + catch (Exception $ex) {} + } + + if (count($userPerms) > 0) { + $this->splicePermissionsForUser($userId, $userPerms); + } + } + } + + protected function splicePermissionsForUser($userId, $permissions) + { + /* + * Look up user and splice the provided permissions in + */ + $user = Db::table('backend_users')->where('id', $userId)->first(); + if (!$user) { + return; + } + + try { + $currentPerms = $user->permissions ? json_decode($user->permissions, true) : []; + $newPerms = array_merge($permissions, $currentPerms); + + Db::table('backend_users')->where('id', $userId)->update([ + 'permissions' => json_encode($newPerms) + ]); + } + catch (Exception $ex) {} + } +} diff --git a/modules/backend/lang/en/lang.php b/modules/backend/lang/en/lang.php index ff8036a8d..344e0f5a9 100644 --- a/modules/backend/lang/en/lang.php +++ b/modules/backend/lang/en/lang.php @@ -116,8 +116,10 @@ return [ 'last_name' => 'Last Name', 'full_name' => 'Full Name', 'email' => 'Email', + 'role_field' => 'Role', + 'role_comment' => 'Roles define user permissions, which can be overriden on the user level, on the Permissions tab.', 'groups' => 'Groups', - 'groups_comment' => 'Specify which groups the account should belong to. Groups define user permissions, which can be overriden on the user level, on the Permissions tab.', + 'groups_comment' => 'Specify which groups this account should belong to.', 'avatar' => 'Avatar', 'password' => 'Password', 'password_confirmation' => 'Confirm Password', @@ -138,8 +140,8 @@ return [ 'updated_at' => 'Updated at', 'group' => [ 'name' => 'Group', - 'name_comment' => 'The name is displayed in the group list on the Create/Edit Administrator form.', 'name_field' => 'Name', + 'name_comment' => 'The name is displayed in the group list on the Create/Edit Administrator form.', 'description_field' => 'Description', 'is_new_user_default_field_label' => 'Default group', 'is_new_user_default_field_comment' => 'Add new administrators to this group by default', @@ -152,6 +154,20 @@ return [ 'return' => 'Return to group list', 'users_count' => 'Users' ], + 'role' => [ + 'name' => 'Role', + 'name_field' => 'Name', + 'name_comment' => 'The name is displayed in the role list on the Create/Edit Administrator form.', + 'description_field' => 'Description', + 'code_field' => 'Code', + 'code_comment' => 'Enter a unique code if you want to access the role object with the API.', + 'menu_label' => 'Manage Roles', + 'list_title' => 'Manage Roles', + 'new' => 'New Role', + 'delete_confirm' => 'Delete this administrator role?', + 'return' => 'Return to role list', + 'users_count' => 'Users' + ], 'preferences' => [ 'not_authenticated' => 'There is no an authenticated user to load or save preferences for.' ] diff --git a/modules/backend/models/User.php b/modules/backend/models/User.php index d381fbc95..35dab3fc1 100644 --- a/modules/backend/models/User.php +++ b/modules/backend/models/User.php @@ -35,6 +35,10 @@ class User extends UserBase 'groups' => [UserGroup::class, 'table' => 'backend_users_groups'] ]; + public $belongsto = [ + 'role' => UserRole::class + ]; + public $attachOne = [ 'avatar' => \System\Models\File::class ]; @@ -144,9 +148,22 @@ class User extends UserBase public function getGroupsOptions() { $result = []; + foreach (UserGroup::all() as $group) { $result[$group->id] = [$group->name, $group->description]; } + + return $result; + } + + public function getRoleOptions() + { + $result = []; + + foreach (UserRole::all() as $role) { + $result[$role->id] = [$role->name, $role->description]; + } + return $result; } } diff --git a/modules/backend/models/UserRole.php b/modules/backend/models/UserRole.php new file mode 100644 index 000000000..d115ebf6e --- /dev/null +++ b/modules/backend/models/UserRole.php @@ -0,0 +1,37 @@ + 'required|between:2,128|unique:backend_user_roles', + ]; + + /** + * @var array Relations + */ + public $hasMany = [ + 'users' => [User::class, 'key' => 'role_id'], + 'users_count' => [User::class, 'key' => 'role_id', 'count' => true] + ]; +} diff --git a/modules/backend/models/user/fields.yaml b/modules/backend/models/user/fields.yaml index 5df53a671..caf687e85 100644 --- a/modules/backend/models/user/fields.yaml +++ b/modules/backend/models/user/fields.yaml @@ -46,11 +46,17 @@ tabs: span: right label: backend::lang.user.password_confirmation - groups: + role: context: [create, update] + label: backend::lang.user.role_field + commentAbove: backend::lang.user.role_comment + type: radio + + groups: label: backend::lang.user.groups commentAbove: backend::lang.user.groups_comment type: checkboxlist + tab: backend::lang.user.groups secondaryTabs: fields: diff --git a/modules/backend/models/usergroup/fields.yaml b/modules/backend/models/usergroup/fields.yaml index be3d0a483..9a4ab8625 100644 --- a/modules/backend/models/usergroup/fields.yaml +++ b/modules/backend/models/usergroup/fields.yaml @@ -22,6 +22,3 @@ fields: label: backend::lang.user.group.description_field type: textarea size: tiny - -tabs: - stretch: true diff --git a/modules/backend/models/userrole/columns.yaml b/modules/backend/models/userrole/columns.yaml new file mode 100644 index 000000000..392ddf6bd --- /dev/null +++ b/modules/backend/models/userrole/columns.yaml @@ -0,0 +1,19 @@ +# =================================== +# Column Definitions +# =================================== + +columns: + name: + label: backend::lang.user.role.name_field + searchable: yes + + description: + label: backend::lang.user.role.description_field + searchable: yes + + users_count: + label: backend::lang.user.role.users_count + relation: users_count + valueFrom: count + default: 0 + sortable: false diff --git a/modules/backend/models/userrole/fields.yaml b/modules/backend/models/userrole/fields.yaml new file mode 100644 index 000000000..f0d7ccfd7 --- /dev/null +++ b/modules/backend/models/userrole/fields.yaml @@ -0,0 +1,22 @@ +# =================================== +# Field Definitions +# =================================== + +fields: + name: + label: backend::lang.user.role.name_field + commentAbove: backend::lang.user.role.name_comment + span: auto + + code: + label: backend::lang.user.role.code_field + commentAbove: backend::lang.user.role.code_comment + span: auto + + description: + label: backend::lang.user.role.description_field + type: textarea + size: tiny + +tabs: + stretch: true