diff --git a/app/contracts/work_packages/base_contract.rb b/app/contracts/work_packages/base_contract.rb index 3545a1f6890..e1e3897dd98 100644 --- a/app/contracts/work_packages/base_contract.rb +++ b/app/contracts/work_packages/base_contract.rb @@ -34,6 +34,7 @@ module WorkPackages attribute :subject attribute :description attribute :status_id, + permission: :change_work_package_status, writable: ->(*) { # If we did not change into the status, # mark unwritable if status and version is closed diff --git a/app/contracts/work_packages/update_contract.rb b/app/contracts/work_packages/update_contract.rb index ee27bc04fde..1fdb59eeb62 100644 --- a/app/contracts/work_packages/update_contract.rb +++ b/app/contracts/work_packages/update_contract.rb @@ -52,6 +52,7 @@ module WorkPackages with_unchanged_project_id do next if @can.allowed?(model, :edit) || @can.allowed?(model, :assign_version) || + @can.allowed?(model, :change_status) || @can.allowed?(model, :manage_subtasks) || @can.allowed?(model, :move) next if allowed_journal_addition? diff --git a/app/policies/work_package_policy.rb b/app/policies/work_package_policy.rb index b7eccc59b6e..a5fbc70bd1b 100644 --- a/app/policies/work_package_policy.rb +++ b/app/policies/work_package_policy.rb @@ -51,6 +51,7 @@ class WorkPackagePolicy < BasePolicy delete: delete_allowed?(work_package), manage_subtasks: manage_subtasks_allowed?(work_package), comment: comment_allowed?(work_package), + change_status: change_status_allowed?(work_package), assign_version: assign_version_allowed?(work_package) } end @@ -124,4 +125,12 @@ class WorkPackagePolicy < BasePolicy @assign_version_cache[work_package.project] end + + def change_status_allowed?(work_package) + @change_status_cache ||= Hash.new do |hash, project| + hash[project] = user.allowed_in_project?(:change_work_package_status, work_package.project) + end + + @change_status_cache[work_package.project] + end end diff --git a/app/seeders/common.yml b/app/seeders/common.yml index f9b46fe7a96..7d5043cb59a 100644 --- a/app/seeders/common.yml +++ b/app/seeders/common.yml @@ -247,6 +247,7 @@ work_package_roles: permissions: - :view_work_packages - :edit_work_packages + - :change_work_package_status - :work_package_assigned - :add_work_package_notes - :edit_own_work_package_notes @@ -300,6 +301,7 @@ project_roles: - :add_work_packages - :move_work_packages - :edit_work_packages + - :change_work_package_status - :assign_versions - :work_package_assigned - :add_work_package_notes diff --git a/config/initializers/permissions.rb b/config/initializers/permissions.rb index d646da8b5c6..54e3d4e36c5 100644 --- a/config/initializers/permissions.rb +++ b/config/initializers/permissions.rb @@ -307,6 +307,11 @@ Rails.application.reloader.to_prepare do permissible_on: :project, dependencies: :view_work_packages + wpt.permission :change_work_package_status, + {}, + permissible_on: :project, + dependencies: :view_work_packages + # A user having the following permission can become assignee and/or responsible of a work package. # This is a passive permission in the sense that a user having the permission isn't eligible to perform # actions but rather to have actions taken together with him/her. diff --git a/config/locales/en.yml b/config/locales/en.yml index 696a91a61f3..e92785aeea3 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2493,6 +2493,8 @@ en: permission_assign_versions: "Assign versions" permission_browse_repository: "Read-only access to repository (browse and checkout)" permission_change_wiki_parent_page: "Change parent wiki page" + permission_change_work_package_status: "Change work package status" + permission_change_work_package_status_explanation: "Allows changing the status of work packages. Note that Edit work packages alone does not allow the status change." permission_comment_news: "Comment news" permission_commit_access: "Read/write access to repository (commit)" permission_copy_projects: "Copy projects" diff --git a/db/migrate/20231106083056_set_change_work_package_status_permission.rb b/db/migrate/20231106083056_set_change_work_package_status_permission.rb new file mode 100644 index 00000000000..be0b2a12753 --- /dev/null +++ b/db/migrate/20231106083056_set_change_work_package_status_permission.rb @@ -0,0 +1,41 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2023 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}/db/migrate/migration_utils/permission_adder" + +class SetChangeWorkPackageStatusPermission < ActiveRecord::Migration[7.0] + def up + ::Migration::MigrationUtils::PermissionAdder + .add(:edit_work_packages, + :change_work_package_status) + end + + def down + # nothing to do + end +end diff --git a/docs/api/apiv3/paths/work_package.yml b/docs/api/apiv3/paths/work_package.yml index 59f163dfddc..84c8758f5a7 100644 --- a/docs/api/apiv3/paths/work_package.yml +++ b/docs/api/apiv3/paths/work_package.yml @@ -169,7 +169,7 @@ patch: description: |- Returned if the client does not have sufficient permissions. - **Required permission:** edit work package, assign version, manage subtasks or move work package + **Required permission:** edit work package, assign version, change work package status, manage subtasks or move work package '404': content: application/hal+json: diff --git a/docs/api/apiv3/paths/work_package_form.yml b/docs/api/apiv3/paths/work_package_form.yml index 5b7ee305bd3..c63fadd615d 100644 --- a/docs/api/apiv3/paths/work_package_form.yml +++ b/docs/api/apiv3/paths/work_package_form.yml @@ -42,7 +42,7 @@ post: description: |- Returned if the client does not have sufficient permissions. - **Required permission:** edit work package, assign version, manage subtasks or move work package + **Required permission:** edit work package, assign version, change work package status, manage subtasks or move work package *Note that you will only receive this error, if you are at least allowed to see the corresponding work package.* headers: {} diff --git a/lib/api/v3/work_packages/work_package_representer.rb b/lib/api/v3/work_packages/work_package_representer.rb index 492d2103f20..f0acabde67f 100644 --- a/lib/api/v3/work_packages/work_package_representer.rb +++ b/lib/api/v3/work_packages/work_package_representer.rb @@ -566,6 +566,7 @@ module API def current_user_update_allowed? @current_user_update_allowed ||= current_user.allowed_in_project?(:edit_work_packages, represented.project) || + current_user.allowed_in_project?(:change_work_package_status, represented.project) || current_user.allowed_in_project?(:assign_versions, represented.project) end diff --git a/modules/backlogs/spec/features/impediments_spec.rb b/modules/backlogs/spec/features/impediments_spec.rb index a23c78f4ab8..0cb8cd168fd 100644 --- a/modules/backlogs/spec/features/impediments_spec.rb +++ b/modules/backlogs/spec/features/impediments_spec.rb @@ -59,6 +59,7 @@ RSpec.describe 'Impediments on taskboard', add_work_packages view_work_packages edit_work_packages + change_work_package_status manage_subtasks assign_versions work_package_assigned)) diff --git a/modules/backlogs/spec/features/stories_in_backlog_spec.rb b/modules/backlogs/spec/features/stories_in_backlog_spec.rb index adf4eb1258d..d0cc292bf92 100644 --- a/modules/backlogs/spec/features/stories_in_backlog_spec.rb +++ b/modules/backlogs/spec/features/stories_in_backlog_spec.rb @@ -56,6 +56,7 @@ RSpec.describe 'Stories in backlog', add_work_packages view_work_packages edit_work_packages + change_work_package_status manage_subtasks assign_versions)) end diff --git a/modules/backlogs/spec/features/tasks_on_taskboard_spec.rb b/modules/backlogs/spec/features/tasks_on_taskboard_spec.rb index 1215c6741af..96f25083c8e 100644 --- a/modules/backlogs/spec/features/tasks_on_taskboard_spec.rb +++ b/modules/backlogs/spec/features/tasks_on_taskboard_spec.rb @@ -56,6 +56,7 @@ RSpec.describe 'Tasks on taskboard', add_work_packages view_work_packages edit_work_packages + change_work_package_status manage_subtasks assign_versions work_package_assigned)) diff --git a/modules/backlogs/spec/services/impediments/update_service_spec.rb b/modules/backlogs/spec/services/impediments/update_service_spec.rb index 5faacddbbd7..62563395129 100644 --- a/modules/backlogs/spec/services/impediments/update_service_spec.rb +++ b/modules/backlogs/spec/services/impediments/update_service_spec.rb @@ -32,7 +32,7 @@ RSpec.describe Impediments::UpdateService, type: :model do let(:instance) { described_class.new(user:, impediment:) } let(:user) { create(:user) } - let(:role) { create(:project_role, permissions: %i(edit_work_packages view_work_packages)) } + let(:role) { create(:project_role, permissions: %i(edit_work_packages change_work_package_status view_work_packages)) } let(:type_feature) { create(:type_feature) } let(:type_task) { create(:type_task) } let(:priority) { impediment.priority } diff --git a/modules/boards/spec/features/action_boards/assignee_board_spec.rb b/modules/boards/spec/features/action_boards/assignee_board_spec.rb index e6a5bd7a672..c962abce8aa 100644 --- a/modules/boards/spec/features/action_boards/assignee_board_spec.rb +++ b/modules/boards/spec/features/action_boards/assignee_board_spec.rb @@ -50,7 +50,7 @@ RSpec.describe 'Assignee action board', let(:permissions) do %i[show_board_views manage_board_views add_work_packages - edit_work_packages view_work_packages manage_public_queries work_package_assigned] + edit_work_packages change_work_package_status view_work_packages manage_public_queries work_package_assigned] end let!(:priority) { create(:default_priority) } diff --git a/modules/boards/spec/features/action_boards/custom_field_filters_spec.rb b/modules/boards/spec/features/action_boards/custom_field_filters_spec.rb index 559cbdd71d8..7214f13a962 100644 --- a/modules/boards/spec/features/action_boards/custom_field_filters_spec.rb +++ b/modules/boards/spec/features/action_boards/custom_field_filters_spec.rb @@ -43,7 +43,7 @@ RSpec.describe 'Custom field filter in boards', js: true, with_ee: %i[board_view let(:permissions) do %i[show_board_views manage_board_views add_work_packages - edit_work_packages view_work_packages manage_public_queries] + edit_work_packages change_work_package_status view_work_packages manage_public_queries] end let!(:priority) { create(:default_priority) } diff --git a/modules/boards/spec/features/action_boards/status_board_spec.rb b/modules/boards/spec/features/action_boards/status_board_spec.rb index 2c889908541..7c26ce69bd0 100644 --- a/modules/boards/spec/features/action_boards/status_board_spec.rb +++ b/modules/boards/spec/features/action_boards/status_board_spec.rb @@ -43,7 +43,7 @@ RSpec.describe 'Status action board', js: true, with_ee: %i[board_view] do let(:permissions) do %i[show_board_views manage_board_views add_work_packages - edit_work_packages move_work_packages view_work_packages manage_public_queries] + edit_work_packages change_work_package_status move_work_packages view_work_packages manage_public_queries] end let!(:priority) { create(:default_priority) } diff --git a/modules/boards/spec/features/action_boards/status_type_moving_board_spec.rb b/modules/boards/spec/features/action_boards/status_type_moving_board_spec.rb index 07770476665..0eb77a7678e 100644 --- a/modules/boards/spec/features/action_boards/status_type_moving_board_spec.rb +++ b/modules/boards/spec/features/action_boards/status_type_moving_board_spec.rb @@ -37,7 +37,7 @@ RSpec.describe 'Status action board', js: true, with_ee: %i[board_view] do end let(:permissions) do %i[show_board_views manage_board_views add_work_packages - edit_work_packages view_work_packages manage_public_queries] + edit_work_packages change_work_package_status view_work_packages manage_public_queries] end let(:role) { create(:project_role, permissions:) } diff --git a/modules/boards/spec/features/action_boards/subproject_board_spec.rb b/modules/boards/spec/features/action_boards/subproject_board_spec.rb index 5dd95bbd36b..d61e40d8190 100644 --- a/modules/boards/spec/features/action_boards/subproject_board_spec.rb +++ b/modules/boards/spec/features/action_boards/subproject_board_spec.rb @@ -51,7 +51,7 @@ RSpec.describe 'Subproject action board', :js, with_ee: %i[board_view] do let(:permissions) do %i[show_board_views manage_board_views add_work_packages - edit_work_packages view_work_packages manage_public_queries move_work_packages] + edit_work_packages change_work_package_status view_work_packages manage_public_queries move_work_packages] end let!(:priority) { create(:default_priority) } diff --git a/modules/boards/spec/features/action_boards/subtasks_board_spec.rb b/modules/boards/spec/features/action_boards/subtasks_board_spec.rb index 73da9c98873..4664bd37e1b 100644 --- a/modules/boards/spec/features/action_boards/subtasks_board_spec.rb +++ b/modules/boards/spec/features/action_boards/subtasks_board_spec.rb @@ -78,7 +78,7 @@ RSpec.describe 'Subtasks action board', js: true, with_ee: %i[board_view] do let(:permissions) do %i[show_board_views manage_board_views add_work_packages - edit_work_packages view_work_packages manage_public_queries manage_subtasks] + edit_work_packages change_work_package_status view_work_packages manage_public_queries manage_subtasks] end it 'allows management of subtasks work packages' do diff --git a/modules/boards/spec/features/action_boards/version_board_spec.rb b/modules/boards/spec/features/action_boards/version_board_spec.rb index 3224f399778..efcd191558d 100644 --- a/modules/boards/spec/features/action_boards/version_board_spec.rb +++ b/modules/boards/spec/features/action_boards/version_board_spec.rb @@ -50,7 +50,7 @@ RSpec.describe 'Version action board', :js, with_ee: %i[board_view] do let(:board_index) { Pages::BoardIndex.new(project) } let(:permissions) do %i[show_board_views manage_board_views add_work_packages manage_versions - edit_work_packages view_work_packages manage_public_queries assign_versions] + edit_work_packages view_work_packages manage_public_queries change_work_package_status assign_versions] end let(:permissions_board_manager) do %i[show_board_views manage_board_views view_work_packages manage_public_queries] diff --git a/modules/boards/spec/features/board_management_spec.rb b/modules/boards/spec/features/board_management_spec.rb index 73c01bcacae..6183e92137d 100644 --- a/modules/boards/spec/features/board_management_spec.rb +++ b/modules/boards/spec/features/board_management_spec.rb @@ -58,6 +58,7 @@ RSpec.describe 'Board management spec', js: true, with_ee: %i[board_view] do add_work_packages view_work_packages edit_work_packages + change_work_package_status manage_public_queries ] end @@ -236,8 +237,8 @@ RSpec.describe 'Board management spec', js: true, with_ee: %i[board_view] do end end - context 'with view boards + edit work package permission' do - let(:permissions) { %i[show_board_views view_work_packages add_work_packages edit_work_packages] } + context 'with view boards + edit work package and change work package status permissions' do + let(:permissions) { %i[show_board_views view_work_packages add_work_packages edit_work_packages change_work_package_status] } let(:board_view) { create(:board_grid_with_queries, project:) } it 'allows viewing boards index and moving items around' do diff --git a/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_spec.rb b/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_spec.rb index a10e1324c88..7c1e4d296ca 100644 --- a/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_spec.rb +++ b/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_spec.rb @@ -844,20 +844,38 @@ RSpec.describe API::V3::WorkPackages::Schema::WorkPackageSchemaRepresenter do end describe 'status' do - it_behaves_like 'has basic schema properties' do - let(:path) { 'status' } - let(:type) { 'Status' } - let(:name) { I18n.t('attributes.status') } - let(:required) { true } - let(:writable) { true } - let(:has_default) { true } - let(:location) { '_links' } + context 'if having the assign_versions permission' do + let(:permissions) { [:change_work_package_status] } + + it_behaves_like 'has basic schema properties' do + let(:path) { 'status' } + let(:type) { 'Status' } + let(:name) { I18n.t('attributes.status') } + let(:required) { true } + let(:writable) { true } + let(:has_default) { true } + let(:location) { '_links' } + end + + it_behaves_like 'has a collection of allowed values' do + let(:json_path) { 'status' } + let(:href_path) { 'statuses' } + let(:factory) { :status } + end end - it_behaves_like 'has a collection of allowed values' do - let(:json_path) { 'status' } - let(:href_path) { 'statuses' } - let(:factory) { :status } + context 'if having the edit_work_packages permission' do + let(:permissions) { [:edit_work_packages] } + + it_behaves_like 'has basic schema properties' do + let(:path) { 'status' } + let(:type) { 'Status' } + let(:name) { I18n.t('attributes.status') } + let(:required) { true } + let(:writable) { false } + let(:has_default) { true } + let(:location) { '_links' } + end end end diff --git a/spec/lib/api/v3/work_packages/work_package_representer_spec.rb b/spec/lib/api/v3/work_packages/work_package_representer_spec.rb index d5918220763..614bb621dac 100644 --- a/spec/lib/api/v3/work_packages/work_package_representer_spec.rb +++ b/spec/lib/api/v3/work_packages/work_package_representer_spec.rb @@ -466,7 +466,7 @@ RSpec.describe API::V3::WorkPackages::WorkPackageRepresenter do end end - context 'when user is lacks edit permission but has assign_versions' do + context 'when user lacks edit permission but has assign_versions' do let(:permissions) { all_permissions - [:edit_work_packages] + [:assign_versions] } it_behaves_like 'has an untitled link' do @@ -479,6 +479,20 @@ RSpec.describe API::V3::WorkPackages::WorkPackageRepresenter do let(:href) { api_v3_paths.work_package(work_package.id) } end end + + context 'when user lacks edit permission but has change_work_package_status' do + let(:permissions) { all_permissions - [:edit_work_packages] + [:change_work_package_status] } + + it_behaves_like 'has an untitled link' do + let(:link) { 'update' } + let(:href) { api_v3_paths.work_package_form(work_package.id) } + end + + it_behaves_like 'has an untitled link' do + let(:link) { 'updateImmediately' } + let(:href) { api_v3_paths.work_package(work_package.id) } + end + end end describe 'status' do