Update upstream source from tag 'upstream/12.8.8'

Update to upstream version '12.8.8'
with Debian dir b2d322041b
This commit is contained in:
Pirate Praveen 2020-03-28 13:23:19 +05:30
commit ae15ed391d
78 changed files with 1382 additions and 451 deletions

View file

@ -1,5 +1,16 @@
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
## 12.8.7 (2020-03-16)
### Fixed (1 change)
- Allow multipart uploads for packages. !26387
## 12.8.6 (2020-03-11)
- No changes.
## 12.8.5 ## 12.8.5
- No changes. - No changes.

View file

@ -2,6 +2,36 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 12.8.8 (2020-03-26)
### Security (17 changes)
- Redact notes in moved confidential issues.
- Ignore empty remote_id params from Workhorse accelerated uploads.
- External user can not create personal snippet through API.
- Prevent malicious entry for group name.
- Restrict mirroring changes to admins only when mirroring is disabled.
- Reject all container registry requests from blocked users.
- Deny localhost requests on fogbugz importer.
- Change GitHub service integration token input to password.
- Add permission check for pipeline status of MR.
- Fix UploadRewriter Path Traversal vulnerability.
- Block hotlinking to repository archives.
- Restrict access to project pipeline metrics reports.
- vulnerability_feedback records should be restricted to a dev role and above.
- Exclude Carrierwave remote URL methods from import.
- Update Nokogiri to fix CVE-2020-7595.
- Prevent updating trigger by other maintainers.
- Fix XSS vulnerability in `admin/email` "Recipient Group" dropdown.
## 12.8.7 (2020-03-16)
### Fixed (1 change, 1 of them is from the community)
- Fix crl_url parsing and certificate visualization. !25876 (Roger Meier)
## 12.8.6 (2020-03-11) ## 12.8.6 (2020-03-11)
### Security (1 change) ### Security (1 change)

View file

@ -1 +1 @@
12.8.6 12.8.8

View file

@ -1 +1 @@
8.21.0 8.21.1

View file

@ -652,7 +652,7 @@ GEM
netrc (0.11.0) netrc (0.11.0)
nio4r (2.5.2) nio4r (2.5.2)
no_proxy_fix (0.1.2) no_proxy_fix (0.1.2)
nokogiri (1.10.7) nokogiri (1.10.8)
mini_portile2 (~> 2.4.0) mini_portile2 (~> 2.4.0)
nokogumbo (1.5.0) nokogumbo (1.5.0)
nokogiri nokogiri

View file

@ -1 +1 @@
12.8.6 12.8.8

View file

@ -45,8 +45,19 @@ export const updateExistingFrequentItem = (frequentItem, item) => {
}; };
}; };
export const sanitizeItem = item => ({ export const sanitizeItem = item => {
// Only sanitize if the key exists on the item
const maybeSanitize = key => {
if (!Object.prototype.hasOwnProperty.call(item, key)) {
return {};
}
return { [key]: sanitize(item[key].toString(), { allowedTags: [] }) };
};
return {
...item, ...item,
name: sanitize(item.name.toString(), { allowedTags: [] }), ...maybeSanitize('name'),
namespace: sanitize(item.namespace.toString(), { allowedTags: [] }), ...maybeSanitize('namespace'),
}); };
};

View file

@ -0,0 +1,15 @@
# frozen_string_literal: true
module HotlinkInterceptor
extend ActiveSupport::Concern
def intercept_hotlinking!
return render_406 if Gitlab::HotlinkingDetector.intercept_hotlinking?(request)
end
private
def render_406
head :not_acceptable
end
end

View file

@ -3,6 +3,7 @@
class Import::FogbugzController < Import::BaseController class Import::FogbugzController < Import::BaseController
before_action :verify_fogbugz_import_enabled before_action :verify_fogbugz_import_enabled
before_action :user_map, only: [:new_user_map, :create_user_map] before_action :user_map, only: [:new_user_map, :create_user_map]
before_action :verify_blocked_uri, only: :callback
rescue_from Fogbugz::AuthenticationException, with: :fogbugz_unauthorized rescue_from Fogbugz::AuthenticationException, with: :fogbugz_unauthorized
@ -106,4 +107,21 @@ class Import::FogbugzController < Import::BaseController
def verify_fogbugz_import_enabled def verify_fogbugz_import_enabled
render_404 unless fogbugz_import_enabled? render_404 unless fogbugz_import_enabled?
end end
def verify_blocked_uri
Gitlab::UrlBlocker.validate!(
params[:uri],
{
allow_localhost: allow_local_requests?,
allow_local_network: allow_local_requests?,
schemes: %w(http https)
}
)
rescue Gitlab::UrlBlocker::BlockedUrlError => e
redirect_to new_import_fogbugz_url, alert: _('Specified URL cannot be used: "%{reason}"') % { reason: e.message }
end
def allow_local_requests?
Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
end
end end

View file

@ -67,7 +67,7 @@ class Projects::MirrorsController < Projects::ApplicationController
end end
def check_mirror_available! def check_mirror_available!
Gitlab::CurrentSettings.current_application_settings.mirror_available || current_user&.admin? render_404 unless can?(current_user, :admin_remote_mirror, project)
end end
def mirror_params_attributes def mirror_params_attributes

View file

@ -3,11 +3,13 @@
class Projects::RepositoriesController < Projects::ApplicationController class Projects::RepositoriesController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
include StaticObjectExternalStorage include StaticObjectExternalStorage
include HotlinkInterceptor
prepend_before_action(only: [:archive]) { authenticate_sessionless_user!(:archive) } prepend_before_action(only: [:archive]) { authenticate_sessionless_user!(:archive) }
# Authorize # Authorize
before_action :require_non_empty_project, except: :create before_action :require_non_empty_project, except: :create
before_action :intercept_hotlinking!, only: :archive
before_action :assign_archive_vars, only: :archive before_action :assign_archive_vars, only: :archive
before_action :assign_append_sha, only: :archive before_action :assign_append_sha, only: :archive
before_action :authorize_download_code! before_action :authorize_download_code!

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
require 'net/ldap/dn'
module X509Helper
def x509_subject(subject, search_keys)
subjects = {}
Net::LDAP::DN.new(subject).each_pair do |key, value|
if key.upcase.start_with?(*search_keys.map(&:upcase))
subjects[key.upcase] = value
end
end
subjects
rescue
{}
end
end

View file

@ -67,6 +67,9 @@ class Group < Namespace
validates :variables, variable_duplicates: true validates :variables, variable_duplicates: true
validates :two_factor_grace_period, presence: true, numericality: { greater_than_or_equal_to: 0 } validates :two_factor_grace_period, presence: true, numericality: { greater_than_or_equal_to: 0 }
validates :name,
format: { with: Gitlab::Regex.group_name_regex,
message: Gitlab::Regex.group_name_regex_message }
add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption, default_enabled: true) ? :optional : :required } add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption, default_enabled: true) ? :optional : :required }

View file

@ -319,9 +319,7 @@ class Issue < ApplicationRecord
true true
elsif project.owner == user elsif project.owner == user
true true
elsif confidential? elsif confidential? && !assignee_or_author?(user)
author == user ||
assignees.include?(user) ||
project.team.member?(user, Gitlab::Access::REPORTER) project.team.member?(user, Gitlab::Access::REPORTER)
else else
project.public? || project.public? ||

View file

@ -53,7 +53,7 @@ class MergeRequestPollWidgetEntity < Grape::Entity
# CI related # CI related
expose :has_ci?, as: :has_ci expose :has_ci?, as: :has_ci
expose :ci_status do |merge_request| expose :ci_status, if: -> (mr, _) { presenter(mr).can_read_pipeline? } do |merge_request|
presenter(merge_request).ci_status presenter(merge_request).ci_status
end end

View file

@ -318,7 +318,7 @@ module ObjectStorage
def cache!(new_file = sanitized_file) def cache!(new_file = sanitized_file)
# We intercept ::UploadedFile which might be stored on remote storage # We intercept ::UploadedFile which might be stored on remote storage
# We use that for "accelerated" uploads, where we store result on remote storage # We use that for "accelerated" uploads, where we store result on remote storage
if new_file.is_a?(::UploadedFile) && new_file.remote_id if new_file.is_a?(::UploadedFile) && new_file.remote_id.present?
return cache_remote_file!(new_file.remote_id, new_file.original_filename) return cache_remote_file!(new_file.remote_id, new_file.original_filename)
end end

View file

@ -1,17 +1,15 @@
.gpg-popover-certificate-details .gpg-popover-certificate-details
%strong= _('Certificate Subject') %strong= _('Certificate Subject')
%ul %ul
- signature.x509_certificate.subject.split(",").each do |i| - x509_subject(signature.x509_certificate.subject, ["CN", "O"]).map do |key, value|
- if i.start_with?("CN", "O") %li= key + "=" + value
%li= i
%li= _('Subject Key Identifier:') %li= _('Subject Key Identifier:')
%li.unstyled= signature.x509_certificate.subject_key_identifier.gsub(":", " ") %li.unstyled= signature.x509_certificate.subject_key_identifier.gsub(":", " ")
.gpg-popover-certificate-details .gpg-popover-certificate-details
%strong= _('Certificate Issuer') %strong= _('Certificate Issuer')
%ul %ul
- signature.x509_certificate.x509_issuer.subject.split(",").each do |i| - x509_subject(signature.x509_certificate.x509_issuer.subject, ["CN", "OU", "O"]).map do |key, value|
- if i.start_with?("CN", "OU", "O") %li= key + "=" + value
%li= i
%li= _('Subject Key Identifier:') %li= _('Subject Key Identifier:')
%li.unstyled= signature.x509_certificate.x509_issuer.subject_key_identifier.gsub(":", " ") %li.unstyled= signature.x509_certificate.x509_issuer.subject_key_identifier.gsub(":", " ")

View file

@ -1,7 +1,9 @@
- expanded = expanded_by_default? - expanded = expanded_by_default?
- protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|') - protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|')
- mirror_settings_enabled = can?(current_user, :admin_remote_mirror, @project)
- mirror_settings_class = "#{'expanded' if expanded} #{'js-mirror-settings' if mirror_settings_enabled}".strip
%section.settings.project-mirror-settings.js-mirror-settings.no-animate#js-push-remote-settings{ class: ('expanded' if expanded), data: { qa_selector: 'mirroring_repositories_settings_section' } } %section.settings.project-mirror-settings.no-animate#js-push-remote-settings{ class: mirror_settings_class, data: { qa_selector: 'mirroring_repositories_settings_section' } }
.settings-header .settings-header
%h4= _('Mirroring repositories') %h4= _('Mirroring repositories')
%button.btn.js-settings-toggle %button.btn.js-settings-toggle
@ -11,6 +13,7 @@
= link_to _('Read more'), help_page_path('workflow/repository_mirroring'), target: '_blank' = link_to _('Read more'), help_page_path('workflow/repository_mirroring'), target: '_blank'
.settings-content .settings-content
- if mirror_settings_enabled
= form_for @project, url: project_mirror_path(@project), html: { class: 'gl-show-field-errors js-mirror-form', autocomplete: 'new-password', data: mirrors_form_data_attributes } do |f| = form_for @project, url: project_mirror_path(@project), html: { class: 'gl-show-field-errors js-mirror-form', autocomplete: 'new-password', data: mirrors_form_data_attributes } do |f|
.panel.panel-default .panel.panel-default
.panel-body .panel-body
@ -31,6 +34,11 @@
.panel-footer .panel-footer
= f.submit _('Mirror repository'), class: 'btn btn-success js-mirror-submit qa-mirror-repository-button', name: :update_remote_mirror = f.submit _('Mirror repository'), class: 'btn btn-success js-mirror-submit qa-mirror-repository-button', name: :update_remote_mirror
- else
.gl-alert.gl-alert-info{ role: 'alert' }
= sprite_icon('information-o', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
.gl-alert-body
= _('Mirror settings are only available to GitLab administrators.')
.panel.panel-default .panel.panel-default
.table-responsive .table-responsive
@ -61,8 +69,10 @@
- if mirror.last_error.present? - if mirror.last_error.present?
.badge.mirror-error-badge{ data: { toggle: 'tooltip', html: 'true', qa_selector: 'mirror_error_badge' }, title: html_escape(mirror.last_error.try(:strip)) }= _('Error') .badge.mirror-error-badge{ data: { toggle: 'tooltip', html: 'true', qa_selector: 'mirror_error_badge' }, title: html_escape(mirror.last_error.try(:strip)) }= _('Error')
%td %td
- if mirror_settings_enabled
.btn-group.mirror-actions-group.pull-right{ role: 'group' } .btn-group.mirror-actions-group.pull-right{ role: 'group' }
- if mirror.ssh_key_auth? - if mirror.ssh_key_auth?
= clipboard_button(text: mirror.ssh_public_key, class: 'btn btn-default', title: _('Copy SSH public key'), qa_selector: 'copy_public_key_button') = clipboard_button(text: mirror.ssh_public_key, class: 'btn btn-default', title: _('Copy SSH public key'), qa_selector: 'copy_public_key_button')
= render 'shared/remote_mirror_update_button', remote_mirror: mirror = render 'shared/remote_mirror_update_button', remote_mirror: mirror
%button.js-delete-mirror.qa-delete-mirror.rspec-delete-mirror.btn.btn-danger{ type: 'button', data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' }, title: _('Remove') }= icon('trash-o') %button.js-delete-mirror.qa-delete-mirror.rspec-delete-mirror.btn.btn-danger{ type: 'button', data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' }, title: _('Remove') }= icon('trash-o')

