Commit ac316a4b authored by Sean McGivern's avatar Sean McGivern

Disallow MR approvals from the MR author

parent 5da64896
...@@ -3,6 +3,7 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -3,6 +3,7 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.9.0 (unreleased) v 8.9.0 (unreleased)
- Fix nil user handling in UpdateMirrorService - Fix nil user handling in UpdateMirrorService
- Allow LDAP to mark users as external based on their group membership. !432 - Allow LDAP to mark users as external based on their group membership. !432
- Forbid MR authors from approving their own MRs
- Instrument instance methods of Gitlab::InsecureKeyFingerprint class - Instrument instance methods of Gitlab::InsecureKeyFingerprint class
- Add API endpoint for Merge Request Approvals !449 - Add API endpoint for Merge Request Approvals !449
......
...@@ -475,8 +475,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -475,8 +475,7 @@ class MergeRequest < ActiveRecord::Base
end end
def approvers_left def approvers_left
user_ids = overall_approvers.map(&:user_id) - approvals.map(&:user_id) User.where(id: overall_approvers.select(:user_id)).where.not(id: approvals.select(:user_id))
User.where id: user_ids
end end
def approvals_required def approvals_required
...@@ -508,6 +507,8 @@ class MergeRequest < ActiveRecord::Base ...@@ -508,6 +507,8 @@ class MergeRequest < ActiveRecord::Base
end end
def can_approve?(user) def can_approve?(user)
return false if user == self.author
approvers_left.include?(user) || approvers_left.include?(user) ||
(any_approver_allowed? && !approved_by?(user)) (any_approver_allowed? && !approved_by?(user))
end end
......
...@@ -33,7 +33,7 @@ independent of changes to the merge request. ...@@ -33,7 +33,7 @@ independent of changes to the merge request.
### Approvers ### Approvers
At approvers you can define the default set of users that need to approve a At approvers you can define the default set of users that need to approve a
merge request. merge request. The author of a merge request cannot approve that merge request.
If there are more approvers than required approvals, any subset of these users If there are more approvers than required approvals, any subset of these users
can approve the merge request. can approve the merge request.
......
...@@ -327,13 +327,6 @@ Feature: Project Merge Requests ...@@ -327,13 +327,6 @@ Feature: Project Merge Requests
And I click link "Close" And I click link "Close"
Then I should see closed merge request "Bug NS-04" Then I should see closed merge request "Bug NS-04"
Scenario: I approve merge request
Given merge request 'Bug NS-04' must be approved
And I click link "Bug NS-04"
And I should not see merge button
When I click link "Approve"
Then I should see approved merge request "Bug NS-04"
Scenario: Reporter can approve merge request Scenario: Reporter can approve merge request
Given I am a "Shop" reporter Given I am a "Shop" reporter
And I visit project "Shop" merge requests page And I visit project "Shop" merge requests page
...@@ -343,13 +336,12 @@ Feature: Project Merge Requests ...@@ -343,13 +336,12 @@ Feature: Project Merge Requests
When I click link "Approve" When I click link "Approve"
Then I should see message that merge request can be merged Then I should see message that merge request can be merged
Scenario: I approve merge request if I am an approver Scenario: I can not approve Merge request if I am the author
Given merge request 'Bug NS-04' must be approved by current user Given merge request 'Bug NS-04' must be approved
And I click link "Bug NS-04" And I click link "Bug NS-04"
And I should not see merge button And I should not see merge button
And I should see message that MR require an approval from me When I should not see Approve button
When I click link "Approve" And I should see message that MR require an approval
Then I should see approved merge request "Bug NS-04"
Scenario: I can not approve merge request if I am not an approver Scenario: I can not approve merge request if I am not an approver
Given merge request 'Bug NS-04' must be approved by some user Given merge request 'Bug NS-04' must be approved by some user
......
...@@ -21,7 +21,9 @@ module Gitlab ...@@ -21,7 +21,9 @@ module Gitlab
list_of_involved_files.each do |path| list_of_involved_files.each do |path|
@repo.commits(@merge_request.target_branch, path: path, limit: COMMITS_TO_CONSIDER).each do |commit| @repo.commits(@merge_request.target_branch, path: path, limit: COMMITS_TO_CONSIDER).each do |commit|
@users[commit.author] += 1 if commit.author if commit.author && commit.author != @merge_request.author
@users[commit.author] += 1
end
end end
end end
end end
......
...@@ -268,6 +268,49 @@ describe Projects::MergeRequestsController do ...@@ -268,6 +268,49 @@ describe Projects::MergeRequestsController do
end end
end end
describe 'POST #approve' do
def approve(user)
post :approve, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid, user: user
end
context 'when the user is the author of the MR' do
before { merge_request.approvers.create(user: merge_request.author) }
it "returns a 404" do
approve(merge_request.author)
expect(response).to have_http_status(404)
end
end
context 'when the user is not allowed to approve the MR' do
it "returns a 404" do
approve(user)
expect(response).to have_http_status(404)
end
end
context 'when the user is allowed to approve the MR' do
before { merge_request.approvers.create(user: user) }
it 'creates an approval' do
service = double(:approval_service)
expect(MergeRequests::ApprovalService).to receive(:new).with(project, anything).and_return(service)
expect(service).to receive(:execute).with(merge_request)
approve(user)
end
it 'redirects to the MR' do
approve(user)
expect(response).to redirect_to(namespace_project_merge_request_path)
end
end
end
describe "DELETE #destroy" do describe "DELETE #destroy" do
it "denies access to users unless they're admin or project owner" do it "denies access to users unless they're admin or project owner" do
delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid
......
...@@ -19,25 +19,41 @@ describe Gitlab::AuthorityAnalyzer, lib: true do ...@@ -19,25 +19,41 @@ describe Gitlab::AuthorityAnalyzer, lib: true do
] ]
end end
let(:approvers) { Gitlab::AuthorityAnalyzer.new(merge_request).calculate(number_of_approvers) } def calculate_approvers(count)
described_class.new(merge_request).calculate(count)
end
before do before do
merge_request.compare = double(:compare, diffs: files) merge_request.compare = double(:compare, diffs: files)
allow(merge_request.target_project.repository).to receive(:commits).and_return(commits) allow(merge_request.target_project.repository).to receive(:commits).and_return(commits)
end end
context 'when there are fewer contributors than requested' do context 'when the MR author is in the top contributors' do
let(:number_of_approvers) { 5 } it 'does not include the MR author' do
approvers = calculate_approvers(2)
expect(approvers).not_to include(author)
end
it 'returns the correct number of contributors' do
approvers = calculate_approvers(2)
expect(approvers.length).to eq(2)
end
end
context 'when there are fewer contributors than requested' do
it 'returns the full number of users' do it 'returns the full number of users' do
approvers = calculate_approvers(5)
expect(approvers.length).to eq(2) expect(approvers.length).to eq(2)
end end
end end
context 'when there are more contributors than requested' do context 'when there are more contributors than requested' do
let(:number_of_approvers) { 1 }
it 'returns only the top n contributors' do it 'returns only the top n contributors' do
approvers = calculate_approvers(1)
expect(approvers).to contain_exactly(user_a) expect(approvers).to contain_exactly(user_a)
end end
end end
......
...@@ -223,7 +223,7 @@ describe MergeRequest, models: true do ...@@ -223,7 +223,7 @@ describe MergeRequest, models: true do
end end
end end
describe "approvers_left" do describe "#approvers_left" do
let(:merge_request) {create :merge_request} let(:merge_request) {create :merge_request}
it "returns correct value" do it "returns correct value" do
...@@ -237,7 +237,7 @@ describe MergeRequest, models: true do ...@@ -237,7 +237,7 @@ describe MergeRequest, models: true do
end end
end end
describe "approvals_required" do describe "#approvals_required" do
let(:merge_request) {create :merge_request} let(:merge_request) {create :merge_request}
it "takes approvals_before_merge" do it "takes approvals_before_merge" do
...@@ -247,6 +247,58 @@ describe MergeRequest, models: true do ...@@ -247,6 +247,58 @@ describe MergeRequest, models: true do
end end
end end
describe "#can_approve?" do
let(:author) { create(:user) }
let(:user) { create(:user) }
let(:merge_request) { create(:merge_request, author: author) }
context "when the user is the MR author" do
it "returns false" do
expect(merge_request.can_approve?(author)).to eq(false)
end
end
context "when the user is not the MR author" do
context "when the user is in the approvers list" do
before { merge_request.approvers.create(user: user) }
context "when the user has not already approved the MR" do
it "returns true" do
expect(merge_request.can_approve?(user)).to eq(true)
end
end
context "when the user has already approved the MR" do
before { merge_request.approvals.create(user: user) }
it "returns false" do
expect(merge_request.can_approve?(user)).to eq(false)
end
end
end
context "when the user is not in the approvers list" do
context "when anyone is allowed to approve the MR" do
before { merge_request.target_project.update_attributes(approvals_before_merge: 1) }
context "when the user has not already approved the MR" do
it "returns true" do
expect(merge_request.can_approve?(user)).to eq(true)
end
end
context "when the user has already approved the MR" do
before { merge_request.approvals.create(user: user) }
it "returns false" do
expect(merge_request.can_approve?(user)).to eq(false)
end
end
end
end
end
end
describe '#can_remove_source_branch?' do describe '#can_remove_source_branch?' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:user2) { create(:user) } let(:user2) { create(:user) }
......
...@@ -634,14 +634,24 @@ describe API::API, api: true do ...@@ -634,14 +634,24 @@ describe API::API, api: true do
end end
describe 'POST :id/merge_requests/:merge_request_id/approve' do describe 'POST :id/merge_requests/:merge_request_id/approve' do
context 'when the user is an allowed approver' do
it 'approves the merge request' do it 'approves the merge request' do
project.update_attribute(:approvals_before_merge, 2) project.update_attribute(:approvals_before_merge, 2)
post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/approve", user) post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/approve", admin)
expect(response.status).to eq(201) expect(response.status).to eq(201)
expect(json_response['approvals_left']).to eq(1) expect(json_response['approvals_left']).to eq(1)
expect(json_response['approved_by'][0]['user']['username']).to eq(user.username) expect(json_response['approved_by'][0]['user']['username']).to eq(admin.username)
end
end
context 'when the user is the MR author' do
it 'returns a not authorised response' do
post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/approve", user)
expect(response.status).to eq(401)
end
end end
end end
......
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