diff --git a/config/initializers/permissions.rb b/config/initializers/permissions.rb index e1a2f82b1ebc..8d369cee59bd 100644 --- a/config/initializers/permissions.rb +++ b/config/initializers/permissions.rb @@ -133,6 +133,18 @@ permissible_on: :project, require: :member + map.permission :view_project_stages_and_gates, + {}, + permissible_on: :project, + dependencies: :view_project + + map.permission :edit_project_stages_and_gates, + {}, + permissible_on: :project, + require: :member, + dependencies: :view_project_stages_and_gates, + contract_actions: { projects: %i[update] } + map.permission :select_project_life_cycle, { "projects/settings/life_cycle_steps": %i[index toggle enable_all disable_all] diff --git a/config/locales/en.yml b/config/locales/en.yml index 711b60a0e861..8a6d759c0e66 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -3220,6 +3220,7 @@ en: permission_edit_own_time_entries: "Edit own time logs" permission_edit_project: "Edit project" permission_edit_project_attributes: "Edit project attributes" + permission_edit_project_stages_and_gates: "Edit project stages and gates" permission_edit_reportings: "Edit reportings" permission_edit_time_entries: "Edit time logs for other users" permission_edit_timelines: "Edit timelines" @@ -3275,6 +3276,7 @@ en: permission_work_package_assigned_explanation: "Work packages can be assigned to users and groups in possession of this role in the respective project" permission_view_project_activity: "View project activity" permission_view_project_attributes: "View project attributes" + permission_view_project_stages_and_gates: "View project stages and gates" permission_save_bcf_queries: "Save BCF queries" permission_manage_public_bcf_queries: "Manage public BCF queries" permission_edit_attribute_help_texts: "Edit attribute help texts" diff --git a/db/migrate/20241126111225_add_project_life_cycle_step_roles.rb b/db/migrate/20241126111225_add_project_life_cycle_step_roles.rb new file mode 100644 index 000000000000..e618a7696ae7 --- /dev/null +++ b/db/migrate/20241126111225_add_project_life_cycle_step_roles.rb @@ -0,0 +1,39 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +require Rails.root.join("db/migrate/migration_utils/permission_adder") + +class AddProjectLifeCycleStepRoles < ActiveRecord::Migration[7.1] + def change + ::Migration::MigrationUtils::PermissionAdder + .add(:view_project, :view_project_stages_and_gates) + + ::Migration::MigrationUtils::PermissionAdder + .add(:edit_project, :edit_project_stages_and_gates) + end +end diff --git a/spec/migrations/add_project_life_cycle_step_roles_spec.rb b/spec/migrations/add_project_life_cycle_step_roles_spec.rb new file mode 100644 index 000000000000..d7317dd8ac1e --- /dev/null +++ b/spec/migrations/add_project_life_cycle_step_roles_spec.rb @@ -0,0 +1,135 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +require "spec_helper" +require Rails.root.join("db/migrate/20241126111225_add_project_life_cycle_step_roles.rb") + +RSpec.describe AddProjectLifeCycleStepRoles, type: :model do + # Silencing migration logs, since we are not interested in that during testing + subject { ActiveRecord::Migration.suppress_messages { described_class.new.change } } + + shared_examples_for "not changing permissions" do + it "is not changed" do + expect { subject }.not_to change { role.reload.permissions } + end + + it "does not adds any new permissions" do + expect { subject }.not_to change(RolePermission, :count) + end + end + + shared_examples_for "migration is idempotent" do + context "when the migration is ran twice" do + before { subject } + + it_behaves_like "not changing permissions" + end + end + + shared_examples_for "adding permissions" do |new_permissions| + it "adds the #{new_permissions} permissions for the role" do + public_permissions = OpenProject::AccessControl.public_permissions.map(&:name) + expect { subject }.to change { role.reload.permissions } + .from(match_array(permissions + public_permissions)) + .to match_array(permissions + public_permissions + new_permissions) + end + + it "adds #{new_permissions.size} new permissions" do + expect { subject }.to change(RolePermission, :count).by(new_permissions.size) + end + end + + context "for a role not eligible to view_project_stages_and_gates" do + let!(:role) do + create(:project_role, + add_public_permissions: false, + permissions: %i[permission1 permission2]) + end + + it_behaves_like "not changing permissions" + it_behaves_like "migration is idempotent" + end + + context "for a role eligible to view_project_stages_and_gates" do + let(:permissions) { %i[view_project permission1 permission2] } + let!(:role) { create(:project_role, permissions:) } + + it_behaves_like "adding permissions", %i[view_project_stages_and_gates] + it_behaves_like "migration is idempotent" + end + + context "for a role with view_project_stages_and_gates" do + let(:permissions) { %i[view_project_stages_and_gates view_project permission1 permission2] } + let!(:role) { create(:project_role, permissions:) } + + it_behaves_like "not changing permissions" + it_behaves_like "migration is idempotent" + end + + context "for a role not eligible to edit_project_stages_and_gates" do + let(:permissions) do + %i[view_project_stages_and_gates permission1 permission2] + end + let!(:role) { create(:project_role, permissions:) } + + it_behaves_like "not changing permissions" + it_behaves_like "migration is idempotent" + end + + context "for a role eligible to edit_project_stages_and_gates having view_project_stages_and_gates" do + let(:permissions) do + %i[view_project_stages_and_gates edit_project + view_project permission1 permission2] + end + let!(:role) { create(:project_role, permissions:) } + + it_behaves_like "adding permissions", %i[edit_project_stages_and_gates] + it_behaves_like "migration is idempotent" + end + + context "for a role eligible to edit_project_stages_and_gates not having view_project_stages_and_gates" do + let(:permissions) do + %i[edit_project view_project permission1 permission2] + end + let!(:role) { create(:project_role, permissions:) } + + it_behaves_like "adding permissions", %i[edit_project_stages_and_gates view_project_stages_and_gates] + it_behaves_like "migration is idempotent" + end + + context "for a role that already has the edit_project_stages_and_gates and view_project_stages_and_gates permission" do + let(:permissions) do + %i[edit_project_stages_and_gates view_project_stages_and_gates + edit_project view_project permission1 permission2] + end + let!(:role) { create(:project_role, permissions:) } + + it_behaves_like "not changing permissions" + it_behaves_like "migration is idempotent" + end +end