Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-08-01 21:10:20 +00:00
parent 9cf7b70ac7
commit cc77bdd6f5
112 changed files with 1731 additions and 331 deletions

View File

@ -5068,7 +5068,6 @@ RSpec/MissingFeatureCategory:
- 'spec/policies/ci/bridge_policy_spec.rb'
- 'spec/policies/ci/build_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/clusters/agent_policy_spec.rb'
- 'spec/policies/clusters/agent_token_policy_spec.rb'

View File

@ -2,6 +2,37 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 16.2.2 (2023-08-01)
### Added (1 change)
- [Add MR reviewers to BitBucketServer import to 16-2](gitlab-org/security/gitlab@aeb33292029aae649352dea089d9e86933e01a80)
### Fixed (2 changes)
- [Disable IAT verification by default](gitlab-org/security/gitlab@b3a0c02721101596c644443d412ca13e8f4ce000)
- [Enable descendant_security_scans by default](gitlab-org/security/gitlab@66eaaabed118b3b4b75fca17ef13e56b64e4eb4b) **GitLab Enterprise Edition**
### Security (17 changes)
- [Fix undefined method licenses for nil:NilClass bug](gitlab-org/security/gitlab@aa4c4dc26a239e7799f9e9aa14d893c7a696d112) ([merge request](gitlab-org/security/gitlab!3471))
- [Fix undefined method page error in list dependencies](gitlab-org/security/gitlab@08acd6aa91d34de2403e1d2a28b437c58af107c1) ([merge request](gitlab-org/security/gitlab!3470))
- [Add pagination for license scanning](gitlab-org/security/gitlab@b58ed3a7c40dd08ab0fe48c0cc4386e1cb7fa48a) ([merge request](gitlab-org/security/gitlab!3467))
- [Prevent leaking emails of newly created users](gitlab-org/security/gitlab@25d75bb2494dffb7e2b55f3b9d190a7302461fe1) ([merge request](gitlab-org/security/gitlab!3449))
- [Added redirect to filtered params](gitlab-org/security/gitlab@a72a1d48e871716ebeae3a4082078d4626cab8a0) ([merge request](gitlab-org/security/gitlab!3441))
- [Relocate PlantUML config and disable SVG support](gitlab-org/security/gitlab@6aac3a3e7223cbb85a62d6e95cf096e8a582cfcf) ([merge request](gitlab-org/security/gitlab!3438))
- [Sanitize multiple hardlinks from import archives](gitlab-org/security/gitlab@286c5b4e79f1a94554ee20b2535376d7c1c329a8) ([merge request](gitlab-org/security/gitlab!3435))
- [Validates project path availability](gitlab-org/security/gitlab@d970c230a0dc1113de469e4636bac020fa7cdeac) ([merge request](gitlab-org/security/gitlab!3426))
- [Fix policy project assign](gitlab-org/security/gitlab@5564547ac37f5f80c58f444778bdaf3e3a491ff7) ([merge request](gitlab-org/security/gitlab!3423))
- [Fix bug where comments on files with incorrect sha breaks UI](gitlab-org/security/gitlab@da3ff8cc07ec7449f8bf1092ad1219fc2debbe53) ([merge request](gitlab-org/security/gitlab!3446))
- [Fix pipeline schedule authorization for protected branch/tag](gitlab-org/security/gitlab@0d9a0ab46faa06cc33144096382f1375f95e91bd) ([merge request](gitlab-org/security/gitlab!3413))
- [Mitigate autolink filter ReDOS](gitlab-org/security/gitlab@f8d9f5fa2617f1e372c6e477bc3840fd2161d7a7) ([merge request](gitlab-org/security/gitlab!3434))
- [Fix XSS vector in Web IDE](gitlab-org/security/gitlab@530b97903782b2108284d47e33421e21f576ec8e) ([merge request](gitlab-org/security/gitlab!3409))
- [Mitigate project reference filter ReDOS](gitlab-org/security/gitlab@93f7983d73955244994a131f7093ab8699347eb6) ([merge request](gitlab-org/security/gitlab!3431))
- [Add a stricter regex for the Harbor search param](gitlab-org/security/gitlab@988280e72ac7570825e7451c528b718d38be681c) ([merge request](gitlab-org/security/gitlab!3408))
- [Update pipeline user to the last policy MR author](gitlab-org/security/gitlab@85f3797d29aa957df88b4f8eaf47ec102021dd1a) ([merge request](gitlab-org/security/gitlab!3421))
- [Prohibit 40 character hex plus a hyphen if branch name is path](gitlab-org/security/gitlab@14fabf71a196dcfa6af566084f0b5266be4bfe2d) ([merge request](gitlab-org/security/gitlab!3405))
## 16.2.1 (2023-07-25)
### Fixed (1 change)
@ -725,6 +756,34 @@ entry.
- [Add schema_version in the commits index mapping](gitlab-org/gitlab@e75b94903b69e1e1588e251217926882875555a8) ([merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123435)) **GitLab Enterprise Edition**
- [Allow to set labels for Redis calls](gitlab-org/gitlab@8ccfff9e2d250eb22afaa7d0243e707b536a5436) ([merge request](gitlab-org/gitlab!122340))
## 16.1.3 (2023-08-01)
### Added (1 change)
- [Add MR reviewers to BitBucketServer import 16-1](gitlab-org/security/gitlab@809b12fa28898efaccece784646092473e9dfb5c)
### Fixed (2 changes)
- [Disable IAT verification by default](gitlab-org/security/gitlab@a39f14f42e1ae503b3ade9333e2bbac7dff514a8)
- [Fix FOUC when new sidebar enabled](gitlab-org/security/gitlab@0bf00a542eb6d83b074ce730dbd843913a1fd202)
### Security (14 changes)
- [Prevent leaking emails of newly created users](gitlab-org/security/gitlab@09a942563ef87dc1f2173564459c2663d69ed890) ([merge request](gitlab-org/security/gitlab!3450))
- [Added redirect to filtered params](gitlab-org/security/gitlab@b3c74cec27cba2f65193d95cd6d4cf574e879e73) ([merge request](gitlab-org/security/gitlab!3442))
- [Relocate PlantUML config and disable SVG support](gitlab-org/security/gitlab@dc66dcbb79a59b9f9669aeb72b7bb0df945f9b5e) ([merge request](gitlab-org/security/gitlab!3439))
- [Sanitize multiple hardlinks from import archives](gitlab-org/security/gitlab@b6fd6a45d0f7352c7c5834b0948256168d41fdf1) ([merge request](gitlab-org/security/gitlab!3436))
- [Validates project path availability](gitlab-org/security/gitlab@33a10c4089b2fb582295378d294914eb35dd6e8f) ([merge request](gitlab-org/security/gitlab!3427))
- [Fix policy project assign](gitlab-org/security/gitlab@a24fefa04d87ad60c042b3df0e47ad0f1cd52ce2) ([merge request](gitlab-org/security/gitlab!3424))
- [Fix bug where comments on files with incorrect sha breaks UI](gitlab-org/security/gitlab@eae7051f11f84ed2cc952253aff1d9fe0c1d5ff2) ([merge request](gitlab-org/security/gitlab!3447))
- [Fix pipeline schedule authorization for protected branch/tag](gitlab-org/security/gitlab@2445a07db2cd5a135da26f09e1abfd047c38f4c8) ([merge request](gitlab-org/security/gitlab!3364))
- [Mitigate autolink filter ReDOS](gitlab-org/security/gitlab@db8358c81b7c801d43bd84edca11d244c8bcdd69) ([merge request](gitlab-org/security/gitlab!3433))
- [Fix XSS vector in Web IDE](gitlab-org/security/gitlab@77fde66185001cb1cf510eb17d027f37c969d21e) ([merge request](gitlab-org/security/gitlab!3410))
- [Mitigate project reference filter ReDOS](gitlab-org/security/gitlab@fafb649e957c37c065420dbb8e577fd4ed24b6c9) ([merge request](gitlab-org/security/gitlab!3430))
- [Add a stricter regex for the Harbor search param](gitlab-org/security/gitlab@0cf36d19bfd32d691ae1e60276ee3ac24c626c6b) ([merge request](gitlab-org/security/gitlab!3395))
- [Update pipeline user to the last policy MR author](gitlab-org/security/gitlab@8b55561d397f848cb879903b47e47bded7af0a75) ([merge request](gitlab-org/security/gitlab!3392))
- [Prohibit 40 character hex plus a hyphen if branch name is path](gitlab-org/security/gitlab@f2bcf18740a4398eebe2a1373b578bbe9f533f44) ([merge request](gitlab-org/security/gitlab!3407))
## 16.1.2 (2023-07-04)
### Fixed (4 changes)
@ -1683,6 +1742,28 @@ entry.
- [Migrate custom CSS to utility classes](gitlab-org/gitlab@a67999317bec111d523c763fc865665d4ded0aaf) ([merge request](gitlab-org/gitlab!120745)) **GitLab Enterprise Edition**
- [Remove the vsa_group_and_project_parity FF](gitlab-org/gitlab@d090818bdedb0e220928d8e456cf36c8bce81f42) ([merge request](gitlab-org/gitlab!120727)) **GitLab Enterprise Edition**
## 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)
### Security (1 change)

