diff --git a/core/app/models/spree/permission_set.rb b/core/app/models/spree/permission_set.rb new file mode 100644 index 00000000000..07a28c19cc7 --- /dev/null +++ b/core/app/models/spree/permission_set.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Spree + class PermissionSet < Spree::Base + has_many :role_permissions + has_many :roles, through: :role_permissions + + validates :name, :group, presence: true + end +end diff --git a/core/app/models/spree/role.rb b/core/app/models/spree/role.rb index 3e2d7d1a234..d5371c935b0 100644 --- a/core/app/models/spree/role.rb +++ b/core/app/models/spree/role.rb @@ -2,13 +2,29 @@ module Spree class Role < Spree::Base + RESERVED_ROLES = ['admin', 'default'].freeze + has_many :role_users, class_name: "Spree::RoleUser", dependent: :destroy + has_many :role_permissions, dependent: :destroy + has_many :permission_sets, through: :role_permissions has_many :users, through: :role_users + scope :non_base_roles, -> { where.not(name: RESERVED_ROLES) } + validates_uniqueness_of :name, case_sensitive: true + validates :name, uniqueness: true + after_save :assign_permissions def admin? name == "admin" end + + def permission_sets_constantized + permission_sets.map(&:name).map(&:constantize) + end + + def assign_permissions + ::Spree::Config.roles.assign_permissions name, permission_sets_constantized + end end end diff --git a/core/app/models/spree/role_permission.rb b/core/app/models/spree/role_permission.rb new file mode 100644 index 00000000000..a194df5b2f0 --- /dev/null +++ b/core/app/models/spree/role_permission.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module Spree + class RolePermission < Spree::Base + belongs_to :role + belongs_to :permission_set + end +end diff --git a/core/db/migrate/20230607100048_create_spree_permission_sets.rb b/core/db/migrate/20230607100048_create_spree_permission_sets.rb new file mode 100644 index 00000000000..360275630cc --- /dev/null +++ b/core/db/migrate/20230607100048_create_spree_permission_sets.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class CreateSpreePermissionSets < ActiveRecord::Migration[7.0] + def change + create_table :spree_permission_sets do |t| + t.string :name + t.string :group + t.timestamps + end + end +end diff --git a/core/db/migrate/20230607100109_create_spree_roles_permissions.rb b/core/db/migrate/20230607100109_create_spree_roles_permissions.rb new file mode 100644 index 00000000000..e2f0e08da0f --- /dev/null +++ b/core/db/migrate/20230607100109_create_spree_roles_permissions.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class CreateSpreeRolesPermissions < ActiveRecord::Migration[7.0] + def change + create_table :spree_role_permissions do |t| + t.references :role + t.references :permission_set + t.timestamps + end + end +end diff --git a/core/lib/spree/app_configuration.rb b/core/lib/spree/app_configuration.rb index 49baffcc554..ee1c7a9522a 100644 --- a/core/lib/spree/app_configuration.rb +++ b/core/lib/spree/app_configuration.rb @@ -605,6 +605,10 @@ def roles @roles ||= Spree::RoleConfiguration.new.tap do |roles| roles.assign_permissions :default, ['Spree::PermissionSets::DefaultCustomer'] roles.assign_permissions :admin, ['Spree::PermissionSets::SuperUser'] + + Spree::Role.non_base_roles.each do |role| + roles.assign_permissions role.name, role.permission_sets_constantized + end end end diff --git a/core/lib/tasks/solidus/import_existing_permission_sets.rake b/core/lib/tasks/solidus/import_existing_permission_sets.rake new file mode 100644 index 00000000000..675aad0b78d --- /dev/null +++ b/core/lib/tasks/solidus/import_existing_permission_sets.rake @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +namespace :solidus do + desc "Import existing permission sets to role permissions table" + task import_existing_permission_sets: :environment do + Zeitwerk::Loader.eager_load_all unless Rails.env.test? + + ActiveRecord::Base.transaction do + Spree::PermissionSets::Base.descendants.each do |permission| + Spree::PermissionSet.find_or_create_by(name: permission.to_s, group: permission.to_s.split("PermissionSets::").last.gsub(/Display|Management/i, "")) + end + + Spree::AppConfiguration.new.roles.roles.each do |role_name, role_config| + role_config.permission_sets.each do |set| + role = Spree::Role.find_or_create_by(name: role_name) + permission_set = Spree::PermissionSet.find_by(name: set.name) + + if permission_set + Spree::RolePermission.find_or_create_by!( + role: role, + permission_set: permission_set + ) + else + puts "#{set} was not found." + end + end + end + end + end +end diff --git a/core/spec/lib/tasks/solidus/import_existing_permission_sets_spec.rb b/core/spec/lib/tasks/solidus/import_existing_permission_sets_spec.rb new file mode 100644 index 00000000000..0baf1b888fd --- /dev/null +++ b/core/spec/lib/tasks/solidus/import_existing_permission_sets_spec.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require 'rails_helper' + +path = Spree::Core::Engine.root.join('lib/tasks/solidus/import_existing_permission_sets.rake') + +RSpec.describe 'solidus' do + describe 'import_existing_permission_sets' do + include_context( + 'rake', + task_path: path, + task_name: 'solidus:import_existing_permission_sets' + ) + + it 'creates permission sets' do + expect(Spree::PermissionSet.pluck(:name)).to eq([]) + + task.invoke + + expect(Spree::PermissionSet.pluck(:name)).to eq(Spree::PermissionSets::Base.subclasses.map(&:to_s)) + end + + context 'when there is a custom role' do + let(:role_name) { :customer_service } + let(:permissions) { ['Spree::PermissionSets::OrderDisplay', 'Spree::PermissionSets::UserDisplay', 'Spree::PermissionSets::ProductDisplay'] } + + before do + roles = Spree::RoleConfiguration.new.tap do |role| + role.assign_permissions :default, ['Spree::PermissionSets::DefaultCustomer'] + role.assign_permissions :admin, ['Spree::PermissionSets::SuperUser'] + role.assign_permissions role_name, permissions + end + + allow_any_instance_of(Spree::AppConfiguration).to receive(:roles).and_return(roles) + end + + it 'creates the new role with permissions' do + expect(Spree::Role.find_by(name: role_name.to_s)).not_to be_present + + task.invoke + + role = Spree::Role.find_by(name: role_name.to_s) + expect(role).to be_present + expect(role.permission_sets.pluck(:name)).to match_array(permissions) + end + end + + context 'when permission set is not found' do + it 'prints out the missing permission set' do + allow(Spree::PermissionSet).to receive(:find_by).and_return(nil) + + expect { task.invoke }.to output(a_string_including('Spree::PermissionSets::DefaultCustomer')).to_stdout + end + end + end +end diff --git a/core/spec/models/spree/role_spec.rb b/core/spec/models/spree/role_spec.rb new file mode 100644 index 00000000000..4433600bf9d --- /dev/null +++ b/core/spec/models/spree/role_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Spree::Role, type: :model do + describe '.non_base_roles' do + subject do + Spree::Role.non_base_roles + end + + context 'when there is a custom role' do + let(:role) { create(:role, name: 'custom role') } + let(:admin_role) { create(:admin_role) } + let(:default_role) { create(:role, name: 'default') } + + it { is_expected.to include(role) } + it { is_expected.not_to include(admin_role, default_role) } + end + + context 'when there is no custom roles' do + it { is_expected.to be_empty } + end + end +end