diff --git a/app/graphql/mutations/ci/job/cancel.rb b/app/graphql/mutations/ci/job/cancel.rb index dc9f4d1977930ca927de2c23f1bb893fb04f5207..44a7772019d1b3534cf6a8f2898bdf999e339bf0 100644 --- a/app/graphql/mutations/ci/job/cancel.rb +++ b/app/graphql/mutations/ci/job/cancel.rb @@ -11,7 +11,7 @@ class Cancel < Base null: true, description: 'Job after the mutation.' - authorize :update_build + authorize :cancel_build def resolve(id:) job = authorized_find!(id: id) diff --git a/app/graphql/types/permission_types/ci/job.rb b/app/graphql/types/permission_types/ci/job.rb index c9a85317e67c7f9ff2b7c6a28566c7eef79ae9d0..35904fb1fc332ebe5fd81f376580b56dc4abecf9 100644 --- a/app/graphql/types/permission_types/ci/job.rb +++ b/app/graphql/types/permission_types/ci/job.rb @@ -8,6 +8,7 @@ class Job < BasePermissionType abilities :read_job_artifacts, :read_build ability_field :update_build, calls_gitaly: true + ability_field :cancel_build, calls_gitaly: true end end end diff --git a/app/policies/ci/build_policy.rb b/app/policies/ci/build_policy.rb index bce7ceafe1749981e980bdc223b7260156483a36..71ea42e1f235cf18f3250b4c49ac54ce11559938 100644 --- a/app/policies/ci/build_policy.rb +++ b/app/policies/ci/build_policy.rb @@ -81,6 +81,7 @@ class BuildPolicy < CommitStatusPolicy end rule { ~can?(:jailbreak) & (archived | protected_ref) }.policy do + prevent :cancel_build prevent :update_build prevent :erase_build end @@ -88,6 +89,7 @@ class BuildPolicy < CommitStatusPolicy rule { can?(:admin_build) | (can?(:update_build) & owner_of_job & unprotected_ref) }.enable :erase_build rule { can?(:public_access) & branch_allows_collaboration }.policy do + enable :cancel_build enable :update_build enable :update_commit_status end diff --git a/app/policies/ci/deployable_policy.rb b/app/policies/ci/deployable_policy.rb index f0105b001f216d9a2da92dfa3233bac51e86d6a6..e83bdd5361a6bb23093448ee5360b2dbbc1c0954 100644 --- a/app/policies/ci/deployable_policy.rb +++ b/app/policies/ci/deployable_policy.rb @@ -11,7 +11,10 @@ module DeployablePolicy @subject.outdated_deployment? end - rule { outdated_deployment }.prevent :update_build + rule { outdated_deployment }.policy do + prevent :cancel_build + prevent :update_build + end end end end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 74d2272b670e581aaa73c56326d824d091450b51..e97e927dab32fcbe51ca9c8f0f49a3c3195bbac5 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -468,6 +468,7 @@ class ProjectPolicy < BasePolicy enable :update_commit_status enable :create_build enable :update_build + enable :cancel_build enable :read_resource_group enable :update_resource_group enable :create_merge_request_from @@ -640,6 +641,7 @@ class ProjectPolicy < BasePolicy rule { builds_disabled | repository_disabled }.policy do prevent(*create_read_update_admin_destroy(:build)) + prevent :cancel_build prevent(*create_read_update_admin_destroy(:pipeline_schedule)) prevent(*create_read_update_admin_destroy(:environment)) prevent(*create_read_update_admin_destroy(:deployment)) diff --git a/app/services/ci/build_cancel_service.rb b/app/services/ci/build_cancel_service.rb index a23418ed7381bdebaa25d7196730c2c467653784..834d4febd10089b6ebb3e44c1a629865d2be3a7b 100644 --- a/app/services/ci/build_cancel_service.rb +++ b/app/services/ci/build_cancel_service.rb @@ -21,7 +21,7 @@ def execute attr_reader :build, :user def allowed? - user.can?(:update_build, build) + user.can?(:cancel_build, build) end def forbidden diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index a622895b4de1586ef77fc6651787f958c61f7078..6ec9b4a233de340aeff1fede9bcf735ade07364f 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -105,7 +105,7 @@ - if can?(current_user, :read_job_artifacts, job) && job.artifacts? = link_button_to nil, download_project_job_artifacts_path(job.project, job), rel: 'nofollow', download: '', title: _('Download artifacts'), icon: 'download' - if can?(current_user, :update_build, job) - - if job.active? + - if job.active? && can?(current_user, :cancel_build, job) = link_button_to nil, cancel_project_job_path(job.project, job, continue: { to: request.fullpath }), method: :post, title: _('Cancel'), icon: 'cancel' - elsif job.scheduled? = render Pajamas::ButtonComponent.new(disabled: true, icon: 'planning') do diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index f73865cc5023476a7fe2fceb9e3e49317d162ae9..a1901ce927964e3141722cfa97bbbbb8ba48732e 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -19952,6 +19952,7 @@ Represents the Geo replication and verification state of a job_artifact. | Name | Type | Description | | ---- | ---- | ----------- | +| `cancelBuild` | [`Boolean!`](#boolean) | If `true`, the user can perform `cancel_build` on this resource. | | `readBuild` | [`Boolean!`](#boolean) | If `true`, the user can perform `read_build` on this resource. | | `readJobArtifacts` | [`Boolean!`](#boolean) | If `true`, the user can perform `read_job_artifacts` on this resource. | | `updateBuild` | [`Boolean!`](#boolean) | If `true`, the user can perform `update_build` on this resource. | diff --git a/ee/app/policies/ee/ci/deployable_policy.rb b/ee/app/policies/ee/ci/deployable_policy.rb index 27fc147080d0b649d8aa57b8fd4f1044e5034971..20a3e8b37c4f8513cf8bd8452522291ca90c2609 100644 --- a/ee/app/policies/ee/ci/deployable_policy.rb +++ b/ee/app/policies/ee/ci/deployable_policy.rb @@ -22,6 +22,7 @@ module DeployablePolicy enable :jailbreak enable :update_commit_status enable :update_build + enable :cancel_build end # Authorizing the user to access to protected entities. @@ -30,6 +31,7 @@ module DeployablePolicy rule { ~can?(:jailbreak) & protected_environment }.policy do prevent :update_commit_status prevent :update_build + prevent :cancel_build prevent :erase_build end end diff --git a/lib/api/ci/jobs.rb b/lib/api/ci/jobs.rb index 47e4135db270b6f63ebed780477b5ec9bf318ecb..250fe2494894fa25c0e1b9612a34b913ce4f183f 100644 --- a/lib/api/ci/jobs.rb +++ b/lib/api/ci/jobs.rb @@ -122,10 +122,10 @@ class Jobs < ::API::Base requires :job_id, type: Integer, desc: 'The ID of a job', documentation: { example: 88 } end post ':id/jobs/:job_id/cancel', urgency: :low, feature_category: :continuous_integration do - authorize_update_builds! + authorize_cancel_builds! build = find_build!(params[:job_id]) - authorize!(:update_build, build) + authorize!(:cancel_build, build) build.cancel diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index a7e0a551283203c01539c0cfb2363f33eeb0c8f4..f17474394e24893f7e5d80de5f3b42d48f0d52b6 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -397,6 +397,10 @@ def authorize_update_builds! authorize! :update_build, user_project end + def authorize_cancel_builds! + authorize! :cancel_build, user_project + end + def require_repository_enabled!(subject = :global) not_found!("Repository") unless user_project.feature_available?(:repository, current_user) end diff --git a/lib/gitlab/ci/status/build/cancelable.rb b/lib/gitlab/ci/status/build/cancelable.rb index 43fb5cdbbe62cdc6af0abf435ea25bdcf178b3fb..b8c8cfa802c8cec3d31b70ba216df635f81f54bc 100644 --- a/lib/gitlab/ci/status/build/cancelable.rb +++ b/lib/gitlab/ci/status/build/cancelable.rb @@ -6,7 +6,7 @@ module Status module Build class Cancelable < Status::Extended def has_action? - can?(user, :update_build, subject) + can?(user, :cancel_build, subject) end def action_icon diff --git a/spec/graphql/types/permission_types/ci/job_spec.rb b/spec/graphql/types/permission_types/ci/job_spec.rb index e4bc54190701fcc6380648292cc6c2fea9723428..238f086c7ee83c013b8862947070bac3df4d47dc 100644 --- a/spec/graphql/types/permission_types/ci/job_spec.rb +++ b/spec/graphql/types/permission_types/ci/job_spec.rb @@ -5,7 +5,7 @@ RSpec.describe Types::PermissionTypes::Ci::Job do it 'has expected permission fields' do expected_permissions = [ - :read_job_artifacts, :read_build, :update_build + :read_job_artifacts, :read_build, :update_build, :cancel_build ] expect(described_class).to have_graphql_fields(expected_permissions).only diff --git a/spec/policies/ci/build_policy_spec.rb b/spec/policies/ci/build_policy_spec.rb index 6ab89daff822e74dbc1f3096cd72e87647f3a2f6..ab92936440c5d7ef89818bebcc394d331e6d0a2a 100644 --- a/spec/policies/ci/build_policy_spec.rb +++ b/spec/policies/ci/build_policy_spec.rb @@ -110,6 +110,7 @@ end it 'enables update_build if user is maintainer' do + expect(policy).to be_allowed :cancel_build expect(policy).to be_allowed :update_build expect(policy).to be_allowed :update_commit_status end @@ -130,6 +131,7 @@ end it 'does not include ability to update build' do + expect(policy).to be_disallowed :cancel_build expect(policy).to be_disallowed :update_build end @@ -139,6 +141,7 @@ end it 'does not include ability to update build' do + expect(policy).to be_disallowed :cancel_build expect(policy).to be_disallowed :update_build end end @@ -150,6 +153,7 @@ end it 'includes ability to update build' do + expect(policy).to be_allowed :cancel_build expect(policy).to be_allowed :update_build end end @@ -162,6 +166,7 @@ end it 'does not include ability to update build' do + expect(policy).to be_disallowed :cancel_build expect(policy).to be_disallowed :update_build end end @@ -172,6 +177,7 @@ end it 'includes ability to update build' do + expect(policy).to be_allowed :cancel_build expect(policy).to be_allowed :update_build end end diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 5e79569e0c42444b9957dc6db7cd587763fc3a7d..6e52ff8ed4fa8834975230042484dbfa277ee639 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -287,7 +287,7 @@ def set_access_level(access_level) it 'disallows all permissions except pipeline when the feature is disabled' do builds_permissions = [ - :create_build, :read_build, :update_build, :admin_build, :destroy_build, + :create_build, :read_build, :update_build, :cancel_build, :admin_build, :destroy_build, :create_pipeline_schedule, :read_pipeline_schedule_variables, :update_pipeline_schedule, :admin_pipeline_schedule, :destroy_pipeline_schedule, :create_environment, :read_environment, :update_environment, :admin_environment, :destroy_environment, :create_deployment, :read_deployment, :update_deployment, :admin_deployment, :destroy_deployment @@ -319,7 +319,7 @@ def set_access_level(access_level) let(:repository_permissions) do [ :create_pipeline, :update_pipeline, :cancel_pipeline, :admin_pipeline, :destroy_pipeline, - :create_build, :read_build, :update_build, :admin_build, :destroy_build, + :create_build, :read_build, :cancel_build, :update_build, :admin_build, :destroy_build, :create_pipeline_schedule, :read_pipeline_schedule, :update_pipeline_schedule, :admin_pipeline_schedule, :destroy_pipeline_schedule, :create_environment, :read_environment, :update_environment, :admin_environment, :destroy_environment, :create_cluster, :read_cluster, :update_cluster, :admin_cluster, diff --git a/spec/support/shared_examples/ci/deployable_policy_shared_examples.rb b/spec/support/shared_examples/ci/deployable_policy_shared_examples.rb index 73bdc094237958fc4f21f63d1a219dfca7b06c44..1f164a66026b282a2b452e6585200c98c892073f 100644 --- a/spec/support/shared_examples/ci/deployable_policy_shared_examples.rb +++ b/spec/support/shared_examples/ci/deployable_policy_shared_examples.rb @@ -20,6 +20,7 @@ end it { expect(policy).not_to be_allowed :update_build } + it { expect(policy).not_to be_allowed :cancel_build } end end end diff --git a/spec/support/shared_examples/ci/deployable_policy_shared_examples_ee.rb b/spec/support/shared_examples/ci/deployable_policy_shared_examples_ee.rb index b1057b3f67a9bc6b8d78cf56539662a14507afa0..10f334a6e2319561953465e88bd9ce395de5c565 100644 --- a/spec/support/shared_examples/ci/deployable_policy_shared_examples_ee.rb +++ b/spec/support/shared_examples/ci/deployable_policy_shared_examples_ee.rb @@ -20,6 +20,12 @@ it_behaves_like 'protected environments access', direct_access: true end + describe '#cancel_build?' do + subject { user.can?(:cancel_build, job) } + + it_behaves_like 'protected environments access', direct_access: true + end + describe '#update_commit_status?' do subject { user.can?(:update_commit_status, job) }