View File

@ -167,7 +167,7 @@ export default {
originalStartLineCode,
...(discussion.line_codes || []),
];
const fileHash = discussion.diff_file.file_hash;
const fileHash = discussion.diff_file?.file_hash;
const lineCheck = (line) =>
discussionLineCodes.some(
(discussionLineCode) =>

View File

@ -77,7 +77,7 @@ export function prepareRawDiffFile({ file, allFiles, meta = false, index = -1 })
}
export function collapsedType(file) {
const isManual = typeof file.viewer?.manuallyCollapsed === 'boolean';
const isManual = typeof file?.viewer?.manuallyCollapsed === 'boolean';
return isManual ? DIFF_FILE_MANUAL_COLLAPSE : DIFF_FILE_AUTOMATIC_COLLAPSE;
}
@ -85,8 +85,8 @@ export function collapsedType(file) {
export function isCollapsed(file) {
const type = collapsedType(file);
const collapsedStates = {
[DIFF_FILE_AUTOMATIC_COLLAPSE]: file.viewer?.automaticallyCollapsed || false,
[DIFF_FILE_MANUAL_COLLAPSE]: file.viewer?.manuallyCollapsed,
[DIFF_FILE_AUTOMATIC_COLLAPSE]: file?.viewer?.automaticallyCollapsed || false,
[DIFF_FILE_MANUAL_COLLAPSE]: file?.viewer?.manuallyCollapsed,
};
return collapsedStates[type];

View File

@ -41,7 +41,7 @@ export default {
return getDiffMode(this.discussion.diff_file);
},
diffViewerMode() {
return this.discussion.diff_file.viewer.name;
return this.discussion.diff_file?.viewer.name;
},
fileDiffRefs() {
return this.discussion.diff_file.diff_refs;
@ -96,6 +96,7 @@ export default {
<template>
<div :class="{ 'text-file': isTextFile }" class="diff-file file-holder">
<diff-file-header
v-if="discussion.diff_file"
:discussion-path="discussion.discussion_path"
:diff-file="discussion.diff_file"
:can-current-user-fork="false"

View File

@ -169,7 +169,7 @@ export default {
return !this.discussionResolved ? this.discussion.resolve_with_issue_path : '';
},
canShowReplyActions() {
if (this.shouldRenderDiffs && !this.discussion.diff_file.diff_refs) {
if (this.shouldRenderDiffs && !this.discussion.diff_file?.diff_refs) {
return false;
}

View File

@ -21,7 +21,6 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
end
def new
@schedule = project.pipeline_schedules.new
end
def create
@ -101,6 +100,15 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
variables_attributes: [:id, :variable_type, :key, :secret_value, :_destroy])
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!
return access_denied! unless can?(current_user, :play_pipeline_schedule, schedule)
end

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
module WorkItems
class NamespaceWorkItemsFinder < WorkItemsFinder
def execute
items = init_collection
sort(items)
end
override :with_confidentiality_access_check
def with_confidentiality_access_check
return klass.none unless parent && current_user&.can?("read_#{parent.to_ability_name}".to_sym, parent)
return model_class.all if params.user_can_see_all_issuables?
# Only admins can see hidden issues, so for non-admins, we filter out any hidden issues
issues = model_class.without_hidden.in_namespaces(parent)
return issues.all if params.user_can_see_all_confidential_issues?
return issues.public_only if params.user_cannot_see_confidential_issues?
issues.with_confidentiality_check(current_user)
end
end
end

View File

@ -0,0 +1,57 @@
# frozen_string_literal: true
module WorkItems
module LookAheadPreloads
extend ActiveSupport::Concern
prepended do
include ::LooksAhead
end
private
def preloads
{
work_item_type: :work_item_type,
web_url: { namespace: :route, project: [:project_namespace, { namespace: :route }] },
widgets: { work_item_type: :enabled_widget_definitions }
}
end
def nested_preloads
{
widgets: widget_preloads,
user_permissions: { update_work_item: :assignees },
project: { jira_import_status: { project: :jira_imports } },
author: {
location: { author: :user_detail },
gitpod_enabled: { author: :user_preference }
}
}
end
def widget_preloads
{
last_edited_by: :last_edited_by,
assignees: :assignees,
parent: :work_item_parent,
children: { work_item_children_by_relative_position: [:author, { project: :project_feature }] },
labels: :labels,
milestone: { milestone: [:project, :group] },
subscribed: [:assignees, :award_emoji, { notes: [:author, :award_emoji] }],
award_emoji: { award_emoji: :awardable }
}
end
def unconditional_includes
[
{
project: [:project_feature, :group]
},
:author
]
end
end
end
WorkItems::LookAheadPreloads.prepend_mod

View File

@ -0,0 +1,32 @@
# frozen_string_literal: true
module Resolvers
module Namespaces
class WorkItemsResolver < BaseResolver
prepend ::WorkItems::LookAheadPreloads
type Types::WorkItemType.connection_type, null: true
def resolve_with_lookahead(**args)
return unless Feature.enabled?(:namespace_level_work_items)
return WorkItem.none if resource_parent.nil?
finder = ::WorkItems::NamespaceWorkItemsFinder.new(current_user, args)
Gitlab::Graphql::Loaders::IssuableLoader.new(resource_parent, finder).batching_find_all do |q|
apply_lookahead(q)
end
end
private
def resource_parent
# The project could have been loaded in batch by `BatchLoader`.
# At this point we need the `id` of the project to query for work items, so
# make sure it's loaded and not `nil` before continuing.
object.respond_to?(:sync) ? object.sync : object
end
strong_memoize_attr :resource_parent
end
end
end

View File

@ -2,8 +2,8 @@
module Resolvers
class WorkItemsResolver < BaseResolver
prepend ::WorkItems::LookAheadPreloads
include SearchArguments
include LooksAhead
include ::WorkItems::SharedFilterArguments
argument :iid,
@ -28,48 +28,6 @@ module Resolvers
private
def preloads
{
work_item_type: :work_item_type,
web_url: { namespace: :route, project: [:project_namespace, { namespace: :route }] },
widgets: { work_item_type: :enabled_widget_definitions }
}
end
def nested_preloads
{
widgets: widget_preloads,
user_permissions: { update_work_item: :assignees },
project: { jira_import_status: { project: :jira_imports } },
author: {
location: { author: :user_detail },
gitpod_enabled: { author: :user_preference }
}
}
end
def widget_preloads
{
last_edited_by: :last_edited_by,
assignees: :assignees,
parent: :work_item_parent,
children: { work_item_children_by_relative_position: [:author, { project: :project_feature }] },
labels: :labels,
milestone: { milestone: [:project, :group] },
subscribed: [:assignees, :award_emoji, { notes: [:author, :award_emoji] }],
award_emoji: { award_emoji: :awardable }
}
end
def unconditional_includes
[
{
project: [:project_feature, :group]
},
:author
]
end
def prepare_finder_params(args)
params = super(args)
params[:iids] ||= [params.delete(:iid)].compact if params[:iid]
@ -88,4 +46,4 @@ module Resolvers
end
end
Resolvers::WorkItemsResolver.prepend_mod_with('Resolvers::WorkItemsResolver')
Resolvers::WorkItemsResolver.prepend_mod

View File

@ -262,6 +262,12 @@ module Types
resolver: Resolvers::DataTransfer::GroupDataTransferResolver,
description: 'Data transfer data point for a specific period. This is mocked data under a development feature flag.'
field :work_items,
null: true,
description: 'Work items that belong to the namespace.',
alpha: { milestone: '16.3' },
resolver: ::Resolvers::Namespaces::WorkItemsResolver
def label(title:)
BatchLoader::GraphQL.for(title).batch(key: group) do |titles, loader, args|
LabelsFinder

View File

@ -52,7 +52,9 @@ module Milestoneable
def milestone_available?
return true if milestone_id.blank?
project_id == milestone&.project_id || project.ancestors_upto.compact.include?(milestone&.group)
(project_id.present? && project_id == milestone&.project_id) ||
try(:namespace)&.self_and_ancestors&.include?(milestone&.group) ||
project&.ancestors_upto&.compact&.include?(milestone&.group)
end
##

View File

@ -585,6 +585,8 @@ class Project < ApplicationRecord
validates :max_artifacts_size, numericality: { only_integer: true, greater_than: 0, allow_nil: true }
validates :suggestion_commit_message, length: { maximum: MAX_SUGGESTIONS_TEMPLATE_LENGTH }
validate :path_availability, if: :path_changed?
# Scopes
scope :pending_delete, -> { where(pending_delete: true) }
scope :without_deleted, -> { where(pending_delete: false) }
@ -3230,6 +3232,15 @@ class Project < ApplicationRecord
group.crm_enabled?
end
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
# overridden in EE

View File

@ -59,6 +59,8 @@ class ProjectSetting < ApplicationRecord
validate :validates_mr_default_target_self
validate :pages_unique_domain_availability, if: :pages_unique_domain_changed?
attribute :legacy_open_source_license_available, default: -> do
Feature.enabled?(:legacy_open_source_license_available, type: :ops)
end
@ -114,6 +116,15 @@ class ProjectSetting < ApplicationRecord
pages_unique_domain_enabled ||
pages_unique_domain_in_database.present?
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
ProjectSetting.prepend_mod

View File

@ -22,6 +22,18 @@ class WorkItem < Issue
foreign_key: :work_item_id, source: :work_item
scope :inc_relations_for_permission_check, -> { includes(:author, project: :project_feature) }
scope :in_namespaces, ->(namespaces) { where(namespace: namespaces) }
scope :with_confidentiality_check, ->(user) {
confidential_query = <<~SQL
issues.confidential = FALSE
OR (issues.confidential = TRUE
AND (issues.author_id = :user_id
OR EXISTS (SELECT TRUE FROM issue_assignees WHERE user_id = :user_id AND issue_id = issues.id)))
SQL
where(confidential_query, user_id: user.id)
}
class << self
def assignee_association_name

View File

@ -5,7 +5,18 @@ module Ci
alias_method :pipeline_schedule, :subject
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
condition(:owner_of_schedule) do
@ -31,6 +42,15 @@ module Ci
enable :take_ownership_pipeline_schedule
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

View File

@ -148,6 +148,7 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
enable :read_group_member
enable :read_custom_emoji
enable :read_counts
enable :read_issue
end
rule { achievements_enabled }.policy do

View File

@ -49,11 +49,7 @@ module BulkImports
end
def validate_symlink
raise(BulkImports::Error, 'Invalid file') if symlink?(filepath)
end
def symlink?(filepath)
File.lstat(filepath).symlink?
raise(BulkImports::Error, 'Invalid file') if Gitlab::Utils::FileInfo.linked?(filepath)
end
def extract_archive

View File

@ -53,7 +53,7 @@ module BulkImports
end
def validate_symlink(filepath)
raise(ServiceError, 'Invalid file') if File.lstat(filepath).symlink?
raise(ServiceError, 'Invalid file') if Gitlab::Utils::FileInfo.linked?(filepath)
end
def decompress_file

View File

@ -26,7 +26,7 @@ module Discussions
active_diff_discussions = merge_request.notes.new_diff_notes.discussions.select do |discussion|
discussion.active?(merge_request.diff_refs)
end
paths = active_diff_discussions.flat_map { |n| n.diff_file.paths }
paths = active_diff_discussions.flat_map { |n| n.diff_file&.paths }
[active_diff_discussions, paths]
end

View File

@ -186,6 +186,7 @@ module Gitlab
# - Any parameter containing `password`
# - Any parameter containing `secret`
# - Any parameter ending with `key`
# - Any parameter named `redirect`, filtered for security concerns of exposing sensitive information
# - Two-factor tokens (:otp_attempt)
# - Repo/Project Import URLs (:import_url)
# - Build traces (:trace)
@ -228,6 +229,7 @@ module Gitlab
variables
content
sharedSecret
redirect
)
# This config option can be removed after Rails 7.1 by https://gitlab.com/gitlab-org/gitlab/-/issues/416270

View File

@ -0,0 +1,8 @@
---
name: use_primary_and_secondary_stores_for_etag_cache
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/127705
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/419889
milestone: '16.3'
type: development
group: group::scalability
default_enabled: false

View File

@ -0,0 +1,8 @@
---
name: use_primary_store_as_default_for_etag_cache
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/127705
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/419889
milestone: '16.3'
type: development
group: group::scalability
default_enabled: false

View File

@ -136,7 +136,7 @@ To use instance-level or group-level default settings for a project integration:
1. Select **Settings > Integrations**.
1. Select an integration.
1. On the right, from the dropdown list, select **Use default settings**.
1. In **Enable integration**, ensure the **Active** checkbox is selected.
1. Under **Enable integration**, ensure the **Active** checkbox is selected.
1. Complete the fields.
1. Select **Save changes**.
@ -148,6 +148,6 @@ To use custom settings for a project or group integration:
1. Select **Settings > Integrations**.
1. Select an integration.
1. On the right, from the dropdown list, select **Use custom settings**.
1. In **Enable integration**, ensure the **Active** checkbox is selected.
1. Under **Enable integration**, ensure the **Active** checkbox is selected.
1. Complete the fields.
1. Select **Save changes**.

View File

@ -16462,6 +16462,7 @@ GPG signature for a signed commit.
| <a id="groupvisibility"></a>`visibility` | [`String`](#string) | Visibility of the namespace. |
| <a id="groupvulnerabilityscanners"></a>`vulnerabilityScanners` | [`VulnerabilityScannerConnection`](#vulnerabilityscannerconnection) | Vulnerability scanners reported on the project vulnerabilities of the group and its subgroups. (see [Connections](#connections)) |
| <a id="groupweburl"></a>`webUrl` | [`String!`](#string) | Web URL of the group. |
| <a id="groupworkitems"></a>`workItems` **{warning-solid}** | [`WorkItemConnection`](#workitemconnection) | **Introduced** in 16.3. This feature is an Experiment. It can be changed or removed at any time. Work items that belong to the namespace. |
#### Fields with arguments

View File

@ -299,7 +299,7 @@ to build and release Android apps. To enable the integration:
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Select **Settings > Integrations**.
1. Select **Google Play**.
1. In **Enable integration**, select the **Active** checkbox.
1. Under **Enable integration**, select the **Active** checkbox.
1. In **Package name**, enter the package name of the app. For example, `com.gitlab.app_name`.
1. In **Service account key (.JSON)** drag or upload your key file.
1. Select **Save changes**.
@ -356,7 +356,7 @@ to build and release apps for iOS, iPadOS, macOS, tvOS, and watchOS. To enable t
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Select **Settings > Integrations**.
1. Select **Apple App Store**.
1. Turn on the **Active** toggle under **Enable Integration**.
1. Under **Enable integration**, select the **Active** checkbox.
1. Provide the Apple App Store Connect configuration information:
- **Issuer ID**: You can find the Apple App Store Connect Issuer ID in the **Keys** section under **Users and Access** in the Apple App Store Connect portal.
- **Key ID**: The key ID of the new private key that was just generated.

View File

@ -123,6 +123,14 @@ index 5fa7ae8a2bc1..5fe996ba0345 100644
def valid?
```
### Working with GitLab Duo Chat
Prompts are the most vital part of GitLab Duo Chat system. Prompts are the instructions sent to the Large Language Model to perform certain tasks.
The state of the prompts is the result of weeks of iteration. If you want to change any prompt in the current tool, you must put it behind a feature flag.
If you have any new or updated prompts, ask members of AI Framework team to review, because they have significant experience with them.
### Setup for GitLab documentation chat (legacy chat)
To populate the embedding database for GitLab chat:

View File

@ -29,7 +29,7 @@ To configure your project settings in GitLab:
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Select **Settings > Integrations**.
1. Select **Jira**.
1. In **Enable integration**, select the **Active** checkbox.
1. Under **Enable integration**, select the **Active** checkbox.
1. Provide connection details:
- **Web URL**: Base URL for the Jira instance web interface you're linking to
this GitLab project (for example, `https://jira.example.com`).

View File

@ -68,7 +68,7 @@ To remove the Harbor Registry for a project:
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project or group.
1. Select **Settings > Integrations**.
1. Select **Harbor** under **Active integrations**.
1. Clear the **Active** checkbox under **Enable integration**.
1. Under **Enable integration**, clear the **Active** checkbox.
1. Select **Save changes**.
The **Operate > Harbor Registry** entry is removed from the sidebar.

View File

@ -32,7 +32,7 @@ GitLab supports enabling the Apple App Store integration at the project level. C
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Select **Settings > Integrations**.
1. Select **Apple App Store Connect**.
1. Turn on the **Active** toggle under **Enable Integration**.
1. Under **Enable integration**, select the **Active** checkbox.
1. Provide the Apple App Store Connect configuration information:
- **Issuer ID**: The Apple App Store Connect issuer ID.
- **Key ID**: The key ID of the generated private key.

View File

@ -17,7 +17,7 @@ To enable the Bugzilla integration in a project:
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Select **Settings > Integrations**.
1. Select **Bugzilla**.
1. Select the checkbox under **Enable integration**.
1. Under **Enable integration**, select the **Active** checkbox.
1. Fill in the required fields:
- **Project URL**: The URL to the project in Bugzilla.

View File

@ -14,7 +14,7 @@ To enable the ClickUp integration in a project:
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Select **Settings > Integrations**.
1. Select **ClickUp**.
1. Select the checkbox under **Enable integration**.
1. Under **Enable integration**, select the **Active** checkbox.
1. Fill in the required fields:
- **Project URL**: The URL to the ClickUp project to link to this GitLab project.

View File

@ -23,7 +23,7 @@ To enable a custom issue tracker in a project:
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Select **Settings > Integrations**.
1. Select **Custom issue tracker**.
1. Select the checkbox under **Enable integration**.
1. Under **Enable integration**, select the **Active** checkbox.
1. Fill in the required fields:
- **Project URL**: The URL to view all the issues in the custom issue tracker.

View File

@ -17,7 +17,7 @@ To enable the EWM integration, in a project:
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Select **Settings > Integrations**.
1. Select **EWM**.
1. Select the checkbox under **Enable integration**.
1. Under **Enable integration**, select the **Active** checkbox.
1. Fill in the required fields:
- **Project URL**: The URL to the EWM project area.

View File

@ -32,7 +32,7 @@ To enable the Google Play integration in GitLab:
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Select **Settings > Integrations**.
1. Select **Google Play**.
1. In **Enable integration**, select the **Active** checkbox.
1. Under **Enable integration**, select the **Active** checkbox.
1. In **Package name**, enter the package name of the app (for example, `com.gitlab.app_name`).
1. In **Service account key (.JSON)**, drag or upload your key file.
1. Select **Save changes**.

View File

@ -28,7 +28,7 @@ GitLab supports integrating Harbor projects at the group or project level. Compl
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Select **Settings > Integrations**.
1. Select **Harbor**.
1. Turn on the **Active** toggle under **Enable Integration**.
1. Under **Enable integration**, select the **Active** checkbox.
1. Provide the Harbor configuration information:
- **Harbor URL**: The base URL of Harbor instance which is being linked to this GitLab project. For example, `https://harbor.example.net`.
- **Harbor project name**: The project name in the Harbor instance. For example, `testproject`.

View File

@ -34,7 +34,7 @@ you can automatically configure Mattermost slash commands:
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Select **Settings > Integrations**.
1. Select **Mattermost slash commands**.
1. In **Enable integration**, ensure the **Active** checkbox is selected.
1. Under **Enable integration**, ensure the **Active** checkbox is selected.
1. Select **Add to Mattermost**, and select **Save changes**.
## Configure manually

View File

@ -12,7 +12,7 @@ To enable the Redmine integration in a project:
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Select **Settings > Integrations**.
1. Select **Redmine**.
1. Select the checkbox under **Enable integration**.
1. Under **Enable integration**, select the **Active** checkbox.
1. Fill in the required fields:
- **Project URL**: The URL to the Redmine project to link to this GitLab project.

View File

@ -26,7 +26,7 @@ To enable the Shimo integration for your project:
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Select **Settings > Integrations**.
1. Select **Shimo**.
1. In **Enable integration**, ensure the **Active** checkbox is selected.
1. Under **Enable integration**, ensure the **Active** checkbox is selected.
1. Provide the **Shimo Workspace URL** you want to link to your group or project (for example, `https://shimo.im/space/aBAYV6VvajUP873j`).
1. Select **Save changes**.

View File

@ -35,7 +35,7 @@ to control GitLab from Slack. Slash commands are configured separately.
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Select **Settings > Integrations**.
1. Select **Slack notifications**.
1. In the **Enable integration** section, select the **Active** checkbox.
1. Under **Enable integration**, select the **Active** checkbox.
1. In the **Trigger** section, select the checkboxes for each type of GitLab
event to send to Slack as a notification. For a full list, see
[Triggers for Slack notifications](#triggers-for-slack-notifications).

View File

@ -47,7 +47,7 @@ After you invite the bot to a Telegram channel, you can configure GitLab to send
1. Select **Admin Area**.
1. Select **Settings > Integrations**.
1. Select **Telegram**.
1. In **Enable integration**, select the **Active** checkbox.
1. Under **Enable integration**, select the **Active** checkbox.
1. In **New token**, [paste the token value from the Telegram bot](#create-a-telegram-bot).
1. In the **Trigger** section, select the checkboxes for the GitLab events you want to receive in Telegram.
1. In **Channel identifier**, [paste the Telegram channel identifier](#configure-the-telegram-bot).

View File

@ -17,7 +17,7 @@ To enable the YouTrack integration in a project:
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Select **Settings > Integrations**.
1. Select **YouTrack**.
1. Select the checkbox under **Enable integration**.
1. Under **Enable integration**, select the **Active** checkbox.
1. Fill in the required fields:
- **Project URL**: The URL to the project in YouTrack.
- **Issue URL**: The URL to view an issue in the YouTrack project.

View File

@ -43,7 +43,7 @@ Complete these steps in GitLab:
1. Go to your project and select **Settings > Integrations**.
1. Select **ZenTao**.
1. Turn on the **Active** toggle under **Enable Integration**.
1. Under **Enable integration**, select the **Active** checkbox.
1. Provide the ZenTao configuration information:
- **ZenTao Web URL**: The base URL of the ZenTao instance web interface you're linking to this GitLab project (for example, `example.zentao.net`).
- **ZenTao API URL** (optional): The base URL to the ZenTao instance API. Defaults to the Web URL value if not set.

View File

@ -287,7 +287,7 @@ To hide the link to an external wiki:
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Select **Settings > Integrations**.
1. Select **External wiki**.
1. In the **Enable integration** section, clear the **Active** checkbox.
1. Under **Enable integration**, clear the **Active** checkbox.
1. Select **Save changes**.
## Disable the project's wiki

View File

@ -65,6 +65,10 @@ Graphql/AuthorizeTypes:
Graphql/Descriptions:
Enabled: false
# This cop doesn't make sense in the context of gems
RSpec/BeforeAll:
Enabled: false
Naming/FileName:
Exclude:
- spec/**/*.rb

View File

@ -8,7 +8,3 @@ RSpec/InstanceVariable:
Gitlab/ChangeTimezone:
Exclude:
- spec/gitlab/rspec/time_travel_spec.rb
RSpec/BeforeAll:
Exclude:
- spec/gitlab/rspec/time_travel_spec.rb

View File

@ -1,7 +1,6 @@
# frozen_string_literal: true
RSpec.describe 'time travel' do
# rubocop:disable RSpec/BeforeAll
before(:all) do
@original_time_zone = Time.zone
Time.zone = 'Eastern Time (US & Canada)'
@ -10,7 +9,6 @@ RSpec.describe 'time travel' do
after(:all) do
Time.zone = @original_time_zone
end
# rubocop:enable RSpec/BeforeAll
describe ':freeze_time' do
it 'freezes time around a spec example', :freeze_time do

View File

@ -34,8 +34,13 @@ module Banzai
# https://github.com/vmg/rinku/blob/v2.0.1/ext/rinku/autolink.c#L65
#
# 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
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
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
# outside the link element. The entity must be marked HTML safe in
# order to be output literally rather than escaped.
match.gsub!(/((?:&[\w#]+;)+)\z/, '')
dropped = (Regexp.last_match(1) || '').html_safe
dropped = ''
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
# character unless there are an equal number of closing and opening
# characters in the link.

View File

@ -11,7 +11,7 @@ module Banzai
def call
return doc unless settings.plantuml_enabled? && doc.at_xpath(lang_tag)
plantuml_setup
Gitlab::Plantuml.configure
doc.xpath(lang_tag).each do |node|
img_tag = Nokogiri::HTML::DocumentFragment.parse(
@ -38,15 +38,6 @@ module Banzai
def settings
Gitlab::CurrentSettings.current_application_settings
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

View File

@ -26,7 +26,7 @@ module BulkImports
return if tar_filepath?(file_path)
return if lfs_json_filepath?(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)
oid = LfsObject.calculate_oid(file_path)

View File

@ -26,7 +26,7 @@ module BulkImports
# Validate that the path is OK to load
Gitlab::PathTraversal.check_allowed_absolute_path_and_path_traversal!(file_path, [Dir.tmpdir])
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)
return save_avatar(file_path) if avatar_path

View File

@ -32,7 +32,7 @@ module BulkImports
end
def validate_symlink
return unless File.lstat(filepath).symlink?
return unless Gitlab::Utils::FileInfo.linked?(filepath)
File.delete(filepath)
raise_error 'Invalid downloaded file'

View File

@ -26,7 +26,7 @@ module BulkImports
return unless portable.lfs_enabled?
return unless File.exist?(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)
end

View File

@ -26,7 +26,7 @@ module BulkImports
return unless File.exist?(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)
end

View File

@ -78,20 +78,11 @@ module Gitlab
context[:pipeline] = :ascii_doc
context[:max_includes] = [MAX_INCLUDES, context[:max_includes]].compact.min
plantuml_setup
Gitlab::Plantuml.configure
html = ::Asciidoctor.convert(input, asciidoc_opts)
html = Banzai.render(html, context)
html.html_safe
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

View File

@ -43,7 +43,7 @@ module Gitlab
def prohibited_branch_checks
return if deletion?
if %r{\A#{Gitlab::Git::Commit::RAW_FULL_SHA_PATTERN}(/|\z)}o.match?(branch_name)
if %r{\A#{Gitlab::Git::Commit::RAW_FULL_SHA_PATTERN}(-/|/|\z)}o.match?(branch_name)
raise GitAccess::ForbiddenError, ERROR_MESSAGES[:prohibited_hex_branch_name]
end

View File

@ -65,7 +65,7 @@ module Gitlab
def validate_archive_path
Gitlab::PathTraversal.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)
end

View File

@ -9,15 +9,15 @@ module Gitlab
SHARED_STATE_NAMESPACE = 'etag:'
def get(key)
Gitlab::Redis::SharedState.with { |redis| redis.get(redis_shared_state_key(key)) }
with_redis { |redis| redis.get(redis_shared_state_key(key)) }
end
def touch(*keys, only_if_missing: false)
etags = keys.map { generate_etag }
Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
Gitlab::Redis::SharedState.with do |redis|
redis.pipelined do |pipeline|
with_redis do |redis|
Gitlab::Redis::CrossSlot::Pipeline.new(redis).pipelined do |pipeline|
keys.each_with_index do |key, i|
pipeline.set(redis_shared_state_key(key), etags[i], ex: EXPIRY_TIME, nx: only_if_missing)
end
@ -30,6 +30,12 @@ module Gitlab
private
def with_redis(&blk)
# We use multistore as n interweaving double-write will result in n-1 subsequent requests
# becoming a cache-miss, however, 2 interweaving .touch will lead to 1 cache miss anyway.
Gitlab::Redis::EtagCache.with(&blk) # rubocop:disable CodeReuse/ActiveRecord
end
def generate_etag
SecureRandom.hex
end

View File

@ -25,7 +25,7 @@ module Gitlab
message: 'params invalid'
}, allow_blank: true
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'
}, allow_blank: true

View File

@ -5,8 +5,11 @@ module Gitlab
module CommandLineUtil
UNTAR_MASK = 'u+rwX,go+rX,go-w'
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:)
tar_with_options(archive: archive, dir: dir, options: 'czf')
@ -90,7 +93,7 @@ module Gitlab
def untar_with_options(archive:, dir:, options:)
execute_cmd(%W(tar -#{options} #{archive} -C #{dir}))
execute_cmd(%W(chmod -R #{UNTAR_MASK} #{dir}))
remove_symlinks(dir)
clean_extraction_dir!(dir)
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
@ -122,17 +125,27 @@ module Gitlab
true
end
def remove_symlinks(dir)
ignore_file_names = %w[. ..]
# Scans and cleans the directory tree.
# 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 "."
Dir.glob("#{dir}/**/*", File::FNM_DOTMATCH)
.reject { |f| ignore_file_names.include?(File.basename(f)) }
.each do |filepath|
FileUtils.rm(filepath) if File.lstat(filepath).symlink?
end
Dir.glob("#{dir}/**/*", File::FNM_DOTMATCH).each do |filepath|
next if CLEAN_DIR_IGNORE_FILE_NAMES.include?(File.basename(filepath))
raise HardLinkError, 'File shares hard link' if Gitlab::Utils::FileInfo.shares_hard_link?(filepath)
FileUtils.rm(filepath) if Gitlab::Utils::FileInfo.linked?(filepath)
end
true
rescue HardLinkError
FileUtils.remove_dir(dir)
raise
end
end
end

View File

@ -87,7 +87,7 @@ module Gitlab
def validate_archive_path
Gitlab::PathTraversal.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)
end

View File

@ -23,7 +23,7 @@ module Gitlab
mkdir_p(@shared.export_path)
mkdir_p(@shared.archive_path)
remove_symlinks(@shared.export_path)
clean_extraction_dir!(@shared.export_path)
copy_archive
wait_for_archived_file do
@ -35,7 +35,7 @@ module Gitlab
false
ensure
remove_import_file
remove_symlinks(@shared.export_path)
clean_extraction_dir!(@shared.export_path)
end
private

View File

@ -21,7 +21,9 @@ module Gitlab
# This reads from `tree/project.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)
json_decode(data)
@ -34,7 +36,7 @@ module Gitlab
# This reads from `tree/project/merge_requests.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|
documents << [json_decode(line), line_num]

View File

@ -57,7 +57,7 @@ module Gitlab
source_child = File.join(source_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)
FileUtils.mkdir_p(target_child, mode: DEFAULT_DIR_MODE) unless File.exist?(target_child)

View File

@ -10,13 +10,12 @@ module Gitlab
def execute
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)
name = host.delete_suffix(gitlab_host)
by_namespace_domain(name) ||
by_unique_domain(name)
by_unique_domain(name) || by_namespace_domain(name)
else
by_custom_domain(host)
end

View File

@ -131,7 +131,7 @@ module Gitlab
# `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.
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_]'
NO_SUFFIX_REGEX = /(?<!\.git|\.atom)/.freeze

20
lib/gitlab/plantuml.rb Normal file
View 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

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
module Gitlab
module Redis
class EtagCache < ::Gitlab::Redis::Wrapper
class << self
def store_name
'Cache'
end
private
def redis
primary_store = ::Redis.new(Gitlab::Redis::Cache.params)
secondary_store = ::Redis.new(Gitlab::Redis::SharedState.params)
MultiStore.new(primary_store, secondary_store, name.demodulize)
end
end
end
end
end

View 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

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
module Sbom
module PurlType
class Converter
PACKAGE_MANAGER_TO_PURL_TYPE_MAP = {
'bundler' => 'gem',
'yarn' => 'npm',
'npm' => 'npm',
'pnpm' => 'npm',
'maven' => 'maven',
'sbt' => 'maven',
'gradle' => 'maven',
'composer' => 'composer',
'conan' => 'conan',
'go' => 'golang',
'nuget' => 'nuget',
'pip' => 'pypi',
'pipenv' => 'pypi',
'setuptools' => 'pypi'
}.with_indifferent_access.freeze
def self.purl_type_for_pkg_manager(package_manager)
PACKAGE_MANAGER_TO_PURL_TYPE_MAP[package_manager]
end
end
end
end

View File

@ -36943,6 +36943,9 @@ msgstr ""
msgid "ProjectSettings|Your project is set up. %{linkStart}View instrumentation instructions%{linkEnd}."
msgstr ""
msgid "ProjectSetting|already in use"
msgstr ""
msgid "ProjectTemplates|.NET Core"
msgstr ""
@ -37240,6 +37243,9 @@ msgstr ""
msgid "ProjectsNew|Your project will be created at:"
msgstr ""
msgid "Project|already in use"
msgstr ""
msgid "PrometheusAlerts|exceeded"
msgstr ""
@ -55008,9 +55014,6 @@ msgstr ""
msgid "eligible users"
msgstr ""
msgid "email '%{email}' is not a verified email."
msgstr ""
msgid "email address settings"
msgstr ""
@ -55301,6 +55304,9 @@ msgstr ""
msgid "is not valid. The iteration group has to match the iteration cadence group."
msgstr ""
msgid "is not verified."
msgstr ""
msgid "is one of"
msgstr ""

View File

@ -61,7 +61,7 @@
"@gitlab/svgs": "3.58.0",
"@gitlab/ui": "64.20.1",
"@gitlab/visual-review-tools": "1.7.3",
"@gitlab/web-ide": "0.0.1-dev-20230713160749",
"@gitlab/web-ide": "0.0.1-dev-20230713160749-patch-1",
"@mattiasbuelens/web-streams-adapter": "^0.1.0",
"@popperjs/core": "^2.11.2",
"@rails/actioncable": "7.0.6",

View File

@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Projects::PipelineSchedulesController, feature_category: :continuous_integration do
include AccessMatchersForController
using RSpec::Parameterized::TableSyntax
let_it_be(:user) { create(:user) }
let_it_be_with_reload(:project) { create(:project, :public, :repository) }
@ -45,6 +46,43 @@ RSpec.describe Projects::PipelineSchedulesController, feature_category: :continu
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
render_views
@ -172,7 +210,9 @@ RSpec.describe Projects::PipelineSchedulesController, feature_category: :continu
end
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
expect { go }.to be_allowed_for(:admin)
@ -191,6 +231,8 @@ RSpec.describe Projects::PipelineSchedulesController, feature_category: :continu
it { expect { go }.to be_denied_for(:user) }
it { expect { go }.to be_denied_for(:external) }
it { expect { go }.to be_denied_for(:visitor) }
it_behaves_like 'protecting ref'
end
def go
@ -457,7 +499,7 @@ RSpec.describe Projects::PipelineSchedulesController, feature_category: :continu
end
describe 'POST #play', :clean_gitlab_redis_rate_limiting do
let(:ref) { 'master' }
let(:ref_name) { 'master' }
before do
project.add_developer(user)
@ -473,7 +515,7 @@ RSpec.describe Projects::PipelineSchedulesController, feature_category: :continu
it 'does not allow pipeline to be executed' do
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)
end
@ -483,16 +525,14 @@ RSpec.describe Projects::PipelineSchedulesController, feature_category: :continu
it 'executes a new pipeline' do
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(response).to have_gitlab_http_status(:found)
end
it 'prevents users from scheduling the same pipeline repeatedly' do
2.times do
post :play, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id }
end
2.times { go }
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.')
@ -500,17 +540,14 @@ RSpec.describe Projects::PipelineSchedulesController, feature_category: :continu
end
end
context 'when a developer attempts to schedule a protected ref' do
it 'does not allow pipeline to be executed' do
create(:protected_branch, project: project, name: ref)
protected_schedule = create(:ci_pipeline_schedule, project: project, ref: ref)
describe 'security' do
let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, ref: "refs/#{ref_prefix}/#{ref_name}") }
expect(RunPipelineScheduleWorker).not_to receive(:perform_async)
it_behaves_like 'protecting ref'
end
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
def go
post :play, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id }
end
end

View File

@ -54,6 +54,10 @@ FactoryBot.define do
end
end
factory :file_diff_position do
position_type { 'file' }
end
factory :image_diff_position do
position_type { 'image' }
x { 1 }

View File

@ -0,0 +1,49 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'User views comment on a diff file', :js, feature_category: :code_review_workflow do
include MergeRequestDiffHelpers
include RepoHelpers
let_it_be(:project) { create(:project, :repository) }
let_it_be(:merge_request) do
create(:merge_request_with_diffs, source_project: project, target_project: project, source_branch: 'merge-test')
end
let_it_be(:user) { create(:user) }
before_all do
project.add_maintainer(user)
end
before do
sign_in(user)
visit(diffs_project_merge_request_path(project, merge_request))
end
context 'with invalid start_sha position' do
before do
diff_refs = Gitlab::Diff::DiffRefs.new(
base_sha: merge_request.diff_refs.base_sha,
start_sha: 'fakesha',
head_sha: merge_request.diff_refs.head_sha
)
position = build(:file_diff_position, file: 'files/ruby/popen.rb', diff_refs: diff_refs)
create(:diff_note_on_merge_request, noteable: merge_request, project: project, position: position)
end
it 'renders diffs' do
visit diffs_project_merge_request_path(project, merge_request)
expect(page).to have_selector('.diff-file')
end
it 'renders discussion on overview tab' do
visit project_merge_request_path(project, merge_request)
expect(page).to have_selector('.note-discussion')
end
end
end

View File

@ -0,0 +1,123 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe WorkItems::NamespaceWorkItemsFinder, feature_category: :team_planning do
include AdminModeHelper
describe '#execute' do
let_it_be(:group) { create(:group, :public) }
let_it_be(:sub_group) { create(:group, :private, parent: group) }
let_it_be(:project) { create(:project, :repository, :public, group: group) }
let_it_be(:user) { create(:user) }
let_it_be(:reporter) { create(:user).tap { |user| group.add_reporter(user) } }
let_it_be(:guest) { create(:user).tap { |user| group.add_guest(user) } }
let_it_be(:guest_author) { create(:user).tap { |user| group.add_guest(user) } }
let_it_be(:banned_user) { create(:banned_user) }
let_it_be(:project_work_item) { create(:work_item, project: project) }
let_it_be(:sub_group_work_item) do
create(:work_item, namespace: sub_group, author: reporter)
end
let_it_be(:group_work_item) do
create(:work_item, namespace: group, author: reporter)
end
let_it_be(:group_confidential_work_item, reload: true) do
create(:work_item, :confidential, namespace: group, author: guest_author)
end
let_it_be(:sub_group_confidential_work_item, reload: true) do
create(:work_item, :confidential, namespace: sub_group, author: guest_author)
end
let_it_be(:hidden_work_item) do
create(:work_item, :confidential, namespace: group, author: banned_user.user)
end
let_it_be(:other_work_item) { create(:work_item) }
let(:finder_params) { {} }
let(:current_user) { user }
let(:namespace) { nil }
subject do
finder = described_class.new(current_user, finder_params)
finder.parent_param = namespace
finder.execute
end
context 'when no parent is provided' do
it { is_expected.to be_empty }
end
context 'when the namespace is private' do
let(:namespace) { sub_group }
context 'when the user cannot read the namespace' do
it { is_expected.to be_empty }
end
context 'when the user can not see confidential work_items' do
let(:current_user) { guest }
it { is_expected.to contain_exactly(sub_group_work_item) }
context 'when the user is the author of the work item' do
let(:current_user) { guest_author }
it { is_expected.to contain_exactly(sub_group_work_item, sub_group_confidential_work_item) }
end
context 'when the user is assigned to a confidential work item' do
before do
sub_group_confidential_work_item.update!(assignees: [current_user])
end
it { is_expected.to contain_exactly(sub_group_work_item, sub_group_confidential_work_item) }
end
end
context 'when the user can see confidential work_items' do
let(:current_user) { reporter }
it { is_expected.to contain_exactly(sub_group_work_item, sub_group_confidential_work_item) }
end
end
context 'when the namespace is public' do
let(:namespace) { group }
context 'when user is admin' do
let(:current_user) { create(:user, :admin).tap { |u| enable_admin_mode!(u) } }
it { is_expected.to match_array(WorkItem.all.to_a) }
end
context 'when the user can not see confidential work_items' do
it { is_expected.to contain_exactly(group_work_item) }
context 'when the user is the author of the work item' do
let(:current_user) { guest_author }
it { is_expected.to contain_exactly(group_work_item, group_confidential_work_item) }
end
context 'when the user is assigned to a confidential work item' do
before do
group_confidential_work_item.update!(assignees: [current_user])
end
it { is_expected.to contain_exactly(group_work_item, group_confidential_work_item) }
end
end
context 'when the user can see confidential work_items' do
let(:current_user) { reporter }
it { is_expected.to contain_exactly(group_work_item, group_confidential_work_item) }
end
end
end
end

View File

@ -192,7 +192,7 @@ describe('InputCopyToggleVisibility', () => {
describe('when input is clicked', () => {
it('selects input value', async () => {
const mockSelect = jest.fn();
wrapper.vm.$refs.input.$el.select = mockSelect;
findFormInput().element.select = mockSelect;
await findFormInput().trigger('click');
expect(mockSelect).toHaveBeenCalled();
@ -202,7 +202,7 @@ describe('InputCopyToggleVisibility', () => {
describe('when label is clicked', () => {
it('selects input value', async () => {
const mockSelect = jest.fn();
wrapper.vm.$refs.input.$el.select = mockSelect;
findFormInput().element.select = mockSelect;
await wrapper.find('label').trigger('click');
expect(mockSelect).toHaveBeenCalled();

View File

@ -26,7 +26,7 @@ RSpec.describe GitlabSchema.types['Group'] do
dependency_proxy_image_prefix dependency_proxy_image_ttl_policy
shared_runners_setting timelogs organization_state_counts organizations
contact_state_counts contacts work_item_types
recent_issue_boards ci_variables releases environment_scopes
recent_issue_boards ci_variables releases environment_scopes work_items
]
expect(described_class).to include_graphql_fields(*expected_fields)
@ -77,6 +77,13 @@ RSpec.describe GitlabSchema.types['Group'] do
it { is_expected.to have_graphql_resolver(Resolvers::GroupReleasesResolver) }
end
describe 'work_items field' do
subject { described_class.fields['workItems'] }
it { is_expected.to have_graphql_type(Types::WorkItemType.connection_type) }
it { is_expected.to have_graphql_resolver(Resolvers::Namespaces::WorkItemsResolver) }
end
it_behaves_like 'a GraphQL type with labels' do
let(:labels_resolver_arguments) { [:search_term, :includeAncestorGroups, :includeDescendantGroups, :onlyGroupLabels] }
end

View File

@ -226,6 +226,22 @@ RSpec.describe Banzai::Filter::AutolinkFilter, feature_category: :team_planning
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
# does. We don't care about that difference for these specs, though.
def unescape(html)

View File

@ -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', "mailto:#{'a-' * 499_000}@aaaaaaaa..aaaaaaaa.example.com"
end
it 'allows references with text after the > character' do

View File

@ -2,7 +2,7 @@
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(:oid) { 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' }
@ -118,13 +118,22 @@ RSpec.describe BulkImports::Common::Pipelines::LfsObjectsPipeline do
context 'when file path is symlink' do
it 'returns' do
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 }
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
it 'returns' do
expect { pipeline.load(context, Dir.tmpdir) }.not_to change { portable.lfs_objects.count }

View File

@ -105,6 +105,7 @@ RSpec.describe BulkImports::Common::Pipelines::UploadsPipeline, feature_category
it 'returns' do
path = File.join(tmpdir, 'test')
FileUtils.touch(path)
expect { pipeline.load(context, path) }.not_to change { portable.uploads.count }
end
end
@ -118,13 +119,22 @@ RSpec.describe BulkImports::Common::Pipelines::UploadsPipeline, feature_category
context 'when path is a symlink' do
it 'does not upload the file' do
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 }
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
it 'does not upload the file' do
path_traversal = "#{uploads_dir_path}/avatar/../../../../etc/passwd"

View File

@ -2,7 +2,7 @@
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(:portable) { create(:project) }
@ -125,9 +125,9 @@ RSpec.describe BulkImports::Projects::Pipelines::DesignBundlePipeline do
context 'when path is symlink' do
it 'returns' do
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)
pipeline.load(context, symlink)
@ -136,6 +136,19 @@ RSpec.describe BulkImports::Projects::Pipelines::DesignBundlePipeline do
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
it 'returns' do
expect { pipeline.load(context, '/home/test.txt') }

View File

@ -2,7 +2,7 @@
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(:portable) { create(:project) }
@ -123,9 +123,9 @@ RSpec.describe BulkImports::Projects::Pipelines::RepositoryBundlePipeline do
context 'when path is symlink' do
it 'returns' do
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)
pipeline.load(context, symlink)
@ -134,6 +134,19 @@ RSpec.describe BulkImports::Projects::Pipelines::RepositoryBundlePipeline do
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
it 'returns' do
expect { pipeline.load(context, '/home/test.txt') }

View File

@ -32,6 +32,12 @@ RSpec.describe Gitlab::Checks::BranchCheck, feature_category: :source_code_manag
expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "You cannot create a branch with a 40-character hexadecimal branch name.")
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 "prohibits 64-character hexadecimal branch names" do
allow(subject).to receive(:branch_name).and_return("09b9fd3ea68e9b95a51b693a29568c898e27d1476bbd83c825664f18467fc175")

View File

@ -105,6 +105,16 @@ RSpec.describe Gitlab::Ci::DecompressedGzipSizeValidator, feature_category: :imp
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
let(:filepath) { Dir.mktmpdir }
let(:filesize) { File.size(filepath) }

View File

@ -2,7 +2,7 @@
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) }
let_it_be(:file_url) { 'https://example.com/avatar.png' }
@ -39,6 +39,26 @@ RSpec.describe Gitlab::GithubImport::AttachmentsDownloader do
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
let_it_be(:file_url) { 'https://example.com/ava%2F..%2Ftar.png' }

View File

@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Gitlab::Harbor::Query do
using RSpec::Parameterized::TableSyntax
let_it_be(:harbor_integration) { create(:harbor_integration) }
let(:params) { {} }
@ -111,19 +113,20 @@ RSpec.describe Gitlab::Harbor::Query do
end
context 'search' do
context 'with valid search' do
let(:params) { { search: 'name=desc' } }
it 'initialize successfully' do
expect(query.valid?).to eq(true)
end
where(:search_param, :is_valid) do
"name=desc" | true
"name=value1,name=value-2" | true
"name=value1,name=value_2" | false
"name=desc,key=value" | false
"name=value1, name=value2" | false
"name" | false
end
context 'with invalid search' do
let(:params) { { search: 'blabla' } }
with_them do
let(:params) { { search: search_param } }
it 'initialize failed' do
expect(query.valid?).to eq(false)
it "validates according to the regex" do
expect(query.valid?).to eq(is_valid)
end
end
end

View File

@ -5,13 +5,16 @@ require 'spec_helper'
RSpec.describe Gitlab::ImportExport::CommandLineUtil, feature_category: :importers do
include ExportFileHelper
let(:path) { "#{Dir.tmpdir}/symlink_test" }
let(:archive) { 'spec/fixtures/symlink_export.tar.gz' }
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 }
subject do
subject(:mock_class) do
Class.new do
include Gitlab::ImportExport::CommandLineUtil
@ -25,38 +28,59 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil, feature_category: :importe
end
before do
FileUtils.mkdir_p(path)
FileUtils.mkdir_p(source_dir)
end
after do
FileUtils.rm_rf(path)
FileUtils.rm_rf(source_dir)
FileUtils.rm_rf(target_dir)
FileUtils.rm_rf(archive_dir)
FileUtils.remove_entry(tmpdir)
end
shared_examples 'deletes symlinks' do |compression, decompression|
it 'deletes the symlinks', :aggregate_failures do
Dir.mkdir("#{tmpdir}/.git")
Dir.mkdir("#{tmpdir}/folder")
FileUtils.touch("#{tmpdir}/file.txt")
FileUtils.touch("#{tmpdir}/folder/file.txt")
FileUtils.touch("#{tmpdir}/.gitignore")
FileUtils.touch("#{tmpdir}/.git/config")
File.symlink('file.txt', "#{tmpdir}/.symlink")
File.symlink('file.txt', "#{tmpdir}/.git/.symlink")
File.symlink('file.txt', "#{tmpdir}/folder/.symlink")
archive = File.join(archive_dir, 'archive')
subject.public_send(compression, archive: archive, dir: tmpdir)
Dir.mkdir("#{source_dir}/.git")
Dir.mkdir("#{source_dir}/folder")
FileUtils.touch("#{source_dir}/file.txt")
FileUtils.touch("#{source_dir}/folder/file.txt")
FileUtils.touch("#{source_dir}/.gitignore")
FileUtils.touch("#{source_dir}/.git/config")
File.symlink('file.txt', "#{source_dir}/.symlink")
File.symlink('file.txt', "#{source_dir}/.git/.symlink")
File.symlink('file.txt', "#{source_dir}/folder/.symlink")
archive_file = File.join(archive_dir, 'symlink_archive.tar.gz')
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)
expect(File.exist?("#{archive_dir}/folder/file.txt")).to eq(true)
expect(File.exist?("#{archive_dir}/.gitignore")).to eq(true)
expect(File.exist?("#{archive_dir}/.git/config")).to eq(true)
expect(File.exist?("#{archive_dir}/.symlink")).to eq(false)
expect(File.exist?("#{archive_dir}/.git/.symlink")).to eq(false)
expect(File.exist?("#{archive_dir}/folder/.symlink")).to eq(false)
shared_examples 'handles shared hard links' do |compression, decompression|
let(:archive_file) { File.join(archive_dir, 'hard_link_archive.tar.gz') }
subject(:decompress) { mock_class.public_send(decompression, archive: archive_file, dir: target_dir) }
before do
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
@ -212,6 +236,8 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil, feature_category: :importe
end
describe '#gzip' do
let(:path) { source_dir }
it 'compresses specified file' do
tempfile = Tempfile.new('test', path)
filename = File.basename(tempfile.path)
@ -229,14 +255,16 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil, feature_category: :importe
end
describe '#gunzip' do
let(:path) { source_dir }
it 'decompresses specified file' do
filename = 'labels.ndjson.gz'
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
context 'when exception occurs' do
@ -250,7 +278,7 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil, feature_category: :importe
it 'archives a folder without compression' do
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(File.exist?(archive_file)).to eq(true)
@ -270,29 +298,35 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil, feature_category: :importe
end
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 'handles shared hard links', :tar_czf, :untar_zxf
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
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
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 'handles shared hard links', :tar_cf, :untar_xf
it 'extracts archive without decompression' do
filename = 'archive.tar.gz'
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)
result = subject.untar_xf(archive: archive_file, dir: archive_dir)

View File

@ -2,7 +2,7 @@
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') }
before_all do
@ -121,7 +121,7 @@ RSpec.describe Gitlab::ImportExport::DecompressedArchiveSizeValidator do
context 'which archive path is a symlink' do
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
FileUtils.ln_s(filepath, filepath, force: true)
@ -132,6 +132,19 @@ RSpec.describe Gitlab::ImportExport::DecompressedArchiveSizeValidator do
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
let(:filepath) { Dir.mktmpdir }
let(:filesize) { File.size(filepath) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Gitlab::ImportExport::FileImporter do
RSpec.describe Gitlab::ImportExport::FileImporter, feature_category: :importers do
include ExportFileHelper
let(:shared) { Gitlab::ImportExport::Shared.new(nil) }
@ -113,28 +113,73 @@ RSpec.describe Gitlab::ImportExport::FileImporter do
end
context 'error' do
subject(:import) { described_class.import(importable: build(:project), archive_file: '', shared: shared) }
before do
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
described_class.import(importable: build(:project), archive_file: '', shared: shared)
end
it 'removes symlinks in root folder' do
import
expect(File.exist?(symlink_file)).to be false
end
it 'removes hidden symlinks in root folder' do
import
expect(File.exist?(hidden_symlink_file)).to be false
end
it 'removes symlinks in subfolders' do
import
expect(File.exist?(subfolder_symlink_file)).to be false
end
it 'does not remove a valid file' do
import
expect(File.exist?(valid_file)).to be true
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
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)
end
it 'returns false' do
expect(subject.import).to eq(false)
it 'returns false and sets an error on shared' do
result = subject.import
expect(result).to eq(false)
expect(shared.errors.join).to eq('Decompressed archive size validation failed.')
end
end

View File

@ -35,16 +35,22 @@ RSpec.describe Gitlab::ImportExport::Json::NdjsonReader, feature_category: :impo
expect(subject).to eq(root_tree)
end
context 'when project.json is symlink' do
it 'raises error an error' do
Dir.mktmpdir do |tmpdir|
FileUtils.touch(File.join(tmpdir, 'passwd'))
File.symlink(File.join(tmpdir, 'passwd'), File.join(tmpdir, 'project.json'))
context 'when project.json is symlink or hard link' do
using RSpec::Parameterized::TableSyntax
ndjson_reader = described_class.new(tmpdir)
where(:link_method) { [:link, :symlink] }
expect { ndjson_reader.consume_attributes(importable_path) }
.to raise_error(Gitlab::ImportExport::Error, 'Invalid file')
with_them do
it 'raises an error' do
Dir.mktmpdir do |tmpdir|
FileUtils.touch(File.join(tmpdir, 'passwd'))
FileUtils.send(link_method, File.join(tmpdir, 'passwd'), File.join(tmpdir, 'project.json'))
ndjson_reader = described_class.new(tmpdir)
expect { ndjson_reader.consume_attributes(importable_path) }
.to raise_error(Gitlab::ImportExport::Error, 'Invalid file')
end
end
end
end
@ -97,18 +103,24 @@ RSpec.describe Gitlab::ImportExport::Json::NdjsonReader, feature_category: :impo
end
end
context 'when relation file is a symlink' do
it 'yields nothing to the Enumerator' do
Dir.mktmpdir do |tmpdir|
Dir.mkdir(File.join(tmpdir, 'project'))
File.write(File.join(tmpdir, 'passwd'), "{}\n{}")
File.symlink(File.join(tmpdir, 'passwd'), File.join(tmpdir, 'project', 'issues.ndjson'))
context 'when relation file is a symlink or hard link' do
using RSpec::Parameterized::TableSyntax
ndjson_reader = described_class.new(tmpdir)
where(:link_method) { [:link, :symlink] }
result = ndjson_reader.consume_relation(importable_path, 'issues')
with_them do
it 'yields nothing to the Enumerator' do
Dir.mktmpdir do |tmpdir|
Dir.mkdir(File.join(tmpdir, 'project'))
File.write(File.join(tmpdir, 'passwd'), "{}\n{}")
FileUtils.send(link_method, File.join(tmpdir, 'passwd'), File.join(tmpdir, 'project', 'issues.ndjson'))
expect(result.to_a).to eq([])
ndjson_reader = described_class.new(tmpdir)
result = ndjson_reader.consume_relation(importable_path, 'issues')
expect(result.to_a).to eq([])
end
end
end
end

View File

@ -4,15 +4,17 @@ require 'spec_helper'
RSpec.describe Gitlab::ImportExport::RecursiveMergeFolders 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|
source = "#{tmpdir}/source"
FileUtils.mkdir_p("#{source}/folder/folder")
FileUtils.touch("#{source}/file1.txt")
FileUtils.touch("#{source}/file_that_shares_hard_links.txt")
FileUtils.touch("#{source}/folder/file2.txt")
FileUtils.touch("#{source}/folder/folder/file3.txt")
FileUtils.ln_s("#{source}/file1.txt", "#{source}/symlink-file1.txt")
FileUtils.ln_s("#{source}/folder", "#{source}/symlink-folder")
FileUtils.link("#{source}/file_that_shares_hard_links.txt", "#{source}/hard_link.txt")
target = "#{tmpdir}/target"
FileUtils.mkdir_p("#{target}/folder/folder")

View File

@ -9,6 +9,10 @@ RSpec.describe Gitlab::Pages::VirtualHostFinder, feature_category: :pages do
project.update_pages_deployment!(create(:pages_deployment, project: project))
end
before do
stub_pages_setting(host: 'example.com')
end
it 'returns nil when host is empty' do
expect(described_class.new(nil).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
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.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
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.cache_key).to be_nil
@ -104,7 +108,7 @@ RSpec.describe Gitlab::Pages::VirtualHostFinder, feature_category: :pages do
end
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.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
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.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
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.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')
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
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)
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
before do
stub_feature_flags(cache_pages_domain_api: false)

View 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
stub_application_setting(plantuml_url: 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

View File

@ -0,0 +1,56 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Redis::EtagCache, feature_category: :shared do
# Note: this is a pseudo-store in front of `Cache`, meant only as a tool
# to move away from `SharedState` for etag cache data. Thus, we use the
# same store configuration as the former.
let(:instance_specific_config_file) { "config/redis.cache.yml" }
include_examples "redis_shared_examples"
describe '#pool' do
let(:config_new_format_host) { "spec/fixtures/config/redis_new_format_host.yml" }
let(:config_new_format_socket) { "spec/fixtures/config/redis_new_format_socket.yml" }
let(:rails_root) { mktmpdir }
subject { described_class.pool }
before do
# Override rails root to avoid having our fixtures overwritten by `redis.yml` if it exists
allow(Gitlab::Redis::SharedState).to receive(:rails_root).and_return(rails_root)
allow(Gitlab::Redis::Cache).to receive(:rails_root).and_return(rails_root)
allow(Gitlab::Redis::SharedState).to receive(:config_file_name).and_return(config_new_format_host)
allow(Gitlab::Redis::Cache).to receive(:config_file_name).and_return(config_new_format_socket)
end
around do |example|
clear_pool
example.run
ensure
clear_pool
end
it 'instantiates an instance of MultiStore' do
subject.with do |redis_instance|
expect(redis_instance).to be_instance_of(::Gitlab::Redis::MultiStore)
expect(redis_instance.primary_store.connection[:id]).to eq("unix:///path/to/redis.sock/0")
expect(redis_instance.secondary_store.connection[:id]).to eq("redis://test-host:6379/99")
expect(redis_instance.instance_name).to eq('EtagCache')
end
end
it_behaves_like 'multi store feature flags', :use_primary_and_secondary_stores_for_etag_cache,
:use_primary_store_as_default_for_etag_cache
end
describe '#store_name' do
it 'returns the name of the Cache store' do
expect(described_class.store_name).to eq('Cache')
end
end
end

View 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

View File

@ -3,13 +3,14 @@
require 'spec_helper'
RSpec.describe Milestoneable do
let(:user) { create(:user) }
let(:milestone) { create(:milestone, project: project) }
let_it_be(:group, reload: true) { create(:group) }
let_it_be(:project) { create(:project, :repository, group: group) }
let_it_be(:user) { create(:user) }
let_it_be(:milestone) { create(:milestone, project: project) }
shared_examples_for 'an object that can be assigned a milestone' do
describe 'Validation' do
describe 'milestone' do
let(:project) { create(:project, :repository) }
let(:milestone_id) { milestone.id }
subject { milestoneable_class.new(params) }
@ -39,8 +40,6 @@ RSpec.describe Milestoneable do
end
describe '#milestone_available?' do
let(:group) { create(:group) }
let(:project) { create(:project, group: group) }
let(:issue) { create(:issue, project: project) }
def build_milestoneable(milestone_id)
@ -62,9 +61,9 @@ RSpec.describe Milestoneable do
it 'returns true with a milestone from the the parent of the issue project group' do
parent = create(:group)
group.update!(parent: parent)
milestone = create(:milestone, group: parent)
parent_milestone = create(:milestone, group: parent)
expect(build_milestoneable(milestone.id).milestone_available?).to be(true)
expect(build_milestoneable(parent_milestone.id).milestone_available?).to be(true)
end
it 'returns true with a blank milestone' do
@ -86,9 +85,6 @@ RSpec.describe Milestoneable do
end
describe '#supports_milestone?' do
let(:group) { create(:group) }
let(:project) { create(:project, group: group) }
context "for issues" do
let(:issue) { build(:issue, project: project) }
@ -215,6 +211,15 @@ RSpec.describe Milestoneable do
end
it_behaves_like 'an object that can be assigned a milestone'
describe '#milestone_available?' do
it 'returns true with a milestone from the issue group' do
milestone = create(:milestone, group: group)
milestoneable = milestoneable_class.new(namespace: group, milestone_id: milestone.id)
expect(milestoneable.milestone_available?).to be_truthy
end
end
end
context 'MergeRequests' do

Some files were not shown because too many files have changed in this diff Show More