Skip to content
Snippets Groups Projects
Commit 3a5df1d8 authored by Robert Speicher's avatar Robert Speicher Committed by Robert Speicher
Browse files

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
No related branches found
No related tags found
No related merge requests found
---
title: Don't allow project guests to subscribe to merge requests through the API
merge_request:
author: Robert Schilling
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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
 
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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
 
Loading
Loading
@@ -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
 
Loading
Loading
@@ -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
 
Loading
Loading
@@ -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?
Loading
Loading
@@ -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
 
Loading
Loading
@@ -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],
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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) },
}
Loading
Loading
Loading
Loading
@@ -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) }
}
 
Loading
Loading
Loading
Loading
@@ -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
Loading
Loading
@@ -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
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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
 
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment