summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-02-03 11:34:26 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-02-03 11:34:26 +0000
commita195c625af869d1753e4283ed0784b2e96f89c0d (patch)
treecce86a8c9e486c6a89ca31b3cd7ee4ed9a1e5efc
parentfc01541880ed7da88f770a04116b6117ca32de44 (diff)
downloadgitlab-ce-a195c625af869d1753e4283ed0784b2e96f89c0d.tar.gz
Add latest changes from gitlab-org/security/gitlab@14-6-stable-ee
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue2
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/functional/delete_package.vue9
-rw-r--r--app/controllers/jira_connect/users_controller.rb10
-rw-r--r--app/graphql/mutations/packages/destroy.rb1
-rw-r--r--app/graphql/types/project_type.rb6
-rw-r--r--app/models/application_setting.rb4
-rw-r--r--app/models/concerns/integrations/enable_ssl_verification.rb32
-rw-r--r--app/models/concerns/integrations/has_web_hook.rb6
-rw-r--r--app/models/integrations/bamboo.rb3
-rw-r--r--app/models/integrations/buildkite.rb2
-rw-r--r--app/models/integrations/drone_ci.rb26
-rw-r--r--app/models/integrations/jenkins.rb1
-rw-r--r--app/models/integrations/jira.rb10
-rw-r--r--app/models/integrations/mock_ci.rb23
-rw-r--r--app/models/integrations/teamcity.rb18
-rw-r--r--app/models/system_note_metadata.rb2
-rw-r--r--app/services/packages/destroy_package_service.rb12
-rw-r--r--db/migrate/20220113135449_add_package_files_limit_to_application_settings.rb7
-rw-r--r--db/migrate/20220113135924_add_application_settings_package_files_limit_constraints.rb15
-rw-r--r--db/schema_migrations/202201131354491
-rw-r--r--db/schema_migrations/202201131359241
-rw-r--r--db/structure.sql2
-rw-r--r--doc/api/graphql/reference/index.md2
-rw-r--r--doc/api/integrations.md6
-rw-r--r--doc/api/packages.md14
-rw-r--r--lib/api/helpers/integrations_helpers.rb20
-rw-r--r--lib/api/project_packages.rb6
-rw-r--r--locale/gitlab.pot9
-rw-r--r--spec/features/issues/notes_on_issues_spec.rb58
-rw-r--r--spec/features/projects/packages_spec.rb16
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/app_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/functional/delete_package_spec.js13
-rw-r--r--spec/graphql/types/project_type_spec.rb41
-rw-r--r--spec/models/application_setting_spec.rb3
-rw-r--r--spec/models/concerns/integrations/enable_ssl_verification_spec.rb23
-rw-r--r--spec/models/integrations/bamboo_spec.rb2
-rw-r--r--spec/models/integrations/drone_ci_spec.rb48
-rw-r--r--spec/models/integrations/jenkins_spec.rb4
-rw-r--r--spec/models/integrations/jira_spec.rb17
-rw-r--r--spec/models/integrations/mock_ci_spec.rb73
-rw-r--r--spec/models/integrations/teamcity_spec.rb60
-rw-r--r--spec/requests/api/graphql/mutations/packages/destroy_spec.rb14
-rw-r--r--spec/requests/api/project_packages_spec.rb17
-rw-r--r--spec/requests/jira_connect/users_controller_spec.rb35
-rw-r--r--spec/services/packages/destroy_package_service_spec.rb18
-rw-r--r--spec/support/shared_contexts/models/concerns/integrations/enable_ssl_verification_shared_context.rb47
-rw-r--r--spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb10
49 files changed, 709 insertions, 50 deletions
diff --git a/Gemfile b/Gemfile
index 1d88df82967..9f152aa5218 100644
--- a/Gemfile
+++ b/Gemfile
@@ -166,7 +166,7 @@ gem 'asciidoctor', '~> 2.0.10'
gem 'asciidoctor-include-ext', '~> 0.3.1', require: false
gem 'asciidoctor-plantuml', '~> 0.0.12'
gem 'asciidoctor-kroki', '~> 0.5.0', require: false
-gem 'rouge', '~> 3.26.1'
+gem 'rouge', '~> 3.27.0'
gem 'truncato', '~> 0.7.11'
gem 'bootstrap_form', '~> 4.2.0'
gem 'nokogiri', '~> 1.11.4'
diff --git a/Gemfile.lock b/Gemfile.lock
index 2f30df91862..d15a27d60e3 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1056,7 +1056,7 @@ GEM
rexml (3.2.5)
rinku (2.0.0)
rotp (6.2.0)
- rouge (3.26.1)
+ rouge (3.27.0)
rqrcode (0.7.0)
chunky_png
rqrcode-rails3 (0.1.7)
@@ -1605,7 +1605,7 @@ DEPENDENCIES
responders (~> 3.0)
retriable (~> 3.1.2)
rexml (~> 3.2.5)
- rouge (~> 3.26.1)
+ rouge (~> 3.27.0)
rqrcode-rails3 (~> 0.1.7)
rspec-parameterized
rspec-rails (~> 5.0.1)
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue
index d49c1be5202..9f6b23d6cf6 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue
@@ -299,7 +299,7 @@ export default {
<delete-package
@start="track($options.trackingActions.DELETE_PACKAGE_TRACKING_ACTION)"
- @end="navigateToListWithSuccessModal"
+ @success="navigateToListWithSuccessModal"
>
<template #default="{ deletePackage }">
<gl-modal
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/functional/delete_package.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/functional/delete_package.vue
index 7a85fd3052e..7f7edd42c42 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/functional/delete_package.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/functional/delete_package.vue
@@ -23,6 +23,12 @@ export default {
successMessage: DELETE_PACKAGE_SUCCESS_MESSAGE,
},
methods: {
+ errorMessageFrom(error) {
+ if (typeof error === 'string') {
+ return error;
+ }
+ return error?.message || this.$options.i18n.errorMessage;
+ },
async deletePackage(packageEntity) {
try {
this.$emit('start');
@@ -44,9 +50,10 @@ export default {
type: 'success',
});
}
+ this.$emit('success');
} catch (error) {
createFlash({
- message: this.$options.i18n.errorMessage,
+ message: this.errorMessageFrom(error),
type: 'warning',
captureError: true,
error,
diff --git a/app/controllers/jira_connect/users_controller.rb b/app/controllers/jira_connect/users_controller.rb
index 569dc42fed3..a37c68de299 100644
--- a/app/controllers/jira_connect/users_controller.rb
+++ b/app/controllers/jira_connect/users_controller.rb
@@ -5,7 +5,17 @@ class JiraConnect::UsersController < ApplicationController
layout 'signup_onboarding'
+ before_action :verify_return_to_url, only: [:show]
+
def show
@jira_app_link = params.delete(:return_to)
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
diff --git a/app/graphql/mutations/packages/destroy.rb b/app/graphql/mutations/packages/destroy.rb
index 979a54da6bd..50b5190cbf6 100644
--- a/app/graphql/mutations/packages/destroy.rb
+++ b/app/graphql/mutations/packages/destroy.rb
@@ -4,6 +4,7 @@ module Mutations
module Packages
class Destroy < ::Mutations::BaseMutation
graphql_name 'DestroyPackage'
+ description 'Destroys a package and its related package files. Restricted to packages with a small number of files'
authorize :destroy_package
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index 3d2ee47a499..4e41ad57f4e 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -432,6 +432,12 @@ module Types
::Security::CiConfiguration::SastParserService.new(object).configuration
end
+ def service_desk_address
+ return unless Ability.allowed?(current_user, :admin_issue, project)
+
+ object.service_desk_address
+ end
+
def tag_list
object.topic_list
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 65472615f42..976340338d9 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -367,6 +367,10 @@ class ApplicationSetting < ApplicationRecord
validates :invisible_captcha_enabled,
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|
validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
end
diff --git a/app/models/concerns/integrations/enable_ssl_verification.rb b/app/models/concerns/integrations/enable_ssl_verification.rb
new file mode 100644
index 00000000000..11dc8a76a2b
--- /dev/null
+++ b/app/models/concerns/integrations/enable_ssl_verification.rb
@@ -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
diff --git a/app/models/concerns/integrations/has_web_hook.rb b/app/models/concerns/integrations/has_web_hook.rb
index dabe7152b18..bc28c32695c 100644
--- a/app/models/concerns/integrations/has_web_hook.rb
+++ b/app/models/concerns/integrations/has_web_hook.rb
@@ -15,7 +15,11 @@ module Integrations
# Return whether the webhook should use SSL verification.
def hook_ssl_verification
- true
+ if respond_to?(:enable_ssl_verification)
+ enable_ssl_verification
+ else
+ true
+ end
end
# Create or update the webhook, raising an exception if it cannot be saved.
diff --git a/app/models/integrations/bamboo.rb b/app/models/integrations/bamboo.rb
index 0774b84b69f..57767c63cf4 100644
--- a/app/models/integrations/bamboo.rb
+++ b/app/models/integrations/bamboo.rb
@@ -4,6 +4,7 @@ module Integrations
class Bamboo < BaseCi
include ActionView::Helpers::UrlHelper
include ReactivelyCached
+ prepend EnableSslVerification
prop_accessor :bamboo_url, :build_key, :username, :password
@@ -162,7 +163,7 @@ module Integrations
end
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?
query_params[:os_authType] = 'basic'
diff --git a/app/models/integrations/buildkite.rb b/app/models/integrations/buildkite.rb
index 9fad3a42647..90593d78a5d 100644
--- a/app/models/integrations/buildkite.rb
+++ b/app/models/integrations/buildkite.rb
@@ -137,7 +137,7 @@ module Integrations
end
def request_options
- { verify: false, extra_log_info: { project_id: project_id } }
+ { extra_log_info: { project_id: project_id } }
end
end
end
diff --git a/app/models/integrations/drone_ci.rb b/app/models/integrations/drone_ci.rb
index 856d14c022d..3c18e5d8732 100644
--- a/app/models/integrations/drone_ci.rb
+++ b/app/models/integrations/drone_ci.rb
@@ -5,10 +5,12 @@ module Integrations
include HasWebHook
include PushDataValidations
include ReactivelyCached
+ prepend EnableSslVerification
extend Gitlab::Utils::Override
+ DRONE_SAAS_HOSTNAME = 'cloud.drone.io'
+
prop_accessor :drone_url, :token
- boolean_accessor :enable_ssl_verification
validates :drone_url, presence: true, public_url: true, if: :activated?
validates :token, presence: true, if: :activated?
@@ -95,8 +97,7 @@ module Integrations
def fields
[
{ 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: 'checkbox', name: 'enable_ssl_verification', title: "Enable SSL verification" }
+ { type: 'text', name: 'drone_url', title: s_('ProjectService|Drone server URL'), placeholder: 'http://drone.example.com', required: true }
]
end
@@ -105,15 +106,24 @@ module Integrations
[drone_url, "/hook", "?owner=#{project.namespace.full_path}", "&name=#{project.path}", "&access_token=#{token}"].join
end
- override :hook_ssl_verification
- def hook_ssl_verification
- !!enable_ssl_verification
- end
-
override :update_web_hook!
def update_web_hook!
# If using a service template, project may not be available
super if project
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
diff --git a/app/models/integrations/jenkins.rb b/app/models/integrations/jenkins.rb
index e5c1d5ad0d7..5ea92170c26 100644
--- a/app/models/integrations/jenkins.rb
+++ b/app/models/integrations/jenkins.rb
@@ -4,6 +4,7 @@ module Integrations
class Jenkins < BaseCi
include HasWebHook
include ActionView::Helpers::UrlHelper
+ prepend EnableSslVerification
extend Gitlab::Utils::Override
prop_accessor :jenkins_url, :project_name, :username, :password
diff --git a/app/models/integrations/jira.rb b/app/models/integrations/jira.rb
index d46299de1be..cdf457de477 100644
--- a/app/models/integrations/jira.rb
+++ b/app/models/integrations/jira.rb
@@ -56,6 +56,12 @@ module Integrations
@reference_pattern ||= /(?<issue>\b#{Gitlab::Regex.jira_issue_key_regex})/
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
{}
end
@@ -569,7 +575,7 @@ module Integrations
end
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
def set_deployment_type_from_url
@@ -582,7 +588,7 @@ module Integrations
# we can only assume it's either Cloud or Server
# 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!
else
data_fields.deployment_server!
diff --git a/app/models/integrations/mock_ci.rb b/app/models/integrations/mock_ci.rb
index 7359be83d4f..568fb609a44 100644
--- a/app/models/integrations/mock_ci.rb
+++ b/app/models/integrations/mock_ci.rb
@@ -3,6 +3,8 @@
# For an example companion mocking service, see https://gitlab.com/gitlab-org/gitlab-mock-ci-service
module Integrations
class MockCi < BaseCi
+ prepend EnableSslVerification
+
ALLOWED_STATES = %w[failed canceled running pending success success-with-warnings skipped not_found].freeze
prop_accessor :mock_service_url
@@ -55,7 +57,7 @@ module Integrations
# # => 'running'
#
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)
rescue Errno::ECONNREFUSED
:error
@@ -68,19 +70,16 @@ module Integrations
end
def read_commit_status(response)
- return :error unless response.code == 200 || response.code == 404
-
- status = if response.code == 404
- 'pending'
- else
- response['status']
- end
+ return :pending if response.code == 404
+ return :error unless response.code == 200
- if status.present? && ALLOWED_STATES.include?(status)
- status
- else
- :error
+ begin
+ status = Gitlab::Json.parse(response.body).try(:fetch, 'status', nil)
+ return status if ALLOWED_STATES.include?(status)
+ rescue JSON::ParserError
end
+
+ :error
end
def testable?
diff --git a/app/models/integrations/teamcity.rb b/app/models/integrations/teamcity.rb
index 008b591c304..f0f83f118d7 100644
--- a/app/models/integrations/teamcity.rb
+++ b/app/models/integrations/teamcity.rb
@@ -4,6 +4,9 @@ module Integrations
class Teamcity < BaseCi
include PushDataValidations
include ReactivelyCached
+ prepend EnableSslVerification
+
+ TEAMCITY_SAAS_HOSTNAME = /\A[^\.]+\.teamcity\.com\z/i.freeze
prop_accessor :teamcity_url, :build_type, :username, :password
@@ -104,8 +107,20 @@ module Integrations
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(teamcity_url)
+ parsed_url&.scheme == 'https' && parsed_url.hostname.match?(TEAMCITY_SAAS_HOSTNAME)
+ rescue Addressable::URI::InvalidURIError
+ false
+ end
+
def execute_push(data)
branch = Gitlab::Git.ref_name(data[:ref])
post_to_build_queue(data, branch) if push_valid?(data)
@@ -155,7 +170,7 @@ module Integrations
end
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
def post_to_build_queue(data, branch)
@@ -165,6 +180,7 @@ module Integrations
"<buildType id=#{build_type.encode(xml: :attr)}/>"\
'</build>',
headers: { 'Content-type' => 'application/xml' },
+ verify: enable_ssl_verification,
basic_auth: basic_auth,
use_read_total_timeout: true
)
diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb
index 7b13109dbc4..a3c9db90b5d 100644
--- a/app/models/system_note_metadata.rb
+++ b/app/models/system_note_metadata.rb
@@ -10,7 +10,7 @@ class SystemNoteMetadata < ApplicationRecord
# in the same project (i.e. with the same permissions)
TYPES_WITH_CROSS_REFERENCES = %w[
commit cross_reference
- close duplicate
+ closed duplicate
moved merge
label milestone
relate unrelate
diff --git a/app/services/packages/destroy_package_service.rb b/app/services/packages/destroy_package_service.rb
index 697f1fa3ac8..2238d459bed 100644
--- a/app/services/packages/destroy_package_service.rb
+++ b/app/services/packages/destroy_package_service.rb
@@ -7,6 +7,10 @@ module Packages
def execute
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.sync_maven_metadata(current_user)
@@ -26,6 +30,14 @@ module Packages
ServiceResponse.success(message: message)
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?
can?(current_user, :destroy_package, package.project)
end
diff --git a/db/migrate/20220113135449_add_package_files_limit_to_application_settings.rb b/db/migrate/20220113135449_add_package_files_limit_to_application_settings.rb
new file mode 100644
index 00000000000..6d3deacdda3
--- /dev/null
+++ b/db/migrate/20220113135449_add_package_files_limit_to_application_settings.rb
@@ -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
diff --git a/db/migrate/20220113135924_add_application_settings_package_files_limit_constraints.rb b/db/migrate/20220113135924_add_application_settings_package_files_limit_constraints.rb
new file mode 100644
index 00000000000..65fbccbd1ae
--- /dev/null
+++ b/db/migrate/20220113135924_add_application_settings_package_files_limit_constraints.rb
@@ -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
diff --git a/db/schema_migrations/20220113135449 b/db/schema_migrations/20220113135449
new file mode 100644
index 00000000000..57e6ede94b5
--- /dev/null
+++ b/db/schema_migrations/20220113135449
@@ -0,0 +1 @@
+46796379175ce9343907234d3ae14a417442c7c5ebbfcf6987db1d759eca2c3a \ No newline at end of file
diff --git a/db/schema_migrations/20220113135924 b/db/schema_migrations/20220113135924
new file mode 100644
index 00000000000..41328a43c37
--- /dev/null
+++ b/db/schema_migrations/20220113135924
@@ -0,0 +1 @@
+2a230758c13111c9e3738794008c31a3608dda2f0d071fbde0ad3cd782d29162 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 716b3e89be1..63bd5127e26 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -10481,9 +10481,11 @@ CREATE TABLE application_settings (
max_ssh_key_lifetime integer,
static_objects_external_storage_auth_token_encrypted text,
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_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_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_yaml_max_depth_positive CHECK ((max_yaml_depth > 0)),
CONSTRAINT app_settings_yaml_max_size_positive CHECK ((max_yaml_size_bytes > 0)),
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 123e65162d8..36a32a91d64 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -2154,6 +2154,8 @@ Input type: `DestroyNoteInput`
### `Mutation.destroyPackage`
+Destroys a package and its related package files. Restricted to packages with a small number of files.
+
Input type: `DestroyPackageInput`
#### Arguments
diff --git a/doc/api/integrations.md b/doc/api/integrations.md
index 8f57e915b4e..933484551e0 100644
--- a/doc/api/integrations.md
+++ b/doc/api/integrations.md
@@ -165,6 +165,7 @@ Parameters:
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `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 |
| `username` | string | true | A user with API access, if applicable |
| `password` | string | true | Password of the user |
@@ -519,7 +520,7 @@ Parameters:
| --------- | ---- | -------- | ----------- |
| `token` | string | true | Drone CI project specific token |
| `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 |
| `merge_requests_events` | boolean | false | Enable notifications for merge request events |
| `tag_push_events` | boolean | false | Enable notifications for tag push events |
@@ -1394,6 +1395,7 @@ Parameters:
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `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 |
| `username` | string | true | A user with permissions to trigger a manual build |
| `password` | string | true | The password of the user |
@@ -1432,6 +1434,7 @@ Parameters:
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `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`. |
| `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. |
@@ -1511,6 +1514,7 @@ Parameters:
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `mock_service_url` | string | true | `http://localhost:4004` |
+| `enable_ssl_verification` | boolean | false | Enable SSL verification. Defaults to true (enabled). |
### Disable MockCI integration
diff --git a/doc/api/packages.md b/doc/api/packages.md
index a75b2e376fa..b34bcf6afb4 100644
--- a/doc/api/packages.md
+++ b/doc/api/packages.md
@@ -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.
+WARNING:
+This endpoint is restricted to the limit set in [Updating the package files limit](#updating-the-package-files-limit).
+
Deletes a project package.
```plaintext
@@ -352,6 +355,17 @@ Can return the following status codes:
- `204 No Content`, if the package was deleted successfully.
- `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
diff --git a/lib/api/helpers/integrations_helpers.rb b/lib/api/helpers/integrations_helpers.rb
index e7fdb6645a5..a67d7717a82 100644
--- a/lib/api/helpers/integrations_helpers.rb
+++ b/lib/api/helpers/integrations_helpers.rb
@@ -197,6 +197,12 @@ module API
desc: 'Bamboo root URL like https://bamboo.example.com'
},
{
+ required: false,
+ name: :enable_ssl_verification,
+ type: Boolean,
+ desc: 'Enable SSL verification'
+ },
+ {
required: true,
name: :build_key,
type: String,
@@ -360,7 +366,7 @@ module API
required: false,
name: :enable_ssl_verification,
type: Boolean,
- desc: 'Enable SSL verification for communication'
+ desc: 'Enable SSL verification'
}
],
'emails-on-push' => [
@@ -460,6 +466,12 @@ module API
desc: 'Jenkins root URL like https://jenkins.example.com'
},
{
+ required: false,
+ name: :enable_ssl_verification,
+ type: Boolean,
+ desc: 'Enable SSL verification'
+ },
+ {
required: true,
name: :project_name,
type: String,
@@ -741,6 +753,12 @@ module API
desc: 'TeamCity root URL like https://teamcity.example.com'
},
{
+ required: false,
+ name: :enable_ssl_verification,
+ type: Boolean,
+ desc: 'Enable SSL verification'
+ },
+ {
required: true,
name: :build_type,
type: String,
diff --git a/lib/api/project_packages.rb b/lib/api/project_packages.rb
index 54c0a0628a7..b06d0685021 100644
--- a/lib/api/project_packages.rb
+++ b/lib/api/project_packages.rb
@@ -71,7 +71,11 @@ module API
.new(user_project, params[:package_id]).execute
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
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 376b87d8432..9846d3a5238 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -18998,6 +18998,9 @@ msgstr ""
msgid "Integrations|Browser limitations"
msgstr ""
+msgid "Integrations|Clear if using a self-signed certificate."
+msgstr ""
+
msgid "Integrations|Comment detail:"
msgstr ""
@@ -19028,6 +19031,9 @@ msgstr ""
msgid "Integrations|Enable GitLab.com slash commands in a Slack workspace."
msgstr ""
+msgid "Integrations|Enable SSL verification"
+msgstr ""
+
msgid "Integrations|Enable comments"
msgstr ""
@@ -19106,6 +19112,9 @@ msgstr ""
msgid "Integrations|Return to GitLab for Jira"
msgstr ""
+msgid "Integrations|SSL verification"
+msgstr ""
+
msgid "Integrations|Save settings?"
msgstr ""
diff --git a/spec/features/issues/notes_on_issues_spec.rb b/spec/features/issues/notes_on_issues_spec.rb
index be85d73d777..4e98062e8b2 100644
--- a/spec/features/issues/notes_on_issues_spec.rb
+++ b/spec/features/issues/notes_on_issues_spec.rb
@@ -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
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
diff --git a/spec/features/projects/packages_spec.rb b/spec/features/projects/packages_spec.rb
index 7fcc8200b1c..fa5d9f1c3a3 100644
--- a/spec/features/projects/packages_spec.rb
+++ b/spec/features/projects/packages_spec.rb
@@ -50,6 +50,22 @@ RSpec.describe 'Packages' do
expect(page).to have_content 'Package deleted successfully'
expect(page).not_to have_content(package.name)
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
it_behaves_like 'shared package sorting' do
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/app_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/app_spec.js
index 0bea84693f6..d3ec4e9dba0 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/app_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/app_spec.js
@@ -209,7 +209,7 @@ describe('PackagesApp', () => {
await waitForPromises();
- findDeletePackage().vm.$emit('end');
+ findDeletePackage().vm.$emit('success');
expect(window.location.replace).toHaveBeenCalledWith(
'projectListUrl?showSuccessDeleteAlert=true',
@@ -223,7 +223,7 @@ describe('PackagesApp', () => {
await waitForPromises();
- findDeletePackage().vm.$emit('end');
+ findDeletePackage().vm.$emit('success');
expect(window.location.replace).toHaveBeenCalledWith(
'groupListUrl?showSuccessDeleteAlert=true',
diff --git a/spec/frontend/packages_and_registries/package_registry/components/functional/delete_package_spec.js b/spec/frontend/packages_and_registries/package_registry/components/functional/delete_package_spec.js
index 5de30829fa5..0492edf42f2 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/functional/delete_package_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/functional/delete_package_spec.js
@@ -99,12 +99,13 @@ describe('DeletePackage', () => {
});
describe('on mutation success', () => {
- it('emits end event', async () => {
+ it('emits end and success events', async () => {
createComponent();
await clickOnButtonAndWait(eventPayload);
expect(wrapper.emitted('end')).toEqual([[]]);
+ expect(wrapper.emitted('success')).toEqual([[]]);
});
it('does not call createFlash', async () => {
@@ -128,10 +129,10 @@ describe('DeletePackage', () => {
});
describe.each`
- errorType | mutationResolverResponse
- ${'connectionError'} | ${jest.fn().mockRejectedValue()}
- ${'localError'} | ${jest.fn().mockResolvedValue(packageDestroyMutationError())}
- `('on mutation $errorType', ({ mutationResolverResponse }) => {
+ errorType | mutationResolverResponse | errorMessage
+ ${'connectionError'} | ${jest.fn().mockRejectedValue()} | ${DeletePackage.i18n.errorMessage}
+ ${'localError'} | ${jest.fn().mockResolvedValue(packageDestroyMutationError())} | ${packageDestroyMutationError().errors[0].message}
+ `('on mutation $errorType', ({ mutationResolverResponse, errorMessage }) => {
beforeEach(() => {
mutationResolver = mutationResolverResponse;
});
@@ -150,7 +151,7 @@ describe('DeletePackage', () => {
await clickOnButtonAndWait(eventPayload);
expect(createFlash).toHaveBeenCalledWith({
- message: DeletePackage.i18n.errorMessage,
+ message: expect.stringContaining(errorMessage),
type: 'warning',
captureError: true,
error: expect.any(Error),
diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb
index adf5507571b..75f774b6239 100644
--- a/spec/graphql/types/project_type_spec.rb
+++ b/spec/graphql/types/project_type_spec.rb
@@ -594,4 +594,45 @@ RSpec.describe GitlabSchema.types['Project'] do
expect(cluster_agent.agent_tokens.size).to be(count)
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
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 67314084c4f..1c8b8b892b7 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -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.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(:wiki_page_max_content_bytes).only_integer.is_greater_than_or_equal_to(1024) }
it { is_expected.to validate_presence_of(:max_artifacts_size) }
diff --git a/spec/models/concerns/integrations/enable_ssl_verification_spec.rb b/spec/models/concerns/integrations/enable_ssl_verification_spec.rb
new file mode 100644
index 00000000000..802e950c0c2
--- /dev/null
+++ b/spec/models/concerns/integrations/enable_ssl_verification_spec.rb
@@ -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
diff --git a/spec/models/integrations/bamboo_spec.rb b/spec/models/integrations/bamboo_spec.rb
index 60ff6685c3d..b5684d153f2 100644
--- a/spec/models/integrations/bamboo_spec.rb
+++ b/spec/models/integrations/bamboo_spec.rb
@@ -23,6 +23,8 @@ RSpec.describe Integrations::Bamboo, :use_clean_rails_memory_store_caching do
)
end
+ include_context Integrations::EnableSslVerification
+
describe 'Validations' do
context 'when active' do
before do
diff --git a/spec/models/integrations/drone_ci_spec.rb b/spec/models/integrations/drone_ci_spec.rb
index 062e23d628e..dd64dcfc52c 100644
--- a/spec/models/integrations/drone_ci_spec.rb
+++ b/spec/models/integrations/drone_ci_spec.rb
@@ -5,6 +5,8 @@ require 'spec_helper'
RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
+ subject(:integration) { described_class.new }
+
describe 'validations' do
context 'active' do
before do
@@ -59,6 +61,52 @@ RSpec.describe Integrations::DroneCi, :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.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
include_context :drone_ci_integration
diff --git a/spec/models/integrations/jenkins_spec.rb b/spec/models/integrations/jenkins_spec.rb
index 9286d026290..3d6393f2793 100644
--- a/spec/models/integrations/jenkins_spec.rb
+++ b/spec/models/integrations/jenkins_spec.rb
@@ -24,6 +24,10 @@ RSpec.describe Integrations::Jenkins do
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
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" }
diff --git a/spec/models/integrations/jira_spec.rb b/spec/models/integrations/jira_spec.rb
index 9163a7ef845..c4af30fc29e 100644
--- a/spec/models/integrations/jira_spec.rb
+++ b/spec/models/integrations/jira_spec.rb
@@ -130,6 +130,23 @@ RSpec.describe Integrations::Jira do
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
let(:params) do
{
diff --git a/spec/models/integrations/mock_ci_spec.rb b/spec/models/integrations/mock_ci_spec.rb
new file mode 100644
index 00000000000..d29c63b3a97
--- /dev/null
+++ b/spec/models/integrations/mock_ci_spec.rb
@@ -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
diff --git a/spec/models/integrations/teamcity_spec.rb b/spec/models/integrations/teamcity_spec.rb
index 0713141ea08..e1f4e577503 100644
--- a/spec/models/integrations/teamcity_spec.rb
+++ b/spec/models/integrations/teamcity_spec.rb
@@ -6,8 +6,8 @@ RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
include StubRequests
- let(:teamcity_url) { 'http://gitlab.com/teamcity' }
- let(:teamcity_full_url) { 'http://gitlab.com/teamcity/httpAuth/app/rest/builds/branch:unspecified:any,revision:123' }
+ let(:teamcity_url) { 'https://gitlab.teamcity.com' }
+ let(:teamcity_full_url) { 'https://gitlab.teamcity.com/httpAuth/app/rest/builds/branch:unspecified:any,revision:123' }
let(:project) { create(:project) }
subject(:integration) do
@@ -22,6 +22,52 @@ RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do
)
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
context 'when integration is active' 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
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
it 'returns a build URL when teamcity_url has no trailing slash' do
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
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
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
@@ -299,7 +345,7 @@ RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do
end
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>)
auth = %w(mic password)
diff --git a/spec/requests/api/graphql/mutations/packages/destroy_spec.rb b/spec/requests/api/graphql/mutations/packages/destroy_spec.rb
index e5ced419ecf..bd44f32dbaa 100644
--- a/spec/requests/api/graphql/mutations/packages/destroy_spec.rb
+++ b/spec/requests/api/graphql/mutations/packages/destroy_spec.rb
@@ -88,6 +88,20 @@ RSpec.describe 'Destroying a package' do
expect(mutation_response['errors']).to eq(['Failed to remove the package'])
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
diff --git a/spec/requests/api/project_packages_spec.rb b/spec/requests/api/project_packages_spec.rb
index 9b7538547f6..c10626ecc6c 100644
--- a/spec/requests/api/project_packages_spec.rb
+++ b/spec/requests/api/project_packages_spec.rb
@@ -355,6 +355,23 @@ RSpec.describe API::ProjectPackages do
expect(response).to have_gitlab_http_status(:no_content)
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
context 'with a maven package' do
diff --git a/spec/requests/jira_connect/users_controller_spec.rb b/spec/requests/jira_connect/users_controller_spec.rb
new file mode 100644
index 00000000000..c648d28c1bc
--- /dev/null
+++ b/spec/requests/jira_connect/users_controller_spec.rb
@@ -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
diff --git a/spec/services/packages/destroy_package_service_spec.rb b/spec/services/packages/destroy_package_service_spec.rb
index 92db8da968c..a284f88a0fe 100644
--- a/spec/services/packages/destroy_package_service_spec.rb
+++ b/spec/services/packages/destroy_package_service_spec.rb
@@ -30,6 +30,24 @@ RSpec.describe Packages::DestroyPackageService do
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
before do
allow(package).to receive(:destroy!).and_raise(StandardError, "test")
diff --git a/spec/support/shared_contexts/models/concerns/integrations/enable_ssl_verification_shared_context.rb b/spec/support/shared_contexts/models/concerns/integrations/enable_ssl_verification_shared_context.rb
new file mode 100644
index 00000000000..c698e06c2a2
--- /dev/null
+++ b/spec/support/shared_contexts/models/concerns/integrations/enable_ssl_verification_shared_context.rb
@@ -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
diff --git a/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb b/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb
index 1fa340a0cf4..ae72cb6ec5d 100644
--- a/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb
+++ b/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb
@@ -37,6 +37,16 @@ RSpec.shared_examples Integrations::HasWebHook do
it 'returns a boolean' do
expect(integration.hook_ssl_verification).to be_in([true, false])
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
describe '#update_web_hook!' do