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:
commit
ae15ed391d
78 changed files with 1382 additions and 451 deletions
|
@ -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.
|
||||||
|
|
30
CHANGELOG.md
30
CHANGELOG.md
|
@ -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)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
12.8.6
|
12.8.8
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
8.21.0
|
8.21.1
|
||||||
|
|
|
@ -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
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
12.8.6
|
12.8.8
|
||||||
|
|
|
@ -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'),
|
||||||
});
|
};
|
||||||
|
};
|
||||||
|
|
15
app/controllers/concerns/hotlink_interceptor.rb
Normal file
15
app/controllers/concerns/hotlink_interceptor.rb
Normal 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
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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!
|
||||||
|
|
19
app/helpers/x509_helper.rb
Normal file
19
app/helpers/x509_helper.rb
Normal 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
|
|
@ -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 }
|
||||||
|
|
||||||
|
|
|
@ -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? ||
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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(":", " ")
|
||||||
|
|
|
@ -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')
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 = {}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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?)
|
||||||
|
|
||||||
|
|
52
lib/gitlab/hotlinking_detector.rb
Normal file
52
lib/gitlab/hotlinking_detector.rb
Normal 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
|
|
@ -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
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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
|
||||||
#
|
#
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 ""
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) }
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
12
spec/fixtures/ssl_certificate.pem
vendored
Normal 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
16
spec/fixtures/ssl_key.pem
vendored
Normal 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-----
|
60
spec/helpers/x509_helper_spec.rb
Normal file
60
spec/helpers/x509_helper_spec.rb
Normal 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
|
|
@ -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 });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
""
|
||||||
|
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
|
||||||
|
|
75
spec/lib/gitlab/hotlinking_detector_spec.rb
Normal file
75
spec/lib/gitlab/hotlinking_detector_spec.rb
Normal 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
|
|
@ -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],
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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]) }
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
||||||
|
|
|
@ -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) }
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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")
|
||||||
|
|
Loading…
Reference in a new issue