View file

@ -1,5 +1,10 @@
## master (unreleased) ## master (unreleased)
## 1.7.0
- Add histogram support to `perf:library` (https://github.com/schneems/derailed_benchmarks/pull/169)
- Fix bug with `Kernel#require` patch when Zeitwerk is enabled (https://github.com/schneems/derailed_benchmarks/pull/170)
## 1.6.0 ## 1.6.0
- Added the `perf:app` command to compare commits within the same application. (https://github.com/schneems/derailed_benchmarks/pull/157) - Added the `perf:app` command to compare commits within the same application. (https://github.com/schneems/derailed_benchmarks/pull/157)

View file

@ -30,6 +30,8 @@ Gem::Specification.new do |gem|
gem.add_dependency "rake", "> 10", "< 14" gem.add_dependency "rake", "> 10", "< 14"
gem.add_dependency "thor", ">= 0.19", "< 2" gem.add_dependency "thor", ">= 0.19", "< 2"
gem.add_dependency "ruby-statistics", ">= 2.1" gem.add_dependency "ruby-statistics", ">= 2.1"
gem.add_dependency "unicode_plot", ">= 0.0.4", "< 1.0.0"
gem.add_dependency "mini_histogram", "~> 0"
gem.add_development_dependency "capybara", "~> 2" gem.add_development_dependency "capybara", "~> 2"
gem.add_development_dependency "m" gem.add_development_dependency "m"

View file

@ -11,14 +11,20 @@ ENV['CUT_OFF'] ||= "0.3"
# Monkey patch kernel to ensure that all `require` calls call the same # Monkey patch kernel to ensure that all `require` calls call the same
# method # method
module Kernel module Kernel
private
alias :original_require :require
REQUIRE_STACK = [] REQUIRE_STACK = []
module_function
alias_method :original_require, :require
alias_method :original_require_relative, :require_relative
def require(file) def require(file)
Kernel.require(file) measure_memory_impact(file) do |file|
# "source_annotation_extractor" is deprecated in Rails 6
# # if we don't skip the library it leads to a crash
# next if file == "rails/source_annotation_extractor" && Rails.version >= '6.0'
original_require(file)
end
end end
def require_relative(file) def require_relative(file)
@ -29,10 +35,7 @@ module Kernel
end end
end end
class << self private
alias :original_require :require
alias :original_require_relative :require_relative
end
# The core extension we use to measure require time of all requires # The core extension we use to measure require time of all requires
# When a file is required we create a tree node with its file name. # When a file is required we create a tree node with its file name.
@ -46,7 +49,7 @@ module Kernel
# When a require returns we remove it from the require stack so we don't # When a require returns we remove it from the require stack so we don't
# accidentally push additional children nodes to it. We then store the # accidentally push additional children nodes to it. We then store the
# memory cost of the require in the tree node. # memory cost of the require in the tree node.
def self.measure_memory_impact(file, &block) def measure_memory_impact(file, &block)
mem = GetProcessMem.new mem = GetProcessMem.new
node = DerailedBenchmarks::RequireTree.new(file) node = DerailedBenchmarks::RequireTree.new(file)
@ -68,15 +71,6 @@ end
TOP_REQUIRE = DerailedBenchmarks::RequireTree.new("TOP") TOP_REQUIRE = DerailedBenchmarks::RequireTree.new("TOP")
REQUIRE_STACK.push(TOP_REQUIRE) REQUIRE_STACK.push(TOP_REQUIRE)
Kernel.define_singleton_method(:require) do |file|
measure_memory_impact(file) do |file|
# "source_annotation_extractor" is deprecated in Rails 6
# # if we don't skip the library it leads to a crash
# next if file == "rails/source_annotation_extractor" && Rails.version >= '6.0'
original_require(file)
end
end
class Object class Object
private private

View file

@ -30,7 +30,7 @@ namespace :perf do
DERAILED_APP.initialize! unless DERAILED_APP.instance_variable_get(:@initialized) DERAILED_APP.initialize! unless DERAILED_APP.instance_variable_get(:@initialized)
end end
if ENV["DERAILED_SKIP_ACTIVE_RECORD"] && defined? ActiveRecord if !ENV["DERAILED_SKIP_ACTIVE_RECORD"] && defined? ActiveRecord
if defined? ActiveRecord::Tasks::DatabaseTasks if defined? ActiveRecord::Tasks::DatabaseTasks
ActiveRecord::Tasks::DatabaseTasks.create_current ActiveRecord::Tasks::DatabaseTasks.create_current
else # Rails 3.2 else # Rails 3.2
@ -39,8 +39,15 @@ namespace :perf do
ActiveRecord::Migrator.migrations_paths = DERAILED_APP.paths['db/migrate'].to_a ActiveRecord::Migrator.migrations_paths = DERAILED_APP.paths['db/migrate'].to_a
ActiveRecord::Migration.verbose = true ActiveRecord::Migration.verbose = true
if Rails.version.start_with? '6'
ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths, ActiveRecord::SchemaMigration).migrate
elsif Rails.version.start_with? '5.2'
ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths).migrate
else
ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, nil) ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, nil)
end end
end
DERAILED_APP.config.consider_all_requests_local = true DERAILED_APP.config.consider_all_requests_local = true
end end

View file

@ -2,6 +2,9 @@
require 'bigdecimal' require 'bigdecimal'
require 'statistics' require 'statistics'
require 'unicode_plot'
require 'stringio'
require 'mini_histogram'
module DerailedBenchmarks module DerailedBenchmarks
# A class used to read several benchmark files # A class used to read several benchmark files
@ -100,7 +103,26 @@ module DerailedBenchmarks
" " * (percent_faster.to_s.index(".") - x_faster.to_s.index(".")) " " * (percent_faster.to_s.index(".") - x_faster.to_s.index("."))
end end
def banner(io = Kernel) def histogram(io = $stdout)
newest_histogram = MiniHistogram.new(newest.values)
oldest_histogram = MiniHistogram.new(oldest.values)
MiniHistogram.set_average_edges!(newest_histogram, oldest_histogram)
{newest => newest_histogram, oldest => oldest_histogram}.each do |report, histogram|
plot = UnicodePlot.histogram(
histogram,
title: "\n#{' ' * 18 }Histogram - [#{report.name}] #{report.desc.inspect}",
ylabel: "Time (s)",
xlabel: "# of runs in range"
)
plot.render(io)
io.puts
end
io.puts
end
def banner(io = $stdout)
io.puts io.puts
if significant? if significant?
io.puts "❤️ ❤️ ❤️ (Statistically Significant) ❤️ ❤️ ❤️" io.puts "❤️ ❤️ ❤️ (Statistically Significant) ❤️ ❤️ ❤️"
@ -122,6 +144,9 @@ module DerailedBenchmarks
io.puts "Is significant? (max > critical): #{significant?}" io.puts "Is significant? (max > critical): #{significant?}"
io.puts "D critical: #{d_critical}" io.puts "D critical: #{d_critical}"
io.puts "D max: #{d_max}" io.puts "D max: #{d_max}"
histogram(io)
io.puts io.puts
end end
end end

View file

@ -88,7 +88,8 @@ namespace :perf do
if (i % 50).zero? if (i % 50).zero?
puts "Intermediate result" puts "Intermediate result"
stats.call.banner stats.call
stats.banner
puts "Continuing execution" puts "Continuing execution"
end end
end end
@ -102,7 +103,8 @@ namespace :perf do
end end
if stats if stats
stats.call.banner stats.call
stats.banner
result_file = out_dir + "results.txt" result_file = out_dir + "results.txt"
File.open(result_file, "w") do |f| File.open(result_file, "w") do |f|

View file

@ -1,5 +1,5 @@
# frozen_string_literal: true # frozen_string_literal: true
module DerailedBenchmarks module DerailedBenchmarks
VERSION = "1.6.0" VERSION = "1.7.0"
end end

View file

@ -29,6 +29,22 @@ class StatsFromDirTest < ActiveSupport::TestCase
assert_equal "11.3844", format % newest.median assert_equal "11.3844", format % newest.median
end end
test "histogram output" do
dir = fixtures_dir("stats/significant")
branch_info = {}
branch_info["loser"] = { desc: "Old commit", time: Time.now, file: dir.join("loser.bench.txt"), name: "loser" }
branch_info["winner"] = { desc: "I am the new commit", time: Time.now + 1, file: dir.join("winner.bench.txt"), name: "winner" }
stats = DerailedBenchmarks::StatsFromDir.new(branch_info).call
io = StringIO.new
stats.call.banner(io)
puts io.string
assert_match(/11\.2 , 11\.28/, io.string)
assert_match(/11\.8 , 11\.88/, io.string)
end
test "alignment" do test "alignment" do
dir = fixtures_dir("stats/significant") dir = fixtures_dir("stats/significant")
branch_info = {} branch_info = {}

View file

@ -26,7 +26,7 @@ class TasksTest < ActiveSupport::TestCase
env_string = env.map {|key, value| "#{key.shellescape}=#{value.to_s.shellescape}" }.join(" ") env_string = env.map {|key, value| "#{key.shellescape}=#{value.to_s.shellescape}" }.join(" ")
cmd = "env #{env_string} bundle exec rake -f perf.rake #{cmd} --trace" cmd = "env #{env_string} bundle exec rake -f perf.rake #{cmd} --trace"
puts "Running: #{cmd}" puts "Running: #{cmd}"
result = `cd '#{rails_app_path}' && #{cmd}` result = Bundler.with_original_env { `cd '#{rails_app_path}' && #{cmd}` }
if assert_success if assert_success
assert $?.success?, "Expected '#{cmd}' to return a success status.\nOutput: #{result}" assert $?.success?, "Expected '#{cmd}' to return a success status.\nOutput: #{result}"
end end

View file

@ -13,6 +13,8 @@ require 'devise'
module Dummy module Dummy
class Application < Rails::Application class Application < Rails::Application
config.load_defaults Rails.version.to_f
config.action_mailer.default_url_options = { host: 'localhost:3000' } config.action_mailer.default_url_options = { host: 'localhost:3000' }
# Settings in config/environments/* take precedence over those specified here. # Settings in config/environments/* take precedence over those specified here.

View file

@ -359,6 +359,10 @@ module API
render_api_error!('405 Method Not Allowed', 405) render_api_error!('405 Method Not Allowed', 405)
end end
def not_acceptable!
render_api_error!('406 Not Acceptable', 406)
end
def conflict!(message = nil) def conflict!(message = nil)
render_api_error!(message || '409 Conflict', 409) render_api_error!(message || '409 Conflict', 409)
end end

View file

@ -89,6 +89,8 @@ module API
optional :format, type: String, desc: 'The archive format' optional :format, type: String, desc: 'The archive format'
end end
get ':id/repository/archive', requirements: { format: Gitlab::PathRegex.archive_formats_regex } do get ':id/repository/archive', requirements: { format: Gitlab::PathRegex.archive_formats_regex } do
not_acceptable! if Gitlab::HotlinkingDetector.intercept_hotlinking?(request)
send_git_archive user_project.repository, ref: params[:sha], format: params[:format], append_sha: true send_git_archive user_project.repository, ref: params[:sha], format: params[:format], append_sha: true
rescue rescue
not_found!('File') not_found!('File')

View file

@ -74,6 +74,8 @@ module API
desc: 'The visibility of the snippet' desc: 'The visibility of the snippet'
end end
post do post do
authorize! :create_snippet
attrs = declared_params(include_missing: false).merge(request: request, api: true) attrs = declared_params(include_missing: false).merge(request: request, api: true)
service_response = ::Snippets::CreateService.new(nil, current_user, attrs).execute service_response = ::Snippets::CreateService.new(nil, current_user, attrs).execute
snippet = service_response.payload[:snippet] snippet = service_response.payload[:snippet]

View file

@ -109,6 +109,8 @@ module API
trigger = user_project.triggers.find(params.delete(:trigger_id)) trigger = user_project.triggers.find(params.delete(:trigger_id))
break not_found!('Trigger') unless trigger break not_found!('Trigger') unless trigger
authorize! :admin_trigger, trigger
if trigger.update(declared_params(include_missing: false)) if trigger.update(declared_params(include_missing: false))
present trigger, with: Entities::Trigger, current_user: current_user present trigger, with: Entities::Trigger, current_user: current_user
else else

View file

