New upstream version 14.6.4+ds1
This commit is contained in:
parent
09bec643e8
commit
1beab03f69
90 changed files with 1002 additions and 278 deletions
|
@ -2,6 +2,10 @@
|
||||||
documentation](doc/development/changelog.md) for instructions on adding your own
|
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||||
entry.
|
entry.
|
||||||
|
|
||||||
|
## 14.6.4 (2022-02-03)
|
||||||
|
|
||||||
|
No changes.
|
||||||
|
|
||||||
## 14.6.3 (2022-01-18)
|
## 14.6.3 (2022-01-18)
|
||||||
|
|
||||||
### Fixed (4 changes)
|
### Fixed (4 changes)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
14.6.3
|
14.6.4
|
2
Gemfile
2
Gemfile
|
@ -166,7 +166,7 @@ gem 'asciidoctor', '~> 2.0.10'
|
||||||
gem 'asciidoctor-include-ext', '~> 0.3.1', require: false
|
gem 'asciidoctor-include-ext', '~> 0.3.1', require: false
|
||||||
gem 'asciidoctor-plantuml', '~> 0.0.12'
|
gem 'asciidoctor-plantuml', '~> 0.0.12'
|
||||||
gem 'asciidoctor-kroki', '~> 0.5.0', require: false
|
gem 'asciidoctor-kroki', '~> 0.5.0', require: false
|
||||||
gem 'rouge', '~> 3.26.1'
|
gem 'rouge', '~> 3.27.0'
|
||||||
gem 'truncato', '~> 0.7.11'
|
gem 'truncato', '~> 0.7.11'
|
||||||
gem 'bootstrap_form', '~> 4.2.0'
|
gem 'bootstrap_form', '~> 4.2.0'
|
||||||
gem 'nokogiri', '~> 1.11.4'
|
gem 'nokogiri', '~> 1.11.4'
|
||||||
|
|
|
@ -1056,7 +1056,7 @@ GEM
|
||||||
rexml (3.2.5)
|
rexml (3.2.5)
|
||||||
rinku (2.0.0)
|
rinku (2.0.0)
|
||||||
rotp (6.2.0)
|
rotp (6.2.0)
|
||||||
rouge (3.26.1)
|
rouge (3.27.0)
|
||||||
rqrcode (0.7.0)
|
rqrcode (0.7.0)
|
||||||
chunky_png
|
chunky_png
|
||||||
rqrcode-rails3 (0.1.7)
|
rqrcode-rails3 (0.1.7)
|
||||||
|
@ -1605,7 +1605,7 @@ DEPENDENCIES
|
||||||
responders (~> 3.0)
|
responders (~> 3.0)
|
||||||
retriable (~> 3.1.2)
|
retriable (~> 3.1.2)
|
||||||
rexml (~> 3.2.5)
|
rexml (~> 3.2.5)
|
||||||
rouge (~> 3.26.1)
|
rouge (~> 3.27.0)
|
||||||
rqrcode-rails3 (~> 0.1.7)
|
rqrcode-rails3 (~> 0.1.7)
|
||||||
rspec-parameterized
|
rspec-parameterized
|
||||||
rspec-rails (~> 5.0.1)
|
rspec-rails (~> 5.0.1)
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
14.6.3
|
14.6.4
|
|
@ -1,4 +1,3 @@
|
||||||
import { escape } from 'lodash';
|
|
||||||
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
|
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
|
||||||
|
|
||||||
export default class CreateItemDropdown {
|
export default class CreateItemDropdown {
|
||||||
|
@ -37,14 +36,14 @@ export default class CreateItemDropdown {
|
||||||
},
|
},
|
||||||
selectable: true,
|
selectable: true,
|
||||||
toggleLabel(selected) {
|
toggleLabel(selected) {
|
||||||
return selected && 'id' in selected ? escape(selected.title) : this.defaultToggleLabel;
|
return selected && 'id' in selected ? selected.title : this.defaultToggleLabel;
|
||||||
},
|
},
|
||||||
fieldName: this.fieldName,
|
fieldName: this.fieldName,
|
||||||
text(item) {
|
text(item) {
|
||||||
return escape(item.text);
|
return item.text;
|
||||||
},
|
},
|
||||||
id(item) {
|
id(item) {
|
||||||
return escape(item.id);
|
return item.id;
|
||||||
},
|
},
|
||||||
onFilter: this.toggleCreateNewButton.bind(this),
|
onFilter: this.toggleCreateNewButton.bind(this),
|
||||||
clicked: (options) => {
|
clicked: (options) => {
|
||||||
|
|
|
@ -30,6 +30,9 @@ export default {
|
||||||
},
|
},
|
||||||
safeHtmlConfig: {
|
safeHtmlConfig: {
|
||||||
ADD_TAGS: ['use'], // to support icon SVGs
|
ADD_TAGS: ['use'], // to support icon SVGs
|
||||||
|
FORBID_TAGS: ['style'],
|
||||||
|
FORBID_ATTR: ['style'],
|
||||||
|
ALLOW_DATA_ATTR: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -299,7 +299,7 @@ export default {
|
||||||
|
|
||||||
<delete-package
|
<delete-package
|
||||||
@start="track($options.trackingActions.DELETE_PACKAGE_TRACKING_ACTION)"
|
@start="track($options.trackingActions.DELETE_PACKAGE_TRACKING_ACTION)"
|
||||||
@end="navigateToListWithSuccessModal"
|
@success="navigateToListWithSuccessModal"
|
||||||
>
|
>
|
||||||
<template #default="{ deletePackage }">
|
<template #default="{ deletePackage }">
|
||||||
<gl-modal
|
<gl-modal
|
||||||
|
|
|
@ -23,6 +23,12 @@ export default {
|
||||||
successMessage: DELETE_PACKAGE_SUCCESS_MESSAGE,
|
successMessage: DELETE_PACKAGE_SUCCESS_MESSAGE,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
errorMessageFrom(error) {
|
||||||
|
if (typeof error === 'string') {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
return error?.message || this.$options.i18n.errorMessage;
|
||||||
|
},
|
||||||
async deletePackage(packageEntity) {
|
async deletePackage(packageEntity) {
|
||||||
try {
|
try {
|
||||||
this.$emit('start');
|
this.$emit('start');
|
||||||
|
@ -44,9 +50,10 @@ export default {
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
this.$emit('success');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
createFlash({
|
createFlash({
|
||||||
message: this.$options.i18n.errorMessage,
|
message: this.errorMessageFrom(error),
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
captureError: true,
|
captureError: true,
|
||||||
error,
|
error,
|
||||||
|
|
|
@ -5,7 +5,17 @@ class JiraConnect::UsersController < ApplicationController
|
||||||
|
|
||||||
layout 'signup_onboarding'
|
layout 'signup_onboarding'
|
||||||
|
|
||||||
|
before_action :verify_return_to_url, only: [:show]
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@jira_app_link = params.delete(:return_to)
|
@jira_app_link = params.delete(:return_to)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def verify_return_to_url
|
||||||
|
return unless params[:return_to].present?
|
||||||
|
|
||||||
|
params.delete(:return_to) unless Integrations::Jira.valid_jira_cloud_url?(params[:return_to])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -74,7 +74,7 @@ class UsersFinder
|
||||||
def by_search(users)
|
def by_search(users)
|
||||||
return users unless params[:search].present?
|
return users unless params[:search].present?
|
||||||
|
|
||||||
users.search(params[:search])
|
users.search(params[:search], with_private_emails: current_user&.admin?)
|
||||||
end
|
end
|
||||||
|
|
||||||
def by_blocked(users)
|
def by_blocked(users)
|
||||||
|
|
|
@ -4,6 +4,7 @@ module Mutations
|
||||||
module Packages
|
module Packages
|
||||||
class Destroy < ::Mutations::BaseMutation
|
class Destroy < ::Mutations::BaseMutation
|
||||||
graphql_name 'DestroyPackage'
|
graphql_name 'DestroyPackage'
|
||||||
|
description 'Destroys a package and its related package files. Restricted to packages with a small number of files'
|
||||||
|
|
||||||
authorize :destroy_package
|
authorize :destroy_package
|
||||||
|
|
||||||
|
|
|
@ -432,6 +432,12 @@ module Types
|
||||||
::Security::CiConfiguration::SastParserService.new(object).configuration
|
::Security::CiConfiguration::SastParserService.new(object).configuration
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def service_desk_address
|
||||||
|
return unless Ability.allowed?(current_user, :admin_issue, project)
|
||||||
|
|
||||||
|
object.service_desk_address
|
||||||
|
end
|
||||||
|
|
||||||
def tag_list
|
def tag_list
|
||||||
object.topic_list
|
object.topic_list
|
||||||
end
|
end
|
||||||
|
|
|
@ -367,6 +367,10 @@ class ApplicationSetting < ApplicationRecord
|
||||||
validates :invisible_captcha_enabled,
|
validates :invisible_captcha_enabled,
|
||||||
inclusion: { in: [true, false], message: _('must be a boolean value') }
|
inclusion: { in: [true, false], message: _('must be a boolean value') }
|
||||||
|
|
||||||
|
validates :max_package_files_for_package_destruction,
|
||||||
|
allow_nil: false,
|
||||||
|
numericality: { only_integer: true, greater_than: 0 }
|
||||||
|
|
||||||
SUPPORTED_KEY_TYPES.each do |type|
|
SUPPORTED_KEY_TYPES.each do |type|
|
||||||
validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
|
validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
|
||||||
end
|
end
|
||||||
|
|
32
app/models/concerns/integrations/enable_ssl_verification.rb
Normal file
32
app/models/concerns/integrations/enable_ssl_verification.rb
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Integrations
|
||||||
|
module EnableSslVerification
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
prepended do
|
||||||
|
boolean_accessor :enable_ssl_verification
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize_properties
|
||||||
|
super
|
||||||
|
|
||||||
|
self.enable_ssl_verification = true if new_record? && enable_ssl_verification.nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
def fields
|
||||||
|
super.tap do |fields|
|
||||||
|
url_index = fields.index { |field| field[:name].ends_with?('_url') }
|
||||||
|
insert_index = url_index ? url_index + 1 : -1
|
||||||
|
|
||||||
|
fields.insert(insert_index, {
|
||||||
|
type: 'checkbox',
|
||||||
|
name: 'enable_ssl_verification',
|
||||||
|
title: s_('Integrations|SSL verification'),
|
||||||
|
checkbox_label: s_('Integrations|Enable SSL verification'),
|
||||||
|
help: s_('Integrations|Clear if using a self-signed certificate.')
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -15,7 +15,11 @@ module Integrations
|
||||||
|
|
||||||
# Return whether the webhook should use SSL verification.
|
# Return whether the webhook should use SSL verification.
|
||||||
def hook_ssl_verification
|
def hook_ssl_verification
|
||||||
true
|
if respond_to?(:enable_ssl_verification)
|
||||||
|
enable_ssl_verification
|
||||||
|
else
|
||||||
|
true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Create or update the webhook, raising an exception if it cannot be saved.
|
# Create or update the webhook, raising an exception if it cannot be saved.
|
||||||
|
|
|
@ -4,6 +4,7 @@ module Integrations
|
||||||
class Bamboo < BaseCi
|
class Bamboo < BaseCi
|
||||||
include ActionView::Helpers::UrlHelper
|
include ActionView::Helpers::UrlHelper
|
||||||
include ReactivelyCached
|
include ReactivelyCached
|
||||||
|
prepend EnableSslVerification
|
||||||
|
|
||||||
prop_accessor :bamboo_url, :build_key, :username, :password
|
prop_accessor :bamboo_url, :build_key, :username, :password
|
||||||
|
|
||||||
|
@ -162,7 +163,7 @@ module Integrations
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_get_params(query_params)
|
def build_get_params(query_params)
|
||||||
params = { verify: false, query: query_params }
|
params = { verify: enable_ssl_verification, query: query_params }
|
||||||
return params if username.blank? && password.blank?
|
return params if username.blank? && password.blank?
|
||||||
|
|
||||||
query_params[:os_authType] = 'basic'
|
query_params[:os_authType] = 'basic'
|
||||||
|
|
|
@ -137,7 +137,7 @@ module Integrations
|
||||||
end
|
end
|
||||||
|
|
||||||
def request_options
|
def request_options
|
||||||
{ verify: false, extra_log_info: { project_id: project_id } }
|
{ extra_log_info: { project_id: project_id } }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,10 +5,12 @@ module Integrations
|
||||||
include HasWebHook
|
include HasWebHook
|
||||||
include PushDataValidations
|
include PushDataValidations
|
||||||
include ReactivelyCached
|
include ReactivelyCached
|
||||||
|
prepend EnableSslVerification
|
||||||
extend Gitlab::Utils::Override
|
extend Gitlab::Utils::Override
|
||||||
|
|
||||||
|
DRONE_SAAS_HOSTNAME = 'cloud.drone.io'
|
||||||
|
|
||||||
prop_accessor :drone_url, :token
|
prop_accessor :drone_url, :token
|
||||||
boolean_accessor :enable_ssl_verification
|
|
||||||
|
|
||||||
validates :drone_url, presence: true, public_url: true, if: :activated?
|
validates :drone_url, presence: true, public_url: true, if: :activated?
|
||||||
validates :token, presence: true, if: :activated?
|
validates :token, presence: true, if: :activated?
|
||||||
|
@ -95,8 +97,7 @@ module Integrations
|
||||||
def fields
|
def fields
|
||||||
[
|
[
|
||||||
{ type: 'text', name: 'token', help: s_('ProjectService|Token for the Drone project.'), required: true },
|
{ type: 'text', name: 'token', help: s_('ProjectService|Token for the Drone project.'), required: true },
|
||||||
{ type: 'text', name: 'drone_url', title: s_('ProjectService|Drone server URL'), placeholder: 'http://drone.example.com', required: true },
|
{ type: 'text', name: 'drone_url', title: s_('ProjectService|Drone server URL'), placeholder: 'http://drone.example.com', required: true }
|
||||||
{ type: 'checkbox', name: 'enable_ssl_verification', title: "Enable SSL verification" }
|
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -105,15 +106,24 @@ module Integrations
|
||||||
[drone_url, "/hook", "?owner=#{project.namespace.full_path}", "&name=#{project.path}", "&access_token=#{token}"].join
|
[drone_url, "/hook", "?owner=#{project.namespace.full_path}", "&name=#{project.path}", "&access_token=#{token}"].join
|
||||||
end
|
end
|
||||||
|
|
||||||
override :hook_ssl_verification
|
|
||||||
def hook_ssl_verification
|
|
||||||
!!enable_ssl_verification
|
|
||||||
end
|
|
||||||
|
|
||||||
override :update_web_hook!
|
override :update_web_hook!
|
||||||
def update_web_hook!
|
def update_web_hook!
|
||||||
# If using a service template, project may not be available
|
# If using a service template, project may not be available
|
||||||
super if project
|
super if project
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def enable_ssl_verification
|
||||||
|
original_value = Gitlab::Utils.to_boolean(properties['enable_ssl_verification'])
|
||||||
|
original_value.nil? ? (new_record? || url_is_saas?) : original_value
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def url_is_saas?
|
||||||
|
parsed_url = Addressable::URI.parse(drone_url)
|
||||||
|
parsed_url&.scheme == 'https' && parsed_url.hostname == DRONE_SAAS_HOSTNAME
|
||||||
|
rescue Addressable::URI::InvalidURIError
|
||||||
|
false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,6 +4,7 @@ module Integrations
|
||||||
class Jenkins < BaseCi
|
class Jenkins < BaseCi
|
||||||
include HasWebHook
|
include HasWebHook
|
||||||
include ActionView::Helpers::UrlHelper
|
include ActionView::Helpers::UrlHelper
|
||||||
|
prepend EnableSslVerification
|
||||||
extend Gitlab::Utils::Override
|
extend Gitlab::Utils::Override
|
||||||
|
|
||||||
prop_accessor :jenkins_url, :project_name, :username, :password
|
prop_accessor :jenkins_url, :project_name, :username, :password
|
||||||
|
|
|
@ -56,6 +56,12 @@ module Integrations
|
||||||
@reference_pattern ||= /(?<issue>\b#{Gitlab::Regex.jira_issue_key_regex})/
|
@reference_pattern ||= /(?<issue>\b#{Gitlab::Regex.jira_issue_key_regex})/
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.valid_jira_cloud_url?(url)
|
||||||
|
return false unless url.present?
|
||||||
|
|
||||||
|
!!URI(url).hostname&.end_with?(JIRA_CLOUD_HOST)
|
||||||
|
end
|
||||||
|
|
||||||
def initialize_properties
|
def initialize_properties
|
||||||
{}
|
{}
|
||||||
end
|
end
|
||||||
|
@ -569,7 +575,7 @@ module Integrations
|
||||||
end
|
end
|
||||||
|
|
||||||
def jira_cloud?
|
def jira_cloud?
|
||||||
server_info['deploymentType'] == 'Cloud' || URI(client_url).hostname.end_with?(JIRA_CLOUD_HOST)
|
server_info['deploymentType'] == 'Cloud' || self.class.valid_jira_cloud_url?(client_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_deployment_type_from_url
|
def set_deployment_type_from_url
|
||||||
|
@ -582,7 +588,7 @@ module Integrations
|
||||||
# we can only assume it's either Cloud or Server
|
# we can only assume it's either Cloud or Server
|
||||||
# based on the URL being *.atlassian.net
|
# based on the URL being *.atlassian.net
|
||||||
|
|
||||||
if URI(client_url).hostname.end_with?(JIRA_CLOUD_HOST)
|
if self.class.valid_jira_cloud_url?(client_url)
|
||||||
data_fields.deployment_cloud!
|
data_fields.deployment_cloud!
|
||||||
else
|
else
|
||||||
data_fields.deployment_server!
|
data_fields.deployment_server!
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
# For an example companion mocking service, see https://gitlab.com/gitlab-org/gitlab-mock-ci-service
|
# For an example companion mocking service, see https://gitlab.com/gitlab-org/gitlab-mock-ci-service
|
||||||
module Integrations
|
module Integrations
|
||||||
class MockCi < BaseCi
|
class MockCi < BaseCi
|
||||||
|
prepend EnableSslVerification
|
||||||
|
|
||||||
ALLOWED_STATES = %w[failed canceled running pending success success-with-warnings skipped not_found].freeze
|
ALLOWED_STATES = %w[failed canceled running pending success success-with-warnings skipped not_found].freeze
|
||||||
|
|
||||||
prop_accessor :mock_service_url
|
prop_accessor :mock_service_url
|
||||||
|
@ -55,7 +57,7 @@ module Integrations
|
||||||
# # => 'running'
|
# # => 'running'
|
||||||
#
|
#
|
||||||
def commit_status(sha, ref)
|
def commit_status(sha, ref)
|
||||||
response = Gitlab::HTTP.get(commit_status_path(sha), verify: false, use_read_total_timeout: true)
|
response = Gitlab::HTTP.get(commit_status_path(sha), verify: enable_ssl_verification, use_read_total_timeout: true)
|
||||||
read_commit_status(response)
|
read_commit_status(response)
|
||||||
rescue Errno::ECONNREFUSED
|
rescue Errno::ECONNREFUSED
|
||||||
:error
|
:error
|
||||||
|
@ -68,19 +70,16 @@ module Integrations
|
||||||
end
|
end
|
||||||
|
|
||||||
def read_commit_status(response)
|
def read_commit_status(response)
|
||||||
return :error unless response.code == 200 || response.code == 404
|
return :pending if response.code == 404
|
||||||
|
return :error unless response.code == 200
|
||||||
|
|
||||||
status = if response.code == 404
|
begin
|
||||||
'pending'
|
status = Gitlab::Json.parse(response.body).try(:fetch, 'status', nil)
|
||||||
else
|
return status if ALLOWED_STATES.include?(status)
|
||||||
response['status']
|
rescue JSON::ParserError
|
||||||
end
|
|
||||||
|
|
||||||
if status.present? && ALLOWED_STATES.include?(status)
|
|
||||||
status
|
|
||||||
else
|
|
||||||
:error
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
:error
|
||||||
end
|
end
|
||||||
|
|
||||||
def testable?
|
def testable?
|
||||||
|
|
|
@ -4,6 +4,9 @@ module Integrations
|
||||||
class Teamcity < BaseCi
|
class Teamcity < BaseCi
|
||||||
include PushDataValidations
|
include PushDataValidations
|
||||||
include ReactivelyCached
|
include ReactivelyCached
|
||||||
|
prepend EnableSslVerification
|
||||||
|
|
||||||
|
TEAMCITY_SAAS_HOSTNAME = /\A[^\.]+\.teamcity\.com\z/i.freeze
|
||||||
|
|
||||||
prop_accessor :teamcity_url, :build_type, :username, :password
|
prop_accessor :teamcity_url, :build_type, :username, :password
|
||||||
|
|
||||||
|
@ -104,8 +107,20 @@ module Integrations
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def enable_ssl_verification
|
||||||
|
original_value = Gitlab::Utils.to_boolean(properties['enable_ssl_verification'])
|
||||||
|
original_value.nil? ? (new_record? || url_is_saas?) : original_value
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def url_is_saas?
|
||||||
|
parsed_url = Addressable::URI.parse(teamcity_url)
|
||||||
|
parsed_url&.scheme == 'https' && parsed_url.hostname.match?(TEAMCITY_SAAS_HOSTNAME)
|
||||||
|
rescue Addressable::URI::InvalidURIError
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
def execute_push(data)
|
def execute_push(data)
|
||||||
branch = Gitlab::Git.ref_name(data[:ref])
|
branch = Gitlab::Git.ref_name(data[:ref])
|
||||||
post_to_build_queue(data, branch) if push_valid?(data)
|
post_to_build_queue(data, branch) if push_valid?(data)
|
||||||
|
@ -155,7 +170,7 @@ module Integrations
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_path(path)
|
def get_path(path)
|
||||||
Gitlab::HTTP.try_get(build_url(path), verify: false, basic_auth: basic_auth, extra_log_info: { project_id: project_id }, use_read_total_timeout: true)
|
Gitlab::HTTP.try_get(build_url(path), verify: enable_ssl_verification, basic_auth: basic_auth, extra_log_info: { project_id: project_id }, use_read_total_timeout: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def post_to_build_queue(data, branch)
|
def post_to_build_queue(data, branch)
|
||||||
|
@ -165,6 +180,7 @@ module Integrations
|
||||||
"<buildType id=#{build_type.encode(xml: :attr)}/>"\
|
"<buildType id=#{build_type.encode(xml: :attr)}/>"\
|
||||||
'</build>',
|
'</build>',
|
||||||
headers: { 'Content-type' => 'application/xml' },
|
headers: { 'Content-type' => 'application/xml' },
|
||||||
|
verify: enable_ssl_verification,
|
||||||
basic_auth: basic_auth,
|
basic_auth: basic_auth,
|
||||||
use_read_total_timeout: true
|
use_read_total_timeout: true
|
||||||
)
|
)
|
||||||
|
|
|
@ -10,7 +10,7 @@ class SystemNoteMetadata < ApplicationRecord
|
||||||
# in the same project (i.e. with the same permissions)
|
# in the same project (i.e. with the same permissions)
|
||||||
TYPES_WITH_CROSS_REFERENCES = %w[
|
TYPES_WITH_CROSS_REFERENCES = %w[
|
||||||
commit cross_reference
|
commit cross_reference
|
||||||
close duplicate
|
closed duplicate
|
||||||
moved merge
|
moved merge
|
||||||
label milestone
|
label milestone
|
||||||
relate unrelate
|
relate unrelate
|
||||||
|
|
|
@ -658,6 +658,7 @@ class User < ApplicationRecord
|
||||||
# This method uses ILIKE on PostgreSQL.
|
# This method uses ILIKE on PostgreSQL.
|
||||||
#
|
#
|
||||||
# query - The search query as a String
|
# query - The search query as a String
|
||||||
|
# with_private_emails - include private emails in search
|
||||||
#
|
#
|
||||||
# Returns an ActiveRecord::Relation.
|
# Returns an ActiveRecord::Relation.
|
||||||
def search(query, **options)
|
def search(query, **options)
|
||||||
|
@ -670,14 +671,16 @@ class User < ApplicationRecord
|
||||||
CASE
|
CASE
|
||||||
WHEN users.name = :query THEN 0
|
WHEN users.name = :query THEN 0
|
||||||
WHEN users.username = :query THEN 1
|
WHEN users.username = :query THEN 1
|
||||||
WHEN users.email = :query THEN 2
|
WHEN users.public_email = :query THEN 2
|
||||||
ELSE 3
|
ELSE 3
|
||||||
END
|
END
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
sanitized_order_sql = Arel.sql(sanitize_sql_array([order, query: query]))
|
sanitized_order_sql = Arel.sql(sanitize_sql_array([order, query: query]))
|
||||||
|
|
||||||
search_with_secondary_emails(query).reorder(sanitized_order_sql, :name)
|
scope = options[:with_private_emails] ? search_with_secondary_emails(query) : search_with_public_emails(query)
|
||||||
|
|
||||||
|
scope.reorder(sanitized_order_sql, :name)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Limits the result set to users _not_ in the given query/list of IDs.
|
# Limits the result set to users _not_ in the given query/list of IDs.
|
||||||
|
@ -692,6 +695,18 @@ class User < ApplicationRecord
|
||||||
reorder(:name)
|
reorder(:name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def search_with_public_emails(query)
|
||||||
|
return none if query.blank?
|
||||||
|
|
||||||
|
query = query.downcase
|
||||||
|
|
||||||
|
where(
|
||||||
|
fuzzy_arel_match(:name, query)
|
||||||
|
.or(fuzzy_arel_match(:username, query))
|
||||||
|
.or(arel_table[:public_email].eq(query))
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def search_without_secondary_emails(query)
|
def search_without_secondary_emails(query)
|
||||||
return none if query.blank?
|
return none if query.blank?
|
||||||
|
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module ProtectedRefNameSanitizer
|
|
||||||
def sanitize_name(name)
|
|
||||||
name = CGI.unescapeHTML(name)
|
|
||||||
name = Sanitize.fragment(name)
|
|
||||||
|
|
||||||
# Sanitize.fragment escapes HTML chars, so unescape again to allow names
|
|
||||||
# like `feature->master`
|
|
||||||
CGI.unescapeHTML(name)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -7,6 +7,10 @@ module Packages
|
||||||
def execute
|
def execute
|
||||||
return service_response_error("You don't have access to this package", 403) unless user_can_delete_package?
|
return service_response_error("You don't have access to this package", 403) unless user_can_delete_package?
|
||||||
|
|
||||||
|
if too_many_package_files?
|
||||||
|
return service_response_error("It's not possible to delete a package with more than #{max_package_files} #{'file'.pluralize(max_package_files)}.", 400)
|
||||||
|
end
|
||||||
|
|
||||||
package.destroy!
|
package.destroy!
|
||||||
|
|
||||||
package.sync_maven_metadata(current_user)
|
package.sync_maven_metadata(current_user)
|
||||||
|
@ -26,6 +30,14 @@ module Packages
|
||||||
ServiceResponse.success(message: message)
|
ServiceResponse.success(message: message)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def too_many_package_files?
|
||||||
|
max_package_files < package.package_files.limit(max_package_files + 1).count
|
||||||
|
end
|
||||||
|
|
||||||
|
def max_package_files
|
||||||
|
::Gitlab::CurrentSettings.current_application_settings.max_package_files_for_package_destruction
|
||||||
|
end
|
||||||
|
|
||||||
def user_can_delete_package?
|
def user_can_delete_package?
|
||||||
can?(current_user, :destroy_package, package.project)
|
can?(current_user, :destroy_package, package.project)
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
module ProtectedBranches
|
module ProtectedBranches
|
||||||
class BaseService < ::BaseService
|
class BaseService < ::BaseService
|
||||||
include ProtectedRefNameSanitizer
|
|
||||||
|
|
||||||
# current_user - The user that performs the action
|
# current_user - The user that performs the action
|
||||||
# params - A hash of parameters
|
# params - A hash of parameters
|
||||||
def initialize(project, current_user = nil, params = {})
|
def initialize(project, current_user = nil, params = {})
|
||||||
|
@ -15,14 +13,5 @@ module ProtectedBranches
|
||||||
def after_execute(*)
|
def after_execute(*)
|
||||||
# overridden in EE::ProtectedBranches module
|
# overridden in EE::ProtectedBranches module
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def filtered_params
|
|
||||||
return unless params
|
|
||||||
|
|
||||||
params[:name] = sanitize_name(params[:name]) if params[:name].present?
|
|
||||||
params
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,7 +21,7 @@ module ProtectedBranches
|
||||||
end
|
end
|
||||||
|
|
||||||
def protected_branch
|
def protected_branch
|
||||||
@protected_branch ||= project.protected_branches.new(filtered_params)
|
@protected_branch ||= project.protected_branches.new(params)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,7 +8,7 @@ module ProtectedBranches
|
||||||
old_merge_access_levels = protected_branch.merge_access_levels.map(&:clone)
|
old_merge_access_levels = protected_branch.merge_access_levels.map(&:clone)
|
||||||
old_push_access_levels = protected_branch.push_access_levels.map(&:clone)
|
old_push_access_levels = protected_branch.push_access_levels.map(&:clone)
|
||||||
|
|
||||||
if protected_branch.update(filtered_params)
|
if protected_branch.update(params)
|
||||||
after_execute(protected_branch: protected_branch, old_merge_access_levels: old_merge_access_levels, old_push_access_levels: old_push_access_levels)
|
after_execute(protected_branch: protected_branch, old_merge_access_levels: old_merge_access_levels, old_push_access_levels: old_push_access_levels)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module ProtectedTags
|
|
||||||
class BaseService < ::BaseService
|
|
||||||
include ProtectedRefNameSanitizer
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def filtered_params
|
|
||||||
return unless params
|
|
||||||
|
|
||||||
params[:name] = sanitize_name(params[:name]) if params[:name].present?
|
|
||||||
params
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,13 +1,13 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module ProtectedTags
|
module ProtectedTags
|
||||||
class CreateService < ProtectedTags::BaseService
|
class CreateService < ::BaseService
|
||||||
attr_reader :protected_tag
|
attr_reader :protected_tag
|
||||||
|
|
||||||
def execute
|
def execute
|
||||||
raise Gitlab::Access::AccessDeniedError unless can?(current_user, :admin_project, project)
|
raise Gitlab::Access::AccessDeniedError unless can?(current_user, :admin_project, project)
|
||||||
|
|
||||||
project.protected_tags.create(filtered_params)
|
project.protected_tags.create(params)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module ProtectedTags
|
module ProtectedTags
|
||||||
class UpdateService < ProtectedTags::BaseService
|
class UpdateService < ::BaseService
|
||||||
def execute(protected_tag)
|
def execute(protected_tag)
|
||||||
raise Gitlab::Access::AccessDeniedError unless can?(current_user, :admin_project, project)
|
raise Gitlab::Access::AccessDeniedError unless can?(current_user, :admin_project, project)
|
||||||
|
|
||||||
protected_tag.update(filtered_params)
|
protected_tag.update(params)
|
||||||
protected_tag
|
protected_tag
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
= render "devise/shared/error_messages", resource: resource
|
= render "devise/shared/error_messages", resource: resource
|
||||||
.form-group
|
.form-group
|
||||||
= f.label :email
|
= f.label :email
|
||||||
= f.email_field :email, class: "form-control gl-form-input", required: true, title: _('Please provide a valid email address.'), value: nil
|
= f.email_field :email, class: "form-control gl-form-input", required: true, autocomplete: 'off', title: _('Please provide a valid email address.'), value: nil
|
||||||
|
|
||||||
%div
|
%div
|
||||||
- if recaptcha_enabled?
|
- if recaptcha_enabled?
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
= render "devise/shared/error_messages", resource: resource
|
= render "devise/shared/error_messages", resource: resource
|
||||||
.form-group
|
.form-group
|
||||||
= f.label :email
|
= f.label :email
|
||||||
= f.email_field :email, class: "form-control gl-form-input", required: true, value: params[:user_email], autofocus: true, title: _('Please provide a valid email address.')
|
= f.email_field :email, class: "form-control gl-form-input", required: true, autocomplete: 'off', value: params[:user_email], autofocus: true, title: _('Please provide a valid email address.')
|
||||||
.form-text.text-muted
|
.form-text.text-muted
|
||||||
= _('Requires your primary GitLab email address.')
|
= _('Requires your primary GitLab email address.')
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
= s_('GroupsNew|Navigate to user settings to find your %{link_start}personal access token%{link_end}.').html_safe % { link_start: pat_link_start, link_end: '</a>'.html_safe }
|
= s_('GroupsNew|Navigate to user settings to find your %{link_start}personal access token%{link_end}.').html_safe % { link_start: pat_link_start, link_end: '</a>'.html_safe }
|
||||||
= f.text_field :bulk_import_gitlab_access_token, placeholder: s_('GroupsNew|e.g. h8d3f016698e...'), class: 'gl-form-input gl-mt-3 col-xs-12 col-sm-8',
|
= f.text_field :bulk_import_gitlab_access_token, placeholder: s_('GroupsNew|e.g. h8d3f016698e...'), class: 'gl-form-input gl-mt-3 col-xs-12 col-sm-8',
|
||||||
required: true,
|
required: true,
|
||||||
|
autocomplete: 'off',
|
||||||
title: s_('GroupsNew|Please fill in your personal access token.'),
|
title: s_('GroupsNew|Please fill in your personal access token.'),
|
||||||
id: 'import_gitlab_token',
|
id: 'import_gitlab_token',
|
||||||
data: { qa_selector: 'import_gitlab_token' }
|
data: { qa_selector: 'import_gitlab_token' }
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
require 'json'
|
require 'json'
|
||||||
require 'socket'
|
require 'socket'
|
||||||
|
require 'resolv'
|
||||||
|
|
||||||
class IrkerWorker # rubocop:disable Scalability/IdempotentWorker
|
class IrkerWorker # rubocop:disable Scalability/IdempotentWorker
|
||||||
include ApplicationWorker
|
include ApplicationWorker
|
||||||
|
@ -43,9 +44,18 @@ class IrkerWorker # rubocop:disable Scalability/IdempotentWorker
|
||||||
private
|
private
|
||||||
|
|
||||||
def start_connection(irker_server, irker_port)
|
def start_connection(irker_server, irker_port)
|
||||||
|
ip_address = Resolv.getaddress(irker_server)
|
||||||
|
# handle IP6 addresses
|
||||||
|
domain = Resolv::IPv6::Regex.match?(ip_address) ? "[#{ip_address}]" : ip_address
|
||||||
|
|
||||||
begin
|
begin
|
||||||
@socket = TCPSocket.new irker_server, irker_port
|
Gitlab::UrlBlocker.validate!(
|
||||||
rescue Errno::ECONNREFUSED => e
|
"irc://#{domain}",
|
||||||
|
allow_localhost: allow_local_requests?,
|
||||||
|
allow_local_network: allow_local_requests?,
|
||||||
|
schemes: ['irc'])
|
||||||
|
@socket = TCPSocket.new ip_address, irker_port
|
||||||
|
rescue Errno::ECONNREFUSED, Gitlab::UrlBlocker::BlockedUrlError => e
|
||||||
logger.fatal "Can't connect to Irker daemon: #{e}"
|
logger.fatal "Can't connect to Irker daemon: #{e}"
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
@ -53,6 +63,10 @@ class IrkerWorker # rubocop:disable Scalability/IdempotentWorker
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def allow_local_requests?
|
||||||
|
Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
|
||||||
|
end
|
||||||
|
|
||||||
def send_to_irker(privmsg)
|
def send_to_irker(privmsg)
|
||||||
to_send = { to: @channels, privmsg: privmsg }
|
to_send = { to: @channels, privmsg: privmsg }
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddPackageFilesLimitToApplicationSettings < Gitlab::Database::Migration[1.0]
|
||||||
|
def change
|
||||||
|
add_column :application_settings, :max_package_files_for_package_destruction, :smallint, default: 100, null: false
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,15 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddApplicationSettingsPackageFilesLimitConstraints < Gitlab::Database::Migration[1.0]
|
||||||
|
CONSTRAINT_NAME = 'app_settings_max_package_files_for_package_destruction_positive'
|
||||||
|
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
def up
|
||||||
|
add_check_constraint :application_settings, 'max_package_files_for_package_destruction > 0', CONSTRAINT_NAME
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
remove_check_constraint :application_settings, CONSTRAINT_NAME
|
||||||
|
end
|
||||||
|
end
|
1
db/schema_migrations/20220113135449
Normal file
1
db/schema_migrations/20220113135449
Normal file
|
@ -0,0 +1 @@
|
||||||
|
46796379175ce9343907234d3ae14a417442c7c5ebbfcf6987db1d759eca2c3a
|
1
db/schema_migrations/20220113135924
Normal file
1
db/schema_migrations/20220113135924
Normal file
|
@ -0,0 +1 @@
|
||||||
|
2a230758c13111c9e3738794008c31a3608dda2f0d071fbde0ad3cd782d29162
|
|
@ -10481,9 +10481,11 @@ CREATE TABLE application_settings (
|
||||||
max_ssh_key_lifetime integer,
|
max_ssh_key_lifetime integer,
|
||||||
static_objects_external_storage_auth_token_encrypted text,
|
static_objects_external_storage_auth_token_encrypted text,
|
||||||
future_subscriptions jsonb DEFAULT '[]'::jsonb NOT NULL,
|
future_subscriptions jsonb DEFAULT '[]'::jsonb NOT NULL,
|
||||||
|
max_package_files_for_package_destruction smallint DEFAULT 100 NOT NULL,
|
||||||
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
|
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
|
||||||
CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)),
|
CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)),
|
||||||
CONSTRAINT app_settings_ext_pipeline_validation_service_url_text_limit CHECK ((char_length(external_pipeline_validation_service_url) <= 255)),
|
CONSTRAINT app_settings_ext_pipeline_validation_service_url_text_limit CHECK ((char_length(external_pipeline_validation_service_url) <= 255)),
|
||||||
|
CONSTRAINT app_settings_max_package_files_for_package_destruction_positive CHECK ((max_package_files_for_package_destruction > 0)),
|
||||||
CONSTRAINT app_settings_registry_exp_policies_worker_capacity_positive CHECK ((container_registry_expiration_policies_worker_capacity >= 0)),
|
CONSTRAINT app_settings_registry_exp_policies_worker_capacity_positive CHECK ((container_registry_expiration_policies_worker_capacity >= 0)),
|
||||||
CONSTRAINT app_settings_yaml_max_depth_positive CHECK ((max_yaml_depth > 0)),
|
CONSTRAINT app_settings_yaml_max_depth_positive CHECK ((max_yaml_depth > 0)),
|
||||||
CONSTRAINT app_settings_yaml_max_size_positive CHECK ((max_yaml_size_bytes > 0)),
|
CONSTRAINT app_settings_yaml_max_size_positive CHECK ((max_yaml_size_bytes > 0)),
|
||||||
|
|
|
@ -2154,6 +2154,8 @@ Input type: `DestroyNoteInput`
|
||||||
|
|
||||||
### `Mutation.destroyPackage`
|
### `Mutation.destroyPackage`
|
||||||
|
|
||||||
|
Destroys a package and its related package files. Restricted to packages with a small number of files.
|
||||||
|
|
||||||
Input type: `DestroyPackageInput`
|
Input type: `DestroyPackageInput`
|
||||||
|
|
||||||
#### Arguments
|
#### Arguments
|
||||||
|
@ -15647,7 +15649,7 @@ Represents an issue link of a vulnerability.
|
||||||
| Name | Type | Description |
|
| Name | Type | Description |
|
||||||
| ---- | ---- | ----------- |
|
| ---- | ---- | ----------- |
|
||||||
| <a id="vulnerabilityissuelinkid"></a>`id` | [`ID!`](#id) | GraphQL ID of the vulnerability. |
|
| <a id="vulnerabilityissuelinkid"></a>`id` | [`ID!`](#id) | GraphQL ID of the vulnerability. |
|
||||||
| <a id="vulnerabilityissuelinkissue"></a>`issue` | [`Issue!`](#issue) | Issue attached to issue link. |
|
| <a id="vulnerabilityissuelinkissue"></a>`issue` | [`Issue`](#issue) | Issue attached to issue link. |
|
||||||
| <a id="vulnerabilityissuelinklinktype"></a>`linkType` | [`VulnerabilityIssueLinkType!`](#vulnerabilityissuelinktype) | Type of the issue link. |
|
| <a id="vulnerabilityissuelinklinktype"></a>`linkType` | [`VulnerabilityIssueLinkType!`](#vulnerabilityissuelinktype) | Type of the issue link. |
|
||||||
|
|
||||||
### `VulnerabilityLink`
|
### `VulnerabilityLink`
|
||||||
|
|
|
@ -165,6 +165,7 @@ Parameters:
|
||||||
| Parameter | Type | Required | Description |
|
| Parameter | Type | Required | Description |
|
||||||
| --------- | ---- | -------- | ----------- |
|
| --------- | ---- | -------- | ----------- |
|
||||||
| `bamboo_url` | string | true | Bamboo root URL. For example, `https://bamboo.example.com`. |
|
| `bamboo_url` | string | true | Bamboo root URL. For example, `https://bamboo.example.com`. |
|
||||||
|
| `enable_ssl_verification` | boolean | false | Enable SSL verification. Defaults to true (enabled). |
|
||||||
| `build_key` | string | true | Bamboo build plan key like KEY |
|
| `build_key` | string | true | Bamboo build plan key like KEY |
|
||||||
| `username` | string | true | A user with API access, if applicable |
|
| `username` | string | true | A user with API access, if applicable |
|
||||||
| `password` | string | true | Password of the user |
|
| `password` | string | true | Password of the user |
|
||||||
|
@ -519,7 +520,7 @@ Parameters:
|
||||||
| --------- | ---- | -------- | ----------- |
|
| --------- | ---- | -------- | ----------- |
|
||||||
| `token` | string | true | Drone CI project specific token |
|
| `token` | string | true | Drone CI project specific token |
|
||||||
| `drone_url` | string | true | `http://drone.example.com` |
|
| `drone_url` | string | true | `http://drone.example.com` |
|
||||||
| `enable_ssl_verification` | boolean | false | Enable SSL verification |
|
| `enable_ssl_verification` | boolean | false | Enable SSL verification. Defaults to true (enabled). |
|
||||||
| `push_events` | boolean | false | Enable notifications for push events |
|
| `push_events` | boolean | false | Enable notifications for push events |
|
||||||
| `merge_requests_events` | boolean | false | Enable notifications for merge request events |
|
| `merge_requests_events` | boolean | false | Enable notifications for merge request events |
|
||||||
| `tag_push_events` | boolean | false | Enable notifications for tag push events |
|
| `tag_push_events` | boolean | false | Enable notifications for tag push events |
|
||||||
|
@ -1394,6 +1395,7 @@ Parameters:
|
||||||
| Parameter | Type | Required | Description |
|
| Parameter | Type | Required | Description |
|
||||||
| --------- | ---- | -------- | ----------- |
|
| --------- | ---- | -------- | ----------- |
|
||||||
| `teamcity_url` | string | true | TeamCity root URL. For example, `https://teamcity.example.com` |
|
| `teamcity_url` | string | true | TeamCity root URL. For example, `https://teamcity.example.com` |
|
||||||
|
| `enable_ssl_verification` | boolean | false | Enable SSL verification. Defaults to true (enabled). |
|
||||||
| `build_type` | string | true | Build configuration ID |
|
| `build_type` | string | true | Build configuration ID |
|
||||||
| `username` | string | true | A user with permissions to trigger a manual build |
|
| `username` | string | true | A user with permissions to trigger a manual build |
|
||||||
| `password` | string | true | The password of the user |
|
| `password` | string | true | The password of the user |
|
||||||
|
@ -1432,6 +1434,7 @@ Parameters:
|
||||||
| Parameter | Type | Required | Description |
|
| Parameter | Type | Required | Description |
|
||||||
| --------- | ---- | -------- | ----------- |
|
| --------- | ---- | -------- | ----------- |
|
||||||
| `jenkins_url` | string | true | Jenkins URL like `http://jenkins.example.com`. |
|
| `jenkins_url` | string | true | Jenkins URL like `http://jenkins.example.com`. |
|
||||||
|
| `enable_ssl_verification` | boolean | false | Enable SSL verification. Defaults to true (enabled). |
|
||||||
| `project_name` | string | true | The URL-friendly project name. Example: `my_project_name`. |
|
| `project_name` | string | true | The URL-friendly project name. Example: `my_project_name`. |
|
||||||
| `username` | string | false | Username for authentication with the Jenkins server, if authentication is required by the server. |
|
| `username` | string | false | Username for authentication with the Jenkins server, if authentication is required by the server. |
|
||||||
| `password` | string | false | Password for authentication with the Jenkins server, if authentication is required by the server. |
|
| `password` | string | false | Password for authentication with the Jenkins server, if authentication is required by the server. |
|
||||||
|
@ -1511,6 +1514,7 @@ Parameters:
|
||||||
| Parameter | Type | Required | Description |
|
| Parameter | Type | Required | Description |
|
||||||
| --------- | ---- | -------- | ----------- |
|
| --------- | ---- | -------- | ----------- |
|
||||||
| `mock_service_url` | string | true | `http://localhost:4004` |
|
| `mock_service_url` | string | true | `http://localhost:4004` |
|
||||||
|
| `enable_ssl_verification` | boolean | false | Enable SSL verification. Defaults to true (enabled). |
|
||||||
|
|
||||||
### Disable MockCI integration
|
### Disable MockCI integration
|
||||||
|
|
||||||
|
|
|
@ -262,11 +262,11 @@ respectively.
|
||||||
GET /groups/:id/billable_members
|
GET /groups/:id/billable_members
|
||||||
```
|
```
|
||||||
|
|
||||||
| Attribute | Type | Required | Description |
|
| Attribute | Type | Required | Description |
|
||||||
| --------- | ---- | -------- | ----------- |
|
| --------- | ---- | -------- |--------------------------------------------------------------------------------------------------------------|
|
||||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](index.md#namespaced-path-encoding) owned by the authenticated user |
|
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](index.md#namespaced-path-encoding) owned by the authenticated user |
|
||||||
| `search` | string | no | A query string to search for group members by name, username, or email. |
|
| `search` | string | no | A query string to search for group members by name, username, or public email. |
|
||||||
| `sort` | string | no | A query string containing parameters that specify the sort attribute and order. See supported values below.|
|
| `sort` | string | no | A query string containing parameters that specify the sort attribute and order. See supported values below. |
|
||||||
|
|
||||||
The supported values for the `sort` attribute are:
|
The supported values for the `sort` attribute are:
|
||||||
|
|
||||||
|
|
|
@ -333,6 +333,9 @@ By default, the `GET` request returns 20 results, because the API is [paginated]
|
||||||
|
|
||||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/9623) in GitLab 11.9.
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/9623) in GitLab 11.9.
|
||||||
|
|
||||||
|
WARNING:
|
||||||
|
This endpoint is restricted to the limit set in [Updating the package files limit](#updating-the-package-files-limit).
|
||||||
|
|
||||||
Deletes a project package.
|
Deletes a project package.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
|
@ -352,6 +355,17 @@ Can return the following status codes:
|
||||||
|
|
||||||
- `204 No Content`, if the package was deleted successfully.
|
- `204 No Content`, if the package was deleted successfully.
|
||||||
- `404 Not Found`, if the package was not found.
|
- `404 Not Found`, if the package was not found.
|
||||||
|
- `400 Bad Request`, if the package has too many package files.
|
||||||
|
|
||||||
|
### Updating the package files limit
|
||||||
|
|
||||||
|
For scalability reasons, deleting a package is limited to packages with less than 100 files. An
|
||||||
|
administrator can update this limit through the [Rails console](../administration/operations/rails_console.md#starting-a-rails-console-session).
|
||||||
|
For example, this command updates this limit to 50 files:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
ApplicationSetting.last.update(max_package_files_for_package_destruction: 50)
|
||||||
|
```
|
||||||
|
|
||||||
## Delete a package file
|
## Delete a package file
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,7 @@ GET /projects/:id/merge_requests/:merge_request_iid/status_checks
|
||||||
## Set status of an external status check
|
## Set status of an external status check
|
||||||
|
|
||||||
For a single merge request, use the API to inform GitLab that a merge request has passed a check by an external service.
|
For a single merge request, use the API to inform GitLab that a merge request has passed a check by an external service.
|
||||||
|
To set the status of an external check, the personal access token used must belong to a user with at least the developer role on the target project of the merge request.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
POST /projects/:id/merge_requests/:merge_request_iid/status_check_responses
|
POST /projects/:id/merge_requests/:merge_request_iid/status_check_responses
|
||||||
|
|
|
@ -39,7 +39,7 @@ GET /users
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also search for users by name, username, primary email, or secondary email, by using `?search=`. For example. `/users?search=John`.
|
You can also search for users by name, username or public email by using `?search=`. For example. `/users?search=John`.
|
||||||
|
|
||||||
In addition, you can lookup users by username:
|
In addition, you can lookup users by username:
|
||||||
|
|
||||||
|
|
|
@ -196,6 +196,12 @@ module API
|
||||||
type: String,
|
type: String,
|
||||||
desc: 'Bamboo root URL like https://bamboo.example.com'
|
desc: 'Bamboo root URL like https://bamboo.example.com'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
required: false,
|
||||||
|
name: :enable_ssl_verification,
|
||||||
|
type: Boolean,
|
||||||
|
desc: 'Enable SSL verification'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
name: :build_key,
|
name: :build_key,
|
||||||
|
@ -360,7 +366,7 @@ module API
|
||||||
required: false,
|
required: false,
|
||||||
name: :enable_ssl_verification,
|
name: :enable_ssl_verification,
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
desc: 'Enable SSL verification for communication'
|
desc: 'Enable SSL verification'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
'emails-on-push' => [
|
'emails-on-push' => [
|
||||||
|
@ -459,6 +465,12 @@ module API
|
||||||
type: String,
|
type: String,
|
||||||
desc: 'Jenkins root URL like https://jenkins.example.com'
|
desc: 'Jenkins root URL like https://jenkins.example.com'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
required: false,
|
||||||
|
name: :enable_ssl_verification,
|
||||||
|
type: Boolean,
|
||||||
|
desc: 'Enable SSL verification'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
name: :project_name,
|
name: :project_name,
|
||||||
|
@ -740,6 +752,12 @@ module API
|
||||||
type: String,
|
type: String,
|
||||||
desc: 'TeamCity root URL like https://teamcity.example.com'
|
desc: 'TeamCity root URL like https://teamcity.example.com'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
required: false,
|
||||||
|
name: :enable_ssl_verification,
|
||||||
|
type: Boolean,
|
||||||
|
desc: 'Enable SSL verification'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
name: :build_type,
|
name: :build_type,
|
||||||
|
|
|
@ -71,7 +71,11 @@ module API
|
||||||
.new(user_project, params[:package_id]).execute
|
.new(user_project, params[:package_id]).execute
|
||||||
|
|
||||||
destroy_conditionally!(package) do |package|
|
destroy_conditionally!(package) do |package|
|
||||||
::Packages::DestroyPackageService.new(container: package, current_user: current_user).execute
|
result = ::Packages::DestroyPackageService.new(container: package, current_user: current_user).execute
|
||||||
|
|
||||||
|
unless result.success?
|
||||||
|
render_api_error!(result.message, result.http_status)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,7 +6,7 @@ module Banzai
|
||||||
REGEX = %r{
|
REGEX = %r{
|
||||||
#{::Gitlab::Regex.markdown_code_or_html_blocks}
|
#{::Gitlab::Regex.markdown_code_or_html_blocks}
|
||||||
|
|
|
|
||||||
(?:
|
(?=^>>>\ *\n.*\n>>>\ *$)(?:
|
||||||
# Blockquote:
|
# Blockquote:
|
||||||
# >>>
|
# >>>
|
||||||
# Anything, including code and HTML blocks
|
# Anything, including code and HTML blocks
|
||||||
|
|
|
@ -148,9 +148,17 @@ module Gitlab
|
||||||
unless allow_local_network
|
unless allow_local_network
|
||||||
validate_local_network(address_info)
|
validate_local_network(address_info)
|
||||||
validate_link_local(address_info)
|
validate_link_local(address_info)
|
||||||
|
validate_shared_address(address_info)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate_shared_address(addrs_info)
|
||||||
|
netmask = IPAddr.new('100.64.0.0/10')
|
||||||
|
return unless addrs_info.any? { |addr| netmask.include?(addr.ip_address) }
|
||||||
|
|
||||||
|
raise BlockedUrlError, "Requests to the shared address space are not allowed"
|
||||||
|
end
|
||||||
|
|
||||||
def get_port(uri)
|
def get_port(uri)
|
||||||
uri.port || uri.default_port
|
uri.port || uri.default_port
|
||||||
end
|
end
|
||||||
|
|
|
@ -18998,6 +18998,9 @@ msgstr ""
|
||||||
msgid "Integrations|Browser limitations"
|
msgid "Integrations|Browser limitations"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Integrations|Clear if using a self-signed certificate."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Integrations|Comment detail:"
|
msgid "Integrations|Comment detail:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -19028,6 +19031,9 @@ msgstr ""
|
||||||
msgid "Integrations|Enable GitLab.com slash commands in a Slack workspace."
|
msgid "Integrations|Enable GitLab.com slash commands in a Slack workspace."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Integrations|Enable SSL verification"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Integrations|Enable comments"
|
msgid "Integrations|Enable comments"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -19106,6 +19112,9 @@ msgstr ""
|
||||||
msgid "Integrations|Return to GitLab for Jira"
|
msgid "Integrations|Return to GitLab for Jira"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Integrations|SSL verification"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Integrations|Save settings?"
|
msgid "Integrations|Save settings?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -150,7 +150,7 @@
|
||||||
"lowlight": "^1.20.0",
|
"lowlight": "^1.20.0",
|
||||||
"marked": "^0.3.12",
|
"marked": "^0.3.12",
|
||||||
"mathjax": "3",
|
"mathjax": "3",
|
||||||
"mermaid": "^8.13.4",
|
"mermaid": "^8.13.10",
|
||||||
"minimatch": "^3.0.4",
|
"minimatch": "^3.0.4",
|
||||||
"monaco-editor": "^0.25.2",
|
"monaco-editor": "^0.25.2",
|
||||||
"monaco-editor-webpack-plugin": "^4.0.0",
|
"monaco-editor-webpack-plugin": "^4.0.0",
|
||||||
|
|
|
@ -103,7 +103,7 @@ RSpec.describe 'Groups > Members > Manage members' do
|
||||||
find('[data-testid="members-token-select-input"]').set('undisclosed_email@gitlab.com')
|
find('[data-testid="members-token-select-input"]').set('undisclosed_email@gitlab.com')
|
||||||
wait_for_requests
|
wait_for_requests
|
||||||
|
|
||||||
expect(page).to have_content("Jane 'invisible' Doe")
|
expect(page).to have_content('Invite "undisclosed_email@gitlab.com" by email')
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when Invite Members modal is disabled' do
|
context 'when Invite Members modal is disabled' do
|
||||||
|
@ -129,7 +129,7 @@ RSpec.describe 'Groups > Members > Manage members' do
|
||||||
select_input.send_keys('undisclosed_email@gitlab.com')
|
select_input.send_keys('undisclosed_email@gitlab.com')
|
||||||
wait_for_requests
|
wait_for_requests
|
||||||
|
|
||||||
expect(page).to have_content("Jane 'invisible' Doe")
|
expect(page).to have_content('Invite "undisclosed_email@gitlab.com" by email')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -91,4 +91,62 @@ RSpec.describe 'Create notes on issues', :js do
|
||||||
|
|
||||||
expect(page).to have_selector '.gfm-project_member.current-user', text: user.username
|
expect(page).to have_selector '.gfm-project_member.current-user', text: user.username
|
||||||
end
|
end
|
||||||
|
|
||||||
|
shared_examples "when reference belongs to a private project" do
|
||||||
|
let(:project) { create(:project, :private, :repository) }
|
||||||
|
let(:issue) { create(:issue, project: project) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
sign_in(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the user does not have permission to see the reference' do
|
||||||
|
before do
|
||||||
|
project.add_guest(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not show the user the reference' do
|
||||||
|
visit project_issue_path(project, issue)
|
||||||
|
|
||||||
|
expect(page).not_to have_content('closed via')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the user has permission to see the reference' do
|
||||||
|
before do
|
||||||
|
project.add_developer(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'shows the user the reference' do
|
||||||
|
visit project_issue_path(project, issue)
|
||||||
|
|
||||||
|
page.within('div#notes li.note .system-note-message') do
|
||||||
|
expect(page).to have_content('closed via')
|
||||||
|
expect(page.find('a')).to have_content(reference_content)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the issue is closed via a merge request' do
|
||||||
|
it_behaves_like "when reference belongs to a private project" do
|
||||||
|
let(:reference) { create(:merge_request, source_project: project) }
|
||||||
|
let(:reference_content) { reference.to_reference }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:resource_state_event, issue: issue, state: :closed, created_at: '2020-02-05', source_merge_request: reference)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the issue is closed via a commit' do
|
||||||
|
it_behaves_like "when reference belongs to a private project" do
|
||||||
|
let(:reference) { create(:commit, project: project) }
|
||||||
|
let(:reference_content) { reference.short_sha }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:resource_state_event, issue: issue, state: :closed, created_at: '2020-02-05', source_commit: reference.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -49,7 +49,7 @@ RSpec.describe "User comments on issue", :js do
|
||||||
|
|
||||||
add_note(comment)
|
add_note(comment)
|
||||||
|
|
||||||
expect(page.find('svg.mermaid')).to have_content html_content
|
expect(page.find('svg.mermaid')).not_to have_content html_content
|
||||||
within('svg.mermaid') { expect(page).not_to have_selector('img') }
|
within('svg.mermaid') { expect(page).not_to have_selector('img') }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,22 @@ RSpec.describe 'Packages' do
|
||||||
expect(page).to have_content 'Package deleted successfully'
|
expect(page).to have_content 'Package deleted successfully'
|
||||||
expect(page).not_to have_content(package.name)
|
expect(page).not_to have_content(package.name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with too many package files' do
|
||||||
|
let_it_be(:package_files) { create_list(:package_file, 3, package: package) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_application_setting(max_package_files_for_package_destruction: 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns an error' do
|
||||||
|
first('[title="Remove package"]').click
|
||||||
|
click_button('Delete package')
|
||||||
|
|
||||||
|
expect(page).to have_content "It's not possible to delete a package with more than 1 file."
|
||||||
|
expect(page).to have_content(package.name)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'shared package sorting' do
|
it_behaves_like 'shared package sorting' do
|
||||||
|
|
|
@ -53,6 +53,17 @@ RSpec.describe 'Protected Branches', :js do
|
||||||
sign_in(user)
|
sign_in(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'allows to create a protected branch with name containing HTML tags' do
|
||||||
|
visit project_protected_branches_path(project)
|
||||||
|
set_defaults
|
||||||
|
set_protected_branch_name('foo<b>bar<\b>')
|
||||||
|
click_on "Protect"
|
||||||
|
|
||||||
|
within(".protected-branches-list") { expect(page).to have_content('foo<b>bar<\b>') }
|
||||||
|
expect(ProtectedBranch.count).to eq(1)
|
||||||
|
expect(ProtectedBranch.last.name).to eq('foo<b>bar<\b>')
|
||||||
|
end
|
||||||
|
|
||||||
describe 'Delete protected branch' do
|
describe 'Delete protected branch' do
|
||||||
before do
|
before do
|
||||||
create(:protected_branch, project: project, name: 'fix')
|
create(:protected_branch, project: project, name: 'fix')
|
||||||
|
|
|
@ -39,6 +39,12 @@ RSpec.describe UsersFinder do
|
||||||
expect(users).to contain_exactly(blocked_user)
|
expect(users).to contain_exactly(blocked_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'does not filter by private emails search' do
|
||||||
|
users = described_class.new(user, search: normal_user.email).execute
|
||||||
|
|
||||||
|
expect(users).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
it 'filters by blocked users' do
|
it 'filters by blocked users' do
|
||||||
users = described_class.new(user, blocked: true).execute
|
users = described_class.new(user, blocked: true).execute
|
||||||
|
|
||||||
|
@ -135,6 +141,12 @@ RSpec.describe UsersFinder do
|
||||||
|
|
||||||
expect(users).to contain_exactly(normal_user)
|
expect(users).to contain_exactly(normal_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'filters by private emails search' do
|
||||||
|
users = described_class.new(admin, search: normal_user.email).execute
|
||||||
|
|
||||||
|
expect(users).to contain_exactly(normal_user)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,6 +17,11 @@ const DROPDOWN_ITEM_DATA = [
|
||||||
id: 'three',
|
id: 'three',
|
||||||
text: 'three',
|
text: 'three',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '<b>four</b>title',
|
||||||
|
id: '<b>four</b>id',
|
||||||
|
text: '<b>four</b>text',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
describe('CreateItemDropdown', () => {
|
describe('CreateItemDropdown', () => {
|
||||||
|
@ -63,6 +68,10 @@ describe('CreateItemDropdown', () => {
|
||||||
const $itemEls = $wrapperEl.find('.js-dropdown-content a');
|
const $itemEls = $wrapperEl.find('.js-dropdown-content a');
|
||||||
|
|
||||||
expect($itemEls.length).toEqual(DROPDOWN_ITEM_DATA.length);
|
expect($itemEls.length).toEqual(DROPDOWN_ITEM_DATA.length);
|
||||||
|
|
||||||
|
DROPDOWN_ITEM_DATA.forEach((dataItem, i) => {
|
||||||
|
expect($($itemEls[i]).text()).toEqual(dataItem.text);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -177,7 +186,7 @@ describe('CreateItemDropdown', () => {
|
||||||
const $itemEls = $wrapperEl.find('.js-dropdown-content a');
|
const $itemEls = $wrapperEl.find('.js-dropdown-content a');
|
||||||
|
|
||||||
expect($itemEls.length).toEqual(1 + DROPDOWN_ITEM_DATA.length);
|
expect($itemEls.length).toEqual(1 + DROPDOWN_ITEM_DATA.length);
|
||||||
expect($($itemEls[3]).text()).toEqual('new-item-text');
|
expect($($itemEls[DROPDOWN_ITEM_DATA.length]).text()).toEqual('new-item-text');
|
||||||
expect($wrapperEl.find('.dropdown-toggle-text').text()).toEqual('new-item-title');
|
expect($wrapperEl.find('.dropdown-toggle-text').text()).toEqual('new-item-title');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,13 +16,20 @@ export default [
|
||||||
'text/html table',
|
'text/html table',
|
||||||
{
|
{
|
||||||
input: [
|
input: [
|
||||||
'<table>\n',
|
'<style type="text/css">\n',
|
||||||
|
'\n',
|
||||||
|
'body {\n',
|
||||||
|
' background: red;\n',
|
||||||
|
'}\n',
|
||||||
|
'\n',
|
||||||
|
'</style>\n',
|
||||||
|
'<table data-myattr="XSS">\n',
|
||||||
'<tr>\n',
|
'<tr>\n',
|
||||||
'<th>Header 1</th>\n',
|
'<th>Header 1</th>\n',
|
||||||
'<th>Header 2</th>\n',
|
'<th>Header 2</th>\n',
|
||||||
'</tr>\n',
|
'</tr>\n',
|
||||||
'<tr>\n',
|
'<tr>\n',
|
||||||
'<td>row 1, cell 1</td>\n',
|
'<td style="background: red;">row 1, cell 1</td>\n',
|
||||||
'<td>row 1, cell 2</td>\n',
|
'<td>row 1, cell 2</td>\n',
|
||||||
'</tr>\n',
|
'</tr>\n',
|
||||||
'<tr>\n',
|
'<tr>\n',
|
||||||
|
|
|
@ -209,7 +209,7 @@ describe('PackagesApp', () => {
|
||||||
|
|
||||||
await waitForPromises();
|
await waitForPromises();
|
||||||
|
|
||||||
findDeletePackage().vm.$emit('end');
|
findDeletePackage().vm.$emit('success');
|
||||||
|
|
||||||
expect(window.location.replace).toHaveBeenCalledWith(
|
expect(window.location.replace).toHaveBeenCalledWith(
|
||||||
'projectListUrl?showSuccessDeleteAlert=true',
|
'projectListUrl?showSuccessDeleteAlert=true',
|
||||||
|
@ -223,7 +223,7 @@ describe('PackagesApp', () => {
|
||||||
|
|
||||||
await waitForPromises();
|
await waitForPromises();
|
||||||
|
|
||||||
findDeletePackage().vm.$emit('end');
|
findDeletePackage().vm.$emit('success');
|
||||||
|
|
||||||
expect(window.location.replace).toHaveBeenCalledWith(
|
expect(window.location.replace).toHaveBeenCalledWith(
|
||||||
'groupListUrl?showSuccessDeleteAlert=true',
|
'groupListUrl?showSuccessDeleteAlert=true',
|
||||||
|
|
|
@ -99,12 +99,13 @@ describe('DeletePackage', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('on mutation success', () => {
|
describe('on mutation success', () => {
|
||||||
it('emits end event', async () => {
|
it('emits end and success events', async () => {
|
||||||
createComponent();
|
createComponent();
|
||||||
|
|
||||||
await clickOnButtonAndWait(eventPayload);
|
await clickOnButtonAndWait(eventPayload);
|
||||||
|
|
||||||
expect(wrapper.emitted('end')).toEqual([[]]);
|
expect(wrapper.emitted('end')).toEqual([[]]);
|
||||||
|
expect(wrapper.emitted('success')).toEqual([[]]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not call createFlash', async () => {
|
it('does not call createFlash', async () => {
|
||||||
|
@ -128,10 +129,10 @@ describe('DeletePackage', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe.each`
|
describe.each`
|
||||||
errorType | mutationResolverResponse
|
errorType | mutationResolverResponse | errorMessage
|
||||||
${'connectionError'} | ${jest.fn().mockRejectedValue()}
|
${'connectionError'} | ${jest.fn().mockRejectedValue()} | ${DeletePackage.i18n.errorMessage}
|
||||||
${'localError'} | ${jest.fn().mockResolvedValue(packageDestroyMutationError())}
|
${'localError'} | ${jest.fn().mockResolvedValue(packageDestroyMutationError())} | ${packageDestroyMutationError().errors[0].message}
|
||||||
`('on mutation $errorType', ({ mutationResolverResponse }) => {
|
`('on mutation $errorType', ({ mutationResolverResponse, errorMessage }) => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mutationResolver = mutationResolverResponse;
|
mutationResolver = mutationResolverResponse;
|
||||||
});
|
});
|
||||||
|
@ -150,7 +151,7 @@ describe('DeletePackage', () => {
|
||||||
await clickOnButtonAndWait(eventPayload);
|
await clickOnButtonAndWait(eventPayload);
|
||||||
|
|
||||||
expect(createFlash).toHaveBeenCalledWith({
|
expect(createFlash).toHaveBeenCalledWith({
|
||||||
message: DeletePackage.i18n.errorMessage,
|
message: expect.stringContaining(errorMessage),
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
captureError: true,
|
captureError: true,
|
||||||
error: expect.any(Error),
|
error: expect.any(Error),
|
||||||
|
|
|
@ -594,4 +594,45 @@ RSpec.describe GitlabSchema.types['Project'] do
|
||||||
expect(cluster_agent.agent_tokens.size).to be(count)
|
expect(cluster_agent.agent_tokens.size).to be(count)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'service_desk_address' do
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
let(:query) do
|
||||||
|
%(
|
||||||
|
query {
|
||||||
|
project(fullPath: "#{project.full_path}") {
|
||||||
|
id
|
||||||
|
serviceDeskAddress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
subject { GitlabSchema.execute(query, context: { current_user: user }).as_json }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(::Gitlab::ServiceDeskEmail).to receive(:enabled?) { true }
|
||||||
|
allow(::Gitlab::ServiceDeskEmail).to receive(:address_for_key) { 'address-suffix@example.com' }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when a user can admin issues' do
|
||||||
|
let(:project) { create(:project, :public, :service_desk_enabled) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
project.add_reporter(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'is present' do
|
||||||
|
expect(subject.dig('data', 'project', 'serviceDeskAddress')).to be_present
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when a user can not admin issues' do
|
||||||
|
let(:project) { create(:project, :public, :service_desk_disabled) }
|
||||||
|
|
||||||
|
it 'is empty' do
|
||||||
|
expect(subject.dig('data', 'project', 'serviceDeskAddress')).to be_blank
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,4 +17,14 @@ RSpec.describe Banzai::Filter::BlockquoteFenceFilter do
|
||||||
it 'allows trailing whitespace on blockquote fence lines' do
|
it 'allows trailing whitespace on blockquote fence lines' do
|
||||||
expect(filter(">>> \ntest\n>>> ")).to eq("\n> test\n")
|
expect(filter(">>> \ntest\n>>> ")).to eq("\n> test\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when incomplete blockquote fences with multiple blocks are present' do
|
||||||
|
it 'does not raise timeout error' do
|
||||||
|
test_string = ">>>#{"\n```\nfoo\n```" * 20}"
|
||||||
|
|
||||||
|
expect do
|
||||||
|
Timeout.timeout(2.seconds) { filter(test_string) }
|
||||||
|
end.not_to raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -279,6 +279,8 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when allow_local_network is' do
|
context 'when allow_local_network is' do
|
||||||
|
let(:shared_address_space_ips) { ['100.64.0.0', '100.64.127.127', '100.64.255.255'] }
|
||||||
|
|
||||||
let(:local_ips) do
|
let(:local_ips) do
|
||||||
[
|
[
|
||||||
'192.168.1.2',
|
'192.168.1.2',
|
||||||
|
@ -292,7 +294,8 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only do
|
||||||
'[::ffff:ac10:20]',
|
'[::ffff:ac10:20]',
|
||||||
'[feef::1]',
|
'[feef::1]',
|
||||||
'[fee2::]',
|
'[fee2::]',
|
||||||
'[fc00:bf8b:e62c:abcd:abcd:aaaa:aaaa:aaaa]'
|
'[fc00:bf8b:e62c:abcd:abcd:aaaa:aaaa:aaaa]',
|
||||||
|
*shared_address_space_ips
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -385,18 +388,7 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only do
|
||||||
'127.0.0.1',
|
'127.0.0.1',
|
||||||
'127.0.0.2',
|
'127.0.0.2',
|
||||||
'192.168.1.1',
|
'192.168.1.1',
|
||||||
'192.168.1.2',
|
*local_ips,
|
||||||
'0:0:0:0:0:ffff:192.168.1.2',
|
|
||||||
'::ffff:c0a8:102',
|
|
||||||
'10.0.0.2',
|
|
||||||
'0:0:0:0:0:ffff:10.0.0.2',
|
|
||||||
'::ffff:a00:2',
|
|
||||||
'172.16.0.2',
|
|
||||||
'0:0:0:0:0:ffff:172.16.0.2',
|
|
||||||
'::ffff:ac10:20',
|
|
||||||
'feef::1',
|
|
||||||
'fee2::',
|
|
||||||
'fc00:bf8b:e62c:abcd:abcd:aaaa:aaaa:aaaa',
|
|
||||||
'0:0:0:0:0:ffff:169.254.169.254',
|
'0:0:0:0:0:ffff:169.254.169.254',
|
||||||
'::ffff:a9fe:a9fe',
|
'::ffff:a9fe:a9fe',
|
||||||
'::ffff:169.254.168.100',
|
'::ffff:169.254.168.100',
|
||||||
|
|
|
@ -80,6 +80,9 @@ RSpec.describe ApplicationSetting do
|
||||||
it { is_expected.to validate_numericality_of(:dependency_proxy_ttl_group_policy_worker_capacity).only_integer.is_greater_than_or_equal_to(0) }
|
it { is_expected.to validate_numericality_of(:dependency_proxy_ttl_group_policy_worker_capacity).only_integer.is_greater_than_or_equal_to(0) }
|
||||||
it { is_expected.not_to allow_value(nil).for(:dependency_proxy_ttl_group_policy_worker_capacity) }
|
it { is_expected.not_to allow_value(nil).for(:dependency_proxy_ttl_group_policy_worker_capacity) }
|
||||||
|
|
||||||
|
it { is_expected.to validate_numericality_of(:max_package_files_for_package_destruction).only_integer.is_greater_than(0) }
|
||||||
|
it { is_expected.not_to allow_value(nil).for(:max_package_files_for_package_destruction) }
|
||||||
|
|
||||||
it { is_expected.to validate_numericality_of(:snippet_size_limit).only_integer.is_greater_than(0) }
|
it { is_expected.to validate_numericality_of(:snippet_size_limit).only_integer.is_greater_than(0) }
|
||||||
it { is_expected.to validate_numericality_of(:wiki_page_max_content_bytes).only_integer.is_greater_than_or_equal_to(1024) }
|
it { is_expected.to validate_numericality_of(:wiki_page_max_content_bytes).only_integer.is_greater_than_or_equal_to(1024) }
|
||||||
it { is_expected.to validate_presence_of(:max_artifacts_size) }
|
it { is_expected.to validate_presence_of(:max_artifacts_size) }
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe Integrations::EnableSslVerification do
|
||||||
|
let(:described_class) do
|
||||||
|
Class.new(Integration) do
|
||||||
|
prepend Integrations::EnableSslVerification
|
||||||
|
|
||||||
|
def fields
|
||||||
|
[
|
||||||
|
{ name: 'main_url' },
|
||||||
|
{ name: 'other_url' },
|
||||||
|
{ name: 'username' }
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:integration) { described_class.new }
|
||||||
|
|
||||||
|
include_context Integrations::EnableSslVerification
|
||||||
|
end
|
|
@ -23,6 +23,8 @@ RSpec.describe Integrations::Bamboo, :use_clean_rails_memory_store_caching do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
include_context Integrations::EnableSslVerification
|
||||||
|
|
||||||
describe 'Validations' do
|
describe 'Validations' do
|
||||||
context 'when active' do
|
context 'when active' do
|
||||||
before do
|
before do
|
||||||
|
|
|
@ -5,6 +5,8 @@ require 'spec_helper'
|
||||||
RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do
|
RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do
|
||||||
include ReactiveCachingHelpers
|
include ReactiveCachingHelpers
|
||||||
|
|
||||||
|
subject(:integration) { described_class.new }
|
||||||
|
|
||||||
describe 'validations' do
|
describe 'validations' do
|
||||||
context 'active' do
|
context 'active' do
|
||||||
before do
|
before do
|
||||||
|
@ -59,6 +61,52 @@ RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
include_context Integrations::EnableSslVerification do
|
||||||
|
describe '#enable_ssl_verification' do
|
||||||
|
before do
|
||||||
|
allow(integration).to receive(:new_record?).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true for a known hostname' do
|
||||||
|
integration.drone_url = 'https://cloud.drone.io'
|
||||||
|
|
||||||
|
expect(integration.enable_ssl_verification).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true for new records' do
|
||||||
|
allow(integration).to receive(:new_record?).and_return(true)
|
||||||
|
integration.drone_url = 'http://example.com'
|
||||||
|
|
||||||
|
expect(integration.enable_ssl_verification).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false for an unknown hostname' do
|
||||||
|
integration.drone_url = 'https://example.com'
|
||||||
|
|
||||||
|
expect(integration.enable_ssl_verification).to be(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false for a HTTP URL' do
|
||||||
|
integration.drone_url = 'http://cloud.drone.io'
|
||||||
|
|
||||||
|
expect(integration.enable_ssl_verification).to be(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false for an invalid URL' do
|
||||||
|
integration.drone_url = 'https://example.com:foo'
|
||||||
|
|
||||||
|
expect(integration.enable_ssl_verification).to be(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns the persisted value if present' do
|
||||||
|
integration.drone_url = 'https://cloud.drone.io'
|
||||||
|
integration.enable_ssl_verification = false
|
||||||
|
|
||||||
|
expect(integration.enable_ssl_verification).to be(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it_behaves_like Integrations::HasWebHook do
|
it_behaves_like Integrations::HasWebHook do
|
||||||
include_context :drone_ci_integration
|
include_context :drone_ci_integration
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
require 'socket'
|
require 'socket'
|
||||||
|
require 'timeout'
|
||||||
require 'json'
|
require 'json'
|
||||||
|
|
||||||
RSpec.describe Integrations::Irker do
|
RSpec.describe Integrations::Irker do
|
||||||
|
@ -37,6 +38,7 @@ RSpec.describe Integrations::Irker do
|
||||||
before do
|
before do
|
||||||
@irker_server = TCPServer.new 'localhost', 0
|
@irker_server = TCPServer.new 'localhost', 0
|
||||||
|
|
||||||
|
allow(Gitlab::CurrentSettings).to receive(:allow_local_requests_from_web_hooks_and_services?).and_return(true)
|
||||||
allow(irker).to receive_messages(
|
allow(irker).to receive_messages(
|
||||||
active: true,
|
active: true,
|
||||||
project: project,
|
project: project,
|
||||||
|
@ -58,13 +60,17 @@ RSpec.describe Integrations::Irker do
|
||||||
irker.execute(sample_data)
|
irker.execute(sample_data)
|
||||||
|
|
||||||
conn = @irker_server.accept
|
conn = @irker_server.accept
|
||||||
conn.each_line do |line|
|
|
||||||
msg = Gitlab::Json.parse(line.chomp("\n"))
|
Timeout.timeout(5) do
|
||||||
expect(msg.keys).to match_array(%w(to privmsg))
|
conn.each_line do |line|
|
||||||
expect(msg['to']).to match_array(["irc://chat.freenode.net/#commits",
|
msg = Gitlab::Json.parse(line.chomp("\n"))
|
||||||
"irc://test.net/#test"])
|
expect(msg.keys).to match_array(%w(to privmsg))
|
||||||
|
expect(msg['to']).to match_array(["irc://chat.freenode.net/#commits",
|
||||||
|
"irc://test.net/#test"])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
conn.close
|
ensure
|
||||||
|
conn.close if conn
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -24,6 +24,10 @@ RSpec.describe Integrations::Jenkins do
|
||||||
|
|
||||||
let(:jenkins_authorization) { "Basic " + ::Base64.strict_encode64(jenkins_username + ':' + jenkins_password) }
|
let(:jenkins_authorization) { "Basic " + ::Base64.strict_encode64(jenkins_username + ':' + jenkins_password) }
|
||||||
|
|
||||||
|
include_context Integrations::EnableSslVerification do
|
||||||
|
let(:integration) { described_class.new(jenkins_params) }
|
||||||
|
end
|
||||||
|
|
||||||
it_behaves_like Integrations::HasWebHook do
|
it_behaves_like Integrations::HasWebHook do
|
||||||
let(:integration) { described_class.new(jenkins_params) }
|
let(:integration) { described_class.new(jenkins_params) }
|
||||||
let(:hook_url) { "http://#{ERB::Util.url_encode jenkins_username}:#{ERB::Util.url_encode jenkins_password}@jenkins.example.com/project/my_project" }
|
let(:hook_url) { "http://#{ERB::Util.url_encode jenkins_username}:#{ERB::Util.url_encode jenkins_password}@jenkins.example.com/project/my_project" }
|
||||||
|
|
|
@ -130,6 +130,23 @@ RSpec.describe Integrations::Jira do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '.valid_jira_cloud_url?' do
|
||||||
|
using RSpec::Parameterized::TableSyntax
|
||||||
|
|
||||||
|
where(:url, :result) do
|
||||||
|
'https://abc.atlassian.net' | true
|
||||||
|
'abc.atlassian.net' | false # This is how it behaves currently, but we may need to consider adding scheme if missing
|
||||||
|
'https://somethingelse.com' | false
|
||||||
|
nil | false
|
||||||
|
end
|
||||||
|
|
||||||
|
with_them do
|
||||||
|
specify do
|
||||||
|
expect(described_class.valid_jira_cloud_url?(url)).to eq(result)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#create' do
|
describe '#create' do
|
||||||
let(:params) do
|
let(:params) do
|
||||||
{
|
{
|
||||||
|
|
73
spec/models/integrations/mock_ci_spec.rb
Normal file
73
spec/models/integrations/mock_ci_spec.rb
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe Integrations::MockCi do
|
||||||
|
let_it_be(:project) { build(:project) }
|
||||||
|
|
||||||
|
subject(:integration) { described_class.new(project: project, mock_service_url: generate(:url)) }
|
||||||
|
|
||||||
|
include_context Integrations::EnableSslVerification
|
||||||
|
|
||||||
|
describe '#commit_status' do
|
||||||
|
let(:sha) { generate(:sha) }
|
||||||
|
|
||||||
|
def stub_request(*args)
|
||||||
|
WebMock.stub_request(:get, integration.commit_status_path(sha)).to_return(*args)
|
||||||
|
end
|
||||||
|
|
||||||
|
def commit_status
|
||||||
|
integration.commit_status(sha, 'master')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns allowed states' do
|
||||||
|
described_class::ALLOWED_STATES.each do |state|
|
||||||
|
stub_request(status: 200, body: { status: state }.to_json)
|
||||||
|
|
||||||
|
expect(commit_status).to eq(state)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns :pending for 404 responses' do
|
||||||
|
stub_request(status: 404)
|
||||||
|
|
||||||
|
expect(commit_status).to eq(:pending)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns :error for responses other than 200 or 404' do
|
||||||
|
stub_request(status: 500)
|
||||||
|
|
||||||
|
expect(commit_status).to eq(:error)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns :error for unknown states' do
|
||||||
|
stub_request(status: 200, body: { status: 'unknown' }.to_json)
|
||||||
|
|
||||||
|
expect(commit_status).to eq(:error)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns :error for invalid JSON' do
|
||||||
|
stub_request(status: 200, body: '')
|
||||||
|
|
||||||
|
expect(commit_status).to eq(:error)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns :error for non-hash JSON responses' do
|
||||||
|
stub_request(status: 200, body: 23.to_json)
|
||||||
|
|
||||||
|
expect(commit_status).to eq(:error)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns :error for JSON responses without a status' do
|
||||||
|
stub_request(status: 200, body: { foo: :bar }.to_json)
|
||||||
|
|
||||||
|
expect(commit_status).to eq(:error)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns :error when connection is refused' do
|
||||||
|
stub_request(status: 500).to_raise(Errno::ECONNREFUSED)
|
||||||
|
|
||||||
|
expect(commit_status).to eq(:error)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -6,8 +6,8 @@ RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do
|
||||||
include ReactiveCachingHelpers
|
include ReactiveCachingHelpers
|
||||||
include StubRequests
|
include StubRequests
|
||||||
|
|
||||||
let(:teamcity_url) { 'http://gitlab.com/teamcity' }
|
let(:teamcity_url) { 'https://gitlab.teamcity.com' }
|
||||||
let(:teamcity_full_url) { 'http://gitlab.com/teamcity/httpAuth/app/rest/builds/branch:unspecified:any,revision:123' }
|
let(:teamcity_full_url) { 'https://gitlab.teamcity.com/httpAuth/app/rest/builds/branch:unspecified:any,revision:123' }
|
||||||
let(:project) { create(:project) }
|
let(:project) { create(:project) }
|
||||||
|
|
||||||
subject(:integration) do
|
subject(:integration) do
|
||||||
|
@ -22,6 +22,52 @@ RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
include_context Integrations::EnableSslVerification do
|
||||||
|
describe '#enable_ssl_verification' do
|
||||||
|
before do
|
||||||
|
allow(integration).to receive(:new_record?).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true for a known hostname' do
|
||||||
|
integration.teamcity_url = 'https://example.teamcity.com'
|
||||||
|
|
||||||
|
expect(integration.enable_ssl_verification).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true for new records' do
|
||||||
|
allow(integration).to receive(:new_record?).and_return(true)
|
||||||
|
integration.teamcity_url = 'http://example.com'
|
||||||
|
|
||||||
|
expect(integration.enable_ssl_verification).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false for an unknown hostname' do
|
||||||
|
integration.teamcity_url = 'https://sub.example.teamcity.com'
|
||||||
|
|
||||||
|
expect(integration.enable_ssl_verification).to be(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false for a HTTP URL' do
|
||||||
|
integration.teamcity_url = 'http://example.teamcity.com'
|
||||||
|
|
||||||
|
expect(integration.enable_ssl_verification).to be(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false for an invalid URL' do
|
||||||
|
integration.teamcity_url = 'https://example.com:foo'
|
||||||
|
|
||||||
|
expect(integration.enable_ssl_verification).to be(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns the persisted value if present' do
|
||||||
|
integration.teamcity_url = 'https://example.teamcity.com'
|
||||||
|
integration.enable_ssl_verification = false
|
||||||
|
|
||||||
|
expect(integration.enable_ssl_verification).to be(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'Validations' do
|
describe 'Validations' do
|
||||||
context 'when integration is active' do
|
context 'when integration is active' do
|
||||||
before do
|
before do
|
||||||
|
@ -140,22 +186,22 @@ RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do
|
||||||
it 'returns a specific URL when status is 500' do
|
it 'returns a specific URL when status is 500' do
|
||||||
stub_request(status: 500)
|
stub_request(status: 500)
|
||||||
|
|
||||||
is_expected.to eq('http://gitlab.com/teamcity/viewLog.html?buildTypeId=foo')
|
is_expected.to eq("#{teamcity_url}/viewLog.html?buildTypeId=foo")
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns a build URL when teamcity_url has no trailing slash' do
|
it 'returns a build URL when teamcity_url has no trailing slash' do
|
||||||
stub_request(body: %q({"build":{"id":"666"}}))
|
stub_request(body: %q({"build":{"id":"666"}}))
|
||||||
|
|
||||||
is_expected.to eq('http://gitlab.com/teamcity/viewLog.html?buildId=666&buildTypeId=foo')
|
is_expected.to eq("#{teamcity_url}/viewLog.html?buildId=666&buildTypeId=foo")
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'teamcity_url has trailing slash' do
|
context 'teamcity_url has trailing slash' do
|
||||||
let(:teamcity_url) { 'http://gitlab.com/teamcity/' }
|
let(:teamcity_url) { 'https://gitlab.teamcity.com/' }
|
||||||
|
|
||||||
it 'returns a build URL' do
|
it 'returns a build URL' do
|
||||||
stub_request(body: %q({"build":{"id":"666"}}))
|
stub_request(body: %q({"build":{"id":"666"}}))
|
||||||
|
|
||||||
is_expected.to eq('http://gitlab.com/teamcity/viewLog.html?buildId=666&buildTypeId=foo')
|
is_expected.to eq('https://gitlab.teamcity.com/viewLog.html?buildId=666&buildTypeId=foo')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -299,7 +345,7 @@ RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do
|
||||||
end
|
end
|
||||||
|
|
||||||
def stub_post_to_build_queue(branch:)
|
def stub_post_to_build_queue(branch:)
|
||||||
teamcity_full_url = 'http://gitlab.com/teamcity/httpAuth/app/rest/buildQueue'
|
teamcity_full_url = "#{teamcity_url}/httpAuth/app/rest/buildQueue"
|
||||||
body ||= %Q(<build branchName=\"#{branch}\"><buildType id=\"foo\"/></build>)
|
body ||= %Q(<build branchName=\"#{branch}\"><buildType id=\"foo\"/></build>)
|
||||||
auth = %w(mic password)
|
auth = %w(mic password)
|
||||||
|
|
||||||
|
|
|
@ -2383,6 +2383,12 @@ RSpec.describe User do
|
||||||
|
|
||||||
describe '.search' do
|
describe '.search' do
|
||||||
let_it_be(:user) { create(:user, name: 'user', username: 'usern', email: 'email@example.com') }
|
let_it_be(:user) { create(:user, name: 'user', username: 'usern', email: 'email@example.com') }
|
||||||
|
let_it_be(:public_email) do
|
||||||
|
create(:email, :confirmed, user: user, email: 'publicemail@example.com').tap do |email|
|
||||||
|
user.update!(public_email: email.email)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
let_it_be(:user2) { create(:user, name: 'user name', username: 'username', email: 'someemail@example.com') }
|
let_it_be(:user2) { create(:user, name: 'user name', username: 'username', email: 'someemail@example.com') }
|
||||||
let_it_be(:user3) { create(:user, name: 'us', username: 'se', email: 'foo@example.com') }
|
let_it_be(:user3) { create(:user, name: 'us', username: 'se', email: 'foo@example.com') }
|
||||||
let_it_be(:email) { create(:email, user: user, email: 'alias@example.com') }
|
let_it_be(:email) { create(:email, user: user, email: 'alias@example.com') }
|
||||||
|
@ -2410,30 +2416,31 @@ RSpec.describe User do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'email matching' do
|
describe 'email matching' do
|
||||||
it 'returns users with a matching Email' do
|
it 'returns users with a matching public email' do
|
||||||
expect(described_class.search(user.email)).to eq([user])
|
expect(described_class.search(user.public_email)).to match_array([user])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not return users with a partially matching Email' do
|
it 'does not return users with a partially matching public email' do
|
||||||
expect(described_class.search(user.email[1...-1])).to be_empty
|
expect(described_class.search(user.public_email[1...-1])).to be_empty
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns users with a matching Email regardless of the casing' do
|
it 'returns users with a matching public email regardless of the casing' do
|
||||||
expect(described_class.search(user2.email.upcase)).to eq([user2])
|
expect(described_class.search(user.public_email.upcase)).to match_array([user])
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'secondary email matching' do
|
|
||||||
it 'returns users with a matching secondary email' do
|
|
||||||
expect(described_class.search(email.email)).to include(email.user)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not return users with a matching part of secondary email' do
|
it 'does not return users with a matching private email' do
|
||||||
expect(described_class.search(email.email[1...-1])).to be_empty
|
expect(described_class.search(user.email)).to be_empty
|
||||||
|
expect(described_class.search(email.email)).to be_empty
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns users with a matching secondary email regardless of the casing' do
|
context 'with private emails search' do
|
||||||
expect(described_class.search(email.email.upcase)).to include(email.user)
|
it 'returns users with matching private email' do
|
||||||
|
expect(described_class.search(user.email, with_private_emails: true)).to match_array([user])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns users with matching private secondary email' do
|
||||||
|
expect(described_class.search(email.email, with_private_emails: true)).to match_array([user])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -2534,6 +2541,80 @@ RSpec.describe User do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '.search_with_public_emails' do
|
||||||
|
let_it_be(:user) { create(:user, name: 'John Doe', username: 'john.doe', email: 'someone.1@example.com' ) }
|
||||||
|
let_it_be(:another_user) { create(:user, name: 'Albert Smith', username: 'albert.smith', email: 'another.2@example.com' ) }
|
||||||
|
let_it_be(:public_email) do
|
||||||
|
create(:email, :confirmed, user: another_user, email: 'alias@example.com').tap do |email|
|
||||||
|
another_user.update!(public_email: email.email)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
let_it_be(:secondary_email) do
|
||||||
|
create(:email, :confirmed, user: another_user, email: 'secondary@example.com')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns users with a matching name' do
|
||||||
|
expect(described_class.search_with_public_emails(user.name)).to match_array([user])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns users with a partially matching name' do
|
||||||
|
expect(described_class.search_with_public_emails(user.name[0..2])).to match_array([user])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns users with a matching name regardless of the casing' do
|
||||||
|
expect(described_class.search_with_public_emails(user.name.upcase)).to match_array([user])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns users with a matching public email' do
|
||||||
|
expect(described_class.search_with_public_emails(another_user.public_email)).to match_array([another_user])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not return users with a partially matching email' do
|
||||||
|
expect(described_class.search_with_public_emails(another_user.public_email[1...-1])).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns users with a matching email regardless of the casing' do
|
||||||
|
expect(described_class.search_with_public_emails(another_user.public_email.upcase)).to match_array([another_user])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns users with a matching username' do
|
||||||
|
expect(described_class.search_with_public_emails(user.username)).to match_array([user])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns users with a partially matching username' do
|
||||||
|
expect(described_class.search_with_public_emails(user.username[0..2])).to match_array([user])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns users with a matching username regardless of the casing' do
|
||||||
|
expect(described_class.search_with_public_emails(user.username.upcase)).to match_array([user])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not return users with a matching whole private email' do
|
||||||
|
expect(described_class.search_with_public_emails(user.email)).not_to include(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not return users with a matching whole private email' do
|
||||||
|
expect(described_class.search_with_public_emails(secondary_email.email)).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not return users with a matching part of secondary email' do
|
||||||
|
expect(described_class.search_with_public_emails(secondary_email.email[1...-1])).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not return users with a matching part of private email' do
|
||||||
|
expect(described_class.search_with_public_emails(user.email[1...-1])).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns no matches for an empty string' do
|
||||||
|
expect(described_class.search_with_public_emails('')).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns no matches for nil' do
|
||||||
|
expect(described_class.search_with_public_emails(nil)).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '.search_with_secondary_emails' do
|
describe '.search_with_secondary_emails' do
|
||||||
let_it_be(:user) { create(:user, name: 'John Doe', username: 'john.doe', email: 'someone.1@example.com' ) }
|
let_it_be(:user) { create(:user, name: 'John Doe', username: 'john.doe', email: 'someone.1@example.com' ) }
|
||||||
let_it_be(:another_user) { create(:user, name: 'Albert Smith', username: 'albert.smith', email: 'another.2@example.com' ) }
|
let_it_be(:another_user) { create(:user, name: 'Albert Smith', username: 'albert.smith', email: 'another.2@example.com' ) }
|
||||||
|
|
|
@ -88,6 +88,20 @@ RSpec.describe 'Destroying a package' do
|
||||||
|
|
||||||
expect(mutation_response['errors']).to eq(['Failed to remove the package'])
|
expect(mutation_response['errors']).to eq(['Failed to remove the package'])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with too many files' do
|
||||||
|
let_it_be(:package_files) { create_list(:package_file, 3, package: package) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_application_setting(max_package_files_for_package_destruction: 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns the errors in the response' do
|
||||||
|
mutation_request
|
||||||
|
|
||||||
|
expect(mutation_response['errors']).to match_array(["It's not possible to delete a package with more than 1 file."])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -355,6 +355,23 @@ RSpec.describe API::ProjectPackages do
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:no_content)
|
expect(response).to have_gitlab_http_status(:no_content)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with too many files' do
|
||||||
|
let!(:package_files) { create_list(:package_file, 3, package: package1) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_application_setting(max_package_files_for_package_destruction: 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns 400' do
|
||||||
|
project.add_maintainer(user)
|
||||||
|
|
||||||
|
delete api(package_url, user)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:bad_request)
|
||||||
|
expect(response.body).to include("It's not possible to delete a package with more than 1 file.")
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with a maven package' do
|
context 'with a maven package' do
|
||||||
|
|
35
spec/requests/jira_connect/users_controller_spec.rb
Normal file
35
spec/requests/jira_connect/users_controller_spec.rb
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe JiraConnect::UsersController do
|
||||||
|
describe 'GET /-/jira_connect/users' do
|
||||||
|
let_it_be(:user) { create(:user) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
sign_in(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a valid host' do
|
||||||
|
let(:return_to) { 'https://testcompany.atlassian.net/plugins/servlet/ac/gitlab-jira-connect-staging.gitlab.com/gitlab-configuration' }
|
||||||
|
|
||||||
|
it 'includes a return url' do
|
||||||
|
get '/-/jira_connect/users', params: { return_to: return_to }
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
expect(response.body).to include('Return to GitLab')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with an invalid host' do
|
||||||
|
let(:return_to) { 'https://evil.com' }
|
||||||
|
|
||||||
|
it 'does not include a return url' do
|
||||||
|
get '/-/jira_connect/users', params: { return_to: return_to }
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
expect(response.body).not_to include('Return to GitLab')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -30,6 +30,24 @@ RSpec.describe Packages::DestroyPackageService do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with too many package files' do
|
||||||
|
let!(:package_files) { create_list(:package_file, 2, package: package) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_application_setting(max_package_files_for_package_destruction: 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns an error ServiceResponse' do
|
||||||
|
response = service.execute
|
||||||
|
|
||||||
|
expect(package).not_to receive(:sync_maven_metadata)
|
||||||
|
expect(response).to be_a(ServiceResponse)
|
||||||
|
expect(response).to be_error
|
||||||
|
expect(response.message).to eq("It's not possible to delete a package with more than 1 file.")
|
||||||
|
expect(response.status).to eq(:error)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when the destroy is not successful' do
|
context 'when the destroy is not successful' do
|
||||||
before do
|
before do
|
||||||
allow(package).to receive(:destroy!).and_raise(StandardError, "test")
|
allow(package).to receive(:destroy!).and_raise(StandardError, "test")
|
||||||
|
|
|
@ -24,38 +24,14 @@ RSpec.describe ProtectedBranches::CreateService do
|
||||||
expect(project.protected_branches.last.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER])
|
expect(project.protected_branches.last.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER])
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when name has escaped HTML' do
|
context 'when protecting a branch with a name that contains HTML tags' do
|
||||||
let(:name) { 'feature->test' }
|
let(:name) { 'foo<b>bar<\b>' }
|
||||||
|
|
||||||
it 'creates the new protected branch matching the unescaped version' do
|
subject(:service) { described_class.new(project, user, params) }
|
||||||
|
|
||||||
|
it 'creates a new protected branch' do
|
||||||
expect { service.execute }.to change(ProtectedBranch, :count).by(1)
|
expect { service.execute }.to change(ProtectedBranch, :count).by(1)
|
||||||
expect(project.protected_branches.last.name).to eq('feature->test')
|
expect(project.protected_branches.last.name).to eq(name)
|
||||||
end
|
|
||||||
|
|
||||||
context 'and name contains HTML tags' do
|
|
||||||
let(:name) { '<b>master</b>' }
|
|
||||||
|
|
||||||
it 'creates the new protected branch with sanitized name' do
|
|
||||||
expect { service.execute }.to change(ProtectedBranch, :count).by(1)
|
|
||||||
expect(project.protected_branches.last.name).to eq('master')
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'and contains unsafe HTML' do
|
|
||||||
let(:name) { '<script>alert('foo');</script>' }
|
|
||||||
|
|
||||||
it 'does not create the new protected branch' do
|
|
||||||
expect { service.execute }.not_to change(ProtectedBranch, :count)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when name contains unescaped HTML tags' do
|
|
||||||
let(:name) { '<b>master</b>' }
|
|
||||||
|
|
||||||
it 'creates the new protected branch with sanitized name' do
|
|
||||||
expect { service.execute }.to change(ProtectedBranch, :count).by(1)
|
|
||||||
expect(project.protected_branches.last.name).to eq('master')
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -18,35 +18,14 @@ RSpec.describe ProtectedBranches::UpdateService do
|
||||||
expect(result.reload.name).to eq(params[:name])
|
expect(result.reload.name).to eq(params[:name])
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when name has escaped HTML' do
|
context 'when updating name of a protected branch to one that contains HTML tags' do
|
||||||
let(:new_name) { 'feature->test' }
|
let(:new_name) { 'foo<b>bar<\b>' }
|
||||||
|
let(:result) { service.execute(protected_branch) }
|
||||||
|
|
||||||
it 'updates protected branch name with unescaped HTML' do
|
subject(:service) { described_class.new(project, user, params) }
|
||||||
expect(result.reload.name).to eq('feature->test')
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'and name contains HTML tags' do
|
it 'updates a protected branch' do
|
||||||
let(:new_name) { '<b>master</b>' }
|
expect(result.reload.name).to eq(new_name)
|
||||||
|
|
||||||
it 'updates protected branch name with sanitized name' do
|
|
||||||
expect(result.reload.name).to eq('master')
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'and contains unsafe HTML' do
|
|
||||||
let(:new_name) { '<script>alert('foo');</script>' }
|
|
||||||
|
|
||||||
it 'does not update the protected branch' do
|
|
||||||
expect(result.reload.name).to eq(protected_branch.name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when name contains unescaped HTML tags' do
|
|
||||||
let(:new_name) { '<b>master</b>' }
|
|
||||||
|
|
||||||
it 'updates protected branch name with sanitized name' do
|
|
||||||
expect(result.reload.name).to eq('master')
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -22,38 +22,14 @@ RSpec.describe ProtectedTags::CreateService do
|
||||||
expect(project.protected_tags.last.create_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER])
|
expect(project.protected_tags.last.create_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER])
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when name has escaped HTML' do
|
context 'protecting a tag with a name that contains HTML tags' do
|
||||||
let(:name) { 'tag->test' }
|
let(:name) { 'foo<b>bar<\b>' }
|
||||||
|
|
||||||
it 'creates the new protected tag matching the unescaped version' do
|
subject(:service) { described_class.new(project, user, params) }
|
||||||
|
|
||||||
|
it 'creates a new protected tag' do
|
||||||
expect { service.execute }.to change(ProtectedTag, :count).by(1)
|
expect { service.execute }.to change(ProtectedTag, :count).by(1)
|
||||||
expect(project.protected_tags.last.name).to eq('tag->test')
|
expect(project.protected_tags.last.name).to eq(name)
|
||||||
end
|
|
||||||
|
|
||||||
context 'and name contains HTML tags' do
|
|
||||||
let(:name) { '<b>tag</b>' }
|
|
||||||
|
|
||||||
it 'creates the new protected tag with sanitized name' do
|
|
||||||
expect { service.execute }.to change(ProtectedTag, :count).by(1)
|
|
||||||
expect(project.protected_tags.last.name).to eq('tag')
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'and contains unsafe HTML' do
|
|
||||||
let(:name) { '<script>alert('foo');</script>' }
|
|
||||||
|
|
||||||
it 'does not create the new protected tag' do
|
|
||||||
expect { service.execute }.not_to change(ProtectedTag, :count)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when name contains unescaped HTML tags' do
|
|
||||||
let(:name) { '<b>tag</b>' }
|
|
||||||
|
|
||||||
it 'creates the new protected tag with sanitized name' do
|
|
||||||
expect { service.execute }.to change(ProtectedTag, :count).by(1)
|
|
||||||
expect(project.protected_tags.last.name).to eq('tag')
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -18,35 +18,14 @@ RSpec.describe ProtectedTags::UpdateService do
|
||||||
expect(result.reload.name).to eq(params[:name])
|
expect(result.reload.name).to eq(params[:name])
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when name has escaped HTML' do
|
context 'when updating protected tag with a name that contains HTML tags' do
|
||||||
let(:new_name) { 'tag->test' }
|
let(:new_name) { 'foo<b>bar<\b>' }
|
||||||
|
let(:result) { service.execute(protected_tag) }
|
||||||
|
|
||||||
it 'updates protected tag name with unescaped HTML' do
|
subject(:service) { described_class.new(project, user, params) }
|
||||||
expect(result.reload.name).to eq('tag->test')
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'and name contains HTML tags' do
|
it 'updates a protected tag' do
|
||||||
let(:new_name) { '<b>tag</b>' }
|
expect(result.reload.name).to eq(new_name)
|
||||||
|
|
||||||
it 'updates protected tag name with sanitized name' do
|
|
||||||
expect(result.reload.name).to eq('tag')
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'and contains unsafe HTML' do
|
|
||||||
let(:new_name) { '<script>alert('foo');</script>' }
|
|
||||||
|
|
||||||
it 'does not update the protected tag' do
|
|
||||||
expect(result.reload.name).to eq(protected_tag.name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when name contains unescaped HTML tags' do
|
|
||||||
let(:new_name) { '<b>tag</b>' }
|
|
||||||
|
|
||||||
it 'updates protected tag name with sanitized name' do
|
|
||||||
expect(result.reload.name).to eq('tag')
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,15 @@ module DnsHelpers
|
||||||
end
|
end
|
||||||
|
|
||||||
def permit_local_dns!
|
def permit_local_dns!
|
||||||
local_addresses = /\A(127|10)\.0\.0\.\d{1,3}|(192\.168|172\.16)\.\d{1,3}\.\d{1,3}|0\.0\.0\.0|localhost\z/i
|
local_addresses = %r{
|
||||||
|
\A
|
||||||
|
::1? | # IPV6
|
||||||
|
(127|10)\.0\.0\.\d{1,3} | # 127.0.0.x or 10.0.0.x local network
|
||||||
|
(192\.168|172\.16)\.\d{1,3}\.\d{1,3} | # 192.168.x.x or 172.16.x.x local network
|
||||||
|
0\.0\.0\.0 | # loopback
|
||||||
|
localhost
|
||||||
|
\z
|
||||||
|
}xi
|
||||||
allow(Addrinfo).to receive(:getaddrinfo).with(local_addresses, anything, nil, :STREAM).and_call_original
|
allow(Addrinfo).to receive(:getaddrinfo).with(local_addresses, anything, nil, :STREAM).and_call_original
|
||||||
allow(Addrinfo).to receive(:getaddrinfo).with(local_addresses, anything, nil, :STREAM, anything, anything, any_args).and_call_original
|
allow(Addrinfo).to receive(:getaddrinfo).with(local_addresses, anything, nil, :STREAM, anything, anything, any_args).and_call_original
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.shared_context Integrations::EnableSslVerification do
|
||||||
|
# This is added to the global setup, to make sure all calls to
|
||||||
|
# `Gitlab::HTTP` in the main model spec are passing the `verify:` option.
|
||||||
|
before do
|
||||||
|
allow(Gitlab::HTTP).to receive(:perform_request)
|
||||||
|
.with(anything, anything, include(verify: true))
|
||||||
|
.and_call_original
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'accessors' do
|
||||||
|
it { is_expected.to respond_to(:enable_ssl_verification) }
|
||||||
|
it { is_expected.to respond_to(:enable_ssl_verification?) }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#initialize_properties' do
|
||||||
|
it 'enables the setting by default' do
|
||||||
|
expect(integration.enable_ssl_verification).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not enable the setting if the record is already persisted' do
|
||||||
|
allow(integration).to receive(:new_record?).and_return(false)
|
||||||
|
|
||||||
|
integration.enable_ssl_verification = false
|
||||||
|
integration.send(:initialize_properties)
|
||||||
|
|
||||||
|
expect(integration.enable_ssl_verification).to be(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not enable the setting if a custom value was set' do
|
||||||
|
integration = described_class.new(enable_ssl_verification: false)
|
||||||
|
|
||||||
|
expect(integration.enable_ssl_verification).to be(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#fields' do
|
||||||
|
it 'inserts the checkbox field after the first URL field, or at the end' do
|
||||||
|
names = integration.fields.pluck(:name)
|
||||||
|
url_index = names.index { |name| name.ends_with?('_url') }
|
||||||
|
insert_index = url_index ? url_index + 1 : names.size - 1
|
||||||
|
|
||||||
|
expect(names.index('enable_ssl_verification')).to eq insert_index
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -37,6 +37,16 @@ RSpec.shared_examples Integrations::HasWebHook do
|
||||||
it 'returns a boolean' do
|
it 'returns a boolean' do
|
||||||
expect(integration.hook_ssl_verification).to be_in([true, false])
|
expect(integration.hook_ssl_verification).to be_in([true, false])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'delegates to #enable_ssl_verification if the concern is included' do
|
||||||
|
next unless integration.is_a?(Integrations::EnableSslVerification)
|
||||||
|
|
||||||
|
[true, false].each do |value|
|
||||||
|
integration.enable_ssl_verification = value
|
||||||
|
|
||||||
|
expect(integration.hook_ssl_verification).to be(value)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#update_web_hook!' do
|
describe '#update_web_hook!' do
|
||||||
|
|
|
@ -21,7 +21,7 @@ RSpec.describe IrkerWorker, '#perform' do
|
||||||
channels,
|
channels,
|
||||||
false,
|
false,
|
||||||
push_data,
|
push_data,
|
||||||
server_settings
|
HashWithIndifferentAccess.new(server_settings)
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -35,6 +35,14 @@ RSpec.describe IrkerWorker, '#perform' do
|
||||||
allow(tcp_socket).to receive(:close).and_return(true)
|
allow(tcp_socket).to receive(:close).and_return(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'local requests are not allowed' do
|
||||||
|
before do
|
||||||
|
allow(Gitlab::CurrentSettings).to receive(:allow_local_requests_from_web_hooks_and_services?).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect(worker.perform(*arguments)).to be_falsey }
|
||||||
|
end
|
||||||
|
|
||||||
context 'connection fails' do
|
context 'connection fails' do
|
||||||
before do
|
before do
|
||||||
allow(TCPSocket).to receive(:new).and_raise(Errno::ECONNREFUSED.new('test'))
|
allow(TCPSocket).to receive(:new).and_raise(Errno::ECONNREFUSED.new('test'))
|
||||||
|
@ -44,6 +52,11 @@ RSpec.describe IrkerWorker, '#perform' do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'connection successful' do
|
context 'connection successful' do
|
||||||
|
before do
|
||||||
|
allow(Gitlab::CurrentSettings)
|
||||||
|
.to receive(:allow_local_requests_from_web_hooks_and_services?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
it { expect(subject.perform(*arguments)).to be_truthy }
|
it { expect(subject.perform(*arguments)).to be_truthy }
|
||||||
|
|
||||||
context 'new branch' do
|
context 'new branch' do
|
||||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -8456,16 +8456,16 @@ merge2@^1.3.0:
|
||||||
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
||||||
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
||||||
|
|
||||||
mermaid@^8.13.4:
|
mermaid@^8.13.10:
|
||||||
version "8.13.4"
|
version "8.13.10"
|
||||||
resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.13.4.tgz#924cb85f39380285e0a99f245c66cfa61014a2e1"
|
resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.13.10.tgz#b9d733b178bbf7416b9b46e39d566c7c28b75688"
|
||||||
integrity sha512-zdWtsXabVy1PEAE25Jkm4zbTDlQe8rqNlTMq2B3j+D+NxDskJEY5OsgalarvNLsw+b5xFa1a8D1xcm/PijrDow==
|
integrity sha512-2ANep359uML87+wiYaWSu83eg9Qc0xCLnNJdCh100m4v0orS3fp8SScsZLcDSElRGHi+1zuVJsEEVEWH05+COQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@braintree/sanitize-url" "^3.1.0"
|
"@braintree/sanitize-url" "^3.1.0"
|
||||||
d3 "^7.0.0"
|
d3 "^7.0.0"
|
||||||
dagre "^0.8.5"
|
dagre "^0.8.5"
|
||||||
dagre-d3 "^0.6.4"
|
dagre-d3 "^0.6.4"
|
||||||
dompurify "2.3.3"
|
dompurify "2.3.4"
|
||||||
graphlib "^2.1.8"
|
graphlib "^2.1.8"
|
||||||
khroma "^1.4.1"
|
khroma "^1.4.1"
|
||||||
moment-mini "^2.24.0"
|
moment-mini "^2.24.0"
|
||||||
|
|
Loading…
Reference in a new issue