From 1ff28a8d8d370efef8bbac2da1edb85b758d4643 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 27 Jul 2022 19:01:26 +0000 Subject: Add latest changes from gitlab-org/security/gitlab@15-2-stable-ee --- app/models/integrations/campfire.rb | 9 +++++ app/models/integrations/drone_ci.rb | 1 + app/models/integrations/jira.rb | 8 ++++- app/models/integrations/packagist.rb | 3 +- app/models/integrations/zentao.rb | 17 +++++++-- app/services/grafana/proxy_service.rb | 10 ++++++ lib/gitlab/regex.rb | 4 +++ spec/models/integrations/campfire_spec.rb | 10 ++++++ spec/models/integrations/drone_ci_spec.rb | 4 +++ spec/models/integrations/jira_spec.rb | 20 ++++++++++- spec/models/integrations/packagist_spec.rb | 4 +++ spec/models/integrations/zentao_spec.rb | 25 +++++++++++++ spec/services/grafana/proxy_service_spec.rb | 42 ++++++++++++++++++---- .../integrations/integrations_shared_context.rb | 2 ++ 14 files changed, 148 insertions(+), 11 deletions(-) diff --git a/app/models/integrations/campfire.rb b/app/models/integrations/campfire.rb index bf1358ac0f6..3f7fa1c51b2 100644 --- a/app/models/integrations/campfire.rb +++ b/app/models/integrations/campfire.rb @@ -2,7 +2,15 @@ module Integrations class Campfire < Integration + SUBDOMAIN_REGEXP = %r{\A[a-z](?:[a-z0-9-]*[a-z0-9])?\z}i.freeze + validates :token, presence: true, if: :activated? + validates :room, + allow_blank: true, + numericality: { only_integer: true, greater_than: 0 } + validates :subdomain, + allow_blank: true, + format: { with: SUBDOMAIN_REGEXP }, length: { in: 1..63 } field :token, type: 'password', @@ -16,6 +24,7 @@ module Integrations field :subdomain, title: -> { _('Campfire subdomain (optional)') }, placeholder: '', + exposes_secrets: true, help: -> do ERB::Util.html_escape( s_('CampfireService|The %{code_open}.campfirenow.com%{code_close} subdomain.') diff --git a/app/models/integrations/drone_ci.rb b/app/models/integrations/drone_ci.rb index b1f72b7144e..de69afeba6a 100644 --- a/app/models/integrations/drone_ci.rb +++ b/app/models/integrations/drone_ci.rb @@ -13,6 +13,7 @@ module Integrations field :drone_url, title: -> { s_('ProjectService|Drone server URL') }, placeholder: 'http://drone.example.com', + exposes_secrets: true, required: true field :token, diff --git a/app/models/integrations/jira.rb b/app/models/integrations/jira.rb index c9c9b9d59d6..566bbc456f8 100644 --- a/app/models/integrations/jira.rb +++ b/app/models/integrations/jira.rb @@ -223,7 +223,9 @@ module Integrations # support any events. end - def find_issue(issue_key, rendered_fields: false, transitions: false) + def find_issue(issue_key, rendered_fields: false, transitions: false, restrict_project_key: false) + return if restrict_project_key && parse_project_from_issue_key(issue_key) != project_key + expands = [] expands << 'renderedFields' if rendered_fields expands << 'transitions' if transitions @@ -321,6 +323,10 @@ module Integrations private + def parse_project_from_issue_key(issue_key) + issue_key.gsub(Gitlab::Regex.jira_issue_key_project_key_extraction_regex, '') + end + def branch_name(commit) commit.first_ref_by_oid(project.repository) end diff --git a/app/models/integrations/packagist.rb b/app/models/integrations/packagist.rb index 05ee919892d..fda4822c19f 100644 --- a/app/models/integrations/packagist.rb +++ b/app/models/integrations/packagist.rb @@ -23,7 +23,8 @@ module Integrations field :server, title: -> { _('Server (optional)') }, help: -> { s_('Enter your Packagist server. Defaults to https://packagist.org.') }, - placeholder: 'https://packagist.org' + placeholder: 'https://packagist.org', + exposes_secrets: true validates :username, presence: true, if: :activated? validates :token, presence: true, if: :activated? diff --git a/app/models/integrations/zentao.rb b/app/models/integrations/zentao.rb index 11db469f7ee..53194089296 100644 --- a/app/models/integrations/zentao.rb +++ b/app/models/integrations/zentao.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Integrations - class Zentao < Integration + class Zentao < BaseIssueTracker include Gitlab::Routing self.field_storage = :data_fields @@ -10,11 +10,13 @@ module Integrations title: -> { s_('ZentaoIntegration|ZenTao Web URL') }, placeholder: 'https://www.zentao.net', help: -> { s_('ZentaoIntegration|Base URL of the ZenTao instance.') }, + exposes_secrets: true, required: true field :api_url, title: -> { s_('ZentaoIntegration|ZenTao API URL (optional)') }, - help: -> { s_('ZentaoIntegration|If different from Web URL.') } + help: -> { s_('ZentaoIntegration|If different from Web URL.') }, + exposes_secrets: true field :api_token, type: 'password', @@ -40,6 +42,17 @@ module Integrations zentao_tracker_data || self.build_zentao_tracker_data end + alias_method :project_url, :url + + def set_default_data + return unless issues_tracker.present? + + return if url + + data_fields.url ||= issues_tracker['url'] + data_fields.api_url ||= issues_tracker['api_url'] + end + def title 'ZenTao' end diff --git a/app/services/grafana/proxy_service.rb b/app/services/grafana/proxy_service.rb index ac4c3cc091c..37272c85638 100644 --- a/app/services/grafana/proxy_service.rb +++ b/app/services/grafana/proxy_service.rb @@ -15,6 +15,10 @@ module Grafana self.reactive_cache_work_type = :external_dependency self.reactive_cache_worker_finder = ->(_id, *args) { from_cache(*args) } + SUPPORTED_DATASOURCE_PATTERN = %r{\A\d+\z}.freeze + + SUPPORTED_PROXY_PATH = Gitlab::Metrics::Dashboard::Stages::GrafanaFormatter::PROXY_PATH + attr_accessor :project, :datasource_id, :proxy_path, :query_params # @param project_id [Integer] Project id for which grafana is configured. @@ -38,6 +42,7 @@ module Grafana end def execute + return cannot_proxy_response unless can_proxy? return cannot_proxy_response unless client with_reactive_cache(*cache_key) { |result| result } @@ -69,6 +74,11 @@ module Grafana private + def can_proxy? + SUPPORTED_PROXY_PATH == proxy_path && + SUPPORTED_DATASOURCE_PATTERN.match?(datasource_id) + end + def client project.grafana_integration&.client end diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index 0534f890152..551750f9798 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -418,6 +418,10 @@ module Gitlab @jira_issue_key_regex ||= /[A-Z][A-Z_0-9]+-\d+/ end + def jira_issue_key_project_key_extraction_regex + @jira_issue_key_project_key_extraction_regex ||= /-\d+/ + end + def jira_transition_id_regex @jira_transition_id_regex ||= /\d+/ end diff --git a/spec/models/integrations/campfire_spec.rb b/spec/models/integrations/campfire_spec.rb index 405a9ff4b3f..48e24299bbd 100644 --- a/spec/models/integrations/campfire_spec.rb +++ b/spec/models/integrations/campfire_spec.rb @@ -5,7 +5,17 @@ require 'spec_helper' RSpec.describe Integrations::Campfire do include StubRequests + it_behaves_like Integrations::ResetSecretFields do + let(:integration) { described_class.new } + end + describe 'Validations' do + it { is_expected.to validate_numericality_of(:room).is_greater_than(0).only_integer } + it { is_expected.to validate_length_of(:subdomain).is_at_most(63) } + it { is_expected.to allow_value("foo").for(:subdomain) } + it { is_expected.not_to allow_value("foo.bar").for(:subdomain) } + it { is_expected.not_to allow_value("foo.bar/#").for(:subdomain) } + context 'when integration is active' do before do subject.active = true diff --git a/spec/models/integrations/drone_ci_spec.rb b/spec/models/integrations/drone_ci_spec.rb index 78d55c49e7b..5ae4af1a665 100644 --- a/spec/models/integrations/drone_ci_spec.rb +++ b/spec/models/integrations/drone_ci_spec.rb @@ -7,6 +7,10 @@ RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do subject(:integration) { described_class.new } + it_behaves_like Integrations::ResetSecretFields do + let(:integration) { subject } + end + describe 'validations' do context 'active' do before do diff --git a/spec/models/integrations/jira_spec.rb b/spec/models/integrations/jira_spec.rb index 2a994540bd3..01c08a0948f 100644 --- a/spec/models/integrations/jira_spec.rb +++ b/spec/models/integrations/jira_spec.rb @@ -12,6 +12,7 @@ RSpec.describe Integrations::Jira do let(:api_url) { 'http://api-jira.example.com' } let(:username) { 'jira-username' } let(:password) { 'jira-password' } + let(:project_key) { nil } let(:transition_id) { 'test27' } let(:server_info_results) { { 'deploymentType' => 'Cloud' } } let(:jira_integration) do @@ -19,7 +20,8 @@ RSpec.describe Integrations::Jira do project: project, url: url, username: username, - password: password + password: password, + project_key: project_key ) end @@ -533,6 +535,22 @@ RSpec.describe Integrations::Jira do expect(WebMock).to have_requested(:get, issue_url) end end + + context 'with restricted restrict_project_key option' do + subject(:find_issue) { jira_integration.find_issue(issue_key, restrict_project_key: true) } + + it { is_expected.to eq(nil) } + + context 'and project_key matches' do + let(:project_key) { 'JIRA' } + + it 'calls the Jira API to get the issue' do + find_issue + + expect(WebMock).to have_requested(:get, issue_url) + end + end + end end describe '#close_issue' do diff --git a/spec/models/integrations/packagist_spec.rb b/spec/models/integrations/packagist_spec.rb index dce96890522..d1976e73e2e 100644 --- a/spec/models/integrations/packagist_spec.rb +++ b/spec/models/integrations/packagist_spec.rb @@ -29,6 +29,10 @@ RSpec.describe Integrations::Packagist do let(:hook_url) { "#{packagist_server}/api/update-package?username=#{packagist_username}&apiToken=#{packagist_token}" } end + it_behaves_like Integrations::ResetSecretFields do + let(:integration) { described_class.new(packagist_params) } + end + describe '#execute' do let(:user) { create(:user) } let(:project) { create(:project, :repository) } diff --git a/spec/models/integrations/zentao_spec.rb b/spec/models/integrations/zentao_spec.rb index 2b0532c7930..4ef977ba3d2 100644 --- a/spec/models/integrations/zentao_spec.rb +++ b/spec/models/integrations/zentao_spec.rb @@ -9,6 +9,31 @@ RSpec.describe Integrations::Zentao do let(:zentao_product_xid) { '3' } let(:zentao_integration) { create(:zentao_integration) } + it_behaves_like Integrations::ResetSecretFields do + let(:integration) { zentao_integration } + end + + describe 'set_default_data' do + let(:project) { create(:project, :repository) } + + context 'when gitlab.yml was initialized' do + it 'is prepopulated with the settings' do + settings = { + 'zentao' => { + 'url' => 'http://zentao.sample/projects/project_a', + 'api_url' => 'http://zentao.sample/api' + } + } + allow(Gitlab.config).to receive(:issues_tracker).and_return(settings) + + integration = project.create_zentao_integration(active: true) + + expect(integration.url).to eq('http://zentao.sample/projects/project_a') + expect(integration.api_url).to eq('http://zentao.sample/api') + end + end + end + describe '#create' do let(:project) { create(:project, :repository) } let(:params) do diff --git a/spec/services/grafana/proxy_service_spec.rb b/spec/services/grafana/proxy_service_spec.rb index 7ddc31d45d9..99120de3593 100644 --- a/spec/services/grafana/proxy_service_spec.rb +++ b/spec/services/grafana/proxy_service_spec.rb @@ -50,12 +50,8 @@ RSpec.describe Grafana::ProxyService do describe '#execute' do subject(:result) { service.execute } - context 'when grafana integration is not configured' do - before do - allow(project).to receive(:grafana_integration).and_return(nil) - end - - it 'returns error' do + shared_examples 'missing proxy support' do + it 'returns API not supported error' do expect(result).to eq( status: :error, message: 'Proxy support for this API is not available currently' @@ -63,6 +59,40 @@ RSpec.describe Grafana::ProxyService do end end + context 'with unsupported proxy path' do + where(:proxy_path) do + %w[ + /api/vl/query_range + api/vl/query_range/ + api/vl/labels + api/v2/query_range + ../../../org/users + ] + end + + with_them do + include_examples 'missing proxy support' + end + end + + context 'with unsupported datasource_id' do + where(:datasource_id) do + ['', '-1', '1str', 'str1', '../../1', '1/../..', "1\n1"] + end + + with_them do + include_examples 'missing proxy support' + end + end + + context 'when grafana integration is not configured' do + before do + allow(project).to receive(:grafana_integration).and_return(nil) + end + + include_examples 'missing proxy support' + end + context 'with caching', :use_clean_rails_memory_store_caching do context 'when value not present in cache' do it 'returns nil' do diff --git a/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb b/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb index 3d3b8c2207d..255c4e6f882 100644 --- a/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb +++ b/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb @@ -66,6 +66,8 @@ Integration.available_integration_names.each do |integration| hash.merge!(k => 'foo@bar.com') elsif (integration == 'slack' || integration == 'mattermost') && k == :labels_to_be_notified_behavior hash.merge!(k => "match_any") + elsif integration == 'campfire' && k = :room + hash.merge!(k => '1234') else hash.merge!(k => "someword") end -- cgit v1.2.3