Commit b78510bf authored by GitLab Release Tools Bot's avatar GitLab Release Tools Bot

Merge branch 'security-protectedbranch-egress-status-checks' into 'master'

Do not allow status checks to exist with external protected branches

See merge request gitlab-org/security/gitlab!1716
parents 88ae1ef2 dd5d6c9d
...@@ -19,6 +19,7 @@ module MergeRequests ...@@ -19,6 +19,7 @@ module MergeRequests
validates :external_url, presence: true, uniqueness: { scope: :project_id }, addressable_url: true validates :external_url, presence: true, uniqueness: { scope: :project_id }, addressable_url: true
validates :name, uniqueness: { scope: :project_id }, presence: true validates :name, uniqueness: { scope: :project_id }, presence: true
validate :protected_branches_must_belong_to_project
def async_execute(data) def async_execute(data)
return unless protected_branches.none? || protected_branches.by_name(data[:object_attributes][:target_branch]).any? return unless protected_branches.none? || protected_branches.by_name(data[:object_attributes][:target_branch]).any?
...@@ -43,5 +44,9 @@ module MergeRequests ...@@ -43,5 +44,9 @@ module MergeRequests
def payload_data(merge_request_hook_data) def payload_data(merge_request_hook_data)
merge_request_hook_data.merge(external_approval_rule: self.to_h) merge_request_hook_data.merge(external_approval_rule: self.to_h)
end end
def protected_branches_must_belong_to_project
errors.add(:base, 'all protected branches must exist within the project') unless protected_branches.all? { |b| project.protected_branches.include?(b) }
end
end end
end end
...@@ -14,6 +14,15 @@ RSpec.describe MergeRequests::ExternalStatusCheck, type: :model do ...@@ -14,6 +14,15 @@ RSpec.describe MergeRequests::ExternalStatusCheck, type: :model do
it { is_expected.to validate_presence_of(:external_url) } it { is_expected.to validate_presence_of(:external_url) }
it { is_expected.to validate_uniqueness_of(:name).scoped_to(:project_id) } it { is_expected.to validate_uniqueness_of(:name).scoped_to(:project_id) }
it { is_expected.to validate_uniqueness_of(:external_url).scoped_to(:project_id) } it { is_expected.to validate_uniqueness_of(:external_url).scoped_to(:project_id) }
describe 'protected_branches_must_belong_to_project' do
let(:check) { build(:external_status_check, protected_branches: [create(:protected_branch)]) }
it 'is invalid' do
expect(check).to be_invalid
expect(check.errors.messages[:base]).to eq ['all protected branches must exist within the project']
end
end
end end
describe 'to_h' do describe 'to_h' do
...@@ -26,8 +35,8 @@ RSpec.describe MergeRequests::ExternalStatusCheck, type: :model do ...@@ -26,8 +35,8 @@ RSpec.describe MergeRequests::ExternalStatusCheck, type: :model do
let_it_be(:merge_request) { create(:merge_request) } let_it_be(:merge_request) { create(:merge_request) }
let_it_be(:check_belonging_to_different_project) { create(:external_status_check) } let_it_be(:check_belonging_to_different_project) { create(:external_status_check) }
let_it_be(:check_with_no_protected_branches) { create(:external_status_check, project: merge_request.project, protected_branches: []) } let_it_be(:check_with_no_protected_branches) { create(:external_status_check, project: merge_request.project, protected_branches: []) }
let_it_be(:check_with_applicable_protected_branches) { create(:external_status_check, project: merge_request.project, protected_branches: [create(:protected_branch, name: merge_request.target_branch)]) } let_it_be(:check_with_applicable_protected_branches) { create(:external_status_check, project: merge_request.project, protected_branches: [create(:protected_branch, name: merge_request.target_branch, project: merge_request.project)]) }
let_it_be(:check_with_non_applicable_protected_branches) { create(:external_status_check, project: merge_request.project, protected_branches: [create(:protected_branch, name: 'testbranch')]) } let_it_be(:check_with_non_applicable_protected_branches) { create(:external_status_check, project: merge_request.project, protected_branches: [create(:protected_branch, name: 'testbranch', project: merge_request.project)]) }
it 'returns the correct collection of checks' do it 'returns the correct collection of checks' do
expect(merge_request.project.external_status_checks.applicable_to_branch(merge_request.target_branch)).to contain_exactly(check_with_no_protected_branches, check_with_applicable_protected_branches) expect(merge_request.project.external_status_checks.applicable_to_branch(merge_request.target_branch)).to contain_exactly(check_with_no_protected_branches, check_with_applicable_protected_branches)
...@@ -58,7 +67,7 @@ RSpec.describe MergeRequests::ExternalStatusCheck, type: :model do ...@@ -58,7 +67,7 @@ RSpec.describe MergeRequests::ExternalStatusCheck, type: :model do
end end
context 'when data target branch matches a protected branch' do context 'when data target branch matches a protected branch' do
let_it_be(:check) { create(:external_status_check, project: merge_request.project, protected_branches: [create(:protected_branch, name: 'test')]) } let_it_be(:check) { create(:external_status_check, project: merge_request.project, protected_branches: [create(:protected_branch, name: 'test', project: merge_request.project)]) }
it 'enqueues the status check' do it 'enqueues the status check' do
expect(ApprovalRules::ExternalApprovalRulePayloadWorker).to receive(:perform_async).once expect(ApprovalRules::ExternalApprovalRulePayloadWorker).to receive(:perform_async).once
...@@ -68,7 +77,7 @@ RSpec.describe MergeRequests::ExternalStatusCheck, type: :model do ...@@ -68,7 +77,7 @@ RSpec.describe MergeRequests::ExternalStatusCheck, type: :model do
end end
context 'when data target branch does not match a protected branch' do context 'when data target branch does not match a protected branch' do
let_it_be(:check) { create(:external_status_check, project: merge_request.project, protected_branches: [create(:protected_branch, name: 'new-branch')]) } let_it_be(:check) { create(:external_status_check, project: merge_request.project, protected_branches: [create(:protected_branch, name: 'new-branch', project: merge_request.project)]) }
it 'does not enqueue the status check' do it 'does not enqueue the status check' do
expect(ApprovalRules::ExternalApprovalRulePayloadWorker).to receive(:perform_async).never expect(ApprovalRules::ExternalApprovalRulePayloadWorker).to receive(:perform_async).never
......
...@@ -211,7 +211,7 @@ RSpec.describe MergeRequestPresenter do ...@@ -211,7 +211,7 @@ RSpec.describe MergeRequestPresenter do
context 'without applicable branches' do context 'without applicable branches' do
before do before do
create(:external_status_check, project: project, protected_branches: [create(:protected_branch, name: 'testbranch')]) create(:external_status_check, project: project, protected_branches: [create(:protected_branch, name: 'testbranch', project: project)])
end end
it { is_expected.to eq(nil) } it { is_expected.to eq(nil) }
...@@ -227,7 +227,7 @@ RSpec.describe MergeRequestPresenter do ...@@ -227,7 +227,7 @@ RSpec.describe MergeRequestPresenter do
context 'with applicable branches' do context 'with applicable branches' do
before do before do
create(:external_status_check, project: project, protected_branches: [create(:protected_branch, name: merge_request.target_branch)]) create(:external_status_check, project: project, protected_branches: [create(:protected_branch, name: merge_request.target_branch, project: project)])
end end
it { is_expected.to eq(exposed_path) } it { is_expected.to eq(exposed_path) }
......
...@@ -39,8 +39,8 @@ RSpec.describe API::StatusChecks do ...@@ -39,8 +39,8 @@ RSpec.describe API::StatusChecks do
end end
context 'when merge request has received status check responses' do context 'when merge request has received status check responses' do
let!(:non_applicable_check) { create(:external_status_check, project: project, protected_branches: [create(:protected_branch, name: 'different-branch')]) } let!(:non_applicable_check) { create(:external_status_check, project: project, protected_branches: [create(:protected_branch, name: 'different-branch', project: project)]) }
let!(:branch_specific_check) { create(:external_status_check, project: project, protected_branches: [create(:protected_branch, name: merge_request.target_branch)]) } let!(:branch_specific_check) { create(:external_status_check, project: project, protected_branches: [create(:protected_branch, name: merge_request.target_branch, project: project)]) }
let!(:status_check_response) { create(:status_check_response, external_status_check: rule, merge_request: merge_request, sha: sha) } let!(:status_check_response) { create(:status_check_response, external_status_check: rule, merge_request: merge_request, sha: sha) }
it 'returns a 200' do it 'returns a 200' do
...@@ -288,6 +288,24 @@ RSpec.describe API::StatusChecks do ...@@ -288,6 +288,24 @@ RSpec.describe API::StatusChecks do
expect(response).to have_gitlab_http_status(:success) expect(response).to have_gitlab_http_status(:success)
end end
context 'when referencing a protected branch outside of the project' do
let_it_be(:protected_branch) { create(:protected_branch) }
let(:params) do
{ name: 'New rule', external_url: 'https://gitlab.com/test/example.json', protected_branch_ids: protected_branch.id }
end
subject do
put api(single_object_url, project.owner), params: params
end
it 'is invalid' do
subject
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
end
context 'with protected branches' do context 'with protected branches' do
let_it_be(:protected_branch) { create(:protected_branch, project: project) } let_it_be(:protected_branch) { create(:protected_branch, project: project) }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment