Commit e810b832 authored by GitLab Bot's avatar GitLab Bot

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

parent 11e9b7b5
...@@ -58,7 +58,7 @@ module WikiActions ...@@ -58,7 +58,7 @@ module WikiActions
render 'shared/wikis/show' render 'shared/wikis/show'
elsif file_blob elsif file_blob
send_blob(wiki.repository, file_blob, allow_caching: container.public?) send_blob(wiki.repository, file_blob)
elsif show_create_form? elsif show_create_form?
# Assign a title to the WikiPage unless `id` is a randomly generated slug from #new # Assign a title to the WikiPage unless `id` is a randomly generated slug from #new
title = params[:id] unless params[:random_title].present? title = params[:id] unless params[:random_title].present?
......
...@@ -33,6 +33,8 @@ class EventsFinder ...@@ -33,6 +33,8 @@ class EventsFinder
end end
def execute def execute
return Event.none if cannot_access_private_profile?
events = get_events events = get_events
events = by_current_user_access(events) events = by_current_user_access(events)
...@@ -103,6 +105,10 @@ class EventsFinder ...@@ -103,6 +105,10 @@ class EventsFinder
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
def cannot_access_private_profile?
source.is_a?(User) && !Ability.allowed?(current_user, :read_user_profile, source)
end
def sort(events) def sort(events)
return events unless params[:sort] return events unless params[:sort]
......
...@@ -518,7 +518,7 @@ class MergeRequest < ApplicationRecord ...@@ -518,7 +518,7 @@ class MergeRequest < ApplicationRecord
participants << merge_user participants << merge_user
end end
participants participants.select { |participant| Ability.allowed?(participant, :read_merge_request, self) }
end end
def first_commit def first_commit
......
# frozen_string_literal: true
module Snippets
class RepositoryValidationService
attr_reader :current_user, :snippet, :repository
RepositoryValidationError = Class.new(StandardError)
def initialize(user, snippet)
@current_user = user
@snippet = snippet
@repository = snippet.repository
end
def execute
if snippet.nil?
return service_response_error('No snippet found.', 404)
end
check_branch_count!
check_branch_name_default!
check_tag_count!
check_file_count!
check_size!
ServiceResponse.success(message: 'Valid snippet repository.')
rescue RepositoryValidationError => e
ServiceResponse.error(message: "Error: #{e.message}", http_status: 400)
end
private
def check_branch_count!
return if repository.branch_count == 1
raise RepositoryValidationError, _('Repository has more than one branch.')
end
def check_branch_name_default!
branches = repository.branch_names
return if branches.first == Gitlab::Checks::SnippetCheck::DEFAULT_BRANCH
raise RepositoryValidationError, _('Repository has an invalid default branch name.')
end
def check_tag_count!
return if repository.tag_count == 0
raise RepositoryValidationError, _('Repository has tags.')
end
def check_file_count!
file_count = repository.ls_files(nil).size
limit = Snippet.max_file_limit(current_user)
if file_count > limit
raise RepositoryValidationError, _('Repository files count over the limit')
end
if file_count == 0
raise RepositoryValidationError, _('Repository must contain at least 1 file.')
end
end
def check_size!
return unless snippet.repository_size_checker.above_size_limit?
raise RepositoryValidationError, _('Repository size is above the limit.')
end
end
end
---
title: Do not show activity for users with private profiles
merge_request:
author:
type: security
---
title: Check access when sending TODOs related to merge requests
merge_request:
author:
type: security
---
title: Disable caching for wiki attachments
merge_request:
author:
type: security
---
title: Add snippet repository validation after bundle import
merge_request:
author:
type: security
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
module Gitlab module Gitlab
module ImportExport module ImportExport
class SnippetRepoRestorer < RepoRestorer class SnippetRepoRestorer < RepoRestorer
attr_reader :snippet attr_reader :snippet, :user
SnippetRepositoryError = Class.new(StandardError) SnippetRepositoryError = Class.new(StandardError)
...@@ -33,6 +33,16 @@ module Gitlab ...@@ -33,6 +33,16 @@ module Gitlab
def create_repository_from_bundle def create_repository_from_bundle
repository.create_from_bundle(path_to_bundle) repository.create_from_bundle(path_to_bundle)
snippet.track_snippet_repository(repository.storage) snippet.track_snippet_repository(repository.storage)
response = Snippets::RepositoryValidationService.new(user, snippet).execute
if response.error?
repository.remove
snippet.snippet_repository.delete
snippet.repository.expire_exists_cache
raise SnippetRepositoryError, _("Invalid repository bundle for snippet with id %{snippet_id}") % { snippet_id: snippet.id }
end
end end
def create_repository_from_db def create_repository_from_db
......
...@@ -12299,6 +12299,9 @@ msgstr "" ...@@ -12299,6 +12299,9 @@ msgstr ""
msgid "Invalid query" msgid "Invalid query"
msgstr "" msgstr ""
msgid "Invalid repository bundle for snippet with id %{snippet_id}"
msgstr ""
msgid "Invalid repository path" msgid "Invalid repository path"
msgstr "" msgstr ""
...@@ -18943,15 +18946,33 @@ msgstr "" ...@@ -18943,15 +18946,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete." msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr "" msgstr ""
msgid "Repository files count over the limit"
msgstr ""
msgid "Repository has an invalid default branch name."
msgstr ""
msgid "Repository has more than one branch."
msgstr ""
msgid "Repository has no locks." msgid "Repository has no locks."
msgstr "" msgstr ""
msgid "Repository has tags."
msgstr ""
msgid "Repository maintenance" msgid "Repository maintenance"
msgstr "" msgstr ""
msgid "Repository mirroring" msgid "Repository mirroring"
msgstr "" msgstr ""
msgid "Repository must contain at least 1 file."
msgstr ""
msgid "Repository size is above the limit."
msgstr ""
msgid "Repository static objects" msgid "Repository static objects"
msgstr "" msgstr ""
......
...@@ -4,6 +4,7 @@ require 'spec_helper' ...@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe EventsFinder do RSpec.describe EventsFinder do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:private_user) { create(:user, private_profile: true) }
let(:other_user) { create(:user) } let(:other_user) { create(:user) }
let(:project1) { create(:project, :private, creator_id: user.id, namespace: user.namespace) } let(:project1) { create(:project, :private, creator_id: user.id, namespace: user.namespace) }
...@@ -57,6 +58,12 @@ RSpec.describe EventsFinder do ...@@ -57,6 +58,12 @@ RSpec.describe EventsFinder do
expect(events).to be_empty expect(events).to be_empty
end end
it 'returns nothing when the target profile is private' do
events = described_class.new(source: private_user, current_user: other_user).execute
expect(events).to be_empty
end
end end
describe 'wiki events feature flag' do describe 'wiki events feature flag' do
......
...@@ -4,9 +4,9 @@ require 'spec_helper' ...@@ -4,9 +4,9 @@ require 'spec_helper'
describe Gitlab::ImportExport::SnippetRepoRestorer do describe Gitlab::ImportExport::SnippetRepoRestorer do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, namespace: user.namespace) }
let(:snippet) { create(:project_snippet, project: project, author: user) }
let(:project) { create(:project, namespace: user.namespace) }
let(:snippet) { create(:project_snippet, project: project, author: user) }
let(:shared) { project.import_export_shared } let(:shared) { project.import_export_shared }
let(:exporter) { Gitlab::ImportExport::SnippetsRepoSaver.new(project: project, shared: shared, current_user: user) } let(:exporter) { Gitlab::ImportExport::SnippetsRepoSaver.new(project: project, shared: shared, current_user: user) }
let(:restorer) do let(:restorer) do
...@@ -57,33 +57,63 @@ describe Gitlab::ImportExport::SnippetRepoRestorer do ...@@ -57,33 +57,63 @@ describe Gitlab::ImportExport::SnippetRepoRestorer do
it_behaves_like 'no bundle file present' it_behaves_like 'no bundle file present'
end end
context 'when the snippet bundle exists' do context 'when the snippet repository bundle exists' do
let!(:snippet_with_repo) { create(:project_snippet, :repository, project: project) } let!(:snippet_with_repo) { create(:project_snippet, :repository, project: project, author: user) }
let(:bundle_path) { ::Gitlab::ImportExport.snippets_repo_bundle_path(shared.export_path) } let(:bundle_path) { ::Gitlab::ImportExport.snippets_repo_bundle_path(shared.export_path) }
let(:snippet_bundle_path) { File.join(bundle_path, "#{snippet_with_repo.hexdigest}.bundle") } let(:snippet_bundle_path) { File.join(bundle_path, "#{snippet_with_repo.hexdigest}.bundle") }
let(:result) { exporter.save } let(:result) { exporter.save }
let(:repository) { snippet.repository }
before do before do
expect(exporter.save).to be_truthy expect(exporter.save).to be_truthy
end end
it 'creates the repository from the bundle' do context 'when it is valid' do
expect(snippet.repository_exists?).to be_falsey before do
expect(snippet.snippet_repository).to be_nil allow(repository).to receive(:branch_count).and_return(1)
expect(snippet.repository).to receive(:create_from_bundle).and_call_original allow(repository).to receive(:tag_count).and_return(0)
allow(repository).to receive(:branch_names).and_return(['master'])
allow(repository).to receive(:ls_files).and_return(['foo'])
end
expect(restorer.restore).to be_truthy it 'creates the repository from the bundle' do
expect(snippet.repository_exists?).to be_truthy expect(snippet.repository_exists?).to be_falsey
expect(snippet.snippet_repository).not_to be_nil expect(snippet.snippet_repository).to be_nil
end expect(repository).to receive(:create_from_bundle).and_call_original
it 'sets same shard in snippet repository as in the repository storage' do expect(restorer.restore).to be_truthy
expect(snippet).to receive(:repository_storage).and_return('picked') expect(snippet.repository_exists?).to be_truthy
expect(snippet.repository).to receive(:create_from_bundle) expect(snippet.snippet_repository).not_to be_nil
end
restorer.restore it 'sets same shard in snippet repository as in the repository storage' do
expect(repository).to receive(:storage).and_return('picked')
expect(repository).to receive(:create_from_bundle)
expect(snippet.snippet_repository.shard_name).to eq 'picked' expect(restorer.restore).to be_truthy
expect(snippet.snippet_repository.shard_name).to eq 'picked'
end
end
context 'when it is invalid' do
it 'returns false and deletes the repository from disk and the database' do
gitlab_shell = Gitlab::Shell.new
shard_name = snippet.repository.shard
path = snippet.disk_path + '.git'
error_response = ServiceResponse.error(message: 'Foo', http_status: 400)
allow_next_instance_of(Snippets::RepositoryValidationService) do |instance|
allow(instance).to receive(:execute).and_return(error_response)
end
aggregate_failures do
expect(restorer.restore).to be false
expect(shared.errors.first).to match(/Invalid repository bundle/)
expect(snippet.repository_exists?).to eq false
expect(snippet.reload.snippet_repository).to be_nil
expect(gitlab_shell.repository_exists?(shard_name, path)).to eq false
end
end
end end
end end
end end
...@@ -38,6 +38,7 @@ describe Gitlab::ImportExport::SnippetsRepoRestorer do ...@@ -38,6 +38,7 @@ describe Gitlab::ImportExport::SnippetsRepoRestorer do
expect(snippet1.repository_exists?).to be false expect(snippet1.repository_exists?).to be false
expect(snippet2.repository_exists?).to be false expect(snippet2.repository_exists?).to be false
allow_any_instance_of(Snippets::RepositoryValidationService).to receive(:execute).and_return(ServiceResponse.success)
expect(Gitlab::ImportExport::SnippetRepoRestorer).to receive(:new).with(hash_including(snippet: snippet1, path_to_bundle: bundle_path(snippet1))).and_call_original expect(Gitlab::ImportExport::SnippetRepoRestorer).to receive(:new).with(hash_including(snippet: snippet1, path_to_bundle: bundle_path(snippet1))).and_call_original
expect(Gitlab::ImportExport::SnippetRepoRestorer).to receive(:new).with(hash_including(snippet: snippet2, path_to_bundle: bundle_path(snippet2))).and_call_original expect(Gitlab::ImportExport::SnippetRepoRestorer).to receive(:new).with(hash_including(snippet: snippet2, path_to_bundle: bundle_path(snippet2))).and_call_original
expect(restorer.restore).to be_truthy expect(restorer.restore).to be_truthy
......
...@@ -3655,7 +3655,7 @@ describe MergeRequest do ...@@ -3655,7 +3655,7 @@ describe MergeRequest do
describe '#merge_participants' do describe '#merge_participants' do
it 'contains author' do it 'contains author' do
expect(subject.merge_participants).to eq([subject.author]) expect(subject.merge_participants).to contain_exactly(subject.author)
end end
describe 'when merge_when_pipeline_succeeds? is true' do describe 'when merge_when_pipeline_succeeds? is true' do
...@@ -3669,8 +3669,20 @@ describe MergeRequest do ...@@ -3669,8 +3669,20 @@ describe MergeRequest do
author: user) author: user)
end end
it 'contains author only' do context 'author is not a project member' do
expect(subject.merge_participants).to eq([subject.author]) it 'is empty' do
expect(subject.merge_participants).to be_empty
end
end
context 'author is a project member' do
before do
subject.project.team.add_reporter(user)
end
it 'contains author only' do
expect(subject.merge_participants).to contain_exactly(subject.author)
end
end end
end end
...@@ -3683,8 +3695,24 @@ describe MergeRequest do ...@@ -3683,8 +3695,24 @@ describe MergeRequest do
merge_user: merge_user) merge_user: merge_user)
end end
it 'contains author and merge user' do before do
expect(subject.merge_participants).to eq([subject.author, merge_user]) subject.project.team.add_reporter(subject.author)
end
context 'merge user is not a member' do
it 'contains author only' do
expect(subject.merge_participants).to contain_exactly(subject.author)
end
end
context 'both author and merge users are project members' do
before do
subject.project.team.add_reporter(merge_user)
end
it 'contains author and merge user' do
expect(subject.merge_participants).to contain_exactly(subject.author, merge_user)
end
end end
end end
end end
......
...@@ -192,6 +192,19 @@ describe API::Events do ...@@ -192,6 +192,19 @@ describe API::Events do
end end
end end
context 'when target users profile is private' do
it 'returns no events' do
user.update!(private_profile: true)
private_project.add_developer(non_member)
get api("/users/#{user.username}/events", non_member)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to eq([])
end
end
context 'when scope is passed' do context 'when scope is passed' do
context 'when unauthenticated' do context 'when unauthenticated' do
it 'returns no user events' do it 'returns no user events' do
......
# frozen_string_literal: true
require 'spec_helper'
describe Snippets::RepositoryValidationService do
describe '#execute' do
let_it_be(:user) { create(:user) }
let_it_be(:snippet) { create(:personal_snippet, :empty_repo, author: user) }
let(:repository) { snippet.repository }
let(:service) { described_class.new(user, snippet) }
subject { service.execute }
before do
allow(repository).to receive(:branch_count).and_return(1)
allow(repository).to receive(:ls_files).and_return(['foo'])
allow(repository).to receive(:branch_names).and_return(['master'])
end
it 'returns error when the repository has more than one branch' do
allow(repository).to receive(:branch_count).and_return(2)
expect(subject).to be_error
expect(subject.message).to match /Repository has more than one branch/
end
it 'returns error when existing branch name is not the default one' do
allow(repository).to receive(:branch_names).and_return(['foo'])
expect(subject).to be_error
expect(subject.message).to match /Repository has an invalid default branch name/
end
it 'returns error when the repository has tags' do
allow(repository).to receive(:tag_count).and_return(1)
expect(subject).to be_error
expect(subject.message).to match /Repository has tags/
end
it 'returns error when the repository has more file than the limit' do
limit = Snippet.max_file_limit(user) + 1
files = Array.new(limit) { FFaker::Filesystem.file_name }
allow(repository).to receive(:ls_files).and_return(files)
expect(subject).to be_error
expect(subject.message).to match /Repository files count over the limit/
end
it 'returns error when the repository has no files' do
allow(repository).to receive(:ls_files).and_return([])
expect(subject).to be_error
expect(subject.message).to match /Repository must contain at least 1 file/
end
it 'returns error when the repository size is over the limit' do
expect_any_instance_of(Gitlab::RepositorySizeChecker).to receive(:above_size_limit?).and_return(true)
expect(subject).to be_error
expect(subject.message).to match /Repository size is above the limit/
end
it 'returns success when no validation errors are raised' do
expect(subject).to be_success
end
end
end
...@@ -158,46 +158,18 @@ RSpec.shared_examples 'wiki controller actions' do ...@@ -158,46 +158,18 @@ RSpec.shared_examples 'wiki controller actions' do
context 'when page is a file' do context 'when page is a file' do
include WikiHelpers include WikiHelpers
let(:id) { upload_file_to_wiki(container, user, file_name) } where(:file_name) { ['dk.png', 'unsanitized.svg', 'git-cheat-sheet.pdf'] }
context 'when file is an image' do with_them do
let(:file_name) { 'dk.png' } let(:id) { upload_file_to_wiki(container, user, file_name) }
it 'delivers the image' do it 'delivers the file with the correct headers' do
subject subject
expect(response.headers['Content-Disposition']).to match(/^inline/) expect(response.headers['Content-Disposition']).to match(/^inline/)
expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true" expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq('true')
end expect(response.cache_control[:public]).to be(false)
expect(response.cache_control[:extras]).to include('no-store')
context 'when file is a svg' do
let(:file_name) { 'unsanitized.svg' }
it 'delivers the image' do
subject
expect(response.headers['Content-Disposition']).to match(/^inline/)
expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
end
end
it_behaves_like 'project cache control headers' do
let(:project) { container }
end
end
context 'when file is a pdf' do
let(:file_name) { 'git-cheat-sheet.pdf' }
it 'sets the content type to sets the content response headers' do
subject
expect(response.headers['Content-Disposition']).to match(/^inline/)
expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
end
it_behaves_like 'project cache control headers' do
let(:project) { container }
end end
end end
end end
......
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
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