@ -171,6 +171,8 @@ module Gitlab
if valid_oauth_token?(token) if valid_oauth_token?(token)
user = User.find_by(id: token.resource_owner_id) user = User.find_by(id: token.resource_owner_id)
return unless user.can?(:log_in)
Gitlab::Auth::Result.new(user, nil, :oauth, full_authentication_abilities) Gitlab::Auth::Result.new(user, nil, :oauth, full_authentication_abilities)
end end
end end
@ -182,7 +184,7 @@ module Gitlab
token = PersonalAccessTokensFinder.new(state: 'active').find_by_token(password) token = PersonalAccessTokensFinder.new(state: 'active').find_by_token(password)
if token && valid_scoped_token?(token, all_available_scopes) if token && valid_scoped_token?(token, all_available_scopes) && token.user.can?(:log_in)
Gitlab::Auth::Result.new(token.user, nil, :personal_access_token, abilities_for_scopes(token.scopes)) Gitlab::Auth::Result.new(token.user, nil, :personal_access_token, abilities_for_scopes(token.scopes))
end end
end end
@ -260,6 +262,8 @@ module Gitlab
return unless build.project.builds_enabled? return unless build.project.builds_enabled?
if build.user if build.user
return unless build.user.can?(:log_in)
# If user is assigned to build, use restricted credentials of user # If user is assigned to build, use restricted credentials of user
Gitlab::Auth::Result.new(build.user, build.project, :build, build_authentication_abilities) Gitlab::Auth::Result.new(build.user, build.project, :build, build_authentication_abilities)
else else

View file

@ -22,6 +22,8 @@ module Gitlab
return @text unless needs_rewrite? return @text unless needs_rewrite?
@text.gsub(@pattern) do |markdown| @text.gsub(@pattern) do |markdown|
Gitlab::Utils.check_path_traversal!($~[:file])
file = find_file(@source_project, $~[:secret], $~[:file]) file = find_file(@source_project, $~[:secret], $~[:file])
break markdown unless file.try(:exists?) break markdown unless file.try(:exists?)

View file

