diff options
author | Fabio Pitino <fpitino@gitlab.com> | 2019-06-25 12:41:11 +0100 |
---|---|---|
committer | Fabio Pitino <fpitino@gitlab.com> | 2019-07-15 15:05:22 +0200 |
commit | 42d99460d3bbae88fdd9d8415970ecbfffc85a01 (patch) | |
tree | 0539b3150651f2a18567a58cf9b7a7aa87abf46d | |
parent | 0e8af2525f16d871510c24e6e15f1bc80f133edd (diff) | |
download | gitlab-ce-42d99460d3bbae88fdd9d8415970ecbfffc85a01.tar.gz |
Allow use of legacy triggers with feature flag
Keep feature flag disabled by default and turn off
all functionality related to legacy triggers.
* Block legacy triggers from creating pipeline
* Highlight legacy triggers to be invalid via the UI
* Make legacy triggers invalid in the model
-rw-r--r-- | app/models/ci/trigger.rb | 8 | ||||
-rw-r--r-- | app/policies/ci/trigger_policy.rb | 2 | ||||
-rw-r--r-- | app/views/projects/triggers/_content.html.haml | 17 | ||||
-rw-r--r-- | app/views/projects/triggers/_trigger.html.haml | 7 | ||||
-rw-r--r-- | changelogs/unreleased/remove-support-for-legacy-pipeline-triggers.yml | 5 | ||||
-rw-r--r-- | lib/gitlab/ci/pipeline/chain/command.rb | 6 | ||||
-rw-r--r-- | lib/gitlab/ci/pipeline/chain/validate/abilities.rb | 4 | ||||
-rw-r--r-- | spec/features/triggers_spec.rb | 78 | ||||
-rw-r--r-- | spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb | 44 | ||||
-rw-r--r-- | spec/models/ci/trigger_spec.rb | 20 | ||||
-rw-r--r-- | spec/policies/ci/trigger_policy_spec.rb | 94 |
11 files changed, 216 insertions, 69 deletions
diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb index 8927bb9bc18..dc9965ce08c 100644 --- a/app/models/ci/trigger.rb +++ b/app/models/ci/trigger.rb @@ -14,6 +14,7 @@ module Ci has_many :trigger_requests validates :token, presence: true, uniqueness: true + validates :owner, presence: true, unless: :supports_legacy_tokens? before_validation :set_default_values @@ -37,8 +38,13 @@ module Ci self.owner_id.blank? end + def supports_legacy_tokens? + Feature.enabled?(:use_legacy_pipeline_triggers, project) + end + def can_access_project? - self.owner_id.blank? || Ability.allowed?(self.owner, :create_build, project) + supports_legacy_tokens? && legacy? || + Ability.allowed?(self.owner, :create_build, project) end end end diff --git a/app/policies/ci/trigger_policy.rb b/app/policies/ci/trigger_policy.rb index 209db44539c..578301d7f7e 100644 --- a/app/policies/ci/trigger_policy.rb +++ b/app/policies/ci/trigger_policy.rb @@ -5,7 +5,7 @@ module Ci delegate { @subject.project } with_options scope: :subject, score: 0 - condition(:legacy) { @subject.legacy? } + condition(:legacy) { @subject.supports_legacy_tokens? && @subject.legacy? } with_score 0 condition(:is_owner) { @user && @subject.owner_id == @user.id } diff --git a/app/views/projects/triggers/_content.html.haml b/app/views/projects/triggers/_content.html.haml index 96a41aa066c..e686068657c 100644 --- a/app/views/projects/triggers/_content.html.haml +++ b/app/views/projects/triggers/_content.html.haml @@ -1,8 +1,9 @@ -%p.append-bottom-default - Triggers with the - %span.badge.badge-primary legacy - label do not have an associated user and only have access to the current project. - %br - = succeed '.' do - Learn more in the - = link_to 'triggers documentation', help_page_path('ci/triggers/README'), target: '_blank' +- if Feature.enabled?(:use_legacy_pipeline_triggers, @project) + %p.append-bottom-default + Triggers with the + %span.badge.badge-primary legacy + label do not have an associated user and only have access to the current project. + %br + = succeed '.' do + Learn more in the + = link_to 'triggers documentation', help_page_path('ci/triggers/README'), target: '_blank' diff --git a/app/views/projects/triggers/_trigger.html.haml b/app/views/projects/triggers/_trigger.html.haml index 6f6f1e5e0c5..31a598ccd5e 100644 --- a/app/views/projects/triggers/_trigger.html.haml +++ b/app/views/projects/triggers/_trigger.html.haml @@ -8,8 +8,11 @@ .label-container - if trigger.legacy? - %span.badge.badge-primary.has-tooltip{ title: "Trigger makes use of deprecated functionality" } legacy - - if !trigger.can_access_project? + - if trigger.supports_legacy_tokens? + %span.badge.badge-primary.has-tooltip{ title: "Trigger makes use of deprecated functionality" } legacy + - else + %span.badge.badge-danger.has-tooltip{ title: "Trigger is invalid due to being a legacy trigger. We recommend replacing it with a new trigger" } invalid + - elsif !trigger.can_access_project? %span.badge.badge-danger.has-tooltip{ title: "Trigger user has insufficient permissions to project" } invalid %td diff --git a/changelogs/unreleased/remove-support-for-legacy-pipeline-triggers.yml b/changelogs/unreleased/remove-support-for-legacy-pipeline-triggers.yml new file mode 100644 index 00000000000..3f4d4bbd432 --- /dev/null +++ b/changelogs/unreleased/remove-support-for-legacy-pipeline-triggers.yml @@ -0,0 +1,5 @@ +--- +title: Remove support for legacy pipeline triggers +merge_request: 30133 +author: +type: removed diff --git a/lib/gitlab/ci/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb index c911bfa7ff6..afad391e8e0 100644 --- a/lib/gitlab/ci/pipeline/chain/command.rb +++ b/lib/gitlab/ci/pipeline/chain/command.rb @@ -20,6 +20,12 @@ module Gitlab end end + def uses_unsupported_legacy_trigger? + trigger_request.present? && + trigger_request.trigger.legacy? && + !trigger_request.trigger.supports_legacy_tokens? + end + def branch_exists? strong_memoize(:is_branch) do project.repository.branch_exists?(ref) diff --git a/lib/gitlab/ci/pipeline/chain/validate/abilities.rb b/lib/gitlab/ci/pipeline/chain/validate/abilities.rb index aaa3daddcc5..357a1d55b3b 100644 --- a/lib/gitlab/ci/pipeline/chain/validate/abilities.rb +++ b/lib/gitlab/ci/pipeline/chain/validate/abilities.rb @@ -14,6 +14,10 @@ module Gitlab return error('Pipelines are disabled!') end + if @command.uses_unsupported_legacy_trigger? + return error('Trigger token is invalid because is not owned by any user') + end + unless allowed_to_trigger_pipeline? if can?(current_user, :create_pipeline, project) return error("Insufficient permissions for protected ref '#{command.ref}'") diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb index 919859c145a..7c44680e9f7 100644 --- a/spec/features/triggers_spec.rb +++ b/spec/features/triggers_spec.rb @@ -66,7 +66,7 @@ describe 'Triggers', :js do it 'edit "legacy" trigger and save' do # Create new trigger without owner association, i.e. Legacy trigger - create(:ci_trigger, owner: nil, project: @project) + create(:ci_trigger, owner: user, project: @project).update_attribute(:owner, nil) visit project_settings_ci_cd_path(@project) # See if the trigger can be edited and description is blank @@ -127,17 +127,19 @@ describe 'Triggers', :js do end describe 'show triggers workflow' do + before do + stub_feature_flags(use_legacy_pipeline_triggers: false) + end + it 'contains trigger description placeholder' do expect(page.find('#trigger_description')['placeholder']).to eq 'Trigger description' end - it 'show "legacy" badge for legacy trigger' do - create(:ci_trigger, owner: nil, project: @project) + it 'show "invalid" badge for legacy trigger' do + create(:ci_trigger, owner: user, project: @project).update_attribute(:owner, nil) visit project_settings_ci_cd_path(@project) - # See if trigger without owner (i.e. legacy) shows "legacy" badge and is editable - expect(page.find('.triggers-list')).to have_content 'legacy' - expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]') + expect(page.find('.triggers-list')).to have_content 'invalid' end it 'show "invalid" badge for trigger with owner having insufficient permissions' do @@ -149,6 +151,19 @@ describe 'Triggers', :js do expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]') end + it 'do not show "Edit" or full token for legacy trigger' do + create(:ci_trigger, owner: user, project: @project, description: trigger_title) + .update_attribute(:owner, nil) + visit project_settings_ci_cd_path(@project) + + # See if trigger not owned shows only first few token chars and doesn't have copy-to-clipboard button + expect(page.find('.triggers-list')).to have_content(@project.triggers.first.token[0..3]) + expect(page.find('.triggers-list')).not_to have_selector('button.btn-clipboard') + + # See if trigger is non-editable + expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]') + end + it 'do not show "Edit" or full token for not owned trigger' do # Create trigger with user different from current_user create(:ci_trigger, owner: user2, project: @project, description: trigger_title) @@ -175,5 +190,56 @@ describe 'Triggers', :js do expect(page.find('.triggers-list .trigger-owner')).to have_content user.name expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]') end + + context 'when :use_legacy_pipeline_triggers feature flag is enabled' do + before do + stub_feature_flags(use_legacy_pipeline_triggers: true) + end + + it 'show "legacy" badge for legacy trigger' do + create(:ci_trigger, owner: nil, project: @project) + visit project_settings_ci_cd_path(@project) + + # See if trigger without owner (i.e. legacy) shows "legacy" badge and is editable + expect(page.find('.triggers-list')).to have_content 'legacy' + expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]') + end + + it 'show "invalid" badge for trigger with owner having insufficient permissions' do + create(:ci_trigger, owner: guest_user, project: @project, description: trigger_title) + visit project_settings_ci_cd_path(@project) + + # See if trigger without owner (i.e. legacy) shows "legacy" badge and is non-editable + expect(page.find('.triggers-list')).to have_content 'invalid' + expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]') + end + + it 'do not show "Edit" or full token for not owned trigger' do + # Create trigger with user different from current_user + create(:ci_trigger, owner: user2, project: @project, description: trigger_title) + visit project_settings_ci_cd_path(@project) + + # See if trigger not owned by current_user shows only first few token chars and doesn't have copy-to-clipboard button + expect(page.find('.triggers-list')).to have_content(@project.triggers.first.token[0..3]) + expect(page.find('.triggers-list')).not_to have_selector('button.btn-clipboard') + + # See if trigger owner name doesn't match with current_user and trigger is non-editable + expect(page.find('.triggers-list .trigger-owner')).not_to have_content user.name + expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]') + end + + it 'show "Edit" and full token for owned trigger' do + create(:ci_trigger, owner: user, project: @project, description: trigger_title) + visit project_settings_ci_cd_path(@project) + + # See if trigger shows full token and has copy-to-clipboard button + expect(page.find('.triggers-list')).to have_content @project.triggers.first.token + expect(page.find('.triggers-list')).to have_selector('button.btn-clipboard') + + # See if trigger owner name matches with current_user and is editable + expect(page.find('.triggers-list .trigger-owner')).to have_content user.name + expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]') + end + end end end diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb index 7d750877d09..b3e58c3dfdb 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb @@ -10,7 +10,11 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do let(:command) do Gitlab::Ci::Pipeline::Chain::Command.new( - project: project, current_user: user, origin_ref: origin_ref, merge_request: merge_request) + project: project, + current_user: user, + origin_ref: origin_ref, + merge_request: merge_request, + trigger_request: trigger_request) end let(:step) { described_class.new(pipeline, command) } @@ -18,6 +22,7 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do let(:ref) { 'master' } let(:origin_ref) { ref } let(:merge_request) { nil } + let(:trigger_request) { nil } shared_context 'detached merge request pipeline' do let(:merge_request) do @@ -69,6 +74,43 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do end end + context 'when pipeline triggered by legacy trigger' do + let(:user) { nil } + let(:trigger_request) do + build_stubbed(:ci_trigger_request, trigger: build_stubbed(:ci_trigger, owner: nil)) + end + + context 'when :use_legacy_pipeline_triggers feature flag is enabled' do + before do + stub_feature_flags(use_legacy_pipeline_triggers: true) + step.perform! + end + + it 'allows legacy triggers to create a pipeline' do + expect(pipeline).to be_valid + end + + it 'does not break the chain' do + expect(step.break?).to eq false + end + end + + context 'when :use_legacy_pipeline_triggers feature flag is disabled' do + before do + stub_feature_flags(use_legacy_pipeline_triggers: false) + step.perform! + end + + it 'prevents legacy triggers from creating a pipeline' do + expect(pipeline.errors.to_a).to include /Trigger token is invalid/ + end + + it 'breaks the pipeline builder chain' do + expect(step.break?).to eq true + end + end + end + describe '#allowed_to_create?' do subject { step.allowed_to_create? } diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb index fde8375f2a5..5b5d6f51b33 100644 --- a/spec/models/ci/trigger_spec.rb +++ b/spec/models/ci/trigger_spec.rb @@ -54,19 +54,31 @@ describe Ci::Trigger do end describe '#can_access_project?' do + let(:owner) { create(:user) } let(:trigger) { create(:ci_trigger, owner: owner, project: project) } context 'when owner is blank' do - let(:owner) { nil } + before do + stub_feature_flags(use_legacy_pipeline_triggers: false) + trigger.update_attribute(:owner, nil) + end subject { trigger.can_access_project? } - it { is_expected.to eq(true) } + it { is_expected.to eq(false) } + + context 'when :use_legacy_pipeline_triggers feature flag is enabled' do + before do + stub_feature_flags(use_legacy_pipeline_triggers: true) + end + + subject { trigger.can_access_project? } + + it { is_expected.to eq(true) } + end end context 'when owner is set' do - let(:owner) { create(:user) } - subject { trigger.can_access_project? } context 'and is member of the project' do diff --git a/spec/policies/ci/trigger_policy_spec.rb b/spec/policies/ci/trigger_policy_spec.rb index d8a63066265..e9a85890082 100644 --- a/spec/policies/ci/trigger_policy_spec.rb +++ b/spec/policies/ci/trigger_policy_spec.rb @@ -3,52 +3,24 @@ require 'spec_helper' describe Ci::TriggerPolicy do let(:user) { create(:user) } let(:project) { create(:project) } - let(:trigger) { create(:ci_trigger, project: project, owner: owner) } + let(:trigger) { create(:ci_trigger, project: project, owner: create(:user)) } - let(:policies) do - described_class.new(user, trigger) - end - - shared_examples 'allows to admin and manage trigger' do - it 'does include ability to admin trigger' do - expect(policies).to be_allowed :admin_trigger - end - - it 'does include ability to manage trigger' do - expect(policies).to be_allowed :manage_trigger - end - end - - shared_examples 'allows to manage trigger' do - it 'does not include ability to admin trigger' do - expect(policies).not_to be_allowed :admin_trigger - end - - it 'does include ability to manage trigger' do - expect(policies).to be_allowed :manage_trigger - end - end - - shared_examples 'disallows to admin and manage trigger' do - it 'does not include ability to admin trigger' do - expect(policies).not_to be_allowed :admin_trigger - end - - it 'does not include ability to manage trigger' do - expect(policies).not_to be_allowed :manage_trigger - end - end + subject { described_class.new(user, trigger) } describe '#rules' do context 'when owner is undefined' do - let(:owner) { nil } + before do + stub_feature_flags(use_legacy_pipeline_triggers: false) + trigger.update_attribute(:owner, nil) + end context 'when user is maintainer of the project' do before do project.add_maintainer(user) end - it_behaves_like 'allows to admin and manage trigger' + it { is_expected.to be_allowed(:manage_trigger) } + it { is_expected.not_to be_allowed(:admin_trigger) } end context 'when user is developer of the project' do @@ -56,35 +28,63 @@ describe Ci::TriggerPolicy do project.add_developer(user) end - it_behaves_like 'disallows to admin and manage trigger' + it { is_expected.not_to be_allowed(:manage_trigger) } + it { is_expected.not_to be_allowed(:admin_trigger) } end - context 'when user is not member of the project' do - it_behaves_like 'disallows to admin and manage trigger' + context 'when :use_legacy_pipeline_triggers feature flag is enabled' do + before do + stub_feature_flags(use_legacy_pipeline_triggers: true) + end + + context 'when user is maintainer of the project' do + before do + project.add_maintainer(user) + end + + it { is_expected.to be_allowed(:manage_trigger) } + it { is_expected.to be_allowed(:admin_trigger) } + end + + context 'when user is developer of the project' do + before do + project.add_developer(user) + end + + it { is_expected.not_to be_allowed(:manage_trigger) } + it { is_expected.not_to be_allowed(:admin_trigger) } + end + + context 'when user is not member of the project' do + it { is_expected.not_to be_allowed(:manage_trigger) } + it { is_expected.not_to be_allowed(:admin_trigger) } + end end end context 'when owner is an user' do - let(:owner) { user } + before do + trigger.update!(owner: user) + end context 'when user is maintainer of the project' do before do project.add_maintainer(user) end - it_behaves_like 'allows to admin and manage trigger' + it { is_expected.to be_allowed(:manage_trigger) } + it { is_expected.to be_allowed(:admin_trigger) } end end context 'when owner is another user' do - let(:owner) { create(:user) } - context 'when user is maintainer of the project' do before do project.add_maintainer(user) end - it_behaves_like 'allows to manage trigger' + it { is_expected.to be_allowed(:manage_trigger) } + it { is_expected.not_to be_allowed(:admin_trigger) } end context 'when user is developer of the project' do @@ -92,11 +92,13 @@ describe Ci::TriggerPolicy do project.add_developer(user) end - it_behaves_like 'disallows to admin and manage trigger' + it { is_expected.not_to be_allowed(:manage_trigger) } + it { is_expected.not_to be_allowed(:admin_trigger) } end context 'when user is not member of the project' do - it_behaves_like 'disallows to admin and manage trigger' + it { is_expected.not_to be_allowed(:manage_trigger) } + it { is_expected.not_to be_allowed(:admin_trigger) } end end end |