New upstream version 16.0.8+ds1
This commit is contained in:
parent
271ccb9124
commit
e55bb021ed
65 changed files with 1338 additions and 534 deletions
|
@ -5126,7 +5126,6 @@ RSpec/MissingFeatureCategory:
|
||||||
- 'spec/policies/ci/bridge_policy_spec.rb'
|
- 'spec/policies/ci/bridge_policy_spec.rb'
|
||||||
- 'spec/policies/ci/build_policy_spec.rb'
|
- 'spec/policies/ci/build_policy_spec.rb'
|
||||||
- 'spec/policies/ci/pipeline_policy_spec.rb'
|
- 'spec/policies/ci/pipeline_policy_spec.rb'
|
||||||
- 'spec/policies/ci/pipeline_schedule_policy_spec.rb'
|
|
||||||
- 'spec/policies/ci/trigger_policy_spec.rb'
|
- 'spec/policies/ci/trigger_policy_spec.rb'
|
||||||
- 'spec/policies/clusters/agent_policy_spec.rb'
|
- 'spec/policies/clusters/agent_policy_spec.rb'
|
||||||
- 'spec/policies/clusters/agent_token_policy_spec.rb'
|
- 'spec/policies/clusters/agent_token_policy_spec.rb'
|
||||||
|
|
22
CHANGELOG.md
22
CHANGELOG.md
|
@ -2,6 +2,28 @@
|
||||||
documentation](doc/development/changelog.md) for instructions on adding your own
|
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||||
entry.
|
entry.
|
||||||
|
|
||||||
|
## 16.0.8 (2023-08-01)
|
||||||
|
|
||||||
|
### Fixed (1 change)
|
||||||
|
|
||||||
|
- [Disable IAT verification by default](gitlab-org/security/gitlab@6d17a50539b8518da18bc68accc03b48d73173a0)
|
||||||
|
|
||||||
|
### Security (13 changes)
|
||||||
|
|
||||||
|
- [Prevent leaking emails of newly created users](gitlab-org/security/gitlab@b2872b398599cd7ee20c4119ae4c8e6ba2a6882d) ([merge request](gitlab-org/security/gitlab!3451))
|
||||||
|
- [Added redirect to filtered params](gitlab-org/security/gitlab@49ffc2cc98af0e66305c8a653c74e0b92ee06ce8) ([merge request](gitlab-org/security/gitlab!3443))
|
||||||
|
- [Relocate PlantUML config and disable SVG support](gitlab-org/security/gitlab@c6ded17a7d17ec8c3ed55cb94b8e6e524b6bbd5e) ([merge request](gitlab-org/security/gitlab!3440))
|
||||||
|
- [Sanitize multiple hardlinks from import archives](gitlab-org/security/gitlab@9dabd8ebca50d8ea3781a0c4955a40cd07c453e7) ([merge request](gitlab-org/security/gitlab!3437))
|
||||||
|
- [Validates project path availability](gitlab-org/security/gitlab@97e6ce4d15c8f4bcc7f60a560b789a023d391531) ([merge request](gitlab-org/security/gitlab!3428))
|
||||||
|
- [Fix policy project assign](gitlab-org/security/gitlab@c1cca8ce8f24f6466563a50463e3254c5c423e97) ([merge request](gitlab-org/security/gitlab!3425))
|
||||||
|
- [Fix pipeline schedule authorization for protected branch/tag](gitlab-org/security/gitlab@0c7017d993a33ef9fc693d4435505a4aea0141d1) ([merge request](gitlab-org/security/gitlab!3363))
|
||||||
|
- [Mitigate autolink filter ReDOS](gitlab-org/security/gitlab@9072c630608a81645548b64b32d9f81bd258ba06) ([merge request](gitlab-org/security/gitlab!3432))
|
||||||
|
- [Fix XSS vector in Web IDE](gitlab-org/security/gitlab@2832d1ae3b3e1bfc42bbeaeb29841a1e5fecac8a) ([merge request](gitlab-org/security/gitlab!3411))
|
||||||
|
- [Mitigate project reference filter ReDOS](gitlab-org/security/gitlab@9c73619acaad3eb3605bf632f066bcee59b86566) ([merge request](gitlab-org/security/gitlab!3429))
|
||||||
|
- [Add a stricter regex for the Harbor search param](gitlab-org/security/gitlab@c27e5e48a02d3411e84617b4fb7fd3f0fb49b618) ([merge request](gitlab-org/security/gitlab!3396))
|
||||||
|
- [Update pipeline user to the last policy MR author](gitlab-org/security/gitlab@b1e9bcb33106ba7e279d5fd42c4f2c1727629f63) ([merge request](gitlab-org/security/gitlab!3393))
|
||||||
|
- [Prohibit 40 character hex plus a hyphen if branch name is path](gitlab-org/security/gitlab@66c81ff6b50d0e53fc1f1b153439ad95614c9d09) ([merge request](gitlab-org/security/gitlab!3406))
|
||||||
|
|
||||||
## 16.0.7 (2023-07-04)
|
## 16.0.7 (2023-07-04)
|
||||||
|
|
||||||
### Security (1 change)
|
### Security (1 change)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
16.0.7
|
16.0.8
|
|
@ -1 +1 @@
|
||||||
16.0.7
|
16.0.8
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
16.0.7
|
16.0.8
|
|
@ -21,7 +21,6 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def new
|
def new
|
||||||
@schedule = project.pipeline_schedules.new
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
@ -102,6 +101,15 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
|
||||||
variables_attributes: [:id, :variable_type, :key, :secret_value, :_destroy])
|
variables_attributes: [:id, :variable_type, :key, :secret_value, :_destroy])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def new_schedule
|
||||||
|
# We need the `ref` here for `authorize_create_pipeline_schedule!`
|
||||||
|
@schedule ||= project.pipeline_schedules.new(ref: params.dig(:schedule, :ref))
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorize_create_pipeline_schedule!
|
||||||
|
return access_denied! unless can?(current_user, :create_pipeline_schedule, new_schedule)
|
||||||
|
end
|
||||||
|
|
||||||
def authorize_play_pipeline_schedule!
|
def authorize_play_pipeline_schedule!
|
||||||
return access_denied! unless can?(current_user, :play_pipeline_schedule, schedule)
|
return access_denied! unless can?(current_user, :play_pipeline_schedule, schedule)
|
||||||
end
|
end
|
||||||
|
|
|
@ -584,6 +584,8 @@ class Project < ApplicationRecord
|
||||||
validates :max_artifacts_size, numericality: { only_integer: true, greater_than: 0, allow_nil: true }
|
validates :max_artifacts_size, numericality: { only_integer: true, greater_than: 0, allow_nil: true }
|
||||||
validates :suggestion_commit_message, length: { maximum: MAX_SUGGESTIONS_TEMPLATE_LENGTH }
|
validates :suggestion_commit_message, length: { maximum: MAX_SUGGESTIONS_TEMPLATE_LENGTH }
|
||||||
|
|
||||||
|
validate :path_availability, if: :path_changed?
|
||||||
|
|
||||||
# Scopes
|
# Scopes
|
||||||
scope :pending_delete, -> { where(pending_delete: true) }
|
scope :pending_delete, -> { where(pending_delete: true) }
|
||||||
scope :without_deleted, -> { where(pending_delete: false) }
|
scope :without_deleted, -> { where(pending_delete: false) }
|
||||||
|
@ -3180,6 +3182,15 @@ class Project < ApplicationRecord
|
||||||
end
|
end
|
||||||
strong_memoize_attr :frozen_outbound_job_token_scopes?
|
strong_memoize_attr :frozen_outbound_job_token_scopes?
|
||||||
|
|
||||||
|
def path_availability
|
||||||
|
base, _, host = path.partition('.')
|
||||||
|
|
||||||
|
return unless host == Gitlab.config.pages&.dig('host')
|
||||||
|
return unless ProjectSetting.where(pages_unique_domain: base).exists?
|
||||||
|
|
||||||
|
errors.add(:path, s_('Project|already in use'))
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def pages_unique_domain_enabled?
|
def pages_unique_domain_enabled?
|
||||||
|
|
|
@ -52,6 +52,8 @@ class ProjectSetting < ApplicationRecord
|
||||||
|
|
||||||
validate :validates_mr_default_target_self
|
validate :validates_mr_default_target_self
|
||||||
|
|
||||||
|
validate :pages_unique_domain_availability, if: :pages_unique_domain_changed?
|
||||||
|
|
||||||
attribute :legacy_open_source_license_available, default: -> do
|
attribute :legacy_open_source_license_available, default: -> do
|
||||||
Feature.enabled?(:legacy_open_source_license_available, type: :ops)
|
Feature.enabled?(:legacy_open_source_license_available, type: :ops)
|
||||||
end
|
end
|
||||||
|
@ -102,6 +104,15 @@ class ProjectSetting < ApplicationRecord
|
||||||
pages_unique_domain_enabled ||
|
pages_unique_domain_enabled ||
|
||||||
pages_unique_domain_in_database.present?
|
pages_unique_domain_in_database.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def pages_unique_domain_availability
|
||||||
|
host = Gitlab.config.pages&.dig('host')
|
||||||
|
|
||||||
|
return if host.blank?
|
||||||
|
return unless Project.where(path: "#{pages_unique_domain}.#{host}").exists?
|
||||||
|
|
||||||
|
errors.add(:pages_unique_domain, s_('ProjectSetting|already in use'))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
ProjectSetting.prepend_mod
|
ProjectSetting.prepend_mod
|
||||||
|
|
|
@ -5,7 +5,18 @@ module Ci
|
||||||
alias_method :pipeline_schedule, :subject
|
alias_method :pipeline_schedule, :subject
|
||||||
|
|
||||||
condition(:protected_ref) do
|
condition(:protected_ref) do
|
||||||
ref_protected?(@user, @subject.project, @subject.project.repository.tag_exists?(@subject.ref), @subject.ref)
|
if full_ref?(@subject.ref)
|
||||||
|
is_tag = Gitlab::Git.tag_ref?(@subject.ref)
|
||||||
|
ref_name = Gitlab::Git.ref_name(@subject.ref)
|
||||||
|
else
|
||||||
|
# NOTE: this block should not be removed
|
||||||
|
# until the full ref validation is in place
|
||||||
|
# and all old refs are updated and validated
|
||||||
|
is_tag = @subject.project.repository.tag_exists?(@subject.ref)
|
||||||
|
ref_name = @subject.ref
|
||||||
|
end
|
||||||
|
|
||||||
|
ref_protected?(@user, @subject.project, is_tag, ref_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
condition(:owner_of_schedule) do
|
condition(:owner_of_schedule) do
|
||||||
|
@ -31,6 +42,15 @@ module Ci
|
||||||
enable :take_ownership_pipeline_schedule
|
enable :take_ownership_pipeline_schedule
|
||||||
end
|
end
|
||||||
|
|
||||||
rule { protected_ref }.prevent :play_pipeline_schedule
|
rule { protected_ref }.policy do
|
||||||
|
prevent :play_pipeline_schedule
|
||||||
|
prevent :create_pipeline_schedule
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def full_ref?(ref)
|
||||||
|
Gitlab::Git.tag_ref?(ref) || Gitlab::Git.branch_ref?(ref)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -49,11 +49,7 @@ module BulkImports
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_symlink
|
def validate_symlink
|
||||||
raise(BulkImports::Error, 'Invalid file') if symlink?(filepath)
|
raise(BulkImports::Error, 'Invalid file') if Gitlab::Utils::FileInfo.linked?(filepath)
|
||||||
end
|
|
||||||
|
|
||||||
def symlink?(filepath)
|
|
||||||
File.lstat(filepath).symlink?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def extract_archive
|
def extract_archive
|
||||||
|
|
|
@ -53,7 +53,7 @@ module BulkImports
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_symlink(filepath)
|
def validate_symlink(filepath)
|
||||||
raise(ServiceError, 'Invalid file') if File.lstat(filepath).symlink?
|
raise(ServiceError, 'Invalid file') if Gitlab::Utils::FileInfo.linked?(filepath)
|
||||||
end
|
end
|
||||||
|
|
||||||
def decompress_file
|
def decompress_file
|
||||||
|
|
|
@ -171,6 +171,7 @@ module Gitlab
|
||||||
# - Any parameter containing `password`
|
# - Any parameter containing `password`
|
||||||
# - Any parameter containing `secret`
|
# - Any parameter containing `secret`
|
||||||
# - Any parameter ending with `key`
|
# - Any parameter ending with `key`
|
||||||
|
# - Any parameter named `redirect`, filtered for security concerns of exposing sensitive information
|
||||||
# - Two-factor tokens (:otp_attempt)
|
# - Two-factor tokens (:otp_attempt)
|
||||||
# - Repo/Project Import URLs (:import_url)
|
# - Repo/Project Import URLs (:import_url)
|
||||||
# - Build traces (:trace)
|
# - Build traces (:trace)
|
||||||
|
@ -213,6 +214,7 @@ module Gitlab
|
||||||
variables
|
variables
|
||||||
content
|
content
|
||||||
sharedSecret
|
sharedSecret
|
||||||
|
redirect
|
||||||
)
|
)
|
||||||
|
|
||||||
# Enable escaping HTML in JSON.
|
# Enable escaping HTML in JSON.
|
||||||
|
|
|
@ -34,8 +34,13 @@ module Banzai
|
||||||
# https://github.com/vmg/rinku/blob/v2.0.1/ext/rinku/autolink.c#L65
|
# https://github.com/vmg/rinku/blob/v2.0.1/ext/rinku/autolink.c#L65
|
||||||
#
|
#
|
||||||
# Rubular: http://rubular.com/r/nrL3r9yUiq
|
# Rubular: http://rubular.com/r/nrL3r9yUiq
|
||||||
|
# Note that it's not possible to use Gitlab::UntrustedRegexp for LINK_PATTERN,
|
||||||
|
# as `(?<!` is unsupported in `re2`, see https://github.com/google/re2/wiki/Syntax
|
||||||
LINK_PATTERN = %r{([a-z][a-z0-9\+\.-]+://[^\s>]+)(?<!\?|!|\.|,|:)}.freeze
|
LINK_PATTERN = %r{([a-z][a-z0-9\+\.-]+://[^\s>]+)(?<!\?|!|\.|,|:)}.freeze
|
||||||
|
|
||||||
|
ENTITY_UNTRUSTED = '((?:&[\w#]+;)+)\z'
|
||||||
|
ENTITY_UNTRUSTED_REGEX = Gitlab::UntrustedRegexp.new(ENTITY_UNTRUSTED, multiline: false)
|
||||||
|
|
||||||
# Text matching LINK_PATTERN inside these elements will not be linked
|
# Text matching LINK_PATTERN inside these elements will not be linked
|
||||||
IGNORE_PARENTS = %w(a code kbd pre script style).to_set
|
IGNORE_PARENTS = %w(a code kbd pre script style).to_set
|
||||||
|
|
||||||
|
@ -85,10 +90,14 @@ module Banzai
|
||||||
# Remove any trailing HTML entities and store them for appending
|
# Remove any trailing HTML entities and store them for appending
|
||||||
# outside the link element. The entity must be marked HTML safe in
|
# outside the link element. The entity must be marked HTML safe in
|
||||||
# order to be output literally rather than escaped.
|
# order to be output literally rather than escaped.
|
||||||
match.gsub!(/((?:&[\w#]+;)+)\z/, '')
|
dropped = ''
|
||||||
dropped = (Regexp.last_match(1) || '').html_safe
|
match = ENTITY_UNTRUSTED_REGEX.replace_gsub(match) do |entities|
|
||||||
|
dropped = entities[1].html_safe
|
||||||
|
|
||||||
# To match the behaviour of Rinku, if the matched link ends with a
|
''
|
||||||
|
end
|
||||||
|
|
||||||
|
# To match the behavior of Rinku, if the matched link ends with a
|
||||||
# closing part of a matched pair of punctuation, we remove that trailing
|
# closing part of a matched pair of punctuation, we remove that trailing
|
||||||
# character unless there are an equal number of closing and opening
|
# character unless there are an equal number of closing and opening
|
||||||
# characters in the link.
|
# characters in the link.
|
||||||
|
|
|
@ -11,7 +11,7 @@ module Banzai
|
||||||
def call
|
def call
|
||||||
return doc unless settings.plantuml_enabled? && doc.at_xpath(lang_tag)
|
return doc unless settings.plantuml_enabled? && doc.at_xpath(lang_tag)
|
||||||
|
|
||||||
plantuml_setup
|
Gitlab::Plantuml.configure
|
||||||
|
|
||||||
doc.xpath(lang_tag).each do |node|
|
doc.xpath(lang_tag).each do |node|
|
||||||
img_tag = Nokogiri::HTML::DocumentFragment.parse(
|
img_tag = Nokogiri::HTML::DocumentFragment.parse(
|
||||||
|
@ -38,15 +38,6 @@ module Banzai
|
||||||
def settings
|
def settings
|
||||||
Gitlab::CurrentSettings.current_application_settings
|
Gitlab::CurrentSettings.current_application_settings
|
||||||
end
|
end
|
||||||
|
|
||||||
def plantuml_setup
|
|
||||||
Asciidoctor::PlantUml.configure do |conf|
|
|
||||||
conf.url = settings.plantuml_url
|
|
||||||
conf.png_enable = settings.plantuml_enabled
|
|
||||||
conf.svg_enable = false
|
|
||||||
conf.txt_enable = false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -24,7 +24,7 @@ module BulkImports
|
||||||
return if tar_filepath?(file_path)
|
return if tar_filepath?(file_path)
|
||||||
return if lfs_json_filepath?(file_path)
|
return if lfs_json_filepath?(file_path)
|
||||||
return if File.directory?(file_path)
|
return if File.directory?(file_path)
|
||||||
return if File.lstat(file_path).symlink?
|
return if Gitlab::Utils::FileInfo.linked?(file_path)
|
||||||
|
|
||||||
size = File.size(file_path)
|
size = File.size(file_path)
|
||||||
oid = LfsObject.calculate_oid(file_path)
|
oid = LfsObject.calculate_oid(file_path)
|
||||||
|
|
|
@ -24,7 +24,7 @@ module BulkImports
|
||||||
# Validate that the path is OK to load
|
# Validate that the path is OK to load
|
||||||
Gitlab::Utils.check_allowed_absolute_path_and_path_traversal!(file_path, [Dir.tmpdir])
|
Gitlab::Utils.check_allowed_absolute_path_and_path_traversal!(file_path, [Dir.tmpdir])
|
||||||
return if File.directory?(file_path)
|
return if File.directory?(file_path)
|
||||||
return if File.lstat(file_path).symlink?
|
return if Gitlab::Utils::FileInfo.linked?(file_path)
|
||||||
|
|
||||||
avatar_path = AVATAR_PATTERN.match(file_path)
|
avatar_path = AVATAR_PATTERN.match(file_path)
|
||||||
return save_avatar(file_path) if avatar_path
|
return save_avatar(file_path) if avatar_path
|
||||||
|
|
|
@ -32,7 +32,7 @@ module BulkImports
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_symlink
|
def validate_symlink
|
||||||
return unless File.lstat(filepath).symlink?
|
return unless Gitlab::Utils::FileInfo.linked?(filepath)
|
||||||
|
|
||||||
File.delete(filepath)
|
File.delete(filepath)
|
||||||
raise_error 'Invalid downloaded file'
|
raise_error 'Invalid downloaded file'
|
||||||
|
|
|
@ -26,7 +26,7 @@ module BulkImports
|
||||||
return unless portable.lfs_enabled?
|
return unless portable.lfs_enabled?
|
||||||
return unless File.exist?(bundle_path)
|
return unless File.exist?(bundle_path)
|
||||||
return if File.directory?(bundle_path)
|
return if File.directory?(bundle_path)
|
||||||
return if File.lstat(bundle_path).symlink?
|
return if Gitlab::Utils::FileInfo.linked?(bundle_path)
|
||||||
|
|
||||||
portable.design_repository.create_from_bundle(bundle_path)
|
portable.design_repository.create_from_bundle(bundle_path)
|
||||||
end
|
end
|
||||||
|
|
|
@ -26,7 +26,7 @@ module BulkImports
|
||||||
|
|
||||||
return unless File.exist?(bundle_path)
|
return unless File.exist?(bundle_path)
|
||||||
return if File.directory?(bundle_path)
|
return if File.directory?(bundle_path)
|
||||||
return if File.lstat(bundle_path).symlink?
|
return if Gitlab::Utils::FileInfo.linked?(bundle_path)
|
||||||
|
|
||||||
portable.repository.create_from_bundle(bundle_path)
|
portable.repository.create_from_bundle(bundle_path)
|
||||||
end
|
end
|
||||||
|
|
|
@ -77,20 +77,11 @@ module Gitlab
|
||||||
context[:pipeline] = :ascii_doc
|
context[:pipeline] = :ascii_doc
|
||||||
context[:max_includes] = [MAX_INCLUDES, context[:max_includes]].compact.min
|
context[:max_includes] = [MAX_INCLUDES, context[:max_includes]].compact.min
|
||||||
|
|
||||||
plantuml_setup
|
Gitlab::Plantuml.configure
|
||||||
|
|
||||||
html = ::Asciidoctor.convert(input, asciidoc_opts)
|
html = ::Asciidoctor.convert(input, asciidoc_opts)
|
||||||
html = Banzai.render(html, context)
|
html = Banzai.render(html, context)
|
||||||
html.html_safe
|
html.html_safe
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.plantuml_setup
|
|
||||||
Asciidoctor::PlantUml.configure do |conf|
|
|
||||||
conf.url = Gitlab::CurrentSettings.plantuml_url
|
|
||||||
conf.svg_enable = Gitlab::CurrentSettings.plantuml_enabled
|
|
||||||
conf.png_enable = Gitlab::CurrentSettings.plantuml_enabled
|
|
||||||
conf.txt_enable = false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -42,7 +42,7 @@ module Gitlab
|
||||||
def prohibited_branch_checks
|
def prohibited_branch_checks
|
||||||
return if deletion?
|
return if deletion?
|
||||||
|
|
||||||
if branch_name =~ %r{\A\h{40}(/|\z)}
|
if branch_name =~ %r{\A\h{40}(-/|/|\z)}
|
||||||
raise GitAccess::ForbiddenError, ERROR_MESSAGES[:prohibited_hex_branch_name]
|
raise GitAccess::ForbiddenError, ERROR_MESSAGES[:prohibited_hex_branch_name]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -65,7 +65,7 @@ module Gitlab
|
||||||
def validate_archive_path
|
def validate_archive_path
|
||||||
Gitlab::Utils.check_path_traversal!(archive_path)
|
Gitlab::Utils.check_path_traversal!(archive_path)
|
||||||
|
|
||||||
raise(ServiceError, 'Archive path is a symlink') if File.lstat(archive_path).symlink?
|
raise(ServiceError, 'Archive path is a symlink or hard link') if Gitlab::Utils::FileInfo.linked?(archive_path)
|
||||||
raise(ServiceError, 'Archive path is not a file') unless File.file?(archive_path)
|
raise(ServiceError, 'Archive path is not a file') unless File.file?(archive_path)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ module Gitlab
|
||||||
message: 'params invalid'
|
message: 'params invalid'
|
||||||
}, allow_blank: true
|
}, allow_blank: true
|
||||||
validates :search, format: {
|
validates :search, format: {
|
||||||
with: /\A([a-z\_]*=[a-zA-Z0-9\- :]*,*)*\z/,
|
with: /\A(name=[a-zA-Z0-9\-:]+(?:,name=[a-zA-Z0-9\-:]+)*)\z/,
|
||||||
message: 'params invalid'
|
message: 'params invalid'
|
||||||
}, allow_blank: true
|
}, allow_blank: true
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,11 @@ module Gitlab
|
||||||
module CommandLineUtil
|
module CommandLineUtil
|
||||||
UNTAR_MASK = 'u+rwX,go+rX,go-w'
|
UNTAR_MASK = 'u+rwX,go+rX,go-w'
|
||||||
DEFAULT_DIR_MODE = 0700
|
DEFAULT_DIR_MODE = 0700
|
||||||
|
CLEAN_DIR_IGNORE_FILE_NAMES = %w[. ..].freeze
|
||||||
|
|
||||||
FileOversizedError = Class.new(StandardError)
|
CommandLineUtilError = Class.new(StandardError)
|
||||||
|
FileOversizedError = Class.new(CommandLineUtilError)
|
||||||
|
HardLinkError = Class.new(CommandLineUtilError)
|
||||||
|
|
||||||
def tar_czf(archive:, dir:)
|
def tar_czf(archive:, dir:)
|
||||||
tar_with_options(archive: archive, dir: dir, options: 'czf')
|
tar_with_options(archive: archive, dir: dir, options: 'czf')
|
||||||
|
@ -90,7 +93,7 @@ module Gitlab
|
||||||
def untar_with_options(archive:, dir:, options:)
|
def untar_with_options(archive:, dir:, options:)
|
||||||
execute_cmd(%W(tar -#{options} #{archive} -C #{dir}))
|
execute_cmd(%W(tar -#{options} #{archive} -C #{dir}))
|
||||||
execute_cmd(%W(chmod -R #{UNTAR_MASK} #{dir}))
|
execute_cmd(%W(chmod -R #{UNTAR_MASK} #{dir}))
|
||||||
remove_symlinks(dir)
|
clean_extraction_dir!(dir)
|
||||||
end
|
end
|
||||||
|
|
||||||
# rubocop:disable Gitlab/ModuleWithInstanceVariables
|
# rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||||
|
@ -122,17 +125,27 @@ module Gitlab
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_symlinks(dir)
|
# Scans and cleans the directory tree.
|
||||||
ignore_file_names = %w[. ..]
|
# Symlinks are considered legal but are removed.
|
||||||
|
# Files sharing hard links are considered illegal and the directory will be removed
|
||||||
|
# and a `HardLinkError` exception will be raised.
|
||||||
|
#
|
||||||
|
# @raise [HardLinkError] if there multiple hard links to the same file detected.
|
||||||
|
# @return [Boolean] true
|
||||||
|
def clean_extraction_dir!(dir)
|
||||||
# Using File::FNM_DOTMATCH to also delete symlinks starting with "."
|
# Using File::FNM_DOTMATCH to also delete symlinks starting with "."
|
||||||
Dir.glob("#{dir}/**/*", File::FNM_DOTMATCH)
|
Dir.glob("#{dir}/**/*", File::FNM_DOTMATCH).each do |filepath|
|
||||||
.reject { |f| ignore_file_names.include?(File.basename(f)) }
|
next if CLEAN_DIR_IGNORE_FILE_NAMES.include?(File.basename(filepath))
|
||||||
.each do |filepath|
|
|
||||||
FileUtils.rm(filepath) if File.lstat(filepath).symlink?
|
raise HardLinkError, 'File shares hard link' if Gitlab::Utils::FileInfo.shares_hard_link?(filepath)
|
||||||
|
|
||||||
|
FileUtils.rm(filepath) if Gitlab::Utils::FileInfo.linked?(filepath)
|
||||||
end
|
end
|
||||||
|
|
||||||
true
|
true
|
||||||
|
rescue HardLinkError
|
||||||
|
FileUtils.remove_dir(dir)
|
||||||
|
raise
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -87,7 +87,7 @@ module Gitlab
|
||||||
def validate_archive_path
|
def validate_archive_path
|
||||||
Gitlab::Utils.check_path_traversal!(@archive_path)
|
Gitlab::Utils.check_path_traversal!(@archive_path)
|
||||||
|
|
||||||
raise(ServiceError, 'Archive path is a symlink') if File.lstat(@archive_path).symlink?
|
raise(ServiceError, 'Archive path is a symlink or hard link') if Gitlab::Utils::FileInfo.linked?(@archive_path)
|
||||||
raise(ServiceError, 'Archive path is not a file') unless File.file?(@archive_path)
|
raise(ServiceError, 'Archive path is not a file') unless File.file?(@archive_path)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ module Gitlab
|
||||||
mkdir_p(@shared.export_path)
|
mkdir_p(@shared.export_path)
|
||||||
mkdir_p(@shared.archive_path)
|
mkdir_p(@shared.archive_path)
|
||||||
|
|
||||||
remove_symlinks(@shared.export_path)
|
clean_extraction_dir!(@shared.export_path)
|
||||||
copy_archive
|
copy_archive
|
||||||
|
|
||||||
wait_for_archived_file do
|
wait_for_archived_file do
|
||||||
|
@ -35,7 +35,7 @@ module Gitlab
|
||||||
false
|
false
|
||||||
ensure
|
ensure
|
||||||
remove_import_file
|
remove_import_file
|
||||||
remove_symlinks(@shared.export_path)
|
clean_extraction_dir!(@shared.export_path)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -21,7 +21,9 @@ module Gitlab
|
||||||
# This reads from `tree/project.json`
|
# This reads from `tree/project.json`
|
||||||
path = file_path("#{importable_path}.json")
|
path = file_path("#{importable_path}.json")
|
||||||
|
|
||||||
raise Gitlab::ImportExport::Error, 'Invalid file' if !File.exist?(path) || File.symlink?(path)
|
if !File.exist?(path) || Gitlab::Utils::FileInfo.linked?(path)
|
||||||
|
raise Gitlab::ImportExport::Error, 'Invalid file'
|
||||||
|
end
|
||||||
|
|
||||||
data = File.read(path, MAX_JSON_DOCUMENT_SIZE)
|
data = File.read(path, MAX_JSON_DOCUMENT_SIZE)
|
||||||
json_decode(data)
|
json_decode(data)
|
||||||
|
@ -34,7 +36,7 @@ module Gitlab
|
||||||
# This reads from `tree/project/merge_requests.ndjson`
|
# This reads from `tree/project/merge_requests.ndjson`
|
||||||
path = file_path(importable_path, "#{key}.ndjson")
|
path = file_path(importable_path, "#{key}.ndjson")
|
||||||
|
|
||||||
next if !File.exist?(path) || File.symlink?(path)
|
next if !File.exist?(path) || Gitlab::Utils::FileInfo.linked?(path)
|
||||||
|
|
||||||
File.foreach(path, MAX_JSON_DOCUMENT_SIZE).with_index do |line, line_num|
|
File.foreach(path, MAX_JSON_DOCUMENT_SIZE).with_index do |line, line_num|
|
||||||
documents << [json_decode(line), line_num]
|
documents << [json_decode(line), line_num]
|
||||||
|
|
|
@ -57,7 +57,7 @@ module Gitlab
|
||||||
source_child = File.join(source_path, child)
|
source_child = File.join(source_path, child)
|
||||||
target_child = File.join(target_path, child)
|
target_child = File.join(target_path, child)
|
||||||
|
|
||||||
next if File.lstat(source_child).symlink?
|
next if Gitlab::Utils::FileInfo.linked?(source_child)
|
||||||
|
|
||||||
if File.directory?(source_child)
|
if File.directory?(source_child)
|
||||||
FileUtils.mkdir_p(target_child, mode: DEFAULT_DIR_MODE) unless File.exist?(target_child)
|
FileUtils.mkdir_p(target_child, mode: DEFAULT_DIR_MODE) unless File.exist?(target_child)
|
||||||
|
|
|
@ -10,13 +10,12 @@ module Gitlab
|
||||||
def execute
|
def execute
|
||||||
return if host.blank?
|
return if host.blank?
|
||||||
|
|
||||||
gitlab_host = ::Settings.pages.host.downcase.prepend(".")
|
gitlab_host = ::Gitlab.config.pages.host.downcase.prepend(".")
|
||||||
|
|
||||||
if host.ends_with?(gitlab_host)
|
if host.ends_with?(gitlab_host)
|
||||||
name = host.delete_suffix(gitlab_host)
|
name = host.delete_suffix(gitlab_host)
|
||||||
|
|
||||||
by_namespace_domain(name) ||
|
by_unique_domain(name) || by_namespace_domain(name)
|
||||||
by_unique_domain(name)
|
|
||||||
else
|
else
|
||||||
by_custom_domain(host)
|
by_custom_domain(host)
|
||||||
end
|
end
|
||||||
|
|
|
@ -130,7 +130,7 @@ module Gitlab
|
||||||
# `NAMESPACE_FORMAT_REGEX`, with the negative lookbehind assertion removed. This means that the client-side validation
|
# `NAMESPACE_FORMAT_REGEX`, with the negative lookbehind assertion removed. This means that the client-side validation
|
||||||
# will pass for usernames ending in `.atom` and `.git`, but will be caught by the server-side validation.
|
# will pass for usernames ending in `.atom` and `.git`, but will be caught by the server-side validation.
|
||||||
PATH_START_CHAR = '[a-zA-Z0-9_\.]'
|
PATH_START_CHAR = '[a-zA-Z0-9_\.]'
|
||||||
PATH_REGEX_STR = PATH_START_CHAR + '[a-zA-Z0-9_\-\.]*'
|
PATH_REGEX_STR = PATH_START_CHAR + '[a-zA-Z0-9_\-\.]' + "{0,#{Namespace::URL_MAX_LENGTH - 1}}"
|
||||||
NAMESPACE_FORMAT_REGEX_JS = PATH_REGEX_STR + '[a-zA-Z0-9_\-]|[a-zA-Z0-9_]'
|
NAMESPACE_FORMAT_REGEX_JS = PATH_REGEX_STR + '[a-zA-Z0-9_\-]|[a-zA-Z0-9_]'
|
||||||
|
|
||||||
NO_SUFFIX_REGEX = /(?<!\.git|\.atom)/.freeze
|
NO_SUFFIX_REGEX = /(?<!\.git|\.atom)/.freeze
|
||||||
|
|
20
lib/gitlab/plantuml.rb
Normal file
20
lib/gitlab/plantuml.rb
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "asciidoctor_plantuml/plantuml"
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module Plantuml
|
||||||
|
class << self
|
||||||
|
def configure
|
||||||
|
Asciidoctor::PlantUml.configure do |conf|
|
||||||
|
conf.url = Gitlab::CurrentSettings.plantuml_url
|
||||||
|
conf.png_enable = Gitlab::CurrentSettings.plantuml_enabled
|
||||||
|
conf.svg_enable = false
|
||||||
|
conf.txt_enable = false
|
||||||
|
|
||||||
|
conf
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
35
lib/gitlab/utils/file_info.rb
Normal file
35
lib/gitlab/utils/file_info.rb
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module Utils
|
||||||
|
module FileInfo
|
||||||
|
class << self
|
||||||
|
# Returns true if:
|
||||||
|
# - File or directory is a symlink.
|
||||||
|
# - File shares a hard link.
|
||||||
|
def linked?(file)
|
||||||
|
stat = to_file_stat(file)
|
||||||
|
|
||||||
|
stat.symlink? || shares_hard_link?(stat)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns:
|
||||||
|
# - true if file shares a hard link with another file.
|
||||||
|
# - false if file is a directory, as directories cannot be hard linked.
|
||||||
|
def shares_hard_link?(file)
|
||||||
|
stat = to_file_stat(file)
|
||||||
|
|
||||||
|
stat.file? && stat.nlink > 1
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def to_file_stat(filepath_or_stat)
|
||||||
|
return filepath_or_stat if filepath_or_stat.is_a?(File::Stat)
|
||||||
|
|
||||||
|
File.lstat(filepath_or_stat)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -4,7 +4,7 @@ require 'jwt'
|
||||||
|
|
||||||
module JSONWebToken
|
module JSONWebToken
|
||||||
class HMACToken < Token
|
class HMACToken < Token
|
||||||
IAT_LEEWAY = 60
|
LEEWAY = 60
|
||||||
JWT_ALGORITHM = 'HS256'
|
JWT_ALGORITHM = 'HS256'
|
||||||
|
|
||||||
def initialize(secret)
|
def initialize(secret)
|
||||||
|
@ -13,7 +13,7 @@ module JSONWebToken
|
||||||
@secret = secret
|
@secret = secret
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.decode(token, secret, leeway: IAT_LEEWAY, verify_iat: true)
|
def self.decode(token, secret, leeway: LEEWAY, verify_iat: false)
|
||||||
JWT.decode(token, secret, true, leeway: leeway, verify_iat: verify_iat, algorithm: JWT_ALGORITHM)
|
JWT.decode(token, secret, true, leeway: leeway, verify_iat: verify_iat, algorithm: JWT_ALGORITHM)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -35713,6 +35713,9 @@ msgstr ""
|
||||||
msgid "ProjectSettings|With GitLab Pages you can host your static websites on GitLab. GitLab Pages uses a caching mechanism for efficiency. Your changes may not take effect until that cache is invalidated, which usually takes less than a minute."
|
msgid "ProjectSettings|With GitLab Pages you can host your static websites on GitLab. GitLab Pages uses a caching mechanism for efficiency. Your changes may not take effect until that cache is invalidated, which usually takes less than a minute."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "ProjectSetting|already in use"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "ProjectTemplates|.NET Core"
|
msgid "ProjectTemplates|.NET Core"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -36007,6 +36010,9 @@ msgstr ""
|
||||||
msgid "ProjectsNew|Your project will be created at:"
|
msgid "ProjectsNew|Your project will be created at:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Project|already in use"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "PrometheusAlerts|exceeded"
|
msgid "PrometheusAlerts|exceeded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -53168,9 +53174,6 @@ msgstr ""
|
||||||
msgid "eligible users"
|
msgid "eligible users"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "email '%{email}' is not a verified email."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "email address settings"
|
msgid "email address settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -53476,6 +53479,9 @@ msgstr ""
|
||||||
msgid "is not valid. The iteration group has to match the iteration cadence group."
|
msgid "is not valid. The iteration group has to match the iteration cadence group."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "is not verified."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "is one of"
|
msgid "is one of"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,7 @@
|
||||||
"@gitlab/svgs": "3.46.0",
|
"@gitlab/svgs": "3.46.0",
|
||||||
"@gitlab/ui": "62.10.0",
|
"@gitlab/ui": "62.10.0",
|
||||||
"@gitlab/visual-review-tools": "1.7.3",
|
"@gitlab/visual-review-tools": "1.7.3",
|
||||||
"@gitlab/web-ide": "0.0.1-dev-20230511143809",
|
"@gitlab/web-ide": "0.0.1-dev-20230713160749-patch-1",
|
||||||
"@mattiasbuelens/web-streams-adapter": "^0.1.0",
|
"@mattiasbuelens/web-streams-adapter": "^0.1.0",
|
||||||
"@popperjs/core": "^2.11.2",
|
"@popperjs/core": "^2.11.2",
|
||||||
"@rails/actioncable": "6.1.4-7",
|
"@rails/actioncable": "6.1.4-7",
|
||||||
|
|
|
@ -4,6 +4,7 @@ require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe Projects::PipelineSchedulesController, feature_category: :continuous_integration do
|
RSpec.describe Projects::PipelineSchedulesController, feature_category: :continuous_integration do
|
||||||
include AccessMatchersForController
|
include AccessMatchersForController
|
||||||
|
using RSpec::Parameterized::TableSyntax
|
||||||
|
|
||||||
let_it_be(:user) { create(:user) }
|
let_it_be(:user) { create(:user) }
|
||||||
let_it_be(:project) { create(:project, :public, :repository) }
|
let_it_be(:project) { create(:project, :public, :repository) }
|
||||||
|
@ -45,6 +46,43 @@ RSpec.describe Projects::PipelineSchedulesController, feature_category: :continu
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
shared_examples 'protecting ref' do
|
||||||
|
where(:branch_access_levels, :tag_access_level, :maintainer_accessible, :developer_accessible) do
|
||||||
|
[:no_one_can_push, :no_one_can_merge] | :no_one_can_create | \
|
||||||
|
:be_denied_for | :be_denied_for
|
||||||
|
[:maintainers_can_push, :maintainers_can_merge] | :maintainers_can_create | \
|
||||||
|
:be_allowed_for | :be_denied_for
|
||||||
|
[:developers_can_push, :developers_can_merge] | :developers_can_create | \
|
||||||
|
:be_allowed_for | :be_allowed_for
|
||||||
|
end
|
||||||
|
|
||||||
|
with_them do
|
||||||
|
context 'when branch is protected' do
|
||||||
|
let(:ref_prefix) { 'heads' }
|
||||||
|
let(:ref_name) { 'master' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:protected_branch, *branch_access_levels, name: ref_name, project: project)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect { go }.to try(maintainer_accessible, :maintainer).of(project) }
|
||||||
|
it { expect { go }.to try(developer_accessible, :developer).of(project) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when tag is protected' do
|
||||||
|
let(:ref_prefix) { 'tags' }
|
||||||
|
let(:ref_name) { 'v1.0.0' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:protected_tag, tag_access_level, name: ref_name, project: project)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect { go }.to try(maintainer_accessible, :maintainer).of(project) }
|
||||||
|
it { expect { go }.to try(developer_accessible, :developer).of(project) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'GET #index' do
|
describe 'GET #index' do
|
||||||
render_views
|
render_views
|
||||||
|
|
||||||
|
@ -158,7 +196,9 @@ RSpec.describe Projects::PipelineSchedulesController, feature_category: :continu
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'security' do
|
describe 'security' do
|
||||||
let(:schedule) { attributes_for(:ci_pipeline_schedule) }
|
let(:schedule) { attributes_for(:ci_pipeline_schedule, ref: "refs/#{ref_prefix}/#{ref_name}") }
|
||||||
|
let(:ref_prefix) { 'heads' }
|
||||||
|
let(:ref_name) { "master" }
|
||||||
|
|
||||||
it 'is allowed for admin when admin mode enabled', :enable_admin_mode do
|
it 'is allowed for admin when admin mode enabled', :enable_admin_mode do
|
||||||
expect { go }.to be_allowed_for(:admin)
|
expect { go }.to be_allowed_for(:admin)
|
||||||
|
@ -177,6 +217,8 @@ RSpec.describe Projects::PipelineSchedulesController, feature_category: :continu
|
||||||
it { expect { go }.to be_denied_for(:user) }
|
it { expect { go }.to be_denied_for(:user) }
|
||||||
it { expect { go }.to be_denied_for(:external) }
|
it { expect { go }.to be_denied_for(:external) }
|
||||||
it { expect { go }.to be_denied_for(:visitor) }
|
it { expect { go }.to be_denied_for(:visitor) }
|
||||||
|
|
||||||
|
it_behaves_like 'protecting ref'
|
||||||
end
|
end
|
||||||
|
|
||||||
def go
|
def go
|
||||||
|
@ -427,7 +469,7 @@ RSpec.describe Projects::PipelineSchedulesController, feature_category: :continu
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'POST #play', :clean_gitlab_redis_rate_limiting do
|
describe 'POST #play', :clean_gitlab_redis_rate_limiting do
|
||||||
let(:ref) { 'master' }
|
let(:ref_name) { 'master' }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
project.add_developer(user)
|
project.add_developer(user)
|
||||||
|
@ -443,7 +485,7 @@ RSpec.describe Projects::PipelineSchedulesController, feature_category: :continu
|
||||||
it 'does not allow pipeline to be executed' do
|
it 'does not allow pipeline to be executed' do
|
||||||
expect(RunPipelineScheduleWorker).not_to receive(:perform_async)
|
expect(RunPipelineScheduleWorker).not_to receive(:perform_async)
|
||||||
|
|
||||||
post :play, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id }
|
go
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:not_found)
|
expect(response).to have_gitlab_http_status(:not_found)
|
||||||
end
|
end
|
||||||
|
@ -453,16 +495,14 @@ RSpec.describe Projects::PipelineSchedulesController, feature_category: :continu
|
||||||
it 'executes a new pipeline' do
|
it 'executes a new pipeline' do
|
||||||
expect(RunPipelineScheduleWorker).to receive(:perform_async).with(pipeline_schedule.id, user.id).and_return('job-123')
|
expect(RunPipelineScheduleWorker).to receive(:perform_async).with(pipeline_schedule.id, user.id).and_return('job-123')
|
||||||
|
|
||||||
post :play, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id }
|
go
|
||||||
|
|
||||||
expect(flash[:notice]).to start_with 'Successfully scheduled a pipeline to run'
|
expect(flash[:notice]).to start_with 'Successfully scheduled a pipeline to run'
|
||||||
expect(response).to have_gitlab_http_status(:found)
|
expect(response).to have_gitlab_http_status(:found)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'prevents users from scheduling the same pipeline repeatedly' do
|
it 'prevents users from scheduling the same pipeline repeatedly' do
|
||||||
2.times do
|
2.times { go }
|
||||||
post :play, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id }
|
|
||||||
end
|
|
||||||
|
|
||||||
expect(flash.to_a.size).to eq(2)
|
expect(flash.to_a.size).to eq(2)
|
||||||
expect(flash[:alert]).to eq _('You cannot play this scheduled pipeline at the moment. Please wait a minute.')
|
expect(flash[:alert]).to eq _('You cannot play this scheduled pipeline at the moment. Please wait a minute.')
|
||||||
|
@ -470,17 +510,14 @@ RSpec.describe Projects::PipelineSchedulesController, feature_category: :continu
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when a developer attempts to schedule a protected ref' do
|
describe 'security' do
|
||||||
it 'does not allow pipeline to be executed' do
|
let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, ref: "refs/#{ref_prefix}/#{ref_name}") }
|
||||||
create(:protected_branch, project: project, name: ref)
|
|
||||||
protected_schedule = create(:ci_pipeline_schedule, project: project, ref: ref)
|
|
||||||
|
|
||||||
expect(RunPipelineScheduleWorker).not_to receive(:perform_async)
|
it_behaves_like 'protecting ref'
|
||||||
|
|
||||||
post :play, params: { namespace_id: project.namespace.to_param, project_id: project, id: protected_schedule.id }
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:not_found)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def go
|
||||||
|
post :play, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -226,6 +226,22 @@ RSpec.describe Banzai::Filter::AutolinkFilter, feature_category: :team_planning
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'protects against malicious backtracking' do
|
||||||
|
doc = "http://#{'&' * 1_000_000}x"
|
||||||
|
|
||||||
|
expect do
|
||||||
|
Timeout.timeout(30.seconds) { filter(doc) }
|
||||||
|
end.not_to raise_error
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not timeout with excessively long scheme' do
|
||||||
|
doc = "#{'h' * 1_000_000}://example.com"
|
||||||
|
|
||||||
|
expect do
|
||||||
|
Timeout.timeout(30.seconds) { filter(doc) }
|
||||||
|
end.not_to raise_error
|
||||||
|
end
|
||||||
|
|
||||||
# Rinku does not escape these characters in HTML attributes, but content_tag
|
# Rinku does not escape these characters in HTML attributes, but content_tag
|
||||||
# does. We don't care about that difference for these specs, though.
|
# does. We don't care about that difference for these specs, though.
|
||||||
def unescape(html)
|
def unescape(html)
|
||||||
|
|
|
@ -39,6 +39,7 @@ RSpec.describe Banzai::Filter::References::ProjectReferenceFilter, feature_categ
|
||||||
|
|
||||||
it_behaves_like 'fails fast', 'A' * 50000
|
it_behaves_like 'fails fast', 'A' * 50000
|
||||||
it_behaves_like 'fails fast', '/a' * 50000
|
it_behaves_like 'fails fast', '/a' * 50000
|
||||||
|
it_behaves_like 'fails fast', "mailto:#{'a-' * 499_000}@aaaaaaaa..aaaaaaaa.example.com"
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'allows references with text after the > character' do
|
it 'allows references with text after the > character' do
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe BulkImports::Common::Pipelines::LfsObjectsPipeline do
|
RSpec.describe BulkImports::Common::Pipelines::LfsObjectsPipeline, feature_category: :importers do
|
||||||
let_it_be(:portable) { create(:project) }
|
let_it_be(:portable) { create(:project) }
|
||||||
let_it_be(:oid) { 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' }
|
let_it_be(:oid) { 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' }
|
||||||
|
|
||||||
|
@ -118,13 +118,22 @@ RSpec.describe BulkImports::Common::Pipelines::LfsObjectsPipeline do
|
||||||
context 'when file path is symlink' do
|
context 'when file path is symlink' do
|
||||||
it 'returns' do
|
it 'returns' do
|
||||||
symlink = File.join(tmpdir, 'symlink')
|
symlink = File.join(tmpdir, 'symlink')
|
||||||
|
FileUtils.ln_s(lfs_file_path, symlink)
|
||||||
|
|
||||||
FileUtils.ln_s(File.join(tmpdir, lfs_file_path), symlink)
|
expect(Gitlab::Utils::FileInfo).to receive(:linked?).with(symlink).and_call_original
|
||||||
|
|
||||||
expect { pipeline.load(context, symlink) }.not_to change { portable.lfs_objects.count }
|
expect { pipeline.load(context, symlink) }.not_to change { portable.lfs_objects.count }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when file path shares multiple hard links' do
|
||||||
|
it 'returns' do
|
||||||
|
FileUtils.link(lfs_file_path, File.join(tmpdir, 'hard_link'))
|
||||||
|
|
||||||
|
expect(Gitlab::Utils::FileInfo).to receive(:linked?).with(lfs_file_path).and_call_original
|
||||||
|
expect { pipeline.load(context, lfs_file_path) }.not_to change { portable.lfs_objects.count }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when path is a directory' do
|
context 'when path is a directory' do
|
||||||
it 'returns' do
|
it 'returns' do
|
||||||
expect { pipeline.load(context, Dir.tmpdir) }.not_to change { portable.lfs_objects.count }
|
expect { pipeline.load(context, Dir.tmpdir) }.not_to change { portable.lfs_objects.count }
|
||||||
|
|
|
@ -105,6 +105,7 @@ RSpec.describe BulkImports::Common::Pipelines::UploadsPipeline, feature_category
|
||||||
it 'returns' do
|
it 'returns' do
|
||||||
path = File.join(tmpdir, 'test')
|
path = File.join(tmpdir, 'test')
|
||||||
FileUtils.touch(path)
|
FileUtils.touch(path)
|
||||||
|
|
||||||
expect { pipeline.load(context, path) }.not_to change { portable.uploads.count }
|
expect { pipeline.load(context, path) }.not_to change { portable.uploads.count }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -118,13 +119,22 @@ RSpec.describe BulkImports::Common::Pipelines::UploadsPipeline, feature_category
|
||||||
context 'when path is a symlink' do
|
context 'when path is a symlink' do
|
||||||
it 'does not upload the file' do
|
it 'does not upload the file' do
|
||||||
symlink = File.join(tmpdir, 'symlink')
|
symlink = File.join(tmpdir, 'symlink')
|
||||||
|
FileUtils.ln_s(upload_file_path, symlink)
|
||||||
|
|
||||||
FileUtils.ln_s(File.join(tmpdir, upload_file_path), symlink)
|
expect(Gitlab::Utils::FileInfo).to receive(:linked?).with(symlink).and_call_original
|
||||||
|
|
||||||
expect { pipeline.load(context, symlink) }.not_to change { portable.uploads.count }
|
expect { pipeline.load(context, symlink) }.not_to change { portable.uploads.count }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when path has multiple hard links' do
|
||||||
|
it 'does not upload the file' do
|
||||||
|
FileUtils.link(upload_file_path, File.join(tmpdir, 'hard_link'))
|
||||||
|
|
||||||
|
expect(Gitlab::Utils::FileInfo).to receive(:linked?).with(upload_file_path).and_call_original
|
||||||
|
expect { pipeline.load(context, upload_file_path) }.not_to change { portable.uploads.count }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when path traverses' do
|
context 'when path traverses' do
|
||||||
it 'does not upload the file' do
|
it 'does not upload the file' do
|
||||||
path_traversal = "#{uploads_dir_path}/avatar/../../../../etc/passwd"
|
path_traversal = "#{uploads_dir_path}/avatar/../../../../etc/passwd"
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe BulkImports::Projects::Pipelines::DesignBundlePipeline do
|
RSpec.describe BulkImports::Projects::Pipelines::DesignBundlePipeline, feature_category: :importers do
|
||||||
let_it_be(:design) { create(:design, :with_file) }
|
let_it_be(:design) { create(:design, :with_file) }
|
||||||
|
|
||||||
let(:portable) { create(:project) }
|
let(:portable) { create(:project) }
|
||||||
|
@ -125,9 +125,9 @@ RSpec.describe BulkImports::Projects::Pipelines::DesignBundlePipeline do
|
||||||
context 'when path is symlink' do
|
context 'when path is symlink' do
|
||||||
it 'returns' do
|
it 'returns' do
|
||||||
symlink = File.join(tmpdir, 'symlink')
|
symlink = File.join(tmpdir, 'symlink')
|
||||||
|
FileUtils.ln_s(design_bundle_path, symlink)
|
||||||
|
|
||||||
FileUtils.ln_s(File.join(tmpdir, design_bundle_path), symlink)
|
expect(Gitlab::Utils::FileInfo).to receive(:linked?).with(symlink).and_call_original
|
||||||
|
|
||||||
expect(portable.design_repository).not_to receive(:create_from_bundle)
|
expect(portable.design_repository).not_to receive(:create_from_bundle)
|
||||||
|
|
||||||
pipeline.load(context, symlink)
|
pipeline.load(context, symlink)
|
||||||
|
@ -136,6 +136,19 @@ RSpec.describe BulkImports::Projects::Pipelines::DesignBundlePipeline do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when path has multiple hard links' do
|
||||||
|
it 'returns' do
|
||||||
|
FileUtils.link(design_bundle_path, File.join(tmpdir, 'hard_link'))
|
||||||
|
|
||||||
|
expect(Gitlab::Utils::FileInfo).to receive(:linked?).with(design_bundle_path).and_call_original
|
||||||
|
expect(portable.design_repository).not_to receive(:create_from_bundle)
|
||||||
|
|
||||||
|
pipeline.load(context, design_bundle_path)
|
||||||
|
|
||||||
|
expect(portable.design_repository.exists?).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when path is not under tmpdir' do
|
context 'when path is not under tmpdir' do
|
||||||
it 'returns' do
|
it 'returns' do
|
||||||
expect { pipeline.load(context, '/home/test.txt') }
|
expect { pipeline.load(context, '/home/test.txt') }
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe BulkImports::Projects::Pipelines::RepositoryBundlePipeline do
|
RSpec.describe BulkImports::Projects::Pipelines::RepositoryBundlePipeline, feature_category: :importers do
|
||||||
let_it_be(:source) { create(:project, :repository) }
|
let_it_be(:source) { create(:project, :repository) }
|
||||||
|
|
||||||
let(:portable) { create(:project) }
|
let(:portable) { create(:project) }
|
||||||
|
@ -123,9 +123,9 @@ RSpec.describe BulkImports::Projects::Pipelines::RepositoryBundlePipeline do
|
||||||
context 'when path is symlink' do
|
context 'when path is symlink' do
|
||||||
it 'returns' do
|
it 'returns' do
|
||||||
symlink = File.join(tmpdir, 'symlink')
|
symlink = File.join(tmpdir, 'symlink')
|
||||||
|
FileUtils.ln_s(bundle_path, symlink)
|
||||||
|
|
||||||
FileUtils.ln_s(File.join(tmpdir, bundle_path), symlink)
|
expect(Gitlab::Utils::FileInfo).to receive(:linked?).with(symlink).and_call_original
|
||||||
|
|
||||||
expect(portable.repository).not_to receive(:create_from_bundle)
|
expect(portable.repository).not_to receive(:create_from_bundle)
|
||||||
|
|
||||||
pipeline.load(context, symlink)
|
pipeline.load(context, symlink)
|
||||||
|
@ -134,6 +134,19 @@ RSpec.describe BulkImports::Projects::Pipelines::RepositoryBundlePipeline do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when path has mutiple hard links' do
|
||||||
|
it 'returns' do
|
||||||
|
FileUtils.link(bundle_path, File.join(tmpdir, 'hard_link'))
|
||||||
|
|
||||||
|
expect(Gitlab::Utils::FileInfo).to receive(:linked?).with(bundle_path).and_call_original
|
||||||
|
expect(portable.repository).not_to receive(:create_from_bundle)
|
||||||
|
|
||||||
|
pipeline.load(context, bundle_path)
|
||||||
|
|
||||||
|
expect(portable.repository.exists?).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when path is not under tmpdir' do
|
context 'when path is not under tmpdir' do
|
||||||
it 'returns' do
|
it 'returns' do
|
||||||
expect { pipeline.load(context, '/home/test.txt') }
|
expect { pipeline.load(context, '/home/test.txt') }
|
||||||
|
|
|
@ -32,6 +32,12 @@ RSpec.describe Gitlab::Checks::BranchCheck do
|
||||||
expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "You cannot create a branch with a 40-character hexadecimal branch name.")
|
expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "You cannot create a branch with a 40-character hexadecimal branch name.")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "prohibits 40-character hexadecimal branch names followed by a dash as the start of a path" do
|
||||||
|
allow(subject).to receive(:branch_name).and_return("267208abfe40e546f5e847444276f7d43a39503e-/test")
|
||||||
|
|
||||||
|
expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "You cannot create a branch with a 40-character hexadecimal branch name.")
|
||||||
|
end
|
||||||
|
|
||||||
it "doesn't prohibit a nested hexadecimal in a branch name" do
|
it "doesn't prohibit a nested hexadecimal in a branch name" do
|
||||||
allow(subject).to receive(:branch_name).and_return("267208abfe40e546f5e847444276f7d43a39503e-fix")
|
allow(subject).to receive(:branch_name).and_return("267208abfe40e546f5e847444276f7d43a39503e-fix")
|
||||||
|
|
||||||
|
|
|
@ -105,6 +105,16 @@ RSpec.describe Gitlab::Ci::DecompressedGzipSizeValidator, feature_category: :imp
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when archive path has multiple hard links' do
|
||||||
|
before do
|
||||||
|
FileUtils.link(filepath, File.join(Dir.mktmpdir, 'hard_link'))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false' do
|
||||||
|
expect(subject).not_to be_valid
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when archive path is not a file' do
|
context 'when archive path is not a file' do
|
||||||
let(:filepath) { Dir.mktmpdir }
|
let(:filepath) { Dir.mktmpdir }
|
||||||
let(:filesize) { File.size(filepath) }
|
let(:filesize) { File.size(filepath) }
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe Gitlab::GithubImport::AttachmentsDownloader do
|
RSpec.describe Gitlab::GithubImport::AttachmentsDownloader, feature_category: :importers do
|
||||||
subject(:downloader) { described_class.new(file_url) }
|
subject(:downloader) { described_class.new(file_url) }
|
||||||
|
|
||||||
let_it_be(:file_url) { 'https://example.com/avatar.png' }
|
let_it_be(:file_url) { 'https://example.com/avatar.png' }
|
||||||
|
@ -39,6 +39,26 @@ RSpec.describe Gitlab::GithubImport::AttachmentsDownloader do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when file shares multiple hard links' do
|
||||||
|
let(:tmpdir) { Dir.mktmpdir }
|
||||||
|
let(:hard_link) { File.join(tmpdir, 'hard_link') }
|
||||||
|
|
||||||
|
before do
|
||||||
|
existing_file = File.join(tmpdir, 'file.txt')
|
||||||
|
FileUtils.touch(existing_file)
|
||||||
|
FileUtils.link(existing_file, hard_link)
|
||||||
|
allow(downloader).to receive(:filepath).and_return(hard_link)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises expected exception' do
|
||||||
|
expect(Gitlab::Utils::FileInfo).to receive(:linked?).with(hard_link).and_call_original
|
||||||
|
expect { downloader.perform }.to raise_exception(
|
||||||
|
described_class::DownloadError,
|
||||||
|
'Invalid downloaded file'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when filename is malicious' do
|
context 'when filename is malicious' do
|
||||||
let_it_be(:file_url) { 'https://example.com/ava%2F..%2Ftar.png' }
|
let_it_be(:file_url) { 'https://example.com/ava%2F..%2Ftar.png' }
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe Gitlab::Harbor::Query do
|
RSpec.describe Gitlab::Harbor::Query do
|
||||||
|
using RSpec::Parameterized::TableSyntax
|
||||||
|
|
||||||
let_it_be(:harbor_integration) { create(:harbor_integration) }
|
let_it_be(:harbor_integration) { create(:harbor_integration) }
|
||||||
|
|
||||||
let(:params) { {} }
|
let(:params) { {} }
|
||||||
|
@ -111,19 +113,20 @@ RSpec.describe Gitlab::Harbor::Query do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'search' do
|
context 'search' do
|
||||||
context 'with valid search' do
|
where(:search_param, :is_valid) do
|
||||||
let(:params) { { search: 'name=desc' } }
|
"name=desc" | true
|
||||||
|
"name=value1,name=value-2" | true
|
||||||
it 'initialize successfully' do
|
"name=value1,name=value_2" | false
|
||||||
expect(query.valid?).to eq(true)
|
"name=desc,key=value" | false
|
||||||
end
|
"name=value1, name=value2" | false
|
||||||
|
"name" | false
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with invalid search' do
|
with_them do
|
||||||
let(:params) { { search: 'blabla' } }
|
let(:params) { { search: search_param } }
|
||||||
|
|
||||||
it 'initialize failed' do
|
it "validates according to the regex" do
|
||||||
expect(query.valid?).to eq(false)
|
expect(query.valid?).to eq(is_valid)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,13 +5,16 @@ require 'spec_helper'
|
||||||
RSpec.describe Gitlab::ImportExport::CommandLineUtil, feature_category: :importers do
|
RSpec.describe Gitlab::ImportExport::CommandLineUtil, feature_category: :importers do
|
||||||
include ExportFileHelper
|
include ExportFileHelper
|
||||||
|
|
||||||
let(:path) { "#{Dir.tmpdir}/symlink_test" }
|
|
||||||
let(:archive) { 'spec/fixtures/symlink_export.tar.gz' }
|
|
||||||
let(:shared) { Gitlab::ImportExport::Shared.new(nil) }
|
let(:shared) { Gitlab::ImportExport::Shared.new(nil) }
|
||||||
let(:tmpdir) { Dir.mktmpdir }
|
# Separate where files are written during this test by their kind, to avoid them interfering with each other:
|
||||||
|
# - `source_dir` Dir to compress files from.
|
||||||
|
# - `target_dir` Dir to decompress archived files into.
|
||||||
|
# - `archive_dir` Dir to write any archive files to.
|
||||||
|
let(:source_dir) { Dir.mktmpdir }
|
||||||
|
let(:target_dir) { Dir.mktmpdir }
|
||||||
let(:archive_dir) { Dir.mktmpdir }
|
let(:archive_dir) { Dir.mktmpdir }
|
||||||
|
|
||||||
subject do
|
subject(:mock_class) do
|
||||||
Class.new do
|
Class.new do
|
||||||
include Gitlab::ImportExport::CommandLineUtil
|
include Gitlab::ImportExport::CommandLineUtil
|
||||||
|
|
||||||
|
@ -25,38 +28,59 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil, feature_category: :importe
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
FileUtils.mkdir_p(path)
|
FileUtils.mkdir_p(source_dir)
|
||||||
end
|
end
|
||||||
|
|
||||||
after do
|
after do
|
||||||
FileUtils.rm_rf(path)
|
FileUtils.rm_rf(source_dir)
|
||||||
|
FileUtils.rm_rf(target_dir)
|
||||||
FileUtils.rm_rf(archive_dir)
|
FileUtils.rm_rf(archive_dir)
|
||||||
FileUtils.remove_entry(tmpdir)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_examples 'deletes symlinks' do |compression, decompression|
|
shared_examples 'deletes symlinks' do |compression, decompression|
|
||||||
it 'deletes the symlinks', :aggregate_failures do
|
it 'deletes the symlinks', :aggregate_failures do
|
||||||
Dir.mkdir("#{tmpdir}/.git")
|
Dir.mkdir("#{source_dir}/.git")
|
||||||
Dir.mkdir("#{tmpdir}/folder")
|
Dir.mkdir("#{source_dir}/folder")
|
||||||
FileUtils.touch("#{tmpdir}/file.txt")
|
FileUtils.touch("#{source_dir}/file.txt")
|
||||||
FileUtils.touch("#{tmpdir}/folder/file.txt")
|
FileUtils.touch("#{source_dir}/folder/file.txt")
|
||||||
FileUtils.touch("#{tmpdir}/.gitignore")
|
FileUtils.touch("#{source_dir}/.gitignore")
|
||||||
FileUtils.touch("#{tmpdir}/.git/config")
|
FileUtils.touch("#{source_dir}/.git/config")
|
||||||
File.symlink('file.txt', "#{tmpdir}/.symlink")
|
File.symlink('file.txt', "#{source_dir}/.symlink")
|
||||||
File.symlink('file.txt', "#{tmpdir}/.git/.symlink")
|
File.symlink('file.txt', "#{source_dir}/.git/.symlink")
|
||||||
File.symlink('file.txt', "#{tmpdir}/folder/.symlink")
|
File.symlink('file.txt', "#{source_dir}/folder/.symlink")
|
||||||
archive = File.join(archive_dir, 'archive')
|
archive_file = File.join(archive_dir, 'symlink_archive.tar.gz')
|
||||||
subject.public_send(compression, archive: archive, dir: tmpdir)
|
subject.public_send(compression, archive: archive_file, dir: source_dir)
|
||||||
|
subject.public_send(decompression, archive: archive_file, dir: target_dir)
|
||||||
|
|
||||||
subject.public_send(decompression, archive: archive, dir: archive_dir)
|
expect(File).to exist("#{target_dir}/file.txt")
|
||||||
|
expect(File).to exist("#{target_dir}/folder/file.txt")
|
||||||
|
expect(File).to exist("#{target_dir}/.gitignore")
|
||||||
|
expect(File).to exist("#{target_dir}/.git/config")
|
||||||
|
expect(File).not_to exist("#{target_dir}/.symlink")
|
||||||
|
expect(File).not_to exist("#{target_dir}/.git/.symlink")
|
||||||
|
expect(File).not_to exist("#{target_dir}/folder/.symlink")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
expect(File.exist?("#{archive_dir}/file.txt")).to eq(true)
|
shared_examples 'handles shared hard links' do |compression, decompression|
|
||||||
expect(File.exist?("#{archive_dir}/folder/file.txt")).to eq(true)
|
let(:archive_file) { File.join(archive_dir, 'hard_link_archive.tar.gz') }
|
||||||
expect(File.exist?("#{archive_dir}/.gitignore")).to eq(true)
|
|
||||||
expect(File.exist?("#{archive_dir}/.git/config")).to eq(true)
|
subject(:decompress) { mock_class.public_send(decompression, archive: archive_file, dir: target_dir) }
|
||||||
expect(File.exist?("#{archive_dir}/.symlink")).to eq(false)
|
|
||||||
expect(File.exist?("#{archive_dir}/.git/.symlink")).to eq(false)
|
before do
|
||||||
expect(File.exist?("#{archive_dir}/folder/.symlink")).to eq(false)
|
Dir.mkdir("#{source_dir}/dir")
|
||||||
|
FileUtils.touch("#{source_dir}/file.txt")
|
||||||
|
FileUtils.touch("#{source_dir}/dir/.file.txt")
|
||||||
|
FileUtils.link("#{source_dir}/file.txt", "#{source_dir}/.hard_linked_file.txt")
|
||||||
|
|
||||||
|
mock_class.public_send(compression, archive: archive_file, dir: source_dir)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises an exception and deletes the extraction dir', :aggregate_failures do
|
||||||
|
expect(FileUtils).to receive(:remove_dir).with(target_dir).and_call_original
|
||||||
|
expect(Dir).to exist(target_dir)
|
||||||
|
expect { decompress }.to raise_error(described_class::HardLinkError)
|
||||||
|
expect(Dir).not_to exist(target_dir)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -212,6 +236,8 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil, feature_category: :importe
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#gzip' do
|
describe '#gzip' do
|
||||||
|
let(:path) { source_dir }
|
||||||
|
|
||||||
it 'compresses specified file' do
|
it 'compresses specified file' do
|
||||||
tempfile = Tempfile.new('test', path)
|
tempfile = Tempfile.new('test', path)
|
||||||
filename = File.basename(tempfile.path)
|
filename = File.basename(tempfile.path)
|
||||||
|
@ -229,14 +255,16 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil, feature_category: :importe
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#gunzip' do
|
describe '#gunzip' do
|
||||||
|
let(:path) { source_dir }
|
||||||
|
|
||||||
it 'decompresses specified file' do
|
it 'decompresses specified file' do
|
||||||
filename = 'labels.ndjson.gz'
|
filename = 'labels.ndjson.gz'
|
||||||
gz_filepath = "spec/fixtures/bulk_imports/gz/#{filename}"
|
gz_filepath = "spec/fixtures/bulk_imports/gz/#{filename}"
|
||||||
FileUtils.copy_file(gz_filepath, File.join(tmpdir, filename))
|
FileUtils.copy_file(gz_filepath, File.join(path, filename))
|
||||||
|
|
||||||
subject.gunzip(dir: tmpdir, filename: filename)
|
subject.gunzip(dir: path, filename: filename)
|
||||||
|
|
||||||
expect(File.exist?(File.join(tmpdir, 'labels.ndjson'))).to eq(true)
|
expect(File.exist?(File.join(path, 'labels.ndjson'))).to eq(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when exception occurs' do
|
context 'when exception occurs' do
|
||||||
|
@ -250,7 +278,7 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil, feature_category: :importe
|
||||||
it 'archives a folder without compression' do
|
it 'archives a folder without compression' do
|
||||||
archive_file = File.join(archive_dir, 'archive.tar')
|
archive_file = File.join(archive_dir, 'archive.tar')
|
||||||
|
|
||||||
result = subject.tar_cf(archive: archive_file, dir: tmpdir)
|
result = subject.tar_cf(archive: archive_file, dir: source_dir)
|
||||||
|
|
||||||
expect(result).to eq(true)
|
expect(result).to eq(true)
|
||||||
expect(File.exist?(archive_file)).to eq(true)
|
expect(File.exist?(archive_file)).to eq(true)
|
||||||
|
@ -270,29 +298,35 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil, feature_category: :importe
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#untar_zxf' do
|
describe '#untar_zxf' do
|
||||||
|
let(:tar_archive_fixture) { 'spec/fixtures/symlink_export.tar.gz' }
|
||||||
|
|
||||||
it_behaves_like 'deletes symlinks', :tar_czf, :untar_zxf
|
it_behaves_like 'deletes symlinks', :tar_czf, :untar_zxf
|
||||||
|
it_behaves_like 'handles shared hard links', :tar_czf, :untar_zxf
|
||||||
|
|
||||||
it 'has the right mask for project.json' do
|
it 'has the right mask for project.json' do
|
||||||
subject.untar_zxf(archive: archive, dir: path)
|
subject.untar_zxf(archive: tar_archive_fixture, dir: target_dir)
|
||||||
|
|
||||||
expect(file_permissions("#{path}/project.json")).to eq(0755) # originally 777
|
expect(file_permissions("#{target_dir}/project.json")).to eq(0755) # originally 777
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'has the right mask for uploads' do
|
it 'has the right mask for uploads' do
|
||||||
subject.untar_zxf(archive: archive, dir: path)
|
subject.untar_zxf(archive: tar_archive_fixture, dir: target_dir)
|
||||||
|
|
||||||
expect(file_permissions("#{path}/uploads")).to eq(0755) # originally 555
|
expect(file_permissions("#{target_dir}/uploads")).to eq(0755) # originally 555
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#untar_xf' do
|
describe '#untar_xf' do
|
||||||
|
let(:tar_archive_fixture) { 'spec/fixtures/symlink_export.tar.gz' }
|
||||||
|
|
||||||
it_behaves_like 'deletes symlinks', :tar_cf, :untar_xf
|
it_behaves_like 'deletes symlinks', :tar_cf, :untar_xf
|
||||||
|
it_behaves_like 'handles shared hard links', :tar_cf, :untar_xf
|
||||||
|
|
||||||
it 'extracts archive without decompression' do
|
it 'extracts archive without decompression' do
|
||||||
filename = 'archive.tar.gz'
|
filename = 'archive.tar.gz'
|
||||||
archive_file = File.join(archive_dir, 'archive.tar')
|
archive_file = File.join(archive_dir, 'archive.tar')
|
||||||
|
|
||||||
FileUtils.copy_file(archive, File.join(archive_dir, filename))
|
FileUtils.copy_file(tar_archive_fixture, File.join(archive_dir, filename))
|
||||||
subject.gunzip(dir: archive_dir, filename: filename)
|
subject.gunzip(dir: archive_dir, filename: filename)
|
||||||
|
|
||||||
result = subject.untar_xf(archive: archive_file, dir: archive_dir)
|
result = subject.untar_xf(archive: archive_file, dir: archive_dir)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe Gitlab::ImportExport::DecompressedArchiveSizeValidator do
|
RSpec.describe Gitlab::ImportExport::DecompressedArchiveSizeValidator, feature_category: :importers do
|
||||||
let_it_be(:filepath) { File.join(Dir.tmpdir, 'decompressed_archive_size_validator_spec.gz') }
|
let_it_be(:filepath) { File.join(Dir.tmpdir, 'decompressed_archive_size_validator_spec.gz') }
|
||||||
|
|
||||||
before(:all) do
|
before(:all) do
|
||||||
|
@ -121,7 +121,7 @@ RSpec.describe Gitlab::ImportExport::DecompressedArchiveSizeValidator do
|
||||||
|
|
||||||
context 'which archive path is a symlink' do
|
context 'which archive path is a symlink' do
|
||||||
let(:filepath) { File.join(Dir.tmpdir, 'symlink') }
|
let(:filepath) { File.join(Dir.tmpdir, 'symlink') }
|
||||||
let(:error_message) { 'Archive path is a symlink' }
|
let(:error_message) { 'Archive path is a symlink or hard link' }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
FileUtils.ln_s(filepath, filepath, force: true)
|
FileUtils.ln_s(filepath, filepath, force: true)
|
||||||
|
@ -132,6 +132,19 @@ RSpec.describe Gitlab::ImportExport::DecompressedArchiveSizeValidator do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when archive path shares multiple hard links' do
|
||||||
|
let(:filesize) { 32 }
|
||||||
|
let(:error_message) { 'Archive path is a symlink or hard link' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
FileUtils.link(filepath, File.join(Dir.mktmpdir, 'hard_link'))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false' do
|
||||||
|
expect(subject).not_to be_valid
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when archive path is not a file' do
|
context 'when archive path is not a file' do
|
||||||
let(:filepath) { Dir.mktmpdir }
|
let(:filepath) { Dir.mktmpdir }
|
||||||
let(:filesize) { File.size(filepath) }
|
let(:filesize) { File.size(filepath) }
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe Gitlab::ImportExport::FileImporter do
|
RSpec.describe Gitlab::ImportExport::FileImporter, feature_category: :importers do
|
||||||
include ExportFileHelper
|
include ExportFileHelper
|
||||||
|
|
||||||
let(:shared) { Gitlab::ImportExport::Shared.new(nil) }
|
let(:shared) { Gitlab::ImportExport::Shared.new(nil) }
|
||||||
|
@ -113,28 +113,73 @@ RSpec.describe Gitlab::ImportExport::FileImporter do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'error' do
|
context 'error' do
|
||||||
|
subject(:import) { described_class.import(importable: build(:project), archive_file: '', shared: shared) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow_next_instance_of(described_class) do |instance|
|
allow_next_instance_of(described_class) do |instance|
|
||||||
allow(instance).to receive(:wait_for_archived_file).and_raise(StandardError)
|
allow(instance).to receive(:wait_for_archived_file).and_raise(StandardError, 'foo')
|
||||||
end
|
end
|
||||||
described_class.import(importable: build(:project), archive_file: '', shared: shared)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'removes symlinks in root folder' do
|
it 'removes symlinks in root folder' do
|
||||||
|
import
|
||||||
|
|
||||||
expect(File.exist?(symlink_file)).to be false
|
expect(File.exist?(symlink_file)).to be false
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'removes hidden symlinks in root folder' do
|
it 'removes hidden symlinks in root folder' do
|
||||||
|
import
|
||||||
|
|
||||||
expect(File.exist?(hidden_symlink_file)).to be false
|
expect(File.exist?(hidden_symlink_file)).to be false
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'removes symlinks in subfolders' do
|
it 'removes symlinks in subfolders' do
|
||||||
|
import
|
||||||
|
|
||||||
expect(File.exist?(subfolder_symlink_file)).to be false
|
expect(File.exist?(subfolder_symlink_file)).to be false
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not remove a valid file' do
|
it 'does not remove a valid file' do
|
||||||
|
import
|
||||||
|
|
||||||
expect(File.exist?(valid_file)).to be true
|
expect(File.exist?(valid_file)).to be true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'returns false and sets an error on shared' do
|
||||||
|
result = import
|
||||||
|
|
||||||
|
expect(result).to eq(false)
|
||||||
|
expect(shared.errors.join).to eq('foo')
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when files in the archive share hard links' do
|
||||||
|
let(:hard_link_file) { "#{shared.export_path}/hard_link_file.txt" }
|
||||||
|
|
||||||
|
before do
|
||||||
|
FileUtils.link(valid_file, hard_link_file)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false and sets an error on shared' do
|
||||||
|
result = import
|
||||||
|
|
||||||
|
expect(result).to eq(false)
|
||||||
|
expect(shared.errors.join).to eq('File shares hard link')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'removes all files in export path' do
|
||||||
|
expect(Dir).to exist(shared.export_path)
|
||||||
|
expect(File).to exist(symlink_file)
|
||||||
|
expect(File).to exist(hard_link_file)
|
||||||
|
expect(File).to exist(valid_file)
|
||||||
|
|
||||||
|
import
|
||||||
|
|
||||||
|
expect(File).not_to exist(symlink_file)
|
||||||
|
expect(File).not_to exist(hard_link_file)
|
||||||
|
expect(File).not_to exist(valid_file)
|
||||||
|
expect(Dir).not_to exist(shared.export_path)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when file exceeds acceptable decompressed size' do
|
context 'when file exceeds acceptable decompressed size' do
|
||||||
|
@ -157,8 +202,10 @@ RSpec.describe Gitlab::ImportExport::FileImporter do
|
||||||
allow(Gitlab::ImportExport::DecompressedArchiveSizeValidator).to receive(:max_bytes).and_return(1)
|
allow(Gitlab::ImportExport::DecompressedArchiveSizeValidator).to receive(:max_bytes).and_return(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns false' do
|
it 'returns false and sets an error on shared' do
|
||||||
expect(subject.import).to eq(false)
|
result = subject.import
|
||||||
|
|
||||||
|
expect(result).to eq(false)
|
||||||
expect(shared.errors.join).to eq('Decompressed archive size validation failed.')
|
expect(shared.errors.join).to eq('Decompressed archive size validation failed.')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -35,11 +35,16 @@ RSpec.describe Gitlab::ImportExport::Json::NdjsonReader, feature_category: :impo
|
||||||
expect(subject).to eq(root_tree)
|
expect(subject).to eq(root_tree)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when project.json is symlink' do
|
context 'when project.json is symlink or hard link' do
|
||||||
it 'raises error an error' do
|
using RSpec::Parameterized::TableSyntax
|
||||||
|
|
||||||
|
where(:link_method) { [:link, :symlink] }
|
||||||
|
|
||||||
|
with_them do
|
||||||
|
it 'raises an error' do
|
||||||
Dir.mktmpdir do |tmpdir|
|
Dir.mktmpdir do |tmpdir|
|
||||||
FileUtils.touch(File.join(tmpdir, 'passwd'))
|
FileUtils.touch(File.join(tmpdir, 'passwd'))
|
||||||
File.symlink(File.join(tmpdir, 'passwd'), File.join(tmpdir, 'project.json'))
|
FileUtils.send(link_method, File.join(tmpdir, 'passwd'), File.join(tmpdir, 'project.json'))
|
||||||
|
|
||||||
ndjson_reader = described_class.new(tmpdir)
|
ndjson_reader = described_class.new(tmpdir)
|
||||||
|
|
||||||
|
@ -49,6 +54,7 @@ RSpec.describe Gitlab::ImportExport::Json::NdjsonReader, feature_category: :impo
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#consume_relation' do
|
describe '#consume_relation' do
|
||||||
let(:dir_path) { fixture }
|
let(:dir_path) { fixture }
|
||||||
|
@ -97,12 +103,17 @@ RSpec.describe Gitlab::ImportExport::Json::NdjsonReader, feature_category: :impo
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when relation file is a symlink' do
|
context 'when relation file is a symlink or hard link' do
|
||||||
|
using RSpec::Parameterized::TableSyntax
|
||||||
|
|
||||||
|
where(:link_method) { [:link, :symlink] }
|
||||||
|
|
||||||
|
with_them do
|
||||||
it 'yields nothing to the Enumerator' do
|
it 'yields nothing to the Enumerator' do
|
||||||
Dir.mktmpdir do |tmpdir|
|
Dir.mktmpdir do |tmpdir|
|
||||||
Dir.mkdir(File.join(tmpdir, 'project'))
|
Dir.mkdir(File.join(tmpdir, 'project'))
|
||||||
File.write(File.join(tmpdir, 'passwd'), "{}\n{}")
|
File.write(File.join(tmpdir, 'passwd'), "{}\n{}")
|
||||||
File.symlink(File.join(tmpdir, 'passwd'), File.join(tmpdir, 'project', 'issues.ndjson'))
|
FileUtils.send(link_method, File.join(tmpdir, 'passwd'), File.join(tmpdir, 'project', 'issues.ndjson'))
|
||||||
|
|
||||||
ndjson_reader = described_class.new(tmpdir)
|
ndjson_reader = described_class.new(tmpdir)
|
||||||
|
|
||||||
|
@ -112,6 +123,7 @@ RSpec.describe Gitlab::ImportExport::Json::NdjsonReader, feature_category: :impo
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'relation file is empty' do
|
context 'relation file is empty' do
|
||||||
let(:key) { 'empty' }
|
let(:key) { 'empty' }
|
||||||
|
|
|
@ -4,15 +4,17 @@ require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe Gitlab::ImportExport::RecursiveMergeFolders do
|
RSpec.describe Gitlab::ImportExport::RecursiveMergeFolders do
|
||||||
describe '.merge' do
|
describe '.merge' do
|
||||||
it 'merge folder and ignore symlinks' do
|
it 'merges folder and ignores symlinks and files that share hard links' do
|
||||||
Dir.mktmpdir do |tmpdir|
|
Dir.mktmpdir do |tmpdir|
|
||||||
source = "#{tmpdir}/source"
|
source = "#{tmpdir}/source"
|
||||||
FileUtils.mkdir_p("#{source}/folder/folder")
|
FileUtils.mkdir_p("#{source}/folder/folder")
|
||||||
FileUtils.touch("#{source}/file1.txt")
|
FileUtils.touch("#{source}/file1.txt")
|
||||||
|
FileUtils.touch("#{source}/file_that_shares_hard_links.txt")
|
||||||
FileUtils.touch("#{source}/folder/file2.txt")
|
FileUtils.touch("#{source}/folder/file2.txt")
|
||||||
FileUtils.touch("#{source}/folder/folder/file3.txt")
|
FileUtils.touch("#{source}/folder/folder/file3.txt")
|
||||||
FileUtils.ln_s("#{source}/file1.txt", "#{source}/symlink-file1.txt")
|
FileUtils.ln_s("#{source}/file1.txt", "#{source}/symlink-file1.txt")
|
||||||
FileUtils.ln_s("#{source}/folder", "#{source}/symlink-folder")
|
FileUtils.ln_s("#{source}/folder", "#{source}/symlink-folder")
|
||||||
|
FileUtils.link("#{source}/file_that_shares_hard_links.txt", "#{source}/hard_link.txt")
|
||||||
|
|
||||||
target = "#{tmpdir}/target"
|
target = "#{tmpdir}/target"
|
||||||
FileUtils.mkdir_p("#{target}/folder/folder")
|
FileUtils.mkdir_p("#{target}/folder/folder")
|
||||||
|
|
|
@ -9,6 +9,10 @@ RSpec.describe Gitlab::Pages::VirtualHostFinder, feature_category: :pages do
|
||||||
project.update_pages_deployment!(create(:pages_deployment, project: project))
|
project.update_pages_deployment!(create(:pages_deployment, project: project))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_pages_setting(host: 'example.com')
|
||||||
|
end
|
||||||
|
|
||||||
it 'returns nil when host is empty' do
|
it 'returns nil when host is empty' do
|
||||||
expect(described_class.new(nil).execute).to be_nil
|
expect(described_class.new(nil).execute).to be_nil
|
||||||
expect(described_class.new('').execute).to be_nil
|
expect(described_class.new('').execute).to be_nil
|
||||||
|
@ -69,7 +73,7 @@ RSpec.describe Gitlab::Pages::VirtualHostFinder, feature_category: :pages do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns the virual domain with no lookup_paths' do
|
it 'returns the virual domain with no lookup_paths' do
|
||||||
virtual_domain = described_class.new("#{project.namespace.path}.#{Settings.pages.host}").execute
|
virtual_domain = described_class.new("#{project.namespace.path}.example.com").execute
|
||||||
|
|
||||||
expect(virtual_domain).to be_an_instance_of(Pages::VirtualDomain)
|
expect(virtual_domain).to be_an_instance_of(Pages::VirtualDomain)
|
||||||
expect(virtual_domain.cache_key).to match(/pages_domain_for_namespace_#{project.namespace.id}_/)
|
expect(virtual_domain.cache_key).to match(/pages_domain_for_namespace_#{project.namespace.id}_/)
|
||||||
|
@ -82,7 +86,7 @@ RSpec.describe Gitlab::Pages::VirtualHostFinder, feature_category: :pages do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns the virual domain with no lookup_paths' do
|
it 'returns the virual domain with no lookup_paths' do
|
||||||
virtual_domain = described_class.new("#{project.namespace.path}.#{Settings.pages.host}".downcase).execute
|
virtual_domain = described_class.new("#{project.namespace.path}.example.com".downcase).execute
|
||||||
|
|
||||||
expect(virtual_domain).to be_an_instance_of(Pages::VirtualDomain)
|
expect(virtual_domain).to be_an_instance_of(Pages::VirtualDomain)
|
||||||
expect(virtual_domain.cache_key).to be_nil
|
expect(virtual_domain.cache_key).to be_nil
|
||||||
|
@ -104,7 +108,7 @@ RSpec.describe Gitlab::Pages::VirtualHostFinder, feature_category: :pages do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns the virual domain when there are pages deployed for the project' do
|
it 'returns the virual domain when there are pages deployed for the project' do
|
||||||
virtual_domain = described_class.new("#{project.namespace.path}.#{Settings.pages.host}").execute
|
virtual_domain = described_class.new("#{project.namespace.path}.example.com").execute
|
||||||
|
|
||||||
expect(virtual_domain).to be_an_instance_of(Pages::VirtualDomain)
|
expect(virtual_domain).to be_an_instance_of(Pages::VirtualDomain)
|
||||||
expect(virtual_domain.cache_key).to match(/pages_domain_for_namespace_#{project.namespace.id}_/)
|
expect(virtual_domain.cache_key).to match(/pages_domain_for_namespace_#{project.namespace.id}_/)
|
||||||
|
@ -113,7 +117,7 @@ RSpec.describe Gitlab::Pages::VirtualHostFinder, feature_category: :pages do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'finds domain with case-insensitive' do
|
it 'finds domain with case-insensitive' do
|
||||||
virtual_domain = described_class.new("#{project.namespace.path}.#{Settings.pages.host.upcase}").execute
|
virtual_domain = described_class.new("#{project.namespace.path}.Example.com").execute
|
||||||
|
|
||||||
expect(virtual_domain).to be_an_instance_of(Pages::VirtualDomain)
|
expect(virtual_domain).to be_an_instance_of(Pages::VirtualDomain)
|
||||||
expect(virtual_domain.cache_key).to match(/pages_domain_for_namespace_#{project.namespace.id}_/)
|
expect(virtual_domain.cache_key).to match(/pages_domain_for_namespace_#{project.namespace.id}_/)
|
||||||
|
@ -127,7 +131,7 @@ RSpec.describe Gitlab::Pages::VirtualHostFinder, feature_category: :pages do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns the virual domain when there are pages deployed for the project' do
|
it 'returns the virual domain when there are pages deployed for the project' do
|
||||||
virtual_domain = described_class.new("#{project.namespace.path}.#{Settings.pages.host}").execute
|
virtual_domain = described_class.new("#{project.namespace.path}.example.com").execute
|
||||||
|
|
||||||
expect(virtual_domain).to be_an_instance_of(Pages::VirtualDomain)
|
expect(virtual_domain).to be_an_instance_of(Pages::VirtualDomain)
|
||||||
expect(virtual_domain.cache_key).to be_nil
|
expect(virtual_domain.cache_key).to be_nil
|
||||||
|
@ -143,7 +147,7 @@ RSpec.describe Gitlab::Pages::VirtualHostFinder, feature_category: :pages do
|
||||||
project.project_setting.update!(pages_unique_domain: 'unique-domain')
|
project.project_setting.update!(pages_unique_domain: 'unique-domain')
|
||||||
end
|
end
|
||||||
|
|
||||||
subject(:virtual_domain) { described_class.new("unique-domain.#{Settings.pages.host.upcase}").execute }
|
subject(:virtual_domain) { described_class.new('unique-domain.example.com').execute }
|
||||||
|
|
||||||
context 'when pages unique domain is enabled' do
|
context 'when pages unique domain is enabled' do
|
||||||
before_all do
|
before_all do
|
||||||
|
@ -171,6 +175,19 @@ RSpec.describe Gitlab::Pages::VirtualHostFinder, feature_category: :pages do
|
||||||
expect(virtual_domain.lookup_paths.first.project_id).to eq(project.id)
|
expect(virtual_domain.lookup_paths.first.project_id).to eq(project.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when a project path conflicts with a unique domain' do
|
||||||
|
it 'prioritizes the unique domain project' do
|
||||||
|
group = create(:group, path: 'unique-domain')
|
||||||
|
other_project = build(:project, path: 'unique-domain.example.com', group: group)
|
||||||
|
other_project.save!(validate: false)
|
||||||
|
other_project.update_pages_deployment!(create(:pages_deployment, project: other_project))
|
||||||
|
other_project.mark_pages_as_deployed
|
||||||
|
|
||||||
|
expect(virtual_domain).to be_an_instance_of(Pages::VirtualDomain)
|
||||||
|
expect(virtual_domain.lookup_paths.first.project_id).to eq(project.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when :cache_pages_domain_api is disabled' do
|
context 'when :cache_pages_domain_api is disabled' do
|
||||||
before do
|
before do
|
||||||
stub_feature_flags(cache_pages_domain_api: false)
|
stub_feature_flags(cache_pages_domain_api: false)
|
||||||
|
|
59
spec/lib/gitlab/plantuml_spec.rb
Normal file
59
spec/lib/gitlab/plantuml_spec.rb
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "spec_helper"
|
||||||
|
|
||||||
|
RSpec.describe Gitlab::Plantuml, feature_category: :shared do
|
||||||
|
describe ".configure" do
|
||||||
|
subject { described_class.configure }
|
||||||
|
|
||||||
|
let(:plantuml_url) { "http://plantuml.foo.bar" }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(Gitlab::CurrentSettings).to receive(:plantuml_url).and_return(plantuml_url)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when PlantUML is enabled" do
|
||||||
|
before do
|
||||||
|
allow(Gitlab::CurrentSettings).to receive(:plantuml_enabled).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "configures the endpoint URL" do
|
||||||
|
expect(subject.url).to eq(plantuml_url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "enables PNG support" do
|
||||||
|
expect(subject.png_enable).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it "disables SVG support" do
|
||||||
|
expect(subject.svg_enable).to be_falsey
|
||||||
|
end
|
||||||
|
|
||||||
|
it "disables TXT support" do
|
||||||
|
expect(subject.txt_enable).to be_falsey
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when PlantUML is disabled" do
|
||||||
|
before do
|
||||||
|
allow(Gitlab::CurrentSettings).to receive(:plantuml_enabled).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "configures the endpoint URL" do
|
||||||
|
expect(subject.url).to eq(plantuml_url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "enables PNG support" do
|
||||||
|
expect(subject.png_enable).to be_falsey
|
||||||
|
end
|
||||||
|
|
||||||
|
it "disables SVG support" do
|
||||||
|
expect(subject.svg_enable).to be_falsey
|
||||||
|
end
|
||||||
|
|
||||||
|
it "disables TXT support" do
|
||||||
|
expect(subject.txt_enable).to be_falsey
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
88
spec/lib/gitlab/utils/file_info_spec.rb
Normal file
88
spec/lib/gitlab/utils/file_info_spec.rb
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'fast_spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe Gitlab::Utils::FileInfo, feature_category: :shared do
|
||||||
|
let(:tmpdir) { Dir.mktmpdir }
|
||||||
|
let(:file_path) { "#{tmpdir}/test.txt" }
|
||||||
|
|
||||||
|
before do
|
||||||
|
FileUtils.touch(file_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
after do
|
||||||
|
FileUtils.rm_rf(tmpdir)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.linked?' do
|
||||||
|
it 'raises an error when file does not exist' do
|
||||||
|
expect { subject.linked?('foo') }.to raise_error(Errno::ENOENT)
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_examples 'identifies a linked file' do
|
||||||
|
it 'returns false when file or dir is not a link' do
|
||||||
|
expect(subject.linked?(tmpdir)).to eq(false)
|
||||||
|
expect(subject.linked?(file)).to eq(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true when file or dir is symlinked' do
|
||||||
|
FileUtils.symlink(tmpdir, "#{tmpdir}/symlinked_dir")
|
||||||
|
FileUtils.symlink(file_path, "#{tmpdir}/symlinked_file.txt")
|
||||||
|
|
||||||
|
expect(subject.linked?("#{tmpdir}/symlinked_dir")).to eq(true)
|
||||||
|
expect(subject.linked?("#{tmpdir}/symlinked_file.txt")).to eq(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true when file has more than one hard link' do
|
||||||
|
FileUtils.link(file_path, "#{tmpdir}/hardlinked_file.txt")
|
||||||
|
|
||||||
|
expect(subject.linked?(file)).to eq(true)
|
||||||
|
expect(subject.linked?("#{tmpdir}/hardlinked_file.txt")).to eq(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when file is a File::Stat' do
|
||||||
|
let(:file) { File.lstat(file_path) }
|
||||||
|
|
||||||
|
it_behaves_like 'identifies a linked file'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when file is path' do
|
||||||
|
let(:file) { file_path }
|
||||||
|
|
||||||
|
it_behaves_like 'identifies a linked file'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.shares_hard_link?' do
|
||||||
|
it 'raises an error when file does not exist' do
|
||||||
|
expect { subject.shares_hard_link?('foo') }.to raise_error(Errno::ENOENT)
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_examples 'identifies a file that shares a hard link' do
|
||||||
|
it 'returns false when file or dir does not share hard links' do
|
||||||
|
expect(subject.shares_hard_link?(tmpdir)).to eq(false)
|
||||||
|
expect(subject.shares_hard_link?(file)).to eq(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true when file has more than one hard link' do
|
||||||
|
FileUtils.link(file_path, "#{tmpdir}/hardlinked_file.txt")
|
||||||
|
|
||||||
|
expect(subject.shares_hard_link?(file)).to eq(true)
|
||||||
|
expect(subject.shares_hard_link?("#{tmpdir}/hardlinked_file.txt")).to eq(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when file is a File::Stat' do
|
||||||
|
let(:file) { File.lstat(file_path) }
|
||||||
|
|
||||||
|
it_behaves_like 'identifies a file that shares a hard link'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when file is path' do
|
||||||
|
let(:file) { file_path }
|
||||||
|
|
||||||
|
it_behaves_like 'identifies a file that shares a hard link'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -25,7 +25,7 @@ RSpec.describe JSONWebToken::HMACToken do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.decode' do
|
describe '.decode' do
|
||||||
let(:leeway) { described_class::IAT_LEEWAY }
|
let(:leeway) { described_class::LEEWAY }
|
||||||
let(:decoded_token) { described_class.decode(encoded_token, secret, leeway: leeway) }
|
let(:decoded_token) { described_class.decode(encoded_token, secret, leeway: leeway) }
|
||||||
|
|
||||||
context 'with an invalid token' do
|
context 'with an invalid token' do
|
||||||
|
|
|
@ -77,12 +77,36 @@ RSpec.describe ProjectSetting, type: :model, feature_category: :projects do
|
||||||
expect(project_setting).not_to be_valid
|
expect(project_setting).not_to be_valid
|
||||||
expect(project_setting.errors.full_messages).to include("Pages unique domain has already been taken")
|
expect(project_setting.errors.full_messages).to include("Pages unique domain has already been taken")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "validates if the pages_unique_domain already exist as a project path" do
|
||||||
|
stub_pages_setting(host: 'example.com')
|
||||||
|
|
||||||
|
create(:project, path: "random-unique-domain.example.com")
|
||||||
|
project_setting = build(:project_setting, pages_unique_domain: "random-unique-domain")
|
||||||
|
|
||||||
|
expect(project_setting).not_to be_valid
|
||||||
|
expect(project_setting.errors.full_messages_for(:pages_unique_domain))
|
||||||
|
.to match(["Pages unique domain already in use"])
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when updating" do
|
||||||
|
it "validates if the pages_unique_domain already exist as a project path" do
|
||||||
|
stub_pages_setting(host: 'example.com')
|
||||||
|
project_setting = create(:project_setting)
|
||||||
|
|
||||||
|
create(:project, path: "random-unique-domain.example.com")
|
||||||
|
|
||||||
|
expect(project_setting.update(pages_unique_domain: "random-unique-domain")).to eq(false)
|
||||||
|
expect(project_setting.errors.full_messages_for(:pages_unique_domain))
|
||||||
|
.to match(["Pages unique domain already in use"])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'target_platforms=' do
|
describe 'target_platforms=' do
|
||||||
it 'stringifies and sorts' do
|
it 'stringifies and sorts' do
|
||||||
project_setting = build(:project_setting, target_platforms: [:watchos, :ios])
|
project_setting = build(:project_setting, target_platforms: [:watchos, :ios])
|
||||||
expect(project_setting.target_platforms).to eq %w(ios watchos)
|
expect(project_setting.target_platforms).to eq %w[ios watchos]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -830,6 +830,37 @@ RSpec.describe Project, factory_default: :keep, feature_category: :projects do
|
||||||
expect(project).to be_valid
|
expect(project).to be_valid
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when validating if path already exist as pages unique domain' do
|
||||||
|
before do
|
||||||
|
stub_pages_setting(host: 'example.com')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'rejects paths that match pages unique domain' do
|
||||||
|
create(:project_setting, pages_unique_domain: 'some-unique-domain')
|
||||||
|
|
||||||
|
project = build(:project, path: 'some-unique-domain.example.com')
|
||||||
|
|
||||||
|
expect(project).not_to be_valid
|
||||||
|
expect(project.errors.full_messages_for(:path)).to match(['Path already in use'])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'accepts path when the host does not match' do
|
||||||
|
create(:project_setting, pages_unique_domain: 'some-unique-domain')
|
||||||
|
|
||||||
|
project = build(:project, path: 'some-unique-domain.another-example.com')
|
||||||
|
|
||||||
|
expect(project).to be_valid
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'accepts path when the domain does not match' do
|
||||||
|
create(:project_setting, pages_unique_domain: 'another-unique-domain')
|
||||||
|
|
||||||
|
project = build(:project, path: 'some-unique-domain.example.com')
|
||||||
|
|
||||||
|
expect(project).to be_valid
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'path is unchanged' do
|
context 'path is unchanged' do
|
||||||
let_it_be(:invalid_path_project) do
|
let_it_be(:invalid_path_project) do
|
||||||
project = create(:project, :repository, :public)
|
project = create(:project, :repository, :public)
|
||||||
|
@ -4825,6 +4856,33 @@ RSpec.describe Project, factory_default: :keep, feature_category: :projects do
|
||||||
project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
|
project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when validating if path already exist as pages unique domain' do
|
||||||
|
before do
|
||||||
|
stub_pages_setting(host: 'example.com')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'rejects paths that match pages unique domain' do
|
||||||
|
stub_pages_setting(host: 'example.com')
|
||||||
|
create(:project_setting, pages_unique_domain: 'some-unique-domain')
|
||||||
|
|
||||||
|
expect(project.update(path: 'some-unique-domain.example.com')).to eq(false)
|
||||||
|
expect(project.errors.full_messages_for(:path)).to match(['Path already in use'])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'accepts path when the host does not match' do
|
||||||
|
create(:project_setting, pages_unique_domain: 'some-unique-domain')
|
||||||
|
|
||||||
|
expect(project.update(path: 'some-unique-domain.another-example.com')).to eq(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'accepts path when the domain does not match' do
|
||||||
|
stub_pages_setting(host: 'example.com')
|
||||||
|
create(:project_setting, pages_unique_domain: 'another-unique-domain')
|
||||||
|
|
||||||
|
expect(project.update(path: 'some-unique-domain.example.com')).to eq(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it 'does not validate the visibility' do
|
it 'does not validate the visibility' do
|
||||||
expect(project).not_to receive(:visibility_level_allowed_as_fork).and_call_original
|
expect(project).not_to receive(:visibility_level_allowed_as_fork).and_call_original
|
||||||
expect(project).not_to receive(:visibility_level_allowed_by_group).and_call_original
|
expect(project).not_to receive(:visibility_level_allowed_by_group).and_call_original
|
||||||
|
|
|
@ -2,10 +2,13 @@
|
||||||
|
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe Ci::PipelineSchedulePolicy, :models, :clean_gitlab_redis_cache do
|
RSpec.describe Ci::PipelineSchedulePolicy, :models, :clean_gitlab_redis_cache, feature_category: :continuous_integration do
|
||||||
|
using RSpec::Parameterized::TableSyntax
|
||||||
|
|
||||||
let_it_be(:user) { create(:user) }
|
let_it_be(:user) { create(:user) }
|
||||||
let_it_be(:project) { create(:project, :repository) }
|
let_it_be_with_reload(:project) { create(:project, :repository, create_tag: tag_ref_name) }
|
||||||
let_it_be(:pipeline_schedule, reload: true) { create(:ci_pipeline_schedule, :nightly, project: project) }
|
let_it_be_with_reload(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly, project: project) }
|
||||||
|
let_it_be(:tag_ref_name) { "v1.0.0" }
|
||||||
|
|
||||||
let(:policy) do
|
let(:policy) do
|
||||||
described_class.new(user, pipeline_schedule)
|
described_class.new(user, pipeline_schedule)
|
||||||
|
@ -13,51 +16,143 @@ RSpec.describe Ci::PipelineSchedulePolicy, :models, :clean_gitlab_redis_cache do
|
||||||
|
|
||||||
describe 'rules' do
|
describe 'rules' do
|
||||||
describe 'rules for protected ref' do
|
describe 'rules for protected ref' do
|
||||||
|
context 'for branch' do
|
||||||
|
%w[refs/heads/master master].each do |branch_ref|
|
||||||
|
context "with #{branch_ref}" do
|
||||||
|
let_it_be(:branch_ref_name) { "master" }
|
||||||
|
let_it_be(:branch_pipeline_schedule) do
|
||||||
|
create(:ci_pipeline_schedule, :nightly, project: project, ref: branch_ref)
|
||||||
|
end
|
||||||
|
|
||||||
|
where(:push_access_level, :merge_access_level, :project_role, :accessible) do
|
||||||
|
:no_one_can_push | :no_one_can_merge | :owner | :be_disallowed
|
||||||
|
:no_one_can_push | :no_one_can_merge | :maintainer | :be_disallowed
|
||||||
|
:no_one_can_push | :no_one_can_merge | :developer | :be_disallowed
|
||||||
|
:no_one_can_push | :no_one_can_merge | :reporter | :be_disallowed
|
||||||
|
:no_one_can_push | :no_one_can_merge | :guest | :be_disallowed
|
||||||
|
|
||||||
|
:maintainers_can_push | :no_one_can_merge | :owner | :be_allowed
|
||||||
|
:maintainers_can_push | :no_one_can_merge | :maintainer | :be_allowed
|
||||||
|
:maintainers_can_push | :no_one_can_merge | :developer | :be_disallowed
|
||||||
|
:maintainers_can_push | :no_one_can_merge | :reporter | :be_disallowed
|
||||||
|
:maintainers_can_push | :no_one_can_merge | :guest | :be_disallowed
|
||||||
|
|
||||||
|
:developers_can_push | :no_one_can_merge | :owner | :be_allowed
|
||||||
|
:developers_can_push | :no_one_can_merge | :maintainer | :be_allowed
|
||||||
|
:developers_can_push | :no_one_can_merge | :developer | :be_allowed
|
||||||
|
:developers_can_push | :no_one_can_merge | :reporter | :be_disallowed
|
||||||
|
:developers_can_push | :no_one_can_merge | :guest | :be_disallowed
|
||||||
|
|
||||||
|
:no_one_can_push | :maintainers_can_merge | :owner | :be_allowed
|
||||||
|
:no_one_can_push | :maintainers_can_merge | :maintainer | :be_allowed
|
||||||
|
:no_one_can_push | :maintainers_can_merge | :developer | :be_disallowed
|
||||||
|
:no_one_can_push | :maintainers_can_merge | :reporter | :be_disallowed
|
||||||
|
:no_one_can_push | :maintainers_can_merge | :guest | :be_disallowed
|
||||||
|
|
||||||
|
:maintainers_can_push | :maintainers_can_merge | :owner | :be_allowed
|
||||||
|
:maintainers_can_push | :maintainers_can_merge | :maintainer | :be_allowed
|
||||||
|
:maintainers_can_push | :maintainers_can_merge | :developer | :be_disallowed
|
||||||
|
:maintainers_can_push | :maintainers_can_merge | :reporter | :be_disallowed
|
||||||
|
:maintainers_can_push | :maintainers_can_merge | :guest | :be_disallowed
|
||||||
|
|
||||||
|
:developers_can_push | :maintainers_can_merge | :owner | :be_allowed
|
||||||
|
:developers_can_push | :maintainers_can_merge | :maintainer | :be_allowed
|
||||||
|
:developers_can_push | :maintainers_can_merge | :developer | :be_allowed
|
||||||
|
:developers_can_push | :maintainers_can_merge | :reporter | :be_disallowed
|
||||||
|
:developers_can_push | :maintainers_can_merge | :guest | :be_disallowed
|
||||||
|
|
||||||
|
:no_one_can_push | :developers_can_merge | :owner | :be_allowed
|
||||||
|
:no_one_can_push | :developers_can_merge | :maintainer | :be_allowed
|
||||||
|
:no_one_can_push | :developers_can_merge | :developer | :be_allowed
|
||||||
|
:no_one_can_push | :developers_can_merge | :reporter | :be_disallowed
|
||||||
|
:no_one_can_push | :developers_can_merge | :guest | :be_disallowed
|
||||||
|
|
||||||
|
:maintainers_can_push | :developers_can_merge | :owner | :be_allowed
|
||||||
|
:maintainers_can_push | :developers_can_merge | :maintainer | :be_allowed
|
||||||
|
:maintainers_can_push | :developers_can_merge | :developer | :be_allowed
|
||||||
|
:maintainers_can_push | :developers_can_merge | :reporter | :be_disallowed
|
||||||
|
:maintainers_can_push | :developers_can_merge | :guest | :be_disallowed
|
||||||
|
|
||||||
|
:developers_can_push | :developers_can_merge | :owner | :be_allowed
|
||||||
|
:developers_can_push | :developers_can_merge | :maintainer | :be_allowed
|
||||||
|
:developers_can_push | :developers_can_merge | :developer | :be_allowed
|
||||||
|
:developers_can_push | :developers_can_merge | :reporter | :be_disallowed
|
||||||
|
:developers_can_push | :developers_can_merge | :guest | :be_disallowed
|
||||||
|
end
|
||||||
|
|
||||||
|
with_them do
|
||||||
before do
|
before do
|
||||||
project.add_developer(user)
|
create(:protected_branch, push_access_level, merge_access_level, name: branch_ref_name,
|
||||||
|
project: project)
|
||||||
|
project.add_role(user, project_role)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when no one can push or merge to the branch' do
|
context 'for create_pipeline_schedule' do
|
||||||
|
subject(:policy) { described_class.new(user, new_branch_pipeline_schedule) }
|
||||||
|
|
||||||
|
let(:new_branch_pipeline_schedule) { project.pipeline_schedules.new(ref: branch_ref) }
|
||||||
|
|
||||||
|
it { expect(policy).to try(accessible, :create_pipeline_schedule) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'for play_pipeline_schedule' do
|
||||||
|
subject(:policy) { described_class.new(user, branch_pipeline_schedule) }
|
||||||
|
|
||||||
|
it { expect(policy).to try(accessible, :play_pipeline_schedule) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'for tag' do
|
||||||
|
%w[refs/tags/v1.0.0 v1.0.0].each do |tag_ref|
|
||||||
|
context "with #{tag_ref}" do
|
||||||
|
let_it_be(:tag_pipeline_schedule) do
|
||||||
|
create(:ci_pipeline_schedule, :nightly, project: project, ref: tag_ref)
|
||||||
|
end
|
||||||
|
|
||||||
|
where(:access_level, :project_role, :accessible) do
|
||||||
|
:no_one_can_create | :owner | :be_disallowed
|
||||||
|
:no_one_can_create | :maintainer | :be_disallowed
|
||||||
|
:no_one_can_create | :developer | :be_disallowed
|
||||||
|
:no_one_can_create | :reporter | :be_disallowed
|
||||||
|
:no_one_can_create | :guest | :be_disallowed
|
||||||
|
|
||||||
|
:maintainers_can_create | :owner | :be_allowed
|
||||||
|
:maintainers_can_create | :maintainer | :be_allowed
|
||||||
|
:maintainers_can_create | :developer | :be_disallowed
|
||||||
|
:maintainers_can_create | :reporter | :be_disallowed
|
||||||
|
:maintainers_can_create | :guest | :be_disallowed
|
||||||
|
|
||||||
|
:developers_can_create | :owner | :be_allowed
|
||||||
|
:developers_can_create | :maintainer | :be_allowed
|
||||||
|
:developers_can_create | :developer | :be_allowed
|
||||||
|
:developers_can_create | :reporter | :be_disallowed
|
||||||
|
:developers_can_create | :guest | :be_disallowed
|
||||||
|
end
|
||||||
|
|
||||||
|
with_them do
|
||||||
before do
|
before do
|
||||||
create(:protected_branch, :no_one_can_push, name: pipeline_schedule.ref, project: project)
|
create(:protected_tag, access_level, name: tag_ref_name, project: project)
|
||||||
|
project.add_role(user, project_role)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not include ability to play pipeline schedule' do
|
context 'for create_pipeline_schedule' do
|
||||||
expect(policy).to be_disallowed :play_pipeline_schedule
|
subject(:policy) { described_class.new(user, new_tag_pipeline_schedule) }
|
||||||
end
|
|
||||||
|
let(:new_tag_pipeline_schedule) { project.pipeline_schedules.new(ref: tag_ref) }
|
||||||
|
|
||||||
|
it { expect(policy).to try(accessible, :create_pipeline_schedule) }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when developers can push to the branch' do
|
context 'for play_pipeline_schedule' do
|
||||||
before do
|
subject(:policy) { described_class.new(user, tag_pipeline_schedule) }
|
||||||
create(:protected_branch, :developers_can_merge, name: pipeline_schedule.ref, project: project)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'includes ability to update pipeline' do
|
it { expect(policy).to try(accessible, :play_pipeline_schedule) }
|
||||||
expect(policy).to be_allowed :play_pipeline_schedule
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when no one can create the tag' do
|
|
||||||
let(:tag) { 'v1.0.0' }
|
|
||||||
|
|
||||||
before do
|
|
||||||
pipeline_schedule.update!(ref: tag)
|
|
||||||
|
|
||||||
create(:protected_tag, :no_one_can_create, name: pipeline_schedule.ref, project: project)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not include ability to play pipeline schedule' do
|
|
||||||
expect(policy).to be_disallowed :play_pipeline_schedule
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when no one can create the tag but it is not a tag' do
|
|
||||||
before do
|
|
||||||
create(:protected_tag, :no_one_can_create, name: pipeline_schedule.ref, project: project)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'includes ability to play pipeline schedule' do
|
|
||||||
expect(policy).to be_allowed :play_pipeline_schedule
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -50,6 +50,17 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'authenticates using a jwt token with an IAT from 10 seconds in the future' do
|
||||||
|
headers =
|
||||||
|
travel_to(Time.now + 10.seconds) do
|
||||||
|
gitlab_shell_internal_api_request_header
|
||||||
|
end
|
||||||
|
|
||||||
|
perform_request(headers: headers)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
end
|
||||||
|
|
||||||
it 'returns 401 when jwt token is expired' do
|
it 'returns 401 when jwt token is expired' do
|
||||||
headers = gitlab_shell_internal_api_request_header
|
headers = gitlab_shell_internal_api_request_header
|
||||||
|
|
||||||
|
|
|
@ -43,13 +43,21 @@ RSpec.describe BulkImports::ArchiveExtractionService, feature_category: :importe
|
||||||
|
|
||||||
context 'when archive file is a symlink' do
|
context 'when archive file is a symlink' do
|
||||||
it 'raises an error' do
|
it 'raises an error' do
|
||||||
FileUtils.ln_s(File.join(tmpdir, filename), File.join(tmpdir, 'symlink'))
|
FileUtils.ln_s(filepath, File.join(tmpdir, 'symlink'))
|
||||||
|
|
||||||
expect { described_class.new(tmpdir: tmpdir, filename: 'symlink').execute }
|
expect { described_class.new(tmpdir: tmpdir, filename: 'symlink').execute }
|
||||||
.to raise_error(BulkImports::Error, 'Invalid file')
|
.to raise_error(BulkImports::Error, 'Invalid file')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when archive file shares multiple hard links' do
|
||||||
|
it 'raises an error' do
|
||||||
|
FileUtils.link(filepath, File.join(tmpdir, 'hard_link'))
|
||||||
|
|
||||||
|
expect { subject.execute }.to raise_error(BulkImports::Error, 'Invalid file')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when filepath is being traversed' do
|
context 'when filepath is being traversed' do
|
||||||
it 'raises an error' do
|
it 'raises an error' do
|
||||||
expect { described_class.new(tmpdir: File.join(Dir.mktmpdir, 'test', '..'), filename: 'name').execute }
|
expect { described_class.new(tmpdir: File.join(Dir.mktmpdir, 'test', '..'), filename: 'name').execute }
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe BulkImports::FileDecompressionService, feature_category: :importers do
|
RSpec.describe BulkImports::FileDecompressionService, feature_category: :importers do
|
||||||
|
using RSpec::Parameterized::TableSyntax
|
||||||
|
|
||||||
let_it_be(:tmpdir) { Dir.mktmpdir }
|
let_it_be(:tmpdir) { Dir.mktmpdir }
|
||||||
let_it_be(:ndjson_filename) { 'labels.ndjson' }
|
let_it_be(:ndjson_filename) { 'labels.ndjson' }
|
||||||
let_it_be(:ndjson_filepath) { File.join(tmpdir, ndjson_filename) }
|
let_it_be(:ndjson_filepath) { File.join(tmpdir, ndjson_filename) }
|
||||||
|
@ -70,39 +72,68 @@ RSpec.describe BulkImports::FileDecompressionService, feature_category: :importe
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when compressed file is a symlink' do
|
shared_examples 'raises an error and removes the file' do |error_message:|
|
||||||
let_it_be(:symlink) { File.join(tmpdir, 'symlink.gz') }
|
specify do
|
||||||
|
|
||||||
before do
|
|
||||||
FileUtils.ln_s(File.join(tmpdir, gz_filename), symlink)
|
|
||||||
end
|
|
||||||
|
|
||||||
subject { described_class.new(tmpdir: tmpdir, filename: 'symlink.gz') }
|
|
||||||
|
|
||||||
it 'raises an error and removes the file' do
|
|
||||||
expect { subject.execute }
|
expect { subject.execute }
|
||||||
.to raise_error(BulkImports::FileDecompressionService::ServiceError, 'File decompression error')
|
.to raise_error(BulkImports::FileDecompressionService::ServiceError, error_message)
|
||||||
|
expect(File).not_to exist(file)
|
||||||
expect(File.exist?(symlink)).to eq(false)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when decompressed file is a symlink' do
|
shared_context 'when compressed file' do
|
||||||
let_it_be(:symlink) { File.join(tmpdir, 'symlink') }
|
let_it_be(:file) { File.join(tmpdir, 'file.gz') }
|
||||||
|
|
||||||
|
subject { described_class.new(tmpdir: tmpdir, filename: 'file.gz') }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
FileUtils.ln_s(File.join(tmpdir, ndjson_filename), symlink)
|
FileUtils.send(link_method, File.join(tmpdir, gz_filename), file)
|
||||||
|
|
||||||
subject.instance_variable_set(:@decompressed_filepath, symlink)
|
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_context 'when decompressed file' do
|
||||||
|
let_it_be(:file) { File.join(tmpdir, 'file.txt') }
|
||||||
|
|
||||||
subject { described_class.new(tmpdir: tmpdir, filename: gz_filename) }
|
subject { described_class.new(tmpdir: tmpdir, filename: gz_filename) }
|
||||||
|
|
||||||
it 'raises an error and removes the file' do
|
before do
|
||||||
expect { subject.execute }.to raise_error(described_class::ServiceError, 'Invalid file')
|
original_file = File.join(tmpdir, 'original_file.txt')
|
||||||
|
FileUtils.touch(original_file)
|
||||||
|
FileUtils.send(link_method, original_file, file)
|
||||||
|
|
||||||
expect(File.exist?(symlink)).to eq(false)
|
subject.instance_variable_set(:@decompressed_filepath, file)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when compressed file is a symlink' do
|
||||||
|
let(:link_method) { :symlink }
|
||||||
|
|
||||||
|
include_context 'when compressed file'
|
||||||
|
|
||||||
|
include_examples 'raises an error and removes the file', error_message: 'File decompression error'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when compressed file shares multiple hard links' do
|
||||||
|
let(:link_method) { :link }
|
||||||
|
|
||||||
|
include_context 'when compressed file'
|
||||||
|
|
||||||
|
include_examples 'raises an error and removes the file', error_message: 'File decompression error'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when decompressed file is a symlink' do
|
||||||
|
let(:link_method) { :symlink }
|
||||||
|
|
||||||
|
include_context 'when decompressed file'
|
||||||
|
|
||||||
|
include_examples 'raises an error and removes the file', error_message: 'Invalid file'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when decompressed file shares multiple hard links' do
|
||||||
|
let(:link_method) { :link }
|
||||||
|
|
||||||
|
include_context 'when decompressed file'
|
||||||
|
|
||||||
|
include_examples 'raises an error and removes the file', error_message: 'Invalid file'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,7 +10,7 @@ RSpec.describe BulkImports::FileDownloadService, feature_category: :importers do
|
||||||
let_it_be(:content_type) { 'application/octet-stream' }
|
let_it_be(:content_type) { 'application/octet-stream' }
|
||||||
let_it_be(:content_disposition) { nil }
|
let_it_be(:content_disposition) { nil }
|
||||||
let_it_be(:filename) { 'file_download_service_spec' }
|
let_it_be(:filename) { 'file_download_service_spec' }
|
||||||
let_it_be(:tmpdir) { Dir.tmpdir }
|
let_it_be(:tmpdir) { Dir.mktmpdir }
|
||||||
let_it_be(:filepath) { File.join(tmpdir, filename) }
|
let_it_be(:filepath) { File.join(tmpdir, filename) }
|
||||||
let_it_be(:content_length) { 1000 }
|
let_it_be(:content_length) { 1000 }
|
||||||
|
|
||||||
|
@ -247,6 +247,36 @@ RSpec.describe BulkImports::FileDownloadService, feature_category: :importers do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when file shares multiple hard links' do
|
||||||
|
let_it_be(:hard_link) { File.join(tmpdir, 'hard_link') }
|
||||||
|
|
||||||
|
before do
|
||||||
|
existing_file = File.join(Dir.mktmpdir, filename)
|
||||||
|
FileUtils.touch(existing_file)
|
||||||
|
FileUtils.link(existing_file, hard_link)
|
||||||
|
end
|
||||||
|
|
||||||
|
subject do
|
||||||
|
described_class.new(
|
||||||
|
configuration: config,
|
||||||
|
relative_url: '/test',
|
||||||
|
tmpdir: tmpdir,
|
||||||
|
filename: 'hard_link',
|
||||||
|
file_size_limit: file_size_limit,
|
||||||
|
allowed_content_types: allowed_content_types
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises an error and removes the file' do
|
||||||
|
expect { subject.execute }.to raise_error(
|
||||||
|
described_class::ServiceError,
|
||||||
|
'Invalid downloaded file'
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(File.exist?(hard_link)).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when dir is not in tmpdir' do
|
context 'when dir is not in tmpdir' do
|
||||||
subject do
|
subject do
|
||||||
described_class.new(
|
described_class.new(
|
||||||
|
|
|
@ -6,7 +6,9 @@ RSpec.describe Ci::PipelineSchedules::UpdateService, feature_category: :continuo
|
||||||
let_it_be(:user) { create(:user) }
|
let_it_be(:user) { create(:user) }
|
||||||
let_it_be(:reporter) { create(:user) }
|
let_it_be(:reporter) { create(:user) }
|
||||||
let_it_be(:project) { create(:project, :public, :repository) }
|
let_it_be(:project) { create(:project, :public, :repository) }
|
||||||
let_it_be(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: user) }
|
let_it_be(:pipeline_schedule) do
|
||||||
|
create(:ci_pipeline_schedule, project: project, owner: user, ref: 'master')
|
||||||
|
end
|
||||||
|
|
||||||
before_all do
|
before_all do
|
||||||
project.add_maintainer(user)
|
project.add_maintainer(user)
|
||||||
|
|
|
@ -1134,10 +1134,10 @@
|
||||||
resolved "https://registry.yarnpkg.com/@gitlab/visual-review-tools/-/visual-review-tools-1.7.3.tgz#9ea641146436da388ffbad25d7f2abe0df52c235"
|
resolved "https://registry.yarnpkg.com/@gitlab/visual-review-tools/-/visual-review-tools-1.7.3.tgz#9ea641146436da388ffbad25d7f2abe0df52c235"
|
||||||
integrity sha512-NMV++7Ew1FSBDN1xiZaauU9tfeSfgDHcOLpn+8bGpP+O5orUPm2Eu66R5eC5gkjBPaXosNAxNWtriee+aFk4+g==
|
integrity sha512-NMV++7Ew1FSBDN1xiZaauU9tfeSfgDHcOLpn+8bGpP+O5orUPm2Eu66R5eC5gkjBPaXosNAxNWtriee+aFk4+g==
|
||||||
|
|
||||||
"@gitlab/web-ide@0.0.1-dev-20230511143809":
|
"@gitlab/web-ide@0.0.1-dev-20230713160749-patch-1":
|
||||||
version "0.0.1-dev-20230511143809"
|
version "0.0.1-dev-20230713160749-patch-1"
|
||||||
resolved "https://registry.yarnpkg.com/@gitlab/web-ide/-/web-ide-0.0.1-dev-20230511143809.tgz#c13dfb4d1edab2e020d4a102d4ec18048917490f"
|
resolved "https://registry.yarnpkg.com/@gitlab/web-ide/-/web-ide-0.0.1-dev-20230713160749-patch-1.tgz#6420b55aae444533f9a4bd6269503d98a72aaa2e"
|
||||||
integrity sha512-caP5WSaTuIhPrPGUWyvPT4np6swkKQHM1Pa9HiBnGhiOhhQ1+3X/+J9EoZXUhnhwiBzS7sp32Uyttam4am/sTA==
|
integrity sha512-Dh8XQyPwDY6fkd/A+hTHCqrD23u5qnlaxKu5myyxDEgBNGgu4SGblFU9B6NHNm8eGUZk6Cs5MuMk+NUvWRKbmA==
|
||||||
|
|
||||||
"@graphql-eslint/eslint-plugin@3.18.0":
|
"@graphql-eslint/eslint-plugin@3.18.0":
|
||||||
version "3.18.0"
|
version "3.18.0"
|
||||||
|
|
Loading…
Reference in a new issue