Skip to content
Snippets Groups Projects
Commit 62f3248f authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/security/gitlab@12-5-stable-ee

parent 586bb7dc
No related branches found
No related tags found
No related merge requests found
Showing
with 263 additions and 45 deletions
---
title: Update excon to 0.71.1 to fix CVE-2019-16779
merge_request:
author:
type: security
---
title: Add workhorse request verification to package upload endpoints
merge_request:
author:
type: security
Loading
Loading
@@ -221,6 +221,11 @@ include::basics.adoc[]
include::https://example.org/installation.adoc[]
```
 
To guarantee good system performance and prevent malicious documents causing
problems, GitLab enforces a **maximum limit** on the number of include directives
processed in any one document. Currently a total of 32 documents can be
included, a number that is inclusive of transitive dependencies.
### Blocks
 
```asciidoc
Loading
Loading
Loading
Loading
@@ -85,6 +85,8 @@ module API
protected: @project.protected_for?(ref))
end
 
authorize! :update_pipeline, pipeline
status = GenericCommitStatus.running_or_pending.find_or_initialize_by(
project: @project,
pipeline: pipeline,
Loading
Loading
Loading
Loading
@@ -154,7 +154,7 @@ module API
 
not_found! 'Commit' unless commit
 
present commit, with: Entities::CommitDetail, stats: params[:stats]
present commit, with: Entities::CommitDetail, stats: params[:stats], current_user: current_user
end
 
desc 'Get the diff for a specific commit of a project' do
Loading
Loading
Loading
Loading
@@ -456,8 +456,18 @@ module API
class CommitDetail < Commit
expose :stats, using: Entities::CommitStats, if: :stats
expose :status
expose :last_pipeline, using: 'API::Entities::PipelineBasic'
expose :project_id
expose :last_pipeline do |commit, options|
pipeline = commit.last_pipeline if can_read_pipeline?
::API::Entities::PipelineBasic.represent(pipeline, options)
end
private
def can_read_pipeline?
Ability.allowed?(options[:current_user], :read_pipeline, object.last_pipeline)
end
end
 
class CommitSignature < Grape::Entity
Loading
Loading
Loading
Loading
@@ -127,6 +127,7 @@ module API
get ":id/repository/files/:file_path/raw", requirements: FILE_ENDPOINT_REQUIREMENTS do
assign_file_vars!
 
no_cache_headers
set_http_headers(blob_data)
 
send_git_blob @repo, @blob
Loading
Loading
Loading
Loading
@@ -256,11 +256,19 @@ module API
end
 
def require_gitlab_workhorse!
verify_workhorse_api!
unless env['HTTP_GITLAB_WORKHORSE'].present?
forbidden!('Request should be executed via GitLab Workhorse')
end
end
 
def verify_workhorse_api!
Gitlab::Workhorse.verify_api_request!(request.headers)
rescue
forbidden!
end
def require_pages_enabled!
not_found! unless user_project.pages_available?
end
Loading
Loading
Loading
Loading
@@ -3,6 +3,8 @@
module API
module Helpers
module HeadersHelpers
include Gitlab::NoCacheHeaders
def set_http_headers(header_data)
header_data.each do |key, value|
if value.is_a?(Enumerable)
Loading
Loading
@@ -12,6 +14,12 @@ module API
header "X-Gitlab-#{key.to_s.split('_').collect(&:capitalize).join('-')}", value.to_s
end
end
def no_cache_headers
DEFAULT_GITLAB_NO_CACHE_HEADERS.each do |k, v|
header k, v
end
end
end
end
end
Loading
Loading
@@ -201,12 +201,14 @@ module Banzai
gather_references(nodes)
end
 
# Gathers the references for the given HTML nodes.
# Gathers the references for the given HTML nodes. Returns visible
# references and a list of nodes which are not visible to the user
def gather_references(nodes)
nodes = nodes_user_can_reference(current_user, nodes)
nodes = nodes_visible_to_user(current_user, nodes)
visible = nodes_visible_to_user(current_user, nodes)
not_visible = nodes - visible
 
referenced_by(nodes)
{ visible: referenced_by(visible), not_visible: not_visible }
end
 
# Returns a Hash containing the projects for a given list of HTML nodes.
Loading
Loading
Loading
Loading
@@ -11,6 +11,7 @@ module Gitlab
# the resulting HTML through HTML pipeline filters.
module Asciidoc
MAX_INCLUDE_DEPTH = 5
MAX_INCLUDES = 32
DEFAULT_ADOC_ATTRS = {
'showtitle' => true,
'sectanchors' => true,
Loading
Loading
@@ -40,6 +41,7 @@ module Gitlab
extensions: extensions }
 
context[:pipeline] = :ascii_doc
context[:max_includes] = [MAX_INCLUDES, context[:max_includes]].compact.min
 
plantuml_setup
 
Loading
Loading
Loading
Loading
@@ -13,7 +13,9 @@ module Gitlab
super(logger: Gitlab::AppLogger)
 
@context = context
@repository = context[:project].try(:repository)
@repository = context[:repository] || context[:project].try(:repository)
@max_includes = context[:max_includes].to_i
@included = []
 
# Note: Asciidoctor calls #freeze on extensions, so we can't set new
# instance variables after initialization.
Loading
Loading
@@ -28,8 +30,11 @@ module Gitlab
def include_allowed?(target, reader)
doc = reader.document
 
return false if doc.attributes.fetch('max-include-depth').to_i < 1
max_include_depth = doc.attributes.fetch('max-include-depth').to_i
return false if max_include_depth < 1
return false if target_uri?(target)
return false if included.size >= max_includes
 
true
end
Loading
Loading
@@ -62,7 +67,7 @@ module Gitlab
 
private
 
attr_accessor :context, :repository, :cache
attr_reader :context, :repository, :cache, :max_includes, :included
 
# Gets a Blob at a path for a specific revision.
# This method will check that the Blob exists and contains readable text.
Loading
Loading
@@ -77,6 +82,8 @@ module Gitlab
raise 'Blob not found' unless blob
raise 'File is not readable' unless blob.readable_text?
 
included << filename
blob
end
 
Loading
Loading
# frozen_string_literal: true
module Gitlab
module Git
class CrossRepoComparer
attr_reader :source_repo, :target_repo
def initialize(source_repo, target_repo)
@source_repo = source_repo
@target_repo = target_repo
end
def compare(source_ref, target_ref, straight:)
ensuring_ref_in_source(target_ref) do |target_commit_id|
Gitlab::Git::Compare.new(
source_repo,
target_commit_id,
source_ref,
straight: straight
)
end
end
private
def ensuring_ref_in_source(ref, &blk)
return yield ref if source_repo == target_repo
# If the commit doesn't exist in the target, there's nothing we can do
commit_id = target_repo.commit(ref)&.sha
return unless commit_id
# The commit pointed to by ref may exist in the source even when they
# are different repositories. This is particularly true of close forks,
# but may also be the case if a temporary ref for this comparison has
# already been created in the past, and the result hasn't been GC'd yet.
return yield commit_id if source_repo.commit(commit_id)
# Worst case: the commit is not in the source repo so we need to fetch
# it. Use a temporary ref and clean up afterwards
with_commit_in_source_tmp(commit_id, &blk)
end
# Fetch the ref into source_repo from target_repo, using a temporary ref
# name that will be deleted once the method completes. This is a no-op if
# fetching the source branch fails
def with_commit_in_source_tmp(commit_id, &blk)
tmp_ref = "refs/tmp/#{SecureRandom.hex}"
yield commit_id if source_repo.fetch_source_branch!(target_repo, commit_id, tmp_ref)
ensure
source_repo.delete_refs(tmp_ref) # best-effort
end
end
end
end
Loading
Loading
@@ -746,29 +746,9 @@ module Gitlab
end
 
def compare_source_branch(target_branch_name, source_repository, source_branch_name, straight:)
reachable_ref =
if source_repository == self
source_branch_name
else
# If a tmp ref was created before for a separate repo comparison (forks),
# we're able to short-circuit the tmp ref re-creation:
# 1. Take the SHA from the source repo
# 2. Read that in the current "target" repo
# 3. If that SHA is still known (readable), it means GC hasn't
# cleaned it up yet, so we can use it instead re-writing the tmp ref.
source_commit_id = source_repository.commit(source_branch_name)&.sha
commit(source_commit_id)&.sha if source_commit_id
end
return compare(target_branch_name, reachable_ref, straight: straight) if reachable_ref
tmp_ref = "refs/tmp/#{SecureRandom.hex}"
return unless fetch_source_branch!(source_repository, source_branch_name, tmp_ref)
compare(target_branch_name, tmp_ref, straight: straight)
ensure
delete_refs(tmp_ref) if tmp_ref
CrossRepoComparer
.new(source_repository, self)
.compare(source_branch_name, target_branch_name, straight: straight)
end
 
def write_ref(ref_path, ref, old_ref: nil)
Loading
Loading
@@ -1045,13 +1025,6 @@ module Gitlab
 
private
 
def compare(base_ref, head_ref, straight:)
Gitlab::Git::Compare.new(self,
base_ref,
head_ref,
straight: straight)
end
def empty_diff_stats
Gitlab::Git::DiffStatsCollection.new([])
end
Loading
Loading
# frozen_string_literal: true
module Gitlab
module NoCacheHeaders
DEFAULT_GITLAB_NO_CACHE_HEADERS = {
'Cache-Control' => "#{ActionDispatch::Http::Cache::Response::DEFAULT_CACHE_CONTROL}, no-store, no-cache",
'Pragma' => 'no-cache', # HTTP 1.0 compatibility
'Expires' => 'Fri, 01 Jan 1990 00:00:00 GMT'
}.freeze
def no_cache_headers
raise "#no_cache_headers is not implemented for this object"
end
end
end
Loading
Loading
@@ -6,11 +6,16 @@ module Gitlab
REFERABLES = %i(user issue label milestone
merge_request snippet commit commit_range directly_addressed_user epic).freeze
attr_accessor :project, :current_user, :author
# This counter is increased by a number of references filtered out by
# banzai reference exctractor. Note that this counter is stateful and
# not idempotent and is increased whenever you call `references`.
attr_reader :stateful_not_visible_counter
 
def initialize(project, current_user = nil)
@project = project
@current_user = current_user
@references = {}
@stateful_not_visible_counter = 0
 
super()
end
Loading
Loading
@@ -20,11 +25,15 @@ module Gitlab
end
 
def references(type)
super(type, project, current_user)
refs = super(type, project, current_user)
@stateful_not_visible_counter += refs[:not_visible].count
refs[:visible]
end
 
def reset_memoized_values
@references = {}
@stateful_not_visible_counter = 0
super()
end
 
Loading
Loading
Loading
Loading
@@ -99,6 +99,7 @@
"jszip": "^3.1.3",
"jszip-utils": "^0.0.2",
"katex": "^0.10.0",
"lodash": "^4.17.15",
"marked": "^0.3.12",
"mermaid": "^8.4.2",
"monaco-editor": "^0.15.6",
Loading
Loading
Loading
Loading
@@ -59,5 +59,48 @@ module QA
a_hash_including(message: '202 Accepted')
)
end
describe 'raw file access' do
let(:svg_file) do
<<-SVG
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
<polygon id="triangle" points="0,0 0,50 50,0" fill="#009900" stroke="#004400"/>
<script type="text/javascript">
alert("surprise");
</script>
</svg>
SVG
end
it 'sets no-cache headers as expected' do
create_project_request = Runtime::API::Request.new(@api_client, '/projects')
post create_project_request.url, path: project_name, name: project_name
create_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/test.svg")
post create_file_request.url, branch: 'master', content: svg_file, commit_message: 'Add test.svg'
get_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/test.svg/raw", ref: 'master')
3.times do
response = get get_file_request.url
# Subsequent responses aren't cached, so headers should match from
# request to request, especially a 200 response rather than a 304
# (indicating a cached response.) Further, :content_disposition
# should include `attachment` for all responses.
#
expect(response.headers[:cache_control]).to include("no-store")
expect(response.headers[:cache_control]).to include("no-cache")
expect(response.headers[:pragma]).to eq("no-cache")
expect(response.headers[:expires]).to eq("Fri, 01 Jan 1990 00:00:00 GMT")
expect(response.headers[:content_disposition]).to include("attachment")
expect(response.headers[:content_disposition]).not_to include("inline")
expect(response.headers[:content_type]).to include("image/svg+xml")
end
end
end
end
end
Loading
Loading
@@ -23,6 +23,47 @@ describe DashboardController do
end
end
 
