Commit 08a8aa66 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/security/gitlab@13-8-stable-ee

parent 09cb1f3e
---
title: Check user access on API merge request read actions
merge_request:
author:
type: security
---
title: Updates authorization for linting API
merge_request:
author:
type: security
...@@ -11,6 +11,8 @@ module API ...@@ -11,6 +11,8 @@ module API
optional :include_merged_yaml, type: Boolean, desc: 'Whether or not to include merged CI config yaml in the response' optional :include_merged_yaml, type: Boolean, desc: 'Whether or not to include merged CI config yaml in the response'
end end
post '/lint' do post '/lint' do
unauthorized! unless Gitlab::CurrentSettings.signup_enabled? && current_user
result = Gitlab::Ci::YamlProcessor.new(params[:content], user: current_user).execute result = Gitlab::Ci::YamlProcessor.new(params[:content], user: current_user).execute
status 200 status 200
...@@ -55,7 +57,7 @@ module API ...@@ -55,7 +57,7 @@ module API
optional :dry_run, type: Boolean, default: false, desc: 'Run pipeline creation simulation, or only do static check.' optional :dry_run, type: Boolean, default: false, desc: 'Run pipeline creation simulation, or only do static check.'
end end
post ':id/ci/lint' do post ':id/ci/lint' do
authorize! :download_code, user_project authorize! :create_pipeline, user_project
result = Gitlab::Ci::Lint result = Gitlab::Ci::Lint
.new(project: user_project, current_user: current_user) .new(project: user_project, current_user: current_user)
......
...@@ -26,6 +26,8 @@ module API ...@@ -26,6 +26,8 @@ module API
# GET /projects/:id/merge_requests/:merge_request_iid/approvals # GET /projects/:id/merge_requests/:merge_request_iid/approvals
desc 'List approvals for merge request' desc 'List approvals for merge request'
get 'approvals' do get 'approvals' do
not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
merge_request = find_merge_request_with_access(params[:merge_request_iid]) merge_request = find_merge_request_with_access(params[:merge_request_iid])
present_approval(merge_request) present_approval(merge_request)
......
...@@ -23,6 +23,8 @@ module API ...@@ -23,6 +23,8 @@ module API
use :pagination use :pagination
end end
get ":id/merge_requests/:merge_request_iid/versions" do get ":id/merge_requests/:merge_request_iid/versions" do
not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
merge_request = find_merge_request_with_access(params[:merge_request_iid]) merge_request = find_merge_request_with_access(params[:merge_request_iid])
present paginate(merge_request.merge_request_diffs.order_id_desc), with: Entities::MergeRequestDiff present paginate(merge_request.merge_request_diffs.order_id_desc), with: Entities::MergeRequestDiff
...@@ -39,6 +41,8 @@ module API ...@@ -39,6 +41,8 @@ module API
end end
get ":id/merge_requests/:merge_request_iid/versions/:version_id" do get ":id/merge_requests/:merge_request_iid/versions/:version_id" do
not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
merge_request = find_merge_request_with_access(params[:merge_request_iid]) merge_request = find_merge_request_with_access(params[:merge_request_iid])
present merge_request.merge_request_diffs.find(params[:version_id]), with: Entities::MergeRequestDiffFull present merge_request.merge_request_diffs.find(params[:version_id]), with: Entities::MergeRequestDiffFull
......
...@@ -246,6 +246,8 @@ module API ...@@ -246,6 +246,8 @@ module API
success Entities::MergeRequest success Entities::MergeRequest
end end
get ':id/merge_requests/:merge_request_iid' do get ':id/merge_requests/:merge_request_iid' do
not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
merge_request = find_merge_request_with_access(params[:merge_request_iid]) merge_request = find_merge_request_with_access(params[:merge_request_iid])
present merge_request, present merge_request,
...@@ -262,7 +264,10 @@ module API ...@@ -262,7 +264,10 @@ module API
success Entities::UserBasic success Entities::UserBasic
end end
get ':id/merge_requests/:merge_request_iid/participants' do get ':id/merge_requests/:merge_request_iid/participants' do
not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
merge_request = find_merge_request_with_access(params[:merge_request_iid]) merge_request = find_merge_request_with_access(params[:merge_request_iid])
participants = ::Kaminari.paginate_array(merge_request.participants) participants = ::Kaminari.paginate_array(merge_request.participants)
present paginate(participants), with: Entities::UserBasic present paginate(participants), with: Entities::UserBasic
...@@ -272,6 +277,8 @@ module API ...@@ -272,6 +277,8 @@ module API
success Entities::Commit success Entities::Commit
end end
get ':id/merge_requests/:merge_request_iid/commits' do get ':id/merge_requests/:merge_request_iid/commits' do
not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
merge_request = find_merge_request_with_access(params[:merge_request_iid]) merge_request = find_merge_request_with_access(params[:merge_request_iid])
commits = commits =
...@@ -353,6 +360,8 @@ module API ...@@ -353,6 +360,8 @@ module API
success Entities::MergeRequestChanges success Entities::MergeRequestChanges
end end
get ':id/merge_requests/:merge_request_iid/changes' do get ':id/merge_requests/:merge_request_iid/changes' do
not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
merge_request = find_merge_request_with_access(params[:merge_request_iid]) merge_request = find_merge_request_with_access(params[:merge_request_iid])
present merge_request, present merge_request,
...@@ -368,6 +377,8 @@ module API ...@@ -368,6 +377,8 @@ module API
get ':id/merge_requests/:merge_request_iid/pipelines' do get ':id/merge_requests/:merge_request_iid/pipelines' do
pipelines = merge_request_pipelines_with_access pipelines = merge_request_pipelines_with_access
not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
present paginate(pipelines), with: Entities::Ci::PipelineBasic present paginate(pipelines), with: Entities::Ci::PipelineBasic
end end
......
...@@ -28,6 +28,11 @@ module API ...@@ -28,6 +28,11 @@ module API
end end
post ":id/#{type}/:#{type_id_str}/todo" do post ":id/#{type}/:#{type_id_str}/todo" do
issuable = instance_exec(params[type_id_str], &finder) issuable = instance_exec(params[type_id_str], &finder)
unless can?(current_user, :read_merge_request, issuable.project)
not_found!(type.split("_").map(&:capitalize).join(" "))
end
todo = TodoService.new.mark_todo(issuable, current_user).first todo = TodoService.new.mark_todo(issuable, current_user).first
if todo if todo
......
...@@ -4,91 +4,136 @@ require 'spec_helper' ...@@ -4,91 +4,136 @@ require 'spec_helper'
RSpec.describe API::Lint do RSpec.describe API::Lint do
describe 'POST /ci/lint' do describe 'POST /ci/lint' do
context 'with valid .gitlab-ci.yaml content' do context 'when signup settings are disabled' do
let(:yaml_content) do Gitlab::CurrentSettings.signup_enabled = false
File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
end
it 'passes validation without warnings or errors' do context 'when unauthenticated' do
post api('/ci/lint'), params: { content: yaml_content } it 'returns authentication error' do
post api('/ci/lint'), params: { content: 'content' }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:unauthorized)
expect(json_response).to be_an Hash end
expect(json_response['status']).to eq('valid')
expect(json_response['warnings']).to eq([])
expect(json_response['errors']).to eq([])
end end
it 'outputs expanded yaml content' do context 'when authenticated' do
post api('/ci/lint'), params: { content: yaml_content, include_merged_yaml: true } it 'returns unauthorized error' do
post api('/ci/lint'), params: { content: 'content' }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:unauthorized)
expect(json_response).to have_key('merged_yaml') end
end end
end end
context 'with valid .gitlab-ci.yaml with warnings' do context 'when signup settings are enabled' do
let(:yaml_content) { { job: { script: 'ls', rules: [{ when: 'always' }] } }.to_yaml } Gitlab::CurrentSettings.signup_enabled = true
it 'passes validation but returns warnings' do context 'when unauthenticated' do
post api('/ci/lint'), params: { content: yaml_content } it 'returns authentication error' do
post api('/ci/lint'), params: { content: 'content' }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:unauthorized)
expect(json_response['status']).to eq('valid') end
expect(json_response['warnings']).not_to be_empty end
expect(json_response['status']).to eq('valid')
expect(json_response['errors']).to eq([]) context 'when authenticated' do
let_it_be(:api_user) { create(:user) }
it 'returns authentication success' do
post api('/ci/lint', api_user), params: { content: 'content' }
expect(response).to have_gitlab_http_status(:ok)
end
end end
end end
context 'with an invalid .gitlab_ci.yml' do context 'when authenticated' do
context 'with invalid syntax' do let_it_be(:api_user) { create(:user) }
let(:yaml_content) { 'invalid content' }
it 'responds with errors about invalid syntax' do context 'with valid .gitlab-ci.yaml content' do
post api('/ci/lint'), params: { content: yaml_content } let(:yaml_content) do
File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
end
it 'passes validation without warnings or errors' do
post api('/ci/lint', api_user), params: { content: yaml_content }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response['status']).to eq('invalid') expect(json_response).to be_an Hash
expect(json_response['status']).to eq('valid')
expect(json_response['warnings']).to eq([]) expect(json_response['warnings']).to eq([])
expect(json_response['errors']).to eq(['Invalid configuration format']) expect(json_response['errors']).to eq([])
end end
it 'outputs expanded yaml content' do it 'outputs expanded yaml content' do
post api('/ci/lint'), params: { content: yaml_content, include_merged_yaml: true } post api('/ci/lint', api_user), params: { content: yaml_content, include_merged_yaml: true }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to have_key('merged_yaml') expect(json_response).to have_key('merged_yaml')
end end
end end
context 'with invalid configuration' do context 'with valid .gitlab-ci.yaml with warnings' do
let(:yaml_content) { '{ image: "ruby:2.7", services: ["postgres"], invalid }' } let(:yaml_content) { { job: { script: 'ls', rules: [{ when: 'always' }] } }.to_yaml }
it 'responds with errors about invalid configuration' do it 'passes validation but returns warnings' do
post api('/ci/lint'), params: { content: yaml_content } post api('/ci/lint', api_user), params: { content: yaml_content }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response['status']).to eq('invalid') expect(json_response['status']).to eq('valid')
expect(json_response['warnings']).to eq([]) expect(json_response['warnings']).not_to be_empty
expect(json_response['errors']).to eq(['jobs invalid config should implement a script: or a trigger: keyword', 'jobs config should contain at least one visible job']) expect(json_response['status']).to eq('valid')
expect(json_response['errors']).to eq([])
end end
end
it 'outputs expanded yaml content' do context 'with an invalid .gitlab_ci.yml' do
post api('/ci/lint'), params: { content: yaml_content, include_merged_yaml: true } context 'with invalid syntax' do
let(:yaml_content) { 'invalid content' }
expect(response).to have_gitlab_http_status(:ok) it 'responds with errors about invalid syntax' do
expect(json_response).to have_key('merged_yaml') post api('/ci/lint', api_user), params: { content: yaml_content }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['status']).to eq('invalid')
expect(json_response['warnings']).to eq([])
expect(json_response['errors']).to eq(['Invalid configuration format'])
end
it 'outputs expanded yaml content' do
post api('/ci/lint', api_user), params: { content: yaml_content, include_merged_yaml: true }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to have_key('merged_yaml')
end
end
context 'with invalid configuration' do
let(:yaml_content) { '{ image: "ruby:2.7", services: ["postgres"] }' }
it 'responds with errors about invalid configuration' do
post api('/ci/lint', api_user), params: { content: yaml_content }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['status']).to eq('invalid')
expect(json_response['warnings']).to eq([])
expect(json_response['errors']).to eq(['jobs config should contain at least one visible job'])
end
it 'outputs expanded yaml content' do
post api('/ci/lint', api_user), params: { content: yaml_content, include_merged_yaml: true }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to have_key('merged_yaml')
end
end end
end end
end
context 'without the content parameter' do context 'without the content parameter' do
it 'responds with validation error about missing content' do it 'responds with validation error about missing content' do
post api('/ci/lint') post api('/ci/lint', api_user)
expect(response).to have_gitlab_http_status(:bad_request) expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to eq('content is missing') expect(json_response['error']).to eq('content is missing')
end
end end
end end
end end
...@@ -364,6 +409,18 @@ RSpec.describe API::Lint do ...@@ -364,6 +409,18 @@ RSpec.describe API::Lint do
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
end end
context 'when project is public' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
end
it 'returns authentication error' do
ci_lint
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end end
context 'when authenticated as non-member' do context 'when authenticated as non-member' do
...@@ -387,13 +444,10 @@ RSpec.describe API::Lint do ...@@ -387,13 +444,10 @@ RSpec.describe API::Lint do
context 'when running as dry run' do context 'when running as dry run' do
let(:dry_run) { true } let(:dry_run) { true }
it 'returns pipeline creation error' do it 'returns authentication error' do
ci_lint ci_lint
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:forbidden)
expect(json_response['merged_yaml']).to eq(nil)
expect(json_response['valid']).to eq(false)
expect(json_response['errors']).to eq(['Insufficient permissions to create a new pipeline'])
end end
end end
...@@ -410,7 +464,11 @@ RSpec.describe API::Lint do ...@@ -410,7 +464,11 @@ RSpec.describe API::Lint do
) )
end end
it_behaves_like 'valid project config' it 'returns authentication error' do
ci_lint
expect(response).to have_gitlab_http_status(:forbidden)
end
end end
end end
end end
......
...@@ -21,6 +21,12 @@ RSpec.describe API::MergeRequestApprovals do ...@@ -21,6 +21,12 @@ RSpec.describe API::MergeRequestApprovals do
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
end end
context 'when merge request author has only guest access' do
it_behaves_like 'rejects user from accessing merge request info' do
let(:url) { "/projects/#{project.id}/merge_requests/#{merge_request.iid}/approvals" }
end
end
end end
describe 'POST :id/merge_requests/:merge_request_iid/approve' do describe 'POST :id/merge_requests/:merge_request_iid/approve' do
......
...@@ -35,6 +35,12 @@ RSpec.describe API::MergeRequestDiffs, 'MergeRequestDiffs' do ...@@ -35,6 +35,12 @@ RSpec.describe API::MergeRequestDiffs, 'MergeRequestDiffs' do
get api("/projects/#{project.id}/merge_requests/0/versions", user) get api("/projects/#{project.id}/merge_requests/0/versions", user)
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
end end
context 'when merge request author has only guest access' do
it_behaves_like 'rejects user from accessing merge request info' do
let(:url) { "/projects/#{project.id}/merge_requests/#{merge_request.iid}/versions" }
end
end
end end
describe 'GET /projects/:id/merge_requests/:merge_request_iid/versions/:version_id' do describe 'GET /projects/:id/merge_requests/:merge_request_iid/versions/:version_id' do
...@@ -63,5 +69,11 @@ RSpec.describe API::MergeRequestDiffs, 'MergeRequestDiffs' do ...@@ -63,5 +69,11 @@ RSpec.describe API::MergeRequestDiffs, 'MergeRequestDiffs' do
get api("/projects/#{project.id}/merge_requests/#{non_existing_record_iid}/versions/#{merge_request_diff.id}", user) get api("/projects/#{project.id}/merge_requests/#{non_existing_record_iid}/versions/#{merge_request_diff.id}", user)
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
end end
context 'when merge request author has only guest access' do
it_behaves_like 'rejects user from accessing merge request info' do
let(:url) { "/projects/#{project.id}/merge_requests/#{merge_request.iid}/versions/#{merge_request_diff.id}" }
end
end
end end
end end
...@@ -1226,6 +1226,12 @@ RSpec.describe API::MergeRequests do ...@@ -1226,6 +1226,12 @@ RSpec.describe API::MergeRequests do
end end
end end
context 'when merge request author has only guest access' do
it_behaves_like 'rejects user from accessing merge request info' do
let(:url) { "/projects/#{project.id}/merge_requests/#{merge_request.iid}" }
end
end
context 'merge_request_metrics' do context 'merge_request_metrics' do
let(:pipeline) { create(:ci_empty_pipeline) } let(:pipeline) { create(:ci_empty_pipeline) }
...@@ -1402,6 +1408,12 @@ RSpec.describe API::MergeRequests do ...@@ -1402,6 +1408,12 @@ RSpec.describe API::MergeRequests do
it_behaves_like 'issuable participants endpoint' do it_behaves_like 'issuable participants endpoint' do
let(:entity) { create(:merge_request, :simple, milestone: milestone1, author: user, assignees: [user], source_project: project, target_project: project, source_branch: 'markdown', title: "Test", created_at: base_time) } let(:entity) { create(:merge_request, :simple, milestone: milestone1, author: user, assignees: [user], source_project: project, target_project: project, source_branch: 'markdown', title: "Test", created_at: base_time) }
end end
context 'when merge request author has only guest access' do
it_behaves_like 'rejects user from accessing merge request info' do
let(:url) { "/projects/#{project.id}/merge_requests/#{merge_request.iid}/participants" }
end
end
end end
describe 'GET /projects/:id/merge_requests/:merge_request_iid/commits' do describe 'GET /projects/:id/merge_requests/:merge_request_iid/commits' do
...@@ -1427,6 +1439,12 @@ RSpec.describe API::MergeRequests do ...@@ -1427,6 +1439,12 @@ RSpec.describe API::MergeRequests do
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
end end
context 'when merge request author has only guest access' do
it_behaves_like 'rejects user from accessing merge request info' do
let(:url) { "/projects/#{project.id}/merge_requests/#{merge_request.iid}/commits" }
end
end
end end
describe 'GET /projects/:id/merge_requests/:merge_request_iid/:context_commits' do describe 'GET /projects/:id/merge_requests/:merge_request_iid/:context_commits' do
...@@ -1502,6 +1520,12 @@ RSpec.describe API::MergeRequests do ...@@ -1502,6 +1520,12 @@ RSpec.describe API::MergeRequests do
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
end end
context 'when merge request author has only guest access' do
it_behaves_like 'rejects user from accessing merge request info' do
let(:url) { "/projects/#{project.id}/merge_requests/#{merge_request.iid}/changes" }
end
end
it_behaves_like 'find an existing merge request' it_behaves_like 'find an existing merge request'
it_behaves_like 'accesses diffs via raw_diffs' it_behaves_like 'accesses diffs via raw_diffs'
...@@ -1591,6 +1615,12 @@ RSpec.describe API::MergeRequests do ...@@ -1591,6 +1615,12 @@ RSpec.describe API::MergeRequests do
expect(response).to have_gitlab_http_status(:forbidden) expect(response).to have_gitlab_http_status(:forbidden)
end end
end end
context 'when merge request author has only guest access' do
it_behaves_like 'rejects user from accessing merge request info' do
let(:url) { "/projects/#{project.id}/merge_requests/#{merge_request.iid}/pipelines" }
end
end
end end
describe 'POST /projects/:id/merge_requests/:merge_request_iid/pipelines' do describe 'POST /projects/:id/merge_requests/:merge_request_iid/pipelines' do
......
...@@ -331,6 +331,14 @@ RSpec.describe API::Todos do ...@@ -331,6 +331,14 @@ RSpec.describe API::Todos do
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
end end
end end
it 'returns an error if the issuable author does not have access' do
project_1.add_guest(issuable.author)
post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.iid}/todo", issuable.author)
expect(response).to have_gitlab_http_status(:not_found)
end
end end
describe 'POST :id/issuable_type/:issueable_id/todo' do describe 'POST :id/issuable_type/:issueable_id/todo' do
......
# frozen_string_literal: true
RSpec.shared_examples 'rejects user from accessing merge request info' do
let(:project) { create(:project, :private) }
let(:merge_request) do
create(:merge_request,
author: user,
source_project: project,
target_project: project
)
end
before do
project.add_guest(user)
end
it 'returns a 404 error' do
get api(url, user)
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 Merge Request Not Found')
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