Commit 3a5df1d8 authored by Robert Speicher's avatar Robert Speicher Committed by Robert Speicher

Merge branch 'fix-api-mr-permissions' into 'security'

Ensure that only privileged users can access merge requests in the API

See merge request !2053
parent d7755ede
---
title: Don't allow project guests to subscribe to merge requests through the API
merge_request:
author: Robert Schilling
......@@ -90,6 +90,12 @@ module API
MergeRequestsFinder.new(current_user, project_id: user_project.id).find(id)
end
def find_merge_request_with_access(id, access_level = :read_merge_request)
merge_request = user_project.merge_requests.find(id)
authorize! access_level, merge_request
merge_request
end
def authenticate!
unauthorized! unless current_user
end
......
......@@ -15,10 +15,8 @@ module API
end
get ":id/merge_requests/:merge_request_id/versions" do
merge_request = user_project.merge_requests.
find(params[:merge_request_id])
merge_request = find_merge_request_with_access(params[:merge_request_id])
authorize! :read_merge_request, merge_request
present merge_request.merge_request_diffs, with: Entities::MergeRequestDiff
end
......@@ -34,10 +32,8 @@ module API
end
get ":id/merge_requests/:merge_request_id/versions/:version_id" do
merge_request = user_project.merge_requests.
find(params[:merge_request_id])
merge_request = find_merge_request_with_access(params[:merge_request_id])
authorize! :read_merge_request, merge_request
present merge_request.merge_request_diffs.find(params[:version_id]), with: Entities::MergeRequestDiffFull
end
end
......
......@@ -118,8 +118,8 @@ module API
success Entities::MergeRequest
end
get path do
merge_request = find_project_merge_request(params[:merge_request_id])
authorize! :read_merge_request, merge_request
merge_request = find_merge_request_with_access(params[:merge_request_id])
present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project
end
......@@ -127,8 +127,8 @@ module API
success Entities::RepoCommit
end
get "#{path}/commits" do
merge_request = find_project_merge_request(params[:merge_request_id])
authorize! :read_merge_request, merge_request
merge_request = find_merge_request_with_access(params[:merge_request_id])
present merge_request.commits, with: Entities::RepoCommit
end
......@@ -136,8 +136,8 @@ module API
success Entities::MergeRequestChanges
end
get "#{path}/changes" do
merge_request = find_project_merge_request(params[:merge_request_id])
authorize! :read_merge_request, merge_request
merge_request = find_merge_request_with_access(params[:merge_request_id])
present merge_request, with: Entities::MergeRequestChanges, current_user: current_user
end
......@@ -155,8 +155,7 @@ module API
:remove_source_branch
end
put path do
merge_request = find_project_merge_request(params.delete(:merge_request_id))
authorize! :update_merge_request, merge_request
merge_request = find_merge_request_with_access(params.delete(:merge_request_id), :update_merge_request)
mr_params = declared_params(include_missing: false)
mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch) if mr_params[:remove_source_branch].present?
......@@ -235,10 +234,7 @@ module API
use :pagination
end
get "#{path}/comments" do
merge_request = find_project_merge_request(params[:merge_request_id])
authorize! :read_merge_request, merge_request
merge_request = find_merge_request_with_access(params[:merge_request_id])
present paginate(merge_request.notes.fresh), with: Entities::MRNote
end
......@@ -250,8 +246,7 @@ module API
requires :note, type: String, desc: 'The text of the comment'
end
post "#{path}/comments" do
merge_request = find_project_merge_request(params[:merge_request_id])
authorize! :create_note, merge_request
merge_request = find_merge_request_with_access(params[:merge_request_id], :create_note)
opts = {
note: params[:note],
......@@ -275,7 +270,7 @@ module API
use :pagination
end
get "#{path}/closes_issues" do
merge_request = find_project_merge_request(params[:merge_request_id])
merge_request = find_merge_request_with_access(params[:merge_request_id])
issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user))
present paginate(issues), with: issue_entity(user_project), current_user: current_user
end
......
......@@ -3,8 +3,8 @@ module API
before { authenticate! }
subscribable_types = {
'merge_request' => proc { |id| user_project.merge_requests.find(id) },
'merge_requests' => proc { |id| user_project.merge_requests.find(id) },
'merge_request' => proc { |id| find_merge_request_with_access(id, :update_merge_request) },
'merge_requests' => proc { |id| find_merge_request_with_access(id, :update_merge_request) },
'issues' => proc { |id| find_project_issue(id) },
'labels' => proc { |id| find_project_label(id) },
}
......
......@@ -5,7 +5,7 @@ module API
before { authenticate! }
ISSUABLE_TYPES = {
'merge_requests' => ->(id) { user_project.merge_requests.find(id) },
'merge_requests' => ->(id) { find_merge_request_with_access(id) },
'issues' => ->(id) { find_project_issue(id) }
}
......
......@@ -627,6 +627,17 @@ describe API::MergeRequests, api: true do
expect(json_response.first['title']).to eq(issue.title)
expect(json_response.first['id']).to eq(issue.id)
end
it 'returns 403 if the user has no access to the merge request' do
project = create(:empty_project, :private)
merge_request = create(:merge_request, :simple, source_project: project)
guest = create(:user)
project.team << [guest, :guest]
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/closes_issues", guest)
expect(response).to have_http_status(403)
end
end
describe 'POST :id/merge_requests/:merge_request_id/subscription' do
......@@ -648,6 +659,15 @@ describe API::MergeRequests, api: true do
expect(response).to have_http_status(404)
end
it 'returns 403 if user has no access to read code' do
guest = create(:user)
project.team << [guest, :guest]
post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", guest)
expect(response).to have_http_status(403)
end
end
describe 'DELETE :id/merge_requests/:merge_request_id/subscription' do
......@@ -669,6 +689,15 @@ describe API::MergeRequests, api: true do
expect(response).to have_http_status(404)
end
it 'returns 403 if user has no access to read code' do
guest = create(:user)
project.team << [guest, :guest]
delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", guest)
expect(response).to have_http_status(403)
end
end
describe 'Time tracking' do
......
......@@ -183,12 +183,25 @@ describe API::Todos, api: true do
expect(response.status).to eq(404)
end
it 'returns an error if the issuable is not accessible' do
guest = create(:user)
project_1.team << [guest, :guest]
post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.id}/todo", guest)
if issuable_type == 'merge_requests'
expect(response).to have_http_status(403)
else
expect(response).to have_http_status(404)
end
end
end
describe 'POST :id/issuable_type/:issueable_id/todo' do
context 'for an issue' do
it_behaves_like 'an issuable', 'issues' do
let(:issuable) { create(:issue, author: author_1, project: project_1) }
let(:issuable) { create(:issue, :confidential, author: author_1, project: project_1) }
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