describe "GET activity as JSON" do
render_views
let(:user) { create(:user) }
let(:project) { create(:project, :public, issues_access_level: ProjectFeature::PRIVATE) }
before do
create(:event, :created, project: project, target: create(:issue))
sign_in(user)
request.cookies[:event_filter] = 'all'
end
context 'when user has permission to see the event' do
before do
project.add_developer(user)
end
it 'returns count' do
get :activity, params: { format: :json }
expect(json_response['count']).to eq(1)
end
end
context 'when user has no permission to see the event' do
it 'filters out invisible event' do
get :activity, params: { format: :json }
expect(json_response['html']).to include(_('No activities found'))
end
it 'filters out invisible event when calculating the count' do
get :activity, params: { format: :json }
expect(json_response['count']).to eq(0)
end
end
end
it_behaves_like 'authenticates sessionless user', :issues, :atom, author_id: User.first
it_behaves_like 'authenticates sessionless user', :issues_calendar, :ics
 
Loading
Loading
Loading
Loading
@@ -47,7 +47,7 @@ describe GroupsController do
 
it 'assigns events for all the projects in the group', :sidekiq_might_not_need_inline do
subject
expect(assigns(:events)).to contain_exactly(event)
expect(assigns(:events).map(&:id)).to contain_exactly(event.id)
end
end
end
Loading
Loading
@@ -119,12 +119,12 @@ describe GroupsController do
describe 'GET #activity' do
render_views
 
before do
sign_in(user)
project
end
context 'as json' do
before do
sign_in(user)
project
end
it 'includes events from all projects in group and subgroups', :sidekiq_might_not_need_inline do
2.times do
project = create(:project, group: group)
Loading
Loading
@@ -141,6 +141,31 @@ describe GroupsController do
expect(assigns(:projects).limit_value).to be_nil
end
end
context 'when user has no permission to see the event' do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:project) { create(:project, group: group) }
let(:project_with_restricted_access) do
create(:project, :public, issues_access_level: ProjectFeature::PRIVATE, group: group)
end
before do
create(:event, project: project)
create(:event, :created, project: project_with_restricted_access, target: create(:issue))
group.add_guest(user)
sign_in(user)
end
it 'filters out invisible event' do
get :activity, params: { id: group.to_param }, format: :json
expect(json_response['count']).to eq(1)
end
end
end
 
describe 'POST #create' do
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