@ -0,0 +1,52 @@
# frozen_string_literal: true
module Gitlab
class HotlinkingDetector
IMAGE_FORMATS = %w(image/jpeg image/apng image/png image/webp image/svg+xml image/*).freeze
MEDIA_FORMATS = %w(video/webm video/ogg video/* application/ogg audio/webm audio/ogg audio/wav audio/*).freeze
CSS_FORMATS = %w(text/css).freeze
INVALID_FORMATS = (IMAGE_FORMATS + MEDIA_FORMATS + CSS_FORMATS).freeze
INVALID_FETCH_MODES = %w(cors no-cors websocket).freeze
class << self
def intercept_hotlinking?(request)
request_accepts = parse_request_accepts(request)
return false unless Feature.enabled?(:repository_archive_hotlinking_interception, default_enabled: true)
# Block attempts to embed as JS
return true if sec_fetch_invalid?(request)
# If no Accept header was set, skip the rest
return false if request_accepts.empty?
# Workaround for IE8 weirdness
return false if IMAGE_FORMATS.include?(request_accepts.first) && request_accepts.include?("application/x-ms-application")
# Block all other media requests if the first format is a media type
return true if INVALID_FORMATS.include?(request_accepts.first)
false
end
private
def sec_fetch_invalid?(request)
fetch_mode = request.headers["Sec-Fetch-Mode"]
return if fetch_mode.blank?
return true if INVALID_FETCH_MODES.include?(fetch_mode)
end
def parse_request_accepts(request)
# Rails will already have parsed the Accept header
return request.accepts if request.respond_to?(:accepts)
# Grape doesn't parse it, so we can use the Rails system for this
return Mime::Type.parse(request.headers["Accept"]) if request.respond_to?(:headers) && request.headers["Accept"].present?
[]
end
end
end
end

View file

@ -11,7 +11,14 @@ module Gitlab
'discussion_id', 'discussion_id',
'custom_attributes' 'custom_attributes'
].freeze ].freeze
PROHIBITED_REFERENCES = Regexp.union(/\Acached_markdown_version\Z/, /_id\Z/, /_ids\Z/, /_html\Z/, /attributes/).freeze PROHIBITED_REFERENCES = Regexp.union(
/\Acached_markdown_version\Z/,
/_id\Z/,
/_ids\Z/,
/_html\Z/,
/attributes/,
/\Aremote_\w+_(url|urls|request_header)\Z/ # carrierwave automatically creates these attribute methods for uploads
).freeze
def self.clean(*args) def self.clean(*args)
new(*args).clean new(*args).clean

View file

@ -84,12 +84,6 @@ module Gitlab
end end
def open_file(params, key) def open_file(params, key)
allowed_paths = [
::FileUploader.root,
Gitlab.config.uploads.storage_path,
File.join(Rails.root, 'public/uploads/tmp')
]
::UploadedFile.from_params(params, key, allowed_paths) ::UploadedFile.from_params(params, key, allowed_paths)
end end
@ -106,6 +100,16 @@ module Gitlab
# inside other env keys, here we ensure everything is updated correctly # inside other env keys, here we ensure everything is updated correctly
ActionDispatch::Request.new(@request.env).update_param(key, value) ActionDispatch::Request.new(@request.env).update_param(key, value)
end end
private
def allowed_paths
[
::FileUploader.root,
Gitlab.config.uploads.storage_path,
File.join(Rails.root, 'public/uploads/tmp')
]
end
end end
def initialize(app) def initialize(app)
@ -125,3 +129,5 @@ module Gitlab
end end
end end
end end
::Gitlab::Middleware::Multipart::Handler.prepend_if_ee('EE::Gitlab::Middleware::Multipart::Handler')

View file

@ -16,6 +16,14 @@ module Gitlab
"It must start with letter, digit, emoji or '_'." "It must start with letter, digit, emoji or '_'."
end end
def group_name_regex
project_name_regex
end
def group_name_regex_message
project_name_regex_message
end
## ##
# Docker Distribution Registry repository / tag name rules # Docker Distribution Registry repository / tag name rules
# #

View file

@ -105,15 +105,24 @@ module Gitlab
def certificate_crl def certificate_crl
extension = get_certificate_extension('crlDistributionPoints') extension = get_certificate_extension('crlDistributionPoints')
extension.split('URI:').each do |item| crl_url = nil
extension.each_line do |line|
break if crl_url
line.split('URI:').each do |item|
item.strip item.strip
if item.start_with?("http") if item.start_with?("http")
return item.strip crl_url = item.strip
break
end end
end end
end end
crl_url
end
def get_certificate_extension(extension) def get_certificate_extension(extension)
cert.extensions.each do |ext| cert.extensions.each do |ext|
if ext.oid == extension if ext.oid == extension

View file

@ -48,7 +48,7 @@ class UploadedFile
return if path.blank? && remote_id.blank? return if path.blank? && remote_id.blank?
file_path = nil file_path = nil
if path if path.present?
file_path = File.realpath(path) file_path = File.realpath(path)
paths = Array(upload_paths) << Dir.tmpdir paths = Array(upload_paths) << Dir.tmpdir

View file

@ -12357,6 +12357,9 @@ msgstr ""
msgid "Mirror repository" msgid "Mirror repository"
msgstr "" msgstr ""
msgid "Mirror settings are only available to GitLab administrators."
msgstr ""
msgid "Mirror user" msgid "Mirror user"
msgstr "" msgstr ""
@ -18176,6 +18179,9 @@ msgstr ""
msgid "Specific Runners" msgid "Specific Runners"
msgstr "" msgstr ""
msgid "Specified URL cannot be used: \"%{reason}\""
msgstr ""
msgid "Specify an e-mail address regex pattern to identify default internal users." msgid "Specify an e-mail address regex pattern to identify default internal users."
msgstr "" msgstr ""

View file

@ -258,6 +258,18 @@ describe GroupsController do
end end
end end
end end
context "malicious group name" do
subject { post :create, params: { group: { name: "<script>alert('Mayday!');</script>", path: "invalid_group_url" } } }
before do
sign_in(user)
end
it { expect { subject }.not_to change { Group.count } }
it { expect(subject).to render_template(:new) }
end
end end
describe 'GET #index' do describe 'GET #index' do
@ -829,6 +841,16 @@ describe GroupsController do
put :update, params: { id: group.to_param, group: { name: 'world' } } put :update, params: { id: group.to_param, group: { name: 'world' } }
end.to change { group.reload.name } end.to change { group.reload.name }
end end
context "malicious group name" do
subject { put :update, params: { id: group.to_param, group: { name: "<script>alert('Attack!');</script>" } } }
it { is_expected.to render_template(:edit) }
it 'does not update name' do
expect { subject }.not_to change { group.reload.name }
end
end
end end
describe 'DELETE #destroy' do describe 'DELETE #destroy' do

View file

@ -25,6 +25,35 @@ describe Import::FogbugzController do
expect(session[:fogbugz_uri]).to eq(uri) expect(session[:fogbugz_uri]).to eq(uri)
expect(response).to redirect_to(new_user_map_import_fogbugz_path) expect(response).to redirect_to(new_user_map_import_fogbugz_path)
end end
context 'verify url' do
shared_examples 'denies local request' do |reason|
it 'does not allow requests' do
post :callback, params: { uri: uri, email: 'test@example.com', password: 'mypassword' }
expect(response).to redirect_to(new_import_fogbugz_url)
expect(flash[:alert]).to eq("Specified URL cannot be used: \"#{reason}\"")
end
end
context 'when host is localhost' do
let(:uri) { 'https://localhost:3000' }
include_examples 'denies local request', 'Requests to localhost are not allowed'
end
context 'when host is on local network' do
let(:uri) { 'http://192.168.0.1/' }
include_examples 'denies local request', 'Requests to the local network are not allowed'
end
context 'when host is ftp protocol' do
let(:uri) { 'ftp://testing' }
include_examples 'denies local request', 'Only allowed schemes are http, https'
end
end
end end
describe 'POST #create_user_map' do describe 'POST #create_user_map' do

View file

@ -5,6 +5,72 @@ require 'spec_helper'
describe Projects::MirrorsController do describe Projects::MirrorsController do
include ReactiveCachingHelpers include ReactiveCachingHelpers
shared_examples 'only admin is allowed when mirroring is disabled' do
let(:subject_action) { raise 'subject_action is required' }
let(:user) { project.owner }
let(:project_settings_path) { project_settings_repository_path(project, anchor: 'js-push-remote-settings') }
context 'when project mirroring is enabled' do
it 'allows requests from a maintainer' do
sign_in(user)
subject_action
expect(response).to redirect_to(project_settings_path)
end
it 'allows requests from an admin user' do
user.update!(admin: true)
sign_in(user)
subject_action
expect(response).to redirect_to(project_settings_path)
end
end
context 'when project mirroring is disabled' do
before do
stub_application_setting(mirror_available: false)
end
it 'disallows requests from a maintainer' do
sign_in(user)
subject_action
expect(response).to have_gitlab_http_status(:not_found)
end
it 'allows requests from an admin user' do
user.update!(admin: true)
sign_in(user)
subject_action
expect(response).to redirect_to(project_settings_path)
end
end
end
describe 'Access control' do
let(:project) { create(:project, :repository) }
describe '#update' do
include_examples 'only admin is allowed when mirroring is disabled' do
let(:subject_action) do
do_put(project, remote_mirrors_attributes: { '0' => { 'enabled' => 1, 'url' => 'http://foo.com' } })
end
end
end
describe '#update_now' do
include_examples 'only admin is allowed when mirroring is disabled' do
let(:options) { { namespace_id: project.namespace, project_id: project } }
let(:subject_action) do
get :update_now, params: options.merge(sync_remote: true)
end
end
end
end
describe 'setting up a remote mirror' do describe 'setting up a remote mirror' do
let_it_be(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }

View file

@ -24,6 +24,12 @@ describe Projects::RepositoriesController do
sign_in(user) sign_in(user)
end end
it_behaves_like "hotlink interceptor" do
let(:http_request) do
get :archive, params: { namespace_id: project.namespace, project_id: project, id: "master" }, format: "zip"
end
end
it "uses Gitlab::Workhorse" do it "uses Gitlab::Workhorse" do
get :archive, params: { namespace_id: project.namespace, project_id: project, id: "master" }, format: "zip" get :archive, params: { namespace_id: project.namespace, project_id: project, id: "master" }, format: "zip"

View file

@ -7,39 +7,11 @@ FactoryBot.define do
enabled_until { 1.week.from_now } enabled_until { 1.week.from_now }
certificate do certificate do
'-----BEGIN CERTIFICATE----- File.read(Rails.root.join('spec/fixtures/', 'ssl_certificate.pem'))
MIICGzCCAYSgAwIBAgIBATANBgkqhkiG9w0BAQUFADAbMRkwFwYDVQQDExB0ZXN0
LWNlcnRpZmljYXRlMB4XDTE2MDIxMjE0MzIwMFoXDTIwMDQxMjE0MzIwMFowGzEZ
MBcGA1UEAxMQdGVzdC1jZXJ0aWZpY2F0ZTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
gYkCgYEApL4J9L0ZxFJ1hI1LPIflAlAGvm6ZEvoT4qKU5Xf2JgU7/2geNR1qlNFa
SvCc08Knupp5yTgmvyK/Xi09U0N82vvp4Zvr/diSc4A/RA6Mta6egLySNT438kdT
nY2tR5feoTLwQpX0t4IMlwGQGT5h6Of2fKmDxzuwuyffcIHqLdsCAwEAAaNvMG0w
DAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUxl9WSxBprB0z0ibJs3rXEk0+95AwCwYD
VR0PBAQDAgXgMBEGCWCGSAGG+EIBAQQEAwIGQDAeBglghkgBhvhCAQ0EERYPeGNh
IGNlcnRpZmljYXRlMA0GCSqGSIb3DQEBBQUAA4GBAGC4T8SlFHK0yPSa+idGLQFQ
joZp2JHYvNlTPkRJ/J4TcXxBTJmArcQgTIuNoBtC+0A/SwdK4MfTCUY4vNWNdese
5A4K65Nb7Oh1AdQieTBHNXXCdyFsva9/ScfQGEl7p55a52jOPs0StPd7g64uvjlg
YHi2yesCrOvVXt+lgPTd
-----END CERTIFICATE-----'
end end
key do key do
'-----BEGIN PRIVATE KEY----- File.read(Rails.root.join('spec/fixtures/', 'ssl_key.pem'))
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKS+CfS9GcRSdYSN
SzyH5QJQBr5umRL6E+KilOV39iYFO/9oHjUdapTRWkrwnNPCp7qaeck4Jr8iv14t
PVNDfNr76eGb6/3YknOAP0QOjLWunoC8kjU+N/JHU52NrUeX3qEy8EKV9LeCDJcB
kBk+Yejn9nypg8c7sLsn33CB6i3bAgMBAAECgYA2D26w80T7WZvazYr86BNMePpd
j2mIAqx32KZHzt/lhh40J/SRtX9+Kl0Y7nBoRR5Ja9u/HkAIxNxLiUjwg9r6cpg/
uITEF5nMt7lAk391BuI+7VOZZGbJDsq2ulPd6lO+C8Kq/PI/e4kXcIjeH6KwQsuR
5vrXfBZ3sQfflaiN4QJBANBt8JY2LIGQF8o89qwUpRL5vbnKQ4IzZ5+TOl4RLR7O
AQpJ81tGuINghO7aunctb6rrcKJrxmEH1whzComybrMCQQDKV49nOBudRBAIgG4K
EnLzsRKISUHMZSJiYTYnablof8cKw1JaQduw7zgrUlLwnroSaAGX88+Jw1f5n2Lh
Vlg5AkBDdUGnrDLtYBCDEQYZHblrkc7ZAeCllDOWjxUV+uMqlCv8A4Ey6omvY57C
m6I8DkWVAQx8VPtozhvHjUw80rZHAkB55HWHAM3h13axKG0htCt7klhPsZHpx6MH
EPjGlXIT+aW2XiPmK3ZlCDcWIenE+lmtbOpI159Wpk8BGXs/s/xBAkEAlAY3ymgx
63BDJEwvOb2IaP8lDDxNsXx9XJNVvQbv5n15vNsLHbjslHfAhAbxnLQ1fLhUPqSi
nNp/xedE1YxutQ==
-----END PRIVATE KEY-----'
end end
trait :disabled do trait :disabled do

View file

@ -7,39 +7,11 @@ FactoryBot.define do
creator { create(:user) } creator { create(:user) }
certificate do certificate do
'-----BEGIN CERTIFICATE----- File.read(Rails.root.join('spec/fixtures/', 'ssl_certificate.pem'))
MIICGzCCAYSgAwIBAgIBATANBgkqhkiG9w0BAQUFADAbMRkwFwYDVQQDExB0ZXN0
LWNlcnRpZmljYXRlMB4XDTE2MDIxMjE0MzIwMFoXDTIwMDQxMjE0MzIwMFowGzEZ
MBcGA1UEAxMQdGVzdC1jZXJ0aWZpY2F0ZTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
gYkCgYEApL4J9L0ZxFJ1hI1LPIflAlAGvm6ZEvoT4qKU5Xf2JgU7/2geNR1qlNFa
SvCc08Knupp5yTgmvyK/Xi09U0N82vvp4Zvr/diSc4A/RA6Mta6egLySNT438kdT
nY2tR5feoTLwQpX0t4IMlwGQGT5h6Of2fKmDxzuwuyffcIHqLdsCAwEAAaNvMG0w
DAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUxl9WSxBprB0z0ibJs3rXEk0+95AwCwYD
VR0PBAQDAgXgMBEGCWCGSAGG+EIBAQQEAwIGQDAeBglghkgBhvhCAQ0EERYPeGNh
IGNlcnRpZmljYXRlMA0GCSqGSIb3DQEBBQUAA4GBAGC4T8SlFHK0yPSa+idGLQFQ
joZp2JHYvNlTPkRJ/J4TcXxBTJmArcQgTIuNoBtC+0A/SwdK4MfTCUY4vNWNdese
5A4K65Nb7Oh1AdQieTBHNXXCdyFsva9/ScfQGEl7p55a52jOPs0StPd7g64uvjlg
YHi2yesCrOvVXt+lgPTd
-----END CERTIFICATE-----'
end end
key do key do
'-----BEGIN PRIVATE KEY----- File.read(Rails.root.join('spec/fixtures/', 'ssl_key.pem'))
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKS+CfS9GcRSdYSN
SzyH5QJQBr5umRL6E+KilOV39iYFO/9oHjUdapTRWkrwnNPCp7qaeck4Jr8iv14t
PVNDfNr76eGb6/3YknOAP0QOjLWunoC8kjU+N/JHU52NrUeX3qEy8EKV9LeCDJcB
kBk+Yejn9nypg8c7sLsn33CB6i3bAgMBAAECgYA2D26w80T7WZvazYr86BNMePpd
j2mIAqx32KZHzt/lhh40J/SRtX9+Kl0Y7nBoRR5Ja9u/HkAIxNxLiUjwg9r6cpg/
uITEF5nMt7lAk391BuI+7VOZZGbJDsq2ulPd6lO+C8Kq/PI/e4kXcIjeH6KwQsuR
5vrXfBZ3sQfflaiN4QJBANBt8JY2LIGQF8o89qwUpRL5vbnKQ4IzZ5+TOl4RLR7O
AQpJ81tGuINghO7aunctb6rrcKJrxmEH1whzComybrMCQQDKV49nOBudRBAIgG4K
EnLzsRKISUHMZSJiYTYnablof8cKw1JaQduw7zgrUlLwnroSaAGX88+Jw1f5n2Lh
Vlg5AkBDdUGnrDLtYBCDEQYZHblrkc7ZAeCllDOWjxUV+uMqlCv8A4Ey6omvY57C
m6I8DkWVAQx8VPtozhvHjUw80rZHAkB55HWHAM3h13axKG0htCt7klhPsZHpx6MH
EPjGlXIT+aW2XiPmK3ZlCDcWIenE+lmtbOpI159Wpk8BGXs/s/xBAkEAlAY3ymgx
63BDJEwvOb2IaP8lDDxNsXx9XJNVvQbv5n15vNsLHbjslHfAhAbxnLQ1fLhUPqSi
nNp/xedE1YxutQ==
-----END PRIVATE KEY-----'
end end
end end
end end

View file

@ -135,43 +135,11 @@ shared_examples 'pages settings editing' do
context 'when pages are exposed on external HTTPS address', :https_pages_enabled, :js do context 'when pages are exposed on external HTTPS address', :https_pages_enabled, :js do
let(:certificate_pem) do let(:certificate_pem) do
<<~PEM attributes_for(:pages_domain)[:certificate]
-----BEGIN CERTIFICATE-----
MIICGzCCAYSgAwIBAgIBATANBgkqhkiG9w0BAQUFADAbMRkwFwYDVQQDExB0ZXN0
LWNlcnRpZmljYXRlMB4XDTE2MDIxMjE0MzIwMFoXDTIwMDQxMjE0MzIwMFowGzEZ
MBcGA1UEAxMQdGVzdC1jZXJ0aWZpY2F0ZTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
gYkCgYEApL4J9L0ZxFJ1hI1LPIflAlAGvm6ZEvoT4qKU5Xf2JgU7/2geNR1qlNFa
SvCc08Knupp5yTgmvyK/Xi09U0N82vvp4Zvr/diSc4A/RA6Mta6egLySNT438kdT
nY2tR5feoTLwQpX0t4IMlwGQGT5h6Of2fKmDxzuwuyffcIHqLdsCAwEAAaNvMG0w
DAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUxl9WSxBprB0z0ibJs3rXEk0+95AwCwYD
VR0PBAQDAgXgMBEGCWCGSAGG+EIBAQQEAwIGQDAeBglghkgBhvhCAQ0EERYPeGNh
IGNlcnRpZmljYXRlMA0GCSqGSIb3DQEBBQUAA4GBAGC4T8SlFHK0yPSa+idGLQFQ
joZp2JHYvNlTPkRJ/J4TcXxBTJmArcQgTIuNoBtC+0A/SwdK4MfTCUY4vNWNdese
5A4K65Nb7Oh1AdQieTBHNXXCdyFsva9/ScfQGEl7p55a52jOPs0StPd7g64uvjlg
YHi2yesCrOvVXt+lgPTd
-----END CERTIFICATE-----
PEM
end end
let(:certificate_key) do let(:certificate_key) do
<<~KEY attributes_for(:pages_domain)[:key]
-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKS+CfS9GcRSdYSN
SzyH5QJQBr5umRL6E+KilOV39iYFO/9oHjUdapTRWkrwnNPCp7qaeck4Jr8iv14t
PVNDfNr76eGb6/3YknOAP0QOjLWunoC8kjU+N/JHU52NrUeX3qEy8EKV9LeCDJcB
kBk+Yejn9nypg8c7sLsn33CB6i3bAgMBAAECgYA2D26w80T7WZvazYr86BNMePpd
j2mIAqx32KZHzt/lhh40J/SRtX9+Kl0Y7nBoRR5Ja9u/HkAIxNxLiUjwg9r6cpg/
uITEF5nMt7lAk391BuI+7VOZZGbJDsq2ulPd6lO+C8Kq/PI/e4kXcIjeH6KwQsuR
5vrXfBZ3sQfflaiN4QJBANBt8JY2LIGQF8o89qwUpRL5vbnKQ4IzZ5+TOl4RLR7O
AQpJ81tGuINghO7aunctb6rrcKJrxmEH1whzComybrMCQQDKV49nOBudRBAIgG4K
EnLzsRKISUHMZSJiYTYnablof8cKw1JaQduw7zgrUlLwnroSaAGX88+Jw1f5n2Lh
Vlg5AkBDdUGnrDLtYBCDEQYZHblrkc7ZAeCllDOWjxUV+uMqlCv8A4Ey6omvY57C
m6I8DkWVAQx8VPtozhvHjUw80rZHAkB55HWHAM3h13axKG0htCt7klhPsZHpx6MH
EPjGlXIT+aW2XiPmK3ZlCDcWIenE+lmtbOpI159Wpk8BGXs/s/xBAkEAlAY3ymgx
63BDJEwvOb2IaP8lDDxNsXx9XJNVvQbv5n15vNsLHbjslHfAhAbxnLQ1fLhUPqSi
nNp/xedE1YxutQ==
-----END PRIVATE KEY-----
KEY
end end
it 'adds new domain with certificate' do it 'adds new domain with certificate' do

View file

@ -142,11 +142,7 @@ describe 'Projects > Settings > Repository settings' do
end end
context 'remote mirror settings' do context 'remote mirror settings' do
let(:user2) { create(:user) }
before do before do
project.add_maintainer(user2)
visit project_settings_repository_path(project) visit project_settings_repository_path(project)
end end
@ -206,6 +202,18 @@ describe 'Projects > Settings > Repository settings' do
expect(page).to have_selector('[title="Copy SSH public key"]') expect(page).to have_selector('[title="Copy SSH public key"]')
end end
context 'when project mirroring is disabled' do
before do
stub_application_setting(mirror_available: false)
visit project_settings_repository_path(project)
end
it 'hides remote mirror settings' do
expect(page.find('.project-mirror-settings')).not_to have_selector('form')
expect(page).to have_content('Mirror settings are only available to GitLab administrators.')
end
end
def select_direction(direction = 'push') def select_direction(direction = 'push')
direction_select = find('#mirror_direction') direction_select = find('#mirror_direction')
@ -270,4 +278,31 @@ describe 'Projects > Settings > Repository settings' do
expect(mirror).not_to have_selector('.rspec-update-now-button') expect(mirror).not_to have_selector('.rspec-update-now-button')
end end
end end
context 'for admin' do
shared_examples_for 'shows mirror settings' do
it 'shows mirror settings' do
expect(page.find('.project-mirror-settings')).to have_selector('form')
expect(page).not_to have_content('Changing mirroring setting is disabled for non-admin users.')
end
end
before do
stub_application_setting(mirror_available: mirror_available)
user.update!(admin: true)
visit project_settings_repository_path(project)
end
context 'when project mirroring is enabled' do
let(:mirror_available) { true }
include_examples 'shows mirror settings'
end
context 'when project mirroring is disabled' do
let(:mirror_available) { false }
include_examples 'shows mirror settings'
end
end
end end

12
spec/fixtures/ssl_certificate.pem vendored Normal file
View file

@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIBrzCCARgCCQDbfQx2zdkNYTANBgkqhkiG9w0BAQsFADAbMRkwFwYDVQQDDBB0
ZXN0LWNlcnRpZmljYXRlMCAXDTIwMDMxNjE0MjAzNFoYDzIyMjAwMTI4MTQyMDM0
WjAbMRkwFwYDVQQDDBB0ZXN0LWNlcnRpZmljYXRlMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCkvgn0vRnEUnWEjUs8h+UCUAa+bpkS+hPiopTld/YmBTv/aB41
HWqU0VpK8JzTwqe6mnnJOCa/Ir9eLT1TQ3za++nhm+v92JJzgD9EDoy1rp6AvJI1
PjfyR1Odja1Hl96hMvBClfS3ggyXAZAZPmHo5/Z8qYPHO7C7J99wgeot2wIDAQAB
MA0GCSqGSIb3DQEBCwUAA4GBACc+chrTAuvnMBTedc4/dy16pEesK6oGjywYUd/0
/FBr8Vry7QUXMSgfraza9S0V+JvFvZFqkkOyJKW+m30kThWzyc/2e+BRxTh/QrxP
0j84QXtmnVtW4jsAwfBBfg78ST27eyp/WhruI6F/kZlXhfAed0RcPbRnbi3yvUPL
Lo4T
-----END CERTIFICATE-----

16
spec/fixtures/ssl_key.pem vendored Normal file
View file

@ -0,0 +1,16 @@
-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKS+CfS9GcRSdYSN
SzyH5QJQBr5umRL6E+KilOV39iYFO/9oHjUdapTRWkrwnNPCp7qaeck4Jr8iv14t
PVNDfNr76eGb6/3YknOAP0QOjLWunoC8kjU+N/JHU52NrUeX3qEy8EKV9LeCDJcB
kBk+Yejn9nypg8c7sLsn33CB6i3bAgMBAAECgYA2D26w80T7WZvazYr86BNMePpd
j2mIAqx32KZHzt/lhh40J/SRtX9+Kl0Y7nBoRR5Ja9u/HkAIxNxLiUjwg9r6cpg/
uITEF5nMt7lAk391BuI+7VOZZGbJDsq2ulPd6lO+C8Kq/PI/e4kXcIjeH6KwQsuR
5vrXfBZ3sQfflaiN4QJBANBt8JY2LIGQF8o89qwUpRL5vbnKQ4IzZ5+TOl4RLR7O
AQpJ81tGuINghO7aunctb6rrcKJrxmEH1whzComybrMCQQDKV49nOBudRBAIgG4K
EnLzsRKISUHMZSJiYTYnablof8cKw1JaQduw7zgrUlLwnroSaAGX88+Jw1f5n2Lh
Vlg5AkBDdUGnrDLtYBCDEQYZHblrkc7ZAeCllDOWjxUV+uMqlCv8A4Ey6omvY57C
m6I8DkWVAQx8VPtozhvHjUw80rZHAkB55HWHAM3h13axKG0htCt7klhPsZHpx6MH
EPjGlXIT+aW2XiPmK3ZlCDcWIenE+lmtbOpI159Wpk8BGXs/s/xBAkEAlAY3ymgx
63BDJEwvOb2IaP8lDDxNsXx9XJNVvQbv5n15vNsLHbjslHfAhAbxnLQ1fLhUPqSi
nNp/xedE1YxutQ==
-----END PRIVATE KEY-----

View file

@ -0,0 +1,60 @@
# frozen_string_literal: true
require 'spec_helper'
describe X509Helper do
describe '#x509_subject' do
let(:search_uppercase) { %w[CN OU O] }
let(:search_lowercase) { %w[cn ou o] }
let(:certificate_attributes) do
{
'CN' => 'CA Issuing',
'OU' => 'Trust Center',
'O' => 'Example'
}
end
context 'with uppercase DN' do
let(:upper_dn) { 'CN=CA Issuing,OU=Trust Center,O=Example,L=World,C=Galaxy' }
it 'returns the attributes on any case search' do
expect(x509_subject(upper_dn, search_lowercase)).to eq(certificate_attributes)
expect(x509_subject(upper_dn, search_uppercase)).to eq(certificate_attributes)
end
end
context 'with lowercase DN' do
let(:lower_dn) { 'cn=CA Issuing,ou=Trust Center,o=Example,l=World,c=Galaxy' }
it 'returns the attributes on any case search' do
expect(x509_subject(lower_dn, search_lowercase)).to eq(certificate_attributes)
expect(x509_subject(lower_dn, search_uppercase)).to eq(certificate_attributes)
end
end
context 'with comma within DN' do
let(:comma_dn) { 'cn=CA\, Issuing,ou=Trust Center,o=Example,l=World,c=Galaxy' }
let(:certificate_attributes) do
{
'CN' => 'CA, Issuing',
'OU' => 'Trust Center',
'O' => 'Example'
}
end
it 'returns the attributes on any case search' do
expect(x509_subject(comma_dn, search_lowercase)).to eq(certificate_attributes)
expect(x509_subject(comma_dn, search_uppercase)).to eq(certificate_attributes)
end
end
context 'with mal formed DN' do
let(:bad_dn) { 'cn=CA, Issuing,ou=Trust Center,o=Example,l=World,c=Galaxy' }
it 'returns nil on any case search' do
expect(x509_subject(bad_dn, search_lowercase)).to eq({})
expect(x509_subject(bad_dn, search_uppercase)).to eq({})
end
end
end
end

View file

@ -108,5 +108,23 @@ describe('Frequent Items utils spec', () => {
expect(sanitizeItem(input)).toEqual({ name: 'test', namespace: 'test', id: 1 }); expect(sanitizeItem(input)).toEqual({ name: 'test', namespace: 'test', id: 1 });
}); });
it("skips `name` key if it doesn't exist on the item", () => {
const input = {
namespace: '<br>test',
id: 1,
};
expect(sanitizeItem(input)).toEqual({ namespace: 'test', id: 1 });
});
it("skips `namespace` key if it doesn't exist on the item", () => {
const input = {
name: '<br><b>test</b>',
id: 1,
};
expect(sanitizeItem(input)).toEqual({ name: 'test', id: 1 });
});
}); });
}); });

View file

@ -523,7 +523,12 @@ describe Banzai::Filter::LabelReferenceFilter do
end end
context 'when group name has HTML entities' do context 'when group name has HTML entities' do
let(:another_group) { create(:group, name: '<img src=x onerror=alert(1)>', path: 'another_group') } let(:another_group) { create(:group, name: 'random', path: 'another_group') }
before do
another_group.name = "<img src=x onerror=alert(1)>"
another_group.save!(validate: false)
end
it 'escapes the HTML entities' do it 'escapes the HTML entities' do
expect(result.text) expect(result.text)

View file

@ -20,8 +20,8 @@ describe Banzai::Filter::ReferenceRedactorFilter do
it 'skips when the skip_redaction flag is set' do it 'skips when the skip_redaction flag is set' do
user = create(:user) user = create(:user)
project = create(:project) project = create(:project)
link = reference_link(project: project.id, reference_type: 'test') link = reference_link(project: project.id, reference_type: 'test')
doc = filter(link, current_user: user, skip_redaction: true) doc = filter(link, current_user: user, skip_redaction: true)
expect(doc.css('a').length).to eq 1 expect(doc.css('a').length).to eq 1
@ -51,8 +51,8 @@ describe Banzai::Filter::ReferenceRedactorFilter do
user = create(:user) user = create(:user)
project = create(:project) project = create(:project)
project.add_maintainer(user) project.add_maintainer(user)
link = reference_link(project: project.id, reference_type: 'test') link = reference_link(project: project.id, reference_type: 'test')
doc = filter(link, current_user: user) doc = filter(link, current_user: user)
expect(doc.css('a').length).to eq 1 expect(doc.css('a').length).to eq 1
@ -69,8 +69,8 @@ describe Banzai::Filter::ReferenceRedactorFilter do
it 'removes unpermitted references' do it 'removes unpermitted references' do
user = create(:user) user = create(:user)
project = create(:project) project = create(:project)
link = reference_link(project: project.id, reference_type: 'test') link = reference_link(project: project.id, reference_type: 'test')
doc = filter(link, current_user: user) doc = filter(link, current_user: user)
expect(doc.css('a').length).to eq 0 expect(doc.css('a').length).to eq 0
@ -90,8 +90,8 @@ describe Banzai::Filter::ReferenceRedactorFilter do
non_member = create(:user) non_member = create(:user)
project = create(:project, :public) project = create(:project, :public)
issue = create(:issue, :confidential, project: project) issue = create(:issue, :confidential, project: project)
link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue') link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue')
doc = filter(link, current_user: non_member) doc = filter(link, current_user: non_member)
expect(doc.css('a').length).to eq 0 expect(doc.css('a').length).to eq 0
@ -124,8 +124,8 @@ describe Banzai::Filter::ReferenceRedactorFilter do
assignee = create(:user) assignee = create(:user)
project = create(:project, :public) project = create(:project, :public)
issue = create(:issue, :confidential, project: project, assignees: [assignee]) issue = create(:issue, :confidential, project: project, assignees: [assignee])
link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue') link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue')
doc = filter(link, current_user: assignee) doc = filter(link, current_user: assignee)
expect(doc.css('a').length).to eq 1 expect(doc.css('a').length).to eq 1
@ -136,8 +136,8 @@ describe Banzai::Filter::ReferenceRedactorFilter do
project = create(:project, :public) project = create(:project, :public)
project.add_developer(member) project.add_developer(member)
issue = create(:issue, :confidential, project: project) issue = create(:issue, :confidential, project: project)
link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue') link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue')
doc = filter(link, current_user: member) doc = filter(link, current_user: member)
expect(doc.css('a').length).to eq 1 expect(doc.css('a').length).to eq 1
@ -147,20 +147,62 @@ describe Banzai::Filter::ReferenceRedactorFilter do
admin = create(:admin) admin = create(:admin)
project = create(:project, :public) project = create(:project, :public)
issue = create(:issue, :confidential, project: project) issue = create(:issue, :confidential, project: project)
link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue') link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue')
doc = filter(link, current_user: admin) doc = filter(link, current_user: admin)
expect(doc.css('a').length).to eq 1 expect(doc.css('a').length).to eq 1
end end
context "when a confidential issue is moved from a public project to a private one" do
let(:public_project) { create(:project, :public) }
let(:private_project) { create(:project, :private) }
it 'removes references for author' do
author = create(:user)
issue = create(:issue, :confidential, project: public_project, author: author)
issue.update!(project: private_project) # move issue to private project
link = reference_link(project: private_project.id, issue: issue.id, reference_type: 'issue')
doc = filter(link, current_user: author)
expect(doc.css('a').length).to eq 0
end
it 'removes references for assignee' do
assignee = create(:user)
issue = create(:issue, :confidential, project: public_project, assignees: [assignee])
issue.update!(project: private_project) # move issue to private project
link = reference_link(project: private_project.id, issue: issue.id, reference_type: 'issue')
doc = filter(link, current_user: assignee)
expect(doc.css('a').length).to eq 0
end
it 'allows references for project members' do
member = create(:user)
project = create(:project, :public)
project_2 = create(:project, :private)
project.add_developer(member)
project_2.add_developer(member)
issue = create(:issue, :confidential, project: project)
issue.update!(project: project_2) # move issue to private project
link = reference_link(project: project_2.id, issue: issue.id, reference_type: 'issue')
doc = filter(link, current_user: member)
expect(doc.css('a').length).to eq 1
end
end
end end
it 'allows references for non confidential issues' do it 'allows references for non confidential issues' do
user = create(:user) user = create(:user)
project = create(:project, :public) project = create(:project, :public)
issue = create(:issue, project: project) issue = create(:issue, project: project)
link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue') link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue')
doc = filter(link, current_user: user) doc = filter(link, current_user: user)
expect(doc.css('a').length).to eq 1 expect(doc.css('a').length).to eq 1
@ -172,8 +214,8 @@ describe Banzai::Filter::ReferenceRedactorFilter do
it 'removes unpermitted Group references' do it 'removes unpermitted Group references' do
user = create(:user) user = create(:user)
group = create(:group, :private) group = create(:group, :private)
link = reference_link(group: group.id, reference_type: 'user') link = reference_link(group: group.id, reference_type: 'user')
doc = filter(link, current_user: user) doc = filter(link, current_user: user)
expect(doc.css('a').length).to eq 0 expect(doc.css('a').length).to eq 0
@ -183,8 +225,8 @@ describe Banzai::Filter::ReferenceRedactorFilter do
user = create(:user) user = create(:user)
group = create(:group, :private) group = create(:group, :private)
group.add_developer(user) group.add_developer(user)
link = reference_link(group: group.id, reference_type: 'user') link = reference_link(group: group.id, reference_type: 'user')
doc = filter(link, current_user: user) doc = filter(link, current_user: user)
expect(doc.css('a').length).to eq 1 expect(doc.css('a').length).to eq 1
@ -200,8 +242,8 @@ describe Banzai::Filter::ReferenceRedactorFilter do
context 'with data-user' do context 'with data-user' do
it 'allows any User reference' do it 'allows any User reference' do
user = create(:user) user = create(:user)
link = reference_link(user: user.id, reference_type: 'user') link = reference_link(user: user.id, reference_type: 'user')
doc = filter(link) doc = filter(link)
expect(doc.css('a').length).to eq 1 expect(doc.css('a').length).to eq 1

View file

@ -165,6 +165,12 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
expect(subject).to eq(Gitlab::Auth::Result.new(build.user, build.project, :build, described_class.build_authentication_abilities)) expect(subject).to eq(Gitlab::Auth::Result.new(build.user, build.project, :build, described_class.build_authentication_abilities))
end end
it 'fails with blocked user token' do
build.update(user: create(:user, :blocked))
expect(subject).to eq(Gitlab::Auth::Result.new(nil, nil, nil, nil))
end
end end
(HasStatus::AVAILABLE_STATUSES - ['running']).each do |build_status| (HasStatus::AVAILABLE_STATUSES - ['running']).each do |build_status|
@ -260,6 +266,15 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
gl_auth.find_for_git_client("oauth2", token_w_api_scope.token, project: nil, ip: 'ip') gl_auth.find_for_git_client("oauth2", token_w_api_scope.token, project: nil, ip: 'ip')
end end
context 'blocked user' do
let(:user) { create(:user, :blocked) }
it 'fails' do
expect(gl_auth.find_for_git_client("oauth2", token_w_api_scope.token, project: nil, ip: 'ip'))
.to eq(Gitlab::Auth::Result.new(nil, nil, nil, nil))
end
end
end end
context 'while using personal access tokens as passwords' do context 'while using personal access tokens as passwords' do
@ -308,9 +323,35 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
it 'fails if password is nil' do it 'fails if password is nil' do
expect_results_with_abilities(nil, nil, false) expect_results_with_abilities(nil, nil, false)
end end
context 'when user is blocked' do
let(:user) { create(:user, :blocked) }
let(:personal_access_token) { create(:personal_access_token, scopes: ['read_registry'], user: user) }
before do
stub_container_registry_config(enabled: true)
end
it 'fails if user is blocked' do
expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip'))
.to eq(Gitlab::Auth::Result.new(nil, nil, nil, nil))
end
end
end end
context 'while using regular user and password' do context 'while using regular user and password' do
it 'fails for a blocked user' do
user = create(
:user,
:blocked,
username: 'normal_user',
password: 'my-secret'
)
expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
.to eq(Gitlab::Auth::Result.new(nil, nil, nil, nil))
end
it 'goes through lfs authentication' do it 'goes through lfs authentication' do
user = create( user = create(
:user, :user,

View file

@ -68,6 +68,16 @@ describe Gitlab::Gfm::UploadsRewriter do
expect(moved_text.scan(/\A\[.*?\]/).count).to eq(1) expect(moved_text.scan(/\A\[.*?\]/).count).to eq(1)
end end
context 'path traversal in file name' do
let(:text) do
"![a](/uploads/11111111111111111111111111111111/../../../../../../../../../../../../../../etc/passwd)"
end
it 'throw an error' do
expect { rewriter.rewrite(new_project) }.to raise_error(an_instance_of(StandardError).and having_attributes(message: "Invalid path"))
end
end
context "file are stored locally" do context "file are stored locally" do
include_examples "files are accessible" include_examples "files are accessible"
end end

View file

@ -0,0 +1,75 @@
# frozen_string_literal: true
require "spec_helper"
RSpec.describe Gitlab::HotlinkingDetector do
describe ".intercept_hotlinking?" do
using RSpec::Parameterized::TableSyntax
subject { described_class.intercept_hotlinking?(request) }
let(:request) { double("request", headers: headers) }
let(:headers) { {} }
context "hotlinked as media" do
where(:return_value, :accept_header) do
# These are default formats in modern browsers, and IE
false | "*/*"
false | "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
false | "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"
false | "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
false | "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"
false | "image/jpeg, application/x-ms-application, image/gif, application/xaml+xml, image/pjpeg, application/x-ms-xbap, application/x-shockwave-flash, application/msword, */*"
false | "text/html, application/xhtml+xml, image/jxr, */*"
false | "text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/webp, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1"
# These are image request formats
true | "image/webp,*/*"
true | "image/png,image/*;q=0.8,*/*;q=0.5"
true | "image/webp,image/apng,image/*,*/*;q=0.8"
true | "image/png,image/svg+xml,image/*;q=0.8, */*;q=0.5"
# Video request formats
true | "video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5"
# Audio request formats
true | "audio/webm,audio/ogg,audio/wav,audio/*;q=0.9,application/ogg;q=0.7,video/*;q=0.6,*/*;q=0.5"
# CSS request formats
true | "text/css,*/*;q=0.1"
true | "text/css"
true | "text/css,*/*;q=0.1"
end
with_them do
let(:headers) do
{ "Accept" => accept_header }
end
it { is_expected.to be(return_value) }
end
end
context "hotlinked as a script" do
where(:return_value, :fetch_mode) do
# Standard navigation fetch modes
false | "navigate"
false | "nested-navigate"
false | "same-origin"
# Fetch modes when linking as JS
true | "cors"
true | "no-cors"
true | "websocket"
end
with_them do
let(:headers) do
{ "Sec-Fetch-Mode" => fetch_mode }
end
it { is_expected.to be(return_value) }
end
end
end
end

View file

@ -32,6 +32,9 @@ describe Gitlab::ImportExport::AttributeCleaner do
'issue_ids' => [1, 2, 3], 'issue_ids' => [1, 2, 3],
'merge_request_ids' => [1, 2, 3], 'merge_request_ids' => [1, 2, 3],
'note_ids' => [1, 2, 3], 'note_ids' => [1, 2, 3],
'remote_attachment_url' => 'http://something.dodgy',
'remote_attachment_request_header' => 'bad value',
'remote_attachment_urls' => %w(http://something.dodgy http://something.okay),
'attributes' => { 'attributes' => {
'issue_ids' => [1, 2, 3], 'issue_ids' => [1, 2, 3],
'merge_request_ids' => [1, 2, 3], 'merge_request_ids' => [1, 2, 3],

View file

@ -5,9 +5,7 @@ require 'spec_helper'
require 'tempfile' require 'tempfile'
describe Gitlab::Middleware::Multipart do describe Gitlab::Middleware::Multipart do
let(:app) { double(:app) } include_context 'multipart middleware context'
let(:middleware) { described_class.new(app) }
let(:original_filename) { 'filename' }
shared_examples_for 'multipart upload files' do shared_examples_for 'multipart upload files' do
it 'opens top-level files' do it 'opens top-level files' do
@ -82,16 +80,7 @@ describe Gitlab::Middleware::Multipart do
end end
it 'allows files in uploads/tmp directory' do it 'allows files in uploads/tmp directory' do
Dir.mktmpdir do |dir| with_tmp_dir('public/uploads/tmp') do |dir, env|
uploads_dir = File.join(dir, 'public/uploads/tmp')
FileUtils.mkdir_p(uploads_dir)
allow(Rails).to receive(:root).and_return(dir)
allow(Dir).to receive(:tmpdir).and_return(File.join(Dir.tmpdir, 'tmpsubdir'))
Tempfile.open('top-level', uploads_dir) do |tempfile|
env = post_env({ 'file' => tempfile.path }, { 'file.name' => original_filename, 'file.path' => tempfile.path }, Gitlab::Workhorse.secret, 'gitlab-workhorse')
expect(app).to receive(:call) do |env| expect(app).to receive(:call) do |env|
expect(get_params(env)['file']).to be_a(::UploadedFile) expect(get_params(env)['file']).to be_a(::UploadedFile)
end end
@ -99,7 +88,6 @@ describe Gitlab::Middleware::Multipart do
middleware.call(env) middleware.call(env)
end end
end end
end
it 'allows symlinks for uploads dir' do it 'allows symlinks for uploads dir' do
Tempfile.open('two-levels') do |tempfile| Tempfile.open('two-levels') do |tempfile|
@ -127,22 +115,4 @@ describe Gitlab::Middleware::Multipart do
middleware.call(env) middleware.call(env)
end end
end end
# Rails 5 doesn't combine the GET/POST parameters in
# ActionDispatch::HTTP::Parameters if action_dispatch.request.parameters is set:
# https://github.com/rails/rails/blob/aea6423f013ca48f7704c70deadf2cd6ac7d70a1/actionpack/lib/action_dispatch/http/parameters.rb#L41
def get_params(env)
req = ActionDispatch::Request.new(env)
req.GET.merge(req.POST)
end
def post_env(rewritten_fields, params, secret, issuer)
token = JWT.encode({ 'iss' => issuer, 'rewritten_fields' => rewritten_fields }, secret, 'HS256')
Rack::MockRequest.env_for(
'/',
method: 'post',
params: params,
described_class::RACK_ENV_KEY => token
)
end
end end

View file

@ -3,9 +3,7 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::Regex do describe Gitlab::Regex do
describe '.project_name_regex' do shared_examples_for 'project/group name regex' do
subject { described_class.project_name_regex }
it { is_expected.to match('gitlab-ce') } it { is_expected.to match('gitlab-ce') }
it { is_expected.to match('GitLab CE') } it { is_expected.to match('GitLab CE') }
it { is_expected.to match('100 lines') } it { is_expected.to match('100 lines') }
@ -15,6 +13,34 @@ describe Gitlab::Regex do
it { is_expected.not_to match('?gitlab') } it { is_expected.not_to match('?gitlab') }
end end
shared_examples_for 'project/group name error message' do
it { is_expected.to eq("can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'.") }
end
describe '.project_name_regex' do
subject { described_class.project_name_regex }
it_behaves_like 'project/group name regex'
end
describe '.group_name_regex' do
subject { described_class.group_name_regex }
it_behaves_like 'project/group name regex'
end
describe '.project_name_regex_message' do
subject { described_class.project_name_regex_message }
it_behaves_like 'project/group name error message'
end
describe '.group_name_regex_message' do
subject { described_class.group_name_regex_message }
it_behaves_like 'project/group name error message'
end
describe '.environment_name_regex' do describe '.environment_name_regex' do
subject { described_class.environment_name_regex } subject { described_class.environment_name_regex }

View file

@ -204,5 +204,38 @@ describe Gitlab::X509::Commit do
expect(described_class.new(commit).signature).to be_nil expect(described_class.new(commit).signature).to be_nil
end end
end end
context 'certificate_crl' do
let!(:commit) { create :commit, project: project, sha: commit_sha, created_at: Time.utc(2019, 1, 1, 20, 15, 0), committer_email: X509Helpers::User1.emails.first }
let(:signed_commit) { described_class.new(commit) }
describe 'valid crlDistributionPoints' do
before do
allow(signed_commit).to receive(:get_certificate_extension).and_call_original
allow(signed_commit).to receive(:get_certificate_extension)
.with('crlDistributionPoints')
.and_return("\nFull Name:\n URI:http://ch.siemens.com/pki?ZZZZZZA2.crl\n URI:ldap://cl.siemens.net/CN=ZZZZZZA2,L=PKI?certificateRevocationList\n URI:ldap://cl.siemens.com/CN=ZZZZZZA2,o=Trustcenter?certificateRevocationList\n")
end
it 'returns an unverified signature' do
expect(signed_commit.signature.x509_certificate.x509_issuer).to have_attributes(user1_issuer_attributes)
end
end
describe 'valid crlDistributionPoints providing multiple http URIs' do
before do
allow(signed_commit).to receive(:get_certificate_extension).and_call_original
allow(signed_commit).to receive(:get_certificate_extension)
.with('crlDistributionPoints')
.and_return("\nFull Name:\n URI:http://cdp1.pca.dfn.de/dfn-ca-global-g2/pub/crl/cacrl.crl\n\nFull Name:\n URI:http://cdp2.pca.dfn.de/dfn-ca-global-g2/pub/crl/cacrl.crl\n")
end
it 'extracts the first URI' do
expect(signed_commit.signature.x509_certificate.x509_issuer.crl_url).to eq("http://cdp1.pca.dfn.de/dfn-ca-global-g2/pub/crl/cacrl.crl")
end
end
end
end end
end end

View file

@ -59,6 +59,16 @@ describe UploadedFile do
expect(subject.sha256).to eq('sha256') expect(subject.sha256).to eq('sha256')
expect(subject.remote_id).to eq('remote_id') expect(subject.remote_id).to eq('remote_id')
end end
it 'handles a blank path' do
params['file.path'] = ''
# Not a real file, so can't determine size itself
params['file.size'] = 1.byte
expect { described_class.from_params(params, :file, upload_path) }
.not_to raise_error
end
end end
end end

View file

@ -48,6 +48,9 @@ describe Group do
describe 'validations' do describe 'validations' do
it { is_expected.to validate_presence_of :name } it { is_expected.to validate_presence_of :name }
it { is_expected.to allow_value('group test_4').for(:name) }
it { is_expected.not_to allow_value('test/../foo').for(:name) }
it { is_expected.not_to allow_value('<script>alert("Attack!")</script>').for(:name) }
it { is_expected.to validate_presence_of :path } it { is_expected.to validate_presence_of :path }
it { is_expected.not_to validate_presence_of :owner } it { is_expected.not_to validate_presence_of :owner }
it { is_expected.to validate_presence_of :two_factor_grace_period } it { is_expected.to validate_presence_of :two_factor_grace_period }

View file

@ -536,88 +536,146 @@ describe Issue do
end end
describe '#visible_to_user?' do describe '#visible_to_user?' do
let(:project) { build(:project) }
let(:issue) { build(:issue, project: project) }
let(:user) { create(:user) }
subject { issue.visible_to_user?(user) }
context 'with a project' do
it 'returns false when feature is disabled' do
project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
is_expected.to eq(false)
end
it 'returns false when restricted for members' do
project.project_feature.update_attribute(:issues_access_level, ProjectFeature::PRIVATE)
is_expected.to eq(false)
end
end
context 'without a user' do context 'without a user' do
let(:issue) { build(:issue) } let(:user) { nil }
it 'returns true when the issue is publicly visible' do it 'returns true when the issue is publicly visible' do
expect(issue).to receive(:publicly_visible?).and_return(true) expect(issue).to receive(:publicly_visible?).and_return(true)
expect(issue.visible_to_user?).to eq(true) is_expected.to eq(true)
end end
it 'returns false when the issue is not publicly visible' do it 'returns false when the issue is not publicly visible' do
expect(issue).to receive(:publicly_visible?).and_return(false) expect(issue).to receive(:publicly_visible?).and_return(false)
expect(issue.visible_to_user?).to eq(false) is_expected.to eq(false)
end end
end end
context 'with a user' do context 'with a user' do
let(:user) { create(:user) } shared_examples 'issue readable by user' do
let(:issue) { build(:issue) } it { is_expected.to eq(true) }
it 'returns true when the issue is readable' do
expect(issue).to receive(:readable_by?).with(user).and_return(true)
expect(issue.visible_to_user?(user)).to eq(true)
end end
it 'returns false when the issue is not readable' do shared_examples 'issue not readable by user' do
expect(issue).to receive(:readable_by?).with(user).and_return(false) it { is_expected.to eq(false) }
expect(issue.visible_to_user?(user)).to eq(false)
end end
it 'returns false when feature is disabled' do shared_examples 'confidential issue readable by user' do
expect(issue).not_to receive(:readable_by?) specify do
issue.confidential = true
issue.project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED) is_expected.to eq(true)
expect(issue.visible_to_user?(user)).to eq(false)
end
it 'returns false when restricted for members' do
expect(issue).not_to receive(:readable_by?)
issue.project.project_feature.update_attribute(:issues_access_level, ProjectFeature::PRIVATE)
expect(issue.visible_to_user?(user)).to eq(false)
end end
end end
describe 'with a regular user that is not a team member' do shared_examples 'confidential issue not readable by user' do
let(:user) { create(:user) } specify do
issue.confidential = true
is_expected.to eq(false)
end
end
context 'with an admin user' do
let(:user) { build(:admin) }
it_behaves_like 'issue readable by user'
it_behaves_like 'confidential issue readable by user'
end
context 'with an owner' do
before do
project.add_maintainer(user)
end
it_behaves_like 'issue readable by user'
it_behaves_like 'confidential issue readable by user'
end
context 'with a reporter user' do
before do
project.add_reporter(user)
end
it_behaves_like 'issue readable by user'
it_behaves_like 'confidential issue readable by user'
end
context 'with a guest user' do
before do
project.add_guest(user)
end
it_behaves_like 'issue readable by user'
it_behaves_like 'confidential issue not readable by user'
context 'when user is an assignee' do
before do
issue.update!(assignees: [user])
end
it_behaves_like 'issue readable by user'
it_behaves_like 'confidential issue readable by user'
end
context 'when user is the author' do
before do
issue.update!(author: user)
end
it_behaves_like 'issue readable by user'
it_behaves_like 'confidential issue readable by user'
end
end
context 'with a user that is not a member' do
context 'using a public project' do context 'using a public project' do
let(:project) { create(:project, :public) } let(:project) { build(:project, :public) }
it 'returns true for a regular issue' do it_behaves_like 'issue readable by user'
issue = build(:issue, project: project) it_behaves_like 'confidential issue not readable by user'
expect(issue.visible_to_user?(user)).to eq(true)
end
it 'returns false for a confidential issue' do
issue = build(:issue, project: project, confidential: true)
expect(issue.visible_to_user?(user)).to eq(false)
end
end end
context 'using an internal project' do context 'using an internal project' do
let(:project) { create(:project, :internal) } let(:project) { build(:project, :internal) }
context 'using an internal user' do context 'using an internal user' do
it 'returns true for a regular issue' do before do
issue = build(:issue, project: project) allow(user).to receive(:external?).and_return(false)
expect(issue.visible_to_user?(user)).to eq(true)
end end
it 'returns false for a confidential issue' do it_behaves_like 'issue readable by user'
issue = build(:issue, :confidential, project: project) it_behaves_like 'confidential issue not readable by user'
end
expect(issue.visible_to_user?(user)).to eq(false) context 'using an external user' do
before do
allow(user).to receive(:external?).and_return(true)
end
it_behaves_like 'issue not readable by user'
it_behaves_like 'confidential issue not readable by user'
end end
end end
@ -626,132 +684,110 @@ describe Issue do
allow(user).to receive(:external?).and_return(true) allow(user).to receive(:external?).and_return(true)
end end
it 'returns false for a regular issue' do it_behaves_like 'issue not readable by user'
issue = build(:issue, project: project) it_behaves_like 'confidential issue not readable by user'
expect(issue.visible_to_user?(user)).to eq(false)
end
it 'returns false for a confidential issue' do
issue = build(:issue, :confidential, project: project)
expect(issue.visible_to_user?(user)).to eq(false)
end
end end
end end
context 'using a private project' do context 'with an external authentication service' do
let(:project) { create(:project, :private) }
it 'returns false for a regular issue' do
issue = build(:issue, project: project)
expect(issue.visible_to_user?(user)).to eq(false)
end
it 'returns false for a confidential issue' do
issue = build(:issue, :confidential, project: project)
expect(issue.visible_to_user?(user)).to eq(false)
end
context 'when the user is the project owner' do
before do before do
project.add_maintainer(user) enable_external_authorization_service_check
end end
it 'returns true for a regular issue' do it 'is `false` when an external authorization service is enabled' do
issue = build(:issue, project: build(:project, :public))
expect(issue).not_to be_visible_to_user
end
it 'checks the external service to determine if an issue is readable by a user' do
project = build(:project, :public,
external_authorization_classification_label: 'a-label')
issue = build(:issue, project: project) issue = build(:issue, project: project)
user = build(:user)
expect(issue.visible_to_user?(user)).to eq(true) expect(::Gitlab::ExternalAuthorization).to receive(:access_allowed?).with(user, 'a-label') { false }
expect(issue.visible_to_user?(user)).to be_falsy
end end
it 'returns true for a confidential issue' do it 'does not check the external service if a user does not have access to the project' do
issue = build(:issue, :confidential, project: project) project = build(:project, :private,
external_authorization_classification_label: 'a-label')
expect(issue.visible_to_user?(user)).to eq(true)
end
end
end
end
context 'with a regular user that is a team member' do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
context 'using a public project' do
before do
project.add_developer(user)
end
it 'returns true for a regular issue' do
issue = build(:issue, project: project) issue = build(:issue, project: project)
user = build(:user)
expect(issue.visible_to_user?(user)).to eq(true) expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?)
expect(issue.visible_to_user?(user)).to be_falsy
end end
it 'returns true for a confidential issue' do it 'does not check the external webservice for admins' do
issue = build(:issue, :confidential, project: project) issue = build(:issue)
user = build(:admin)
expect(issue.visible_to_user?(user)).to eq(true) expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?)
issue.visible_to_user?(user)
end end
end end
context 'using an internal project' do context 'when issue is moved to a private project' do
let(:project) { create(:project, :internal) } let(:private_project) { build(:project, :private)}
before do before do
project.add_developer(user) issue.update(project: private_project) # move issue to private project
end end
it 'returns true for a regular issue' do shared_examples 'issue visible if user has guest access' do
issue = build(:issue, project: project) context 'when user is not a member' do
it_behaves_like 'issue not readable by user'
expect(issue.visible_to_user?(user)).to eq(true) it_behaves_like 'confidential issue not readable by user'
end end
it 'returns true for a confidential issue' do context 'when user is a guest' do
issue = build(:issue, :confidential, project: project)
expect(issue.visible_to_user?(user)).to eq(true)
end
end
context 'using a private project' do
let(:project) { create(:project, :private) }
before do before do
project.add_developer(user) private_project.add_guest(user)
end end
it 'returns true for a regular issue' do it_behaves_like 'issue readable by user'
issue = build(:issue, project: project) it_behaves_like 'confidential issue readable by user'
expect(issue.visible_to_user?(user)).to eq(true)
end
it 'returns true for a confidential issue' do
issue = build(:issue, :confidential, project: project)
expect(issue.visible_to_user?(user)).to eq(true)
end end
end end
context 'when user is the author of the original issue' do
before do
issue.update!(author: user)
end end
context 'with an admin user' do it_behaves_like 'issue visible if user has guest access'
let(:project) { create(:project) }
let(:user) { create(:admin) }
it 'returns true for a regular issue' do
issue = build(:issue, project: project)
expect(issue.visible_to_user?(user)).to eq(true)
end end
it 'returns true for a confidential issue' do context 'when user is an assignee in the original issue' do
issue = build(:issue, :confidential, project: project) before do
issue.update!(assignees: [user])
end
expect(issue.visible_to_user?(user)).to eq(true) it_behaves_like 'issue visible if user has guest access'
end
context 'when user is not the author or an assignee in original issue' do
context 'when user is a guest' do
before do
private_project.add_guest(user)
end
it_behaves_like 'issue readable by user'
it_behaves_like 'confidential issue not readable by user'
end
context 'when user is a reporter' do
before do
private_project.add_reporter(user)
end
it_behaves_like 'issue readable by user'
it_behaves_like 'confidential issue readable by user'
end
end
end end
end end
end end
@ -875,49 +911,6 @@ describe Issue do
subject { create(:issue, updated_at: 1.hour.ago) } subject { create(:issue, updated_at: 1.hour.ago) }
end end
context 'when an external authentication service' do
before do
enable_external_authorization_service_check
end
describe '#visible_to_user?' do
it 'is `false` when an external authorization service is enabled' do
issue = build(:issue, project: build(:project, :public))
expect(issue).not_to be_visible_to_user
end
it 'checks the external service to determine if an issue is readable by a user' do
project = build(:project, :public,
external_authorization_classification_label: 'a-label')
issue = build(:issue, project: project)
user = build(:user)
expect(::Gitlab::ExternalAuthorization).to receive(:access_allowed?).with(user, 'a-label') { false }
expect(issue.visible_to_user?(user)).to be_falsy
end
it 'does not check the external service if a user does not have access to the project' do
project = build(:project, :private,
external_authorization_classification_label: 'a-label')
issue = build(:issue, project: project)
user = build(:user)
expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?)
expect(issue.visible_to_user?(user)).to be_falsy
end
it 'does not check the external webservice for admins' do
issue = build(:issue)
user = build(:admin)
expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?)
issue.visible_to_user?(user)
end
end
end
describe "#labels_hook_attrs" do describe "#labels_hook_attrs" do
let(:label) { create(:label) } let(:label) { create(:label) }
let(:issue) { create(:labeled_issue, labels: [label]) } let(:issue) { create(:labeled_issue, labels: [label]) }

View file

@ -96,8 +96,8 @@ describe PagesDomain do
it 'saves validity time' do it 'saves validity time' do
domain.save domain.save
expect(domain.certificate_valid_not_before).to be_like_time(Time.parse("2016-02-12 14:32:00 UTC")) expect(domain.certificate_valid_not_before).to be_like_time(Time.parse("2020-03-16 14:20:34 UTC"))
expect(domain.certificate_valid_not_after).to be_like_time(Time.parse("2020-04-12 14:32:00 UTC")) expect(domain.certificate_valid_not_after).to be_like_time(Time.parse("2220-01-28 14:20:34 UTC"))
end end
end end

View file

@ -103,12 +103,24 @@ describe IssuePolicy do
expect(permissions(author, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue) expect(permissions(author, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue)
end end
it 'does not allow issue author to read or update confidential issue moved to an private project' do
confidential_issue.project = build(:project, :private)
expect(permissions(author, confidential_issue)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue)
end
it 'allows issue assignees to read and update their confidential issues' do it 'allows issue assignees to read and update their confidential issues' do
expect(permissions(assignee, confidential_issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue) expect(permissions(assignee, confidential_issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue)
expect(permissions(assignee, confidential_issue)).to be_disallowed(:admin_issue) expect(permissions(assignee, confidential_issue)).to be_disallowed(:admin_issue)
expect(permissions(assignee, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue) expect(permissions(assignee, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue)
end end
it 'does not allow issue assignees to read or update confidential issue moved to an private project' do
confidential_issue.project = build(:project, :private)
expect(permissions(assignee, confidential_issue)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue)
end
end end
end end

View file

@ -568,6 +568,20 @@ describe API::Groups do
expect(json_response['shared_projects'].length).to eq(0) expect(json_response['shared_projects'].length).to eq(0)
end end
context 'malicious group name' do
subject { put api("/groups/#{group1.id}", user1), params: { name: "<SCRIPT>alert('DOUBLE-ATTACK!')</SCRIPT>" } }
it 'returns bad request' do
subject
expect(response).to have_gitlab_http_status(:bad_request)
end
it 'does not update group name' do
expect { subject }.not_to change { group1.reload.name }
end
end
it 'returns 404 for a non existing group' do it 'returns 404 for a non existing group' do
put api('/groups/1328', user1), params: { name: new_group_name } put api('/groups/1328', user1), params: { name: new_group_name }
@ -999,6 +1013,20 @@ describe API::Groups do
expect(json_response["parent_id"]).to eq(parent.id) expect(json_response["parent_id"]).to eq(parent.id)
end end
context 'malicious group name' do
subject { post api("/groups", user3), params: group_params }
let(:group_params) { attributes_for_group_api name: "<SCRIPT>alert('ATTACKED!')</SCRIPT>", path: "unique-url" }
it 'returns bad request' do
subject
expect(response).to have_gitlab_http_status(:bad_request)
end
it { expect { subject }.not_to change { Group.count } }
end
it "does not create group, duplicate" do it "does not create group, duplicate" do
post api("/groups", user3), params: { name: 'Duplicate Test', path: group2.path } post api("/groups", user3), params: { name: 'Duplicate Test', path: group2.path }

View file

@ -98,6 +98,30 @@ describe API::ProjectSnippets do
} }
end end
context 'with an external user' do
let(:user) { create(:user, :external) }
context 'that belongs to the project' do
before do
project.add_developer(user)
end
it 'creates a new snippet' do
post api("/projects/#{project.id}/snippets/", user), params: params
expect(response).to have_gitlab_http_status(201)
end
end
context 'that does not belong to the project' do
it 'does not create a new snippet' do
post api("/projects/#{project.id}/snippets/", user), params: params
expect(response).to have_gitlab_http_status(403)
end
end
end
context 'with a regular user' do context 'with a regular user' do
let(:user) { create(:user) } let(:user) { create(:user) }

View file

@ -263,6 +263,18 @@ describe API::Repositories do
let(:message) { '404 File Not Found' } let(:message) { '404 File Not Found' }
end end
end end
context "when hotlinking detection is enabled" do
before do
Feature.enable(:repository_archive_hotlinking_interception)
end
it_behaves_like "hotlink interceptor" do
let(:http_request) do
get api(route, current_user), headers: headers
end
end
end
end end
context 'when unauthenticated', 'and project is public' do context 'when unauthenticated', 'and project is public' do

View file

@ -224,6 +224,16 @@ describe API::Snippets do
it_behaves_like 'snippet creation' it_behaves_like 'snippet creation'
context 'with an external user' do
let(:user) { create(:user, :external) }
it 'does not create a new snippet' do
post api("/snippets/", user), params: params
expect(response).to have_gitlab_http_status(403)
end
end
it 'returns 400 for missing parameters' do it 'returns 400 for missing parameters' do
params.delete(:title) params.delete(:title)

View file

@ -238,24 +238,44 @@ describe API::Triggers do
end end
describe 'PUT /projects/:id/triggers/:trigger_id' do describe 'PUT /projects/:id/triggers/:trigger_id' do
context 'authenticated user with valid permissions' do context 'user is maintainer of the project' do
context 'the trigger belongs to user' do
let(:new_description) { 'new description' } let(:new_description) { 'new description' }
it 'updates description' do it 'updates description' do
put api("/projects/#{project.id}/triggers/#{trigger.id}", user), put api("/projects/#{project.id}/triggers/#{trigger.id}", user),
params: { description: new_description } params: { description: new_description }
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to include('description' => new_description) expect(json_response).to include('description' => new_description)
expect(trigger.reload.description).to eq(new_description) expect(trigger.reload.description).to eq(new_description)
end end
end end
context 'authenticated user with invalid permissions' do context 'the trigger does not belong to user' do
it 'does not update trigger' do
put api("/projects/#{project.id}/triggers/#{trigger2.id}", user)
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
context 'user is developer of the project' do
context 'the trigger belongs to user' do
it 'does not update trigger' do
put api("/projects/#{project.id}/triggers/#{trigger2.id}", user2)
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'the trigger does not belong to user' do
it 'does not update trigger' do it 'does not update trigger' do
put api("/projects/#{project.id}/triggers/#{trigger.id}", user2) put api("/projects/#{project.id}/triggers/#{trigger.id}", user2)
expect(response).to have_gitlab_http_status(403) expect(response).to have_gitlab_http_status(:forbidden)
end
end end
end end

View file

@ -25,6 +25,17 @@ describe JwtController do
end end
context 'when using authenticated request' do context 'when using authenticated request' do
shared_examples 'rejecting a blocked user' do
context 'with blocked user' do
let(:user) { create(:user, :blocked) }
it 'rejects the request as unauthorized' do
expect(response).to have_gitlab_http_status(:unauthorized)
expect(response.body).to include('HTTP Basic: Access denied')
end
end
end
context 'using CI token' do context 'using CI token' do
let(:build) { create(:ci_build, :running) } let(:build) { create(:ci_build, :running) }
let(:project) { build.project } let(:project) { build.project }
@ -61,6 +72,8 @@ describe JwtController do
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(service_class).to have_received(:new).with(nil, user, ActionController::Parameters.new(parameters).permit!) expect(service_class).to have_received(:new).with(nil, user, ActionController::Parameters.new(parameters).permit!)
end end
it_behaves_like 'rejecting a blocked user'
end end
end end
@ -72,6 +85,8 @@ describe JwtController do
it { expect(service_class).to have_received(:new).with(nil, user, ActionController::Parameters.new(parameters).permit!) } it { expect(service_class).to have_received(:new).with(nil, user, ActionController::Parameters.new(parameters).permit!) }
it_behaves_like 'rejecting a blocked user'
context 'when passing a flat array of scopes' do context 'when passing a flat array of scopes' do
# We use this trick to make rails to generate a query_string: # We use this trick to make rails to generate a query_string:
# scope=scope1&scope=scope2 # scope=scope1&scope=scope2

View file

@ -138,7 +138,7 @@ describe MergeRequestPollWidgetEntity do
end end
describe 'pipeline' do describe 'pipeline' do
let(:pipeline) { create(:ci_empty_pipeline, project: project, ref: resource.source_branch, sha: resource.source_branch_sha, head_pipeline_of: resource) } let!(:pipeline) { create(:ci_empty_pipeline, project: project, ref: resource.source_branch, sha: resource.source_branch_sha, head_pipeline_of: resource) }
before do before do
allow_any_instance_of(MergeRequestPresenter).to receive(:can?).and_call_original allow_any_instance_of(MergeRequestPresenter).to receive(:can?).and_call_original
@ -158,6 +158,10 @@ describe MergeRequestPollWidgetEntity do
expect(subject[:pipeline]).to eq(pipeline_payload) expect(subject[:pipeline]).to eq(pipeline_payload)
end end
it 'returns ci_status' do
expect(subject[:ci_status]).to eq('pending')
end
end end
context 'when is not up to date' do context 'when is not up to date' do
@ -171,10 +175,15 @@ describe MergeRequestPollWidgetEntity do
context 'when user does not have access to pipelines' do context 'when user does not have access to pipelines' do
let(:result) { false } let(:result) { false }
let(:req) { double('request', current_user: user, project: project) }
it 'does not have pipeline' do it 'does not have pipeline' do
expect(subject[:pipeline]).to eq(nil) expect(subject[:pipeline]).to eq(nil)
end end
it 'does not return ci_status' do
expect(subject[:ci_status]).to eq(nil)
end
end end
end end
end end

View file

@ -76,7 +76,7 @@ module WorkhorseHelpers
"#{key}.size" => file.size "#{key}.size" => file.size
}.tap do |params| }.tap do |params|
params["#{key}.path"] = file.path if file.path params["#{key}.path"] = file.path if file.path
params["#{key}.remote_id"] = file.remote_id if file.respond_to?(:remote_id) && file.remote_id params["#{key}.remote_id"] = file.remote_id if file.respond_to?(:remote_id) && file.remote_id.present?
end end
end end

View file

@ -0,0 +1,42 @@
# frozen_string_literal: true
RSpec.shared_context 'multipart middleware context' do
let(:app) { double(:app) }
let(:middleware) { described_class.new(app) }
let(:original_filename) { 'filename' }
# Rails 5 doesn't combine the GET/POST parameters in
# ActionDispatch::HTTP::Parameters if action_dispatch.request.parameters is set:
# https://github.com/rails/rails/blob/aea6423f013ca48f7704c70deadf2cd6ac7d70a1/actionpack/lib/action_dispatch/http/parameters.rb#L41
def get_params(env)
req = ActionDispatch::Request.new(env)
req.GET.merge(req.POST)
end
def post_env(rewritten_fields, params, secret, issuer)
token = JWT.encode({ 'iss' => issuer, 'rewritten_fields' => rewritten_fields }, secret, 'HS256')
Rack::MockRequest.env_for(
'/',
method: 'post',
params: params,
described_class::RACK_ENV_KEY => token
)
end
def with_tmp_dir(uploads_sub_dir, storage_path = '')
Dir.mktmpdir do |dir|
upload_dir = File.join(dir, storage_path, uploads_sub_dir)
FileUtils.mkdir_p(upload_dir)
allow(Rails).to receive(:root).and_return(dir)
allow(Dir).to receive(:tmpdir).and_return(File.join(Dir.tmpdir, 'tmpsubdir'))
allow(GitlabUploader).to receive(:root).and_return(File.join(dir, storage_path))
Tempfile.open('top-level', upload_dir) do |tempfile|
env = post_env({ 'file' => tempfile.path }, { 'file.name' => original_filename, 'file.path' => tempfile.path }, Gitlab::Workhorse.secret, 'gitlab-workhorse')
yield dir, env
end
end
end
end

View file

@ -0,0 +1,87 @@
# frozen_string_literal: true
RSpec.shared_examples "hotlink interceptor" do
let(:http_request) { nil }
let(:headers) { nil }
describe "DDOS prevention" do
using RSpec::Parameterized::TableSyntax
context "hotlinked as media" do
where(:response_status, :accept_header) do
# These are default formats in modern browsers, and IE
:ok | "*/*"
:ok | "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
:ok | "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"
:ok | "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
:ok | "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"
:ok | "image/jpeg, application/x-ms-application, image/gif, application/xaml+xml, image/pjpeg, application/x-ms-xbap, application/x-shockwave-flash, application/msword, */*"
:ok | "text/html, application/xhtml+xml, image/jxr, */*"
:ok | "text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/webp, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1"
# These are image request formats
:not_acceptable | "image/webp,*/*"
:not_acceptable | "image/png,image/*;q=0.8,*/*;q=0.5"
:not_acceptable | "image/webp,image/apng,image/*,*/*;q=0.8"
:not_acceptable | "image/png,image/svg+xml,image/*;q=0.8, */*;q=0.5"
# Video request formats
:not_acceptable | "video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5"
# Audio request formats
:not_acceptable | "audio/webm,audio/ogg,audio/wav,audio/*;q=0.9,application/ogg;q=0.7,video/*;q=0.6,*/*;q=0.5"
# CSS request formats
:not_acceptable | "text/css,*/*;q=0.1"
:not_acceptable | "text/css"
:not_acceptable | "text/css,*/*;q=0.1"
end
with_them do
let(:headers) do
{ "Accept" => accept_header }
end
before do
request.headers.merge!(headers) if request.present?
end
it "renders the response" do
http_request
expect(response).to have_gitlab_http_status(response_status)
end
end
end
context "hotlinked as a script" do
where(:response_status, :fetch_mode) do
# Standard navigation fetch modes
:ok | "navigate"
:ok | "nested-navigate"
:ok | "same-origin"
# Fetch modes when linking as JS
:not_acceptable | "cors"
:not_acceptable | "no-cors"
:not_acceptable | "websocket"
end
with_them do
let(:headers) do
{ "Sec-Fetch-Mode" => fetch_mode }
end
before do
request.headers.merge!(headers) if request.present?
end
it "renders the response" do
http_request
expect(response).to have_gitlab_http_status(response_status)
end
end
end
end
end

View file

@ -714,6 +714,19 @@ describe ObjectStorage do
end end
end end
context 'when empty remote_id is specified' do
let(:uploaded_file) do
UploadedFile.new(temp_file.path, remote_id: '')
end
it 'uses local storage' do
subject
expect(uploader).to be_file_storage
expect(uploader.object_store).to eq(described_class::Store::LOCAL)
end
end
context 'when valid file is specified' do context 'when valid file is specified' do
let(:uploaded_file) do let(:uploaded_file) do
UploadedFile.new(temp_file.path, filename: "my_file.txt", remote_id: "test/123123") UploadedFile.new(temp_file.path, filename: "my_file.txt", remote_id: "test/123123")