From e6572d41b847c839ce49bc022a8cd1b99216798b Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 30 Nov 2022 04:50:46 +0000 Subject: Add latest changes from gitlab-org/security/gitlab@15-6-stable-ee --- Gemfile | 2 +- Gemfile.checksum | 2 +- Gemfile.lock | 4 +- app/models/hooks/web_hook.rb | 6 + app/models/hooks/web_hook_log.rb | 7 + app/models/integrations/jira.rb | 5 +- app/models/repository.rb | 8 +- app/services/markup/rendering_service.rb | 8 +- .../packages/nuget/metadata_extraction_service.rb | 10 +- app/services/projects/import_service.rb | 32 +++- app/services/web_hooks/log_execution_service.rb | 2 + app/views/projects/tags/_release_link.html.haml | 9 +- app/views/projects/tags/show.html.haml | 13 +- lib/gitlab/git/repository.rb | 13 +- lib/gitlab/gitaly_client/repository_service.rb | 10 +- spec/features/tags/developer_views_tags_spec.rb | 2 + .../packages/nuget/corrupted_package.nupkg | Bin 0 -> 3513 bytes spec/lib/gitlab/git/repository_spec.rb | 7 +- .../gitaly_client/repository_service_spec.rb | 63 +++++++- spec/models/hooks/web_hook_log_spec.rb | 18 +++ spec/models/hooks/web_hook_spec.rb | 30 ++++ spec/models/integrations/jira_spec.rb | 13 +- spec/models/project_import_state_spec.rb | 2 +- spec/models/repository_spec.rb | 13 +- .../requests/jira_connect/users_controller_spec.rb | 11 ++ spec/services/markup/rendering_service_spec.rb | 5 +- .../nuget/metadata_extraction_service_spec.rb | 11 ++ spec/services/projects/import_service_spec.rb | 163 +++++++++++++++++++-- .../web_hooks/log_execution_service_spec.rb | 15 +- .../features/user_views_tag_shared_examples.rb | 47 ++++-- 30 files changed, 449 insertions(+), 82 deletions(-) create mode 100644 spec/fixtures/packages/nuget/corrupted_package.nupkg diff --git a/Gemfile b/Gemfile index 87869f6f1e4..4dd695e69d1 100644 --- a/Gemfile +++ b/Gemfile @@ -500,7 +500,7 @@ gem 'ssh_data', '~> 1.3' gem 'spamcheck', '~> 1.0.0' # Gitaly GRPC protocol definitions -gem 'gitaly', '~> 15.5.0' +gem 'gitaly', '~> 15.5.2' # KAS GRPC protocol definitions gem 'kas-grpc', '~> 0.0.2' diff --git a/Gemfile.checksum b/Gemfile.checksum index f8202c49abe..a8ae702878f 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -199,7 +199,7 @@ {"name":"gettext_i18n_rails","version":"1.8.0","platform":"ruby","checksum":"95e5cf8440b1e08705b27f2bccb56143272c5a7a0dabcf54ea1bd701140a496f"}, {"name":"gettext_i18n_rails_js","version":"1.3.0","platform":"ruby","checksum":"5d10afe4be3639bff78c50a56768c20f39aecdabc580c08aa45573911c2bd687"}, {"name":"git","version":"1.11.0","platform":"ruby","checksum":"7e95ba4da8298a0373ef1a6862aa22007d761f3c8274b675aa787966fecea0f1"}, -{"name":"gitaly","version":"15.5.0","platform":"ruby","checksum":"d85dd4890a1f0fd95f935c848bcedf03f19b78872f20f04b9811e602bea4ef42"}, +{"name":"gitaly","version":"15.5.2","platform":"ruby","checksum":"62babe0596a4505bf95051ea50f17160055e6cf6cacf209273691542120d7881"}, {"name":"gitlab","version":"4.16.1","platform":"ruby","checksum":"13fd7059cbdad5a1a21b15fa2cf9070b97d92e27f8c688581fe3d84dc038074f"}, {"name":"gitlab-chronic","version":"0.10.5","platform":"ruby","checksum":"f80f18dc699b708870a80685243331290bc10cfeedb6b99c92219722f729c875"}, {"name":"gitlab-dangerfiles","version":"3.6.2","platform":"ruby","checksum":"88585532bbb5c0e862ad0776b3804a32129eab06c6a8a7bc96b577baa7aac6c5"}, diff --git a/Gemfile.lock b/Gemfile.lock index c6345cb7f5d..183f99e66fa 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -554,7 +554,7 @@ GEM rails (>= 3.2.0) git (1.11.0) rchardet (~> 1.8) - gitaly (15.5.0) + gitaly (15.5.2) grpc (~> 1.0) gitlab (4.16.1) httparty (~> 0.14, >= 0.14.0) @@ -1663,7 +1663,7 @@ DEPENDENCIES gettext (~> 3.3) gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails_js (~> 1.3) - gitaly (~> 15.5.0) + gitaly (~> 15.5.2) gitlab-chronic (~> 0.10.5) gitlab-dangerfiles (~> 3.6.2) gitlab-experiment (~> 0.7.1) diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index 05e50c17988..946cdda2e75 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -39,6 +39,8 @@ class WebHook < ApplicationRecord validates :token, format: { without: /\n/ } after_initialize :initialize_url_variables + + before_validation :reset_token before_validation :set_branch_filter_nil, \ if: -> { branch_filter_strategy_all_branches? && enhanced_webhook_support_regex? } validates :push_events_branch_filter, \ @@ -218,6 +220,10 @@ class WebHook < ApplicationRecord private + def reset_token + self.token = nil if url_changed? && !encrypted_token_changed? + end + def next_failure_count recent_failures.succ.clamp(1, MAX_FAILURES) end diff --git a/app/models/hooks/web_hook_log.rb b/app/models/hooks/web_hook_log.rb index 2b26147b494..9de6f2a1b57 100644 --- a/app/models/hooks/web_hook_log.rb +++ b/app/models/hooks/web_hook_log.rb @@ -48,6 +48,13 @@ class WebHookLog < ApplicationRecord request_data == OVERSIZE_REQUEST_DATA end + def request_headers + super unless web_hook.token? + super if self[:request_headers]['X-Gitlab-Token'] == _('[REDACTED]') + + self[:request_headers].merge('X-Gitlab-Token' => _('[REDACTED]')) + end + private def obfuscate_basic_auth diff --git a/app/models/integrations/jira.rb b/app/models/integrations/jira.rb index 30497c0110e..65492bfd9c2 100644 --- a/app/models/integrations/jira.rb +++ b/app/models/integrations/jira.rb @@ -97,7 +97,10 @@ module Integrations def self.valid_jira_cloud_url?(url) return false unless url.present? - !!URI(url).hostname&.end_with?(JIRA_CLOUD_HOST) + uri = URI.parse(url) + uri.is_a?(URI::HTTPS) && !!uri.hostname&.end_with?(JIRA_CLOUD_HOST) + rescue URI::InvalidURIError + false end def data_fields diff --git a/app/models/repository.rb b/app/models/repository.rb index 95d1b815e74..90e87de4a5b 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -984,12 +984,12 @@ class Repository end end - def clone_as_mirror(url, http_authorization_header: "") - import_repository(url, http_authorization_header: http_authorization_header, mirror: true) + def clone_as_mirror(url, http_authorization_header: "", resolved_address: "") + import_repository(url, http_authorization_header: http_authorization_header, mirror: true, resolved_address: resolved_address) end - def fetch_as_mirror(url, forced: false, refmap: :all_refs, prune: true, http_authorization_header: "") - fetch_remote(url, refmap: refmap, forced: forced, prune: prune, http_authorization_header: http_authorization_header) + def fetch_as_mirror(url, forced: false, refmap: :all_refs, prune: true, http_authorization_header: "", resolved_address: "") + fetch_remote(url, refmap: refmap, forced: forced, prune: prune, http_authorization_header: http_authorization_header, resolved_address: resolved_address) end def fetch_source_branch!(source_repository, source_branch, local_ref) diff --git a/app/services/markup/rendering_service.rb b/app/services/markup/rendering_service.rb index 0142d600522..c4abbb6b5b0 100644 --- a/app/services/markup/rendering_service.rb +++ b/app/services/markup/rendering_service.rb @@ -2,8 +2,6 @@ module Markup class RenderingService - include ActionView::Helpers::TextHelper - # Let's increase the render timeout # For a smaller one, a test that renders the blob content statically fails # We can consider removing this custom timeout when markup_rendering_timeout FF is removed: @@ -51,7 +49,7 @@ module Markup rescue StandardError => e Gitlab::ErrorTracking.track_exception(e, project_id: context[:project]&.id, file_name: file_name) - simple_format(text) + ActionController::Base.helpers.simple_format(text) end def markdown_unsafe @@ -63,7 +61,9 @@ module Markup end def plain_unsafe - "
#{text}
" + ActionController::Base.helpers.content_tag :pre, class: 'plain-readme' do + text + end end def other_markup_unsafe diff --git a/app/services/packages/nuget/metadata_extraction_service.rb b/app/services/packages/nuget/metadata_extraction_service.rb index 66abd189153..02086b2a282 100644 --- a/app/services/packages/nuget/metadata_extraction_service.rb +++ b/app/services/packages/nuget/metadata_extraction_service.rb @@ -104,9 +104,15 @@ module Packages entry = zip_file.glob('*.nuspec').first raise ExtractionError, 'nuspec file not found' unless entry - raise ExtractionError, 'nuspec file too big' if entry.size > MAX_FILE_SIZE + raise ExtractionError, 'nuspec file too big' if MAX_FILE_SIZE < entry.size - entry.get_input_stream.read + Tempfile.open("nuget_extraction_package_file_#{@package_file_id}") do |file| + entry.extract(file.path) { true } # allow #extract to overwrite the file + file.unlink + file.read + end + rescue Zip::EntrySizeError => e + raise ExtractionError, "nuspec file has the wrong entry size: #{e.message}" end end diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb index de7ede4eabf..6a13b8e38c1 100644 --- a/app/services/projects/import_service.rb +++ b/app/services/projects/import_service.rb @@ -53,6 +53,8 @@ module Projects private + attr_reader :resolved_address + def after_execute_hook # Defined in EE::Projects::ImportService end @@ -64,11 +66,7 @@ module Projects def add_repository_to_project if project.external_import? && !unknown_url? begin - Gitlab::UrlBlocker.validate!( - project.import_url, - schemes: Project::VALID_IMPORT_PROTOCOLS, - ports: Project::VALID_IMPORT_PORTS - ) + @resolved_address = get_resolved_address rescue Gitlab::UrlBlocker::BlockedUrlError => e raise e, s_("ImportProjects|Blocked import URL: %{message}") % { message: e.message } end @@ -97,9 +95,9 @@ module Projects if refmap project.ensure_repository - project.repository.fetch_as_mirror(project.import_url, refmap: refmap) + project.repository.fetch_as_mirror(project.import_url, refmap: refmap, resolved_address: resolved_address) else - project.repository.import_repository(project.import_url) + project.repository.import_repository(project.import_url, resolved_address: resolved_address) end rescue ::Gitlab::Git::CommandError => e # Expire cache to prevent scenarios such as: @@ -157,6 +155,26 @@ module Projects def importer_imports_repository? has_importer? && importer_class.try(:imports_repository?) end + + def get_resolved_address + Gitlab::UrlBlocker + .validate!( + project.import_url, + schemes: Project::VALID_IMPORT_PROTOCOLS, + ports: Project::VALID_IMPORT_PORTS, + dns_rebind_protection: dns_rebind_protection?) + .then do |(import_url, resolved_host)| + next '' if resolved_host.nil? || !import_url.scheme.in?(%w[http https]) + + import_url.host.to_s + end + end + + def dns_rebind_protection? + return false if Gitlab.http_proxy_env? + + Gitlab::CurrentSettings.dns_rebinding_protection_enabled? + end end end diff --git a/app/services/web_hooks/log_execution_service.rb b/app/services/web_hooks/log_execution_service.rb index 1a40c877bda..448bb7d4097 100644 --- a/app/services/web_hooks/log_execution_service.rb +++ b/app/services/web_hooks/log_execution_service.rb @@ -24,6 +24,8 @@ module WebHooks private def log_execution + log_data[:request_headers]['X-Gitlab-Token'] = _('[REDACTED]') if hook.token? + WebHookLog.create!(web_hook: hook, **log_data) end diff --git a/app/views/projects/tags/_release_link.html.haml b/app/views/projects/tags/_release_link.html.haml index c942d122a58..6c79b13f438 100644 --- a/app/views/projects/tags/_release_link.html.haml +++ b/app/views/projects/tags/_release_link.html.haml @@ -1,4 +1,5 @@ -.gl-text-secondary - = sprite_icon("rocket", size: 12) - = _("Release") - = link_to release.name, project_release_path(project, release), class: "gl-text-blue-600!" +- if can?(current_user, :read_release, release) + .gl-text-secondary + = sprite_icon("rocket", size: 12) + = _("Release") + = link_to release.name, project_release_path(project, release), class: "gl-text-blue-600!" diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml index cb7751ecf2e..a9c3309e38c 100644 --- a/app/views/projects/tags/show.html.haml +++ b/app/views/projects/tags/show.html.haml @@ -57,12 +57,13 @@ %pre.wrap{ data: { qa_selector: 'tag_message_content' } } = strip_signature(@tag.message) -.gl-mb-3.gl-mt-3 - - if @release&.description.present? - .description.md{ data: { qa_selector: 'tag_release_notes_content' } } - = markdown_field(@release, :description) - - else - = s_('TagsPage|This tag has no release notes.') +- if can?(current_user, :read_release, @release) + .gl-mb-3.gl-mt-3 + - if @release&.description.present? + .description.md{ data: { qa_selector: 'tag_release_notes_content' } } + = markdown_field(@release, :description) + - else + = s_('TagsPage|This tag has no release notes.') - if can?(current_user, :admin_tag, @project) .js-delete-tag-modal diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index b8f4ff0e9c4..3b5151ef4f2 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -861,7 +861,11 @@ module Gitlab # no_tags - should we use --no-tags flag? # prune - should we use --prune flag? # check_tags_changed - should we ask gitaly to calculate whether any tags changed? - def fetch_remote(url, refmap: nil, ssh_auth: nil, forced: false, no_tags: false, prune: true, check_tags_changed: false, http_authorization_header: "") + # resolved_address - resolved IP address for provided URL + def fetch_remote( # rubocop:disable Metrics/ParameterLists + url, + refmap: nil, ssh_auth: nil, forced: false, no_tags: false, prune: true, + check_tags_changed: false, http_authorization_header: "", resolved_address: "") wrapped_gitaly_errors do gitaly_repository_client.fetch_remote( url, @@ -872,16 +876,17 @@ module Gitlab prune: prune, check_tags_changed: check_tags_changed, timeout: GITLAB_PROJECTS_TIMEOUT, - http_authorization_header: http_authorization_header + http_authorization_header: http_authorization_header, + resolved_address: resolved_address ) end end - def import_repository(url, http_authorization_header: '', mirror: false) + def import_repository(url, http_authorization_header: '', mirror: false, resolved_address: '') raise ArgumentError, "don't use disk paths with import_repository: #{url.inspect}" if url.start_with?('.', '/') wrapped_gitaly_errors do - gitaly_repository_client.import_repository(url, http_authorization_header: http_authorization_header, mirror: mirror) + gitaly_repository_client.import_repository(url, http_authorization_header: http_authorization_header, mirror: mirror, resolved_address: resolved_address) end end diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb index e6565bd33c2..daaf18c711d 100644 --- a/lib/gitlab/gitaly_client/repository_service.rb +++ b/lib/gitlab/gitaly_client/repository_service.rb @@ -81,7 +81,7 @@ module Gitlab # rubocop: disable Metrics/ParameterLists # The `remote` parameter is going away soonish anyway, at which point the # Rubocop warning can be enabled again. - def fetch_remote(url, refmap:, ssh_auth:, forced:, no_tags:, timeout:, prune: true, check_tags_changed: false, http_authorization_header: "") + def fetch_remote(url, refmap:, ssh_auth:, forced:, no_tags:, timeout:, prune: true, check_tags_changed: false, http_authorization_header: "", resolved_address: "") request = Gitaly::FetchRemoteRequest.new( repository: @gitaly_repo, force: forced, @@ -92,7 +92,8 @@ module Gitlab remote_params: Gitaly::Remote.new( url: url, mirror_refmaps: Array.wrap(refmap).map(&:to_s), - http_authorization_header: http_authorization_header + http_authorization_header: http_authorization_header, + resolved_address: resolved_address ) ) @@ -148,12 +149,13 @@ module Gitlab ) end - def import_repository(source, http_authorization_header: '', mirror: false) + def import_repository(source, http_authorization_header: '', mirror: false, resolved_address: '') request = Gitaly::CreateRepositoryFromURLRequest.new( repository: @gitaly_repo, url: source, http_authorization_header: http_authorization_header, - mirror: mirror + mirror: mirror, + resolved_address: resolved_address ) gitaly_client_call( diff --git a/spec/features/tags/developer_views_tags_spec.rb b/spec/features/tags/developer_views_tags_spec.rb index 57e1f7da04e..e2399dd9978 100644 --- a/spec/features/tags/developer_views_tags_spec.rb +++ b/spec/features/tags/developer_views_tags_spec.rb @@ -53,6 +53,8 @@ RSpec.describe 'Developer views tags' do end it 'views a specific tag page' do + create(:release, project: project, tag: 'v1.0.0', name: 'v1.0.0', description: nil) + click_on 'v1.0.0' expect(page).to have_current_path( diff --git a/spec/fixtures/packages/nuget/corrupted_package.nupkg b/spec/fixtures/packages/nuget/corrupted_package.nupkg new file mode 100644 index 00000000000..54f05c62aea Binary files /dev/null and b/spec/fixtures/packages/nuget/corrupted_package.nupkg differ diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 5e27979cbf3..197662943a0 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -524,12 +524,13 @@ RSpec.describe Gitlab::Git::Repository do prune: false, check_tags_changed: false, refmap: nil, - http_authorization_header: "" + http_authorization_header: "", + resolved_address: '172.16.123.1' } expect(repository.gitaly_repository_client).to receive(:fetch_remote).with(url, expected_opts) - repository.fetch_remote(url, ssh_auth: ssh_auth, forced: true, no_tags: true, prune: false, check_tags_changed: false) + repository.fetch_remote(url, ssh_auth: ssh_auth, forced: true, no_tags: true, prune: false, check_tags_changed: false, resolved_address: '172.16.123.1') end it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RepositoryService, :fetch_remote do @@ -2448,7 +2449,7 @@ RSpec.describe Gitlab::Git::Repository do it 'delegates to Gitaly' do expect_next_instance_of(Gitlab::GitalyClient::RepositoryService) do |svc| - expect(svc).to receive(:import_repository).with(url, http_authorization_header: '', mirror: false).and_return(nil) + expect(svc).to receive(:import_repository).with(url, http_authorization_header: '', mirror: false, resolved_address: '').and_return(nil) end repository.import_repository(url) diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb index 58ace05b0d3..5aef250afac 100644 --- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb @@ -133,6 +133,40 @@ RSpec.describe Gitlab::GitalyClient::RepositoryService do end end + describe '#import_repository' do + let(:source) { 'https://example.com/git/repo.git' } + + it 'sends a create_repository_from_url message' do + expected_request = gitaly_request_with_params( + url: source, + resolved_address: '' + ) + + expect_any_instance_of(Gitaly::RepositoryService::Stub) + .to receive(:create_repository_from_url) + .with(expected_request, kind_of(Hash)) + .and_return(double(value: true)) + + client.import_repository(source) + end + + context 'when http_host is provided' do + it 'sends a create_repository_from_url message with http_host provided in the request' do + expected_request = gitaly_request_with_params( + url: source, + resolved_address: '172.16.123.1' + ) + + expect_any_instance_of(Gitaly::RepositoryService::Stub) + .to receive(:create_repository_from_url) + .with(expected_request, kind_of(Hash)) + .and_return(double(value: true)) + + client.import_repository(source, resolved_address: '172.16.123.1') + end + end + end + describe '#fetch_remote' do let(:url) { 'https://example.com/git/repo.git' } @@ -141,7 +175,8 @@ RSpec.describe Gitlab::GitalyClient::RepositoryService do remote_params: Gitaly::Remote.new( url: url, http_authorization_header: "", - mirror_refmaps: [] + mirror_refmaps: [], + resolved_address: '' ), ssh_key: '', known_hosts: '', @@ -159,6 +194,32 @@ RSpec.describe Gitlab::GitalyClient::RepositoryService do client.fetch_remote(url, refmap: nil, ssh_auth: nil, forced: false, no_tags: false, timeout: 1, check_tags_changed: false) end + context 'with resolved address' do + it 'sends a fetch_remote_request message' do + expected_request = gitaly_request_with_params( + remote_params: Gitaly::Remote.new( + url: url, + http_authorization_header: "", + mirror_refmaps: [], + resolved_address: '172.16.123.1' + ), + ssh_key: '', + known_hosts: '', + force: false, + no_tags: false, + no_prune: false, + check_tags_changed: false + ) + + expect_any_instance_of(Gitaly::RepositoryService::Stub) + .to receive(:fetch_remote) + .with(expected_request, kind_of(Hash)) + .and_return(double(value: true)) + + client.fetch_remote(url, refmap: nil, ssh_auth: nil, forced: false, no_tags: false, timeout: 1, check_tags_changed: false, resolved_address: '172.16.123.1') + end + end + context 'SSH auth' do where(:ssh_mirror_url, :ssh_key_auth, :ssh_private_key, :ssh_known_hosts, :expected_params) do false | false | 'key' | 'known_hosts' | {} diff --git a/spec/models/hooks/web_hook_log_spec.rb b/spec/models/hooks/web_hook_log_spec.rb index fafca144cae..2f0bfbd4fed 100644 --- a/spec/models/hooks/web_hook_log_spec.rb +++ b/spec/models/hooks/web_hook_log_spec.rb @@ -188,4 +188,22 @@ RSpec.describe WebHookLog do it { expect(web_hook_log.internal_error?).to be_truthy } end end + + describe '#request_headers' do + let(:hook) { build(:project_hook, :token) } + let(:web_hook_log) { build(:web_hook_log, request_headers: request_headers) } + let(:expected_headers) { { 'X-Gitlab-Token' => _('[REDACTED]') } } + + context 'with redacted headers token' do + let(:request_headers) { { 'X-Gitlab-Token' => _('[REDACTED]') } } + + it { expect(web_hook_log.request_headers).to eq(expected_headers) } + end + + context 'with exposed headers token' do + let(:request_headers) { { 'X-Gitlab-Token' => hook.token } } + + it { expect(web_hook_log.request_headers).to eq(expected_headers) } + end + end end diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb index db854670cc3..9b55db15f3b 100644 --- a/spec/models/hooks/web_hook_spec.rb +++ b/spec/models/hooks/web_hook_spec.rb @@ -195,6 +195,36 @@ RSpec.describe WebHook do end end + describe 'before_validation :reset_token' do + subject(:hook) { build_stubbed(:project_hook, :token, project: project) } + + it 'resets token if url changed' do + hook.url = 'https://webhook.example.com/new-hook' + + expect(hook).to be_valid + expect(hook.token).to be_nil + end + + it 'does not reset token if new url is set together with the same token' do + hook.url = 'https://webhook.example.com/new-hook' + current_token = hook.token + hook.token = current_token + + expect(hook).to be_valid + expect(hook.token).to eq(current_token) + expect(hook.url).to eq('https://webhook.example.com/new-hook') + end + + it 'does not reset token if new url is set together with a new token' do + hook.url = 'https://webhook.example.com/new-hook' + hook.token = 'token' + + expect(hook).to be_valid + expect(hook.token).to eq('token') + expect(hook.url).to eq('https://webhook.example.com/new-hook') + end + end + it "only consider these branch filter strategies are valid" do expected_valid_types = %w[all_branches regex wildcard] expect(described_class.branch_filter_strategies.keys).to contain_exactly(*expected_valid_types) diff --git a/spec/models/integrations/jira_spec.rb b/spec/models/integrations/jira_spec.rb index 819dad9d46d..af1112cf50d 100644 --- a/spec/models/integrations/jira_spec.rb +++ b/spec/models/integrations/jira_spec.rb @@ -230,9 +230,12 @@ RSpec.describe Integrations::Jira do where(:url, :result) do 'https://abc.atlassian.net' | true + 'http://abc.atlassian.net' | false '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 + 'javascript://test.atlassian.net/%250dalert(document.domain)' | false + 'https://example.com".atlassian.net' | false + nil | false end with_them do @@ -289,7 +292,7 @@ RSpec.describe Integrations::Jira do let(:server_info_results) { { 'deploymentType' => 'FutureCloud' } } context 'and URL ends in .atlassian.net' do - let(:api_url) { 'http://example-api.atlassian.net' } + let(:api_url) { 'https://example-api.atlassian.net' } it 'deployment_type is set to cloud' do expect(integration.jira_tracker_data).to be_deployment_cloud @@ -297,7 +300,7 @@ RSpec.describe Integrations::Jira do end context 'and URL is something else' do - let(:api_url) { 'http://my-jira-api.someserver.com' } + let(:api_url) { 'https://my-jira-api.someserver.com' } it 'deployment_type is set to server' do expect(integration.jira_tracker_data).to be_deployment_server @@ -309,7 +312,7 @@ RSpec.describe Integrations::Jira do let(:server_info_results) { {} } context 'and URL ends in .atlassian.net' do - let(:api_url) { 'http://example-api.atlassian.net' } + let(:api_url) { 'https://example-api.atlassian.net' } it 'deployment_type is set to cloud' do expect(Gitlab::AppLogger).to receive(:warn).with(message: "Jira API returned no ServerInfo, setting deployment_type from URL", server_info: server_info_results, url: api_url) @@ -318,7 +321,7 @@ RSpec.describe Integrations::Jira do end context 'and URL is something else' do - let(:api_url) { 'http://my-jira-api.someserver.com' } + let(:api_url) { 'https://my-jira-api.someserver.com' } it 'deployment_type is set to server' do expect(Gitlab::AppLogger).to receive(:warn).with(message: "Jira API returned no ServerInfo, setting deployment_type from URL", server_info: server_info_results, url: api_url) diff --git a/spec/models/project_import_state_spec.rb b/spec/models/project_import_state_spec.rb index db79185d759..ba1a29a8b27 100644 --- a/spec/models/project_import_state_spec.rb +++ b/spec/models/project_import_state_spec.rb @@ -22,7 +22,7 @@ RSpec.describe ProjectImportState, type: :model do before do allow_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:import_repository) - .with(project.import_url, http_authorization_header: '', mirror: false).and_return(true) + .with(project.import_url, http_authorization_header: '', mirror: false, resolved_address: '').and_return(true) # Works around https://github.com/rspec/rspec-mocks/issues/910 allow(Project).to receive(:find).with(project.id).and_return(project) diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 93872bcd827..c17e180f282 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -1223,11 +1223,22 @@ RSpec.describe Repository do it 'fetches the URL without creating a remote' do expect(repository) .to receive(:fetch_remote) - .with(url, forced: false, prune: true, refmap: :all_refs, http_authorization_header: "") + .with(url, forced: false, prune: true, refmap: :all_refs, http_authorization_header: "", resolved_address: '') .and_return(nil) repository.fetch_as_mirror(url) end + + context 'with http_host provided' do + it 'fetches the URL with resolved_address value' do + expect(repository) + .to receive(:fetch_remote) + .with(url, forced: false, prune: true, refmap: :all_refs, http_authorization_header: "", resolved_address: '172.16.123.1') + .and_return(nil) + + repository.fetch_as_mirror(url, resolved_address: '172.16.123.1') + end + end end describe '#fetch_ref' do diff --git a/spec/requests/jira_connect/users_controller_spec.rb b/spec/requests/jira_connect/users_controller_spec.rb index c648d28c1bc..6e927aaba91 100644 --- a/spec/requests/jira_connect/users_controller_spec.rb +++ b/spec/requests/jira_connect/users_controller_spec.rb @@ -31,5 +31,16 @@ RSpec.describe JiraConnect::UsersController do expect(response.body).not_to include('Return to GitLab') end end + + context 'with a script injected' do + let(:return_to) { 'javascript://test.atlassian.net/%250dalert(document.domain)' } + + 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/markup/rendering_service_spec.rb b/spec/services/markup/rendering_service_spec.rb index a5711a8cbc4..d54bc71f0a4 100644 --- a/spec/services/markup/rendering_service_spec.rb +++ b/spec/services/markup/rendering_service_spec.rb @@ -110,9 +110,12 @@ RSpec.describe Markup::RenderingService do context 'when file is a regular text file' do let(:file_name) { 'foo.txt' } + let(:text) { 'Noël
' } it 'returns html (rendered by ActionView::TagHelper)' do - is_expected.to eq('
Noël
') + expect(ActionController::Base.helpers).to receive(:content_tag).and_call_original + + is_expected.to eq('
Noël <form>
') end end diff --git a/spec/services/packages/nuget/metadata_extraction_service_spec.rb b/spec/services/packages/nuget/metadata_extraction_service_spec.rb index fc21cfd502e..12bab30b4a7 100644 --- a/spec/services/packages/nuget/metadata_extraction_service_spec.rb +++ b/spec/services/packages/nuget/metadata_extraction_service_spec.rb @@ -114,5 +114,16 @@ RSpec.describe Packages::Nuget::MetadataExtractionService do it { expect { subject }.to raise_error(::Packages::Nuget::MetadataExtractionService::ExtractionError, 'nuspec file too big') } end + + context 'with a corrupted nupkg file with a wrong entry size' do + let(:nupkg_fixture_path) { expand_fixture_path('packages/nuget/corrupted_package.nupkg') } + let(:expected_error) { "nuspec file has the wrong entry size: entry 'DummyProject.DummyPackage.nuspec' should be 255B, but is larger when inflated." } + + before do + allow(Zip::File).to receive(:new).and_return(Zip::File.new(nupkg_fixture_path, false, false)) + end + + it { expect { subject }.to raise_error(::Packages::Nuget::MetadataExtractionService::ExtractionError, expected_error) } + end end end diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb index 6dc72948541..b3f8980a7bd 100644 --- a/spec/services/projects/import_service_spec.rb +++ b/spec/services/projects/import_service_spec.rb @@ -127,30 +127,67 @@ RSpec.describe Projects::ImportService do project.import_type = 'bitbucket' end - it 'succeeds if repository import is successful' do - expect(project.repository).to receive(:import_repository).and_return(true) - expect_next_instance_of(Gitlab::BitbucketImport::Importer) do |importer| - expect(importer).to receive(:execute).and_return(true) + context 'when importer supports refmap' do + before do + project.import_type = 'gitea' end - expect_next_instance_of(Projects::LfsPointers::LfsImportService) do |service| - expect(service).to receive(:execute).and_return(status: :success) + it 'succeeds if repository fetch as mirror is successful' do + expect(project).to receive(:ensure_repository) + expect(project.repository).to receive(:fetch_as_mirror).with('https://bitbucket.org/vim/vim.git', refmap: Gitlab::LegacyGithubImport::Importer.refmap, resolved_address: '').and_return(true) + expect_next_instance_of(Gitlab::LegacyGithubImport::Importer) do |importer| + expect(importer).to receive(:execute).and_return(true) + end + + expect_next_instance_of(Projects::LfsPointers::LfsImportService) do |service| + expect(service).to receive(:execute).and_return(status: :success) + end + + result = subject.execute + + expect(result[:status]).to eq :success end - result = subject.execute + it 'fails if repository fetch as mirror fails' do + expect(project).to receive(:ensure_repository) + expect(project.repository) + .to receive(:fetch_as_mirror) + .and_raise(Gitlab::Git::CommandError, 'Failed to import the repository /a/b/c') - expect(result[:status]).to eq :success + result = subject.execute + + expect(result[:status]).to eq :error + expect(result[:message]).to eq "Error importing repository #{project.safe_import_url} into #{project.full_path} - Failed to import the repository [FILTERED]" + end end - it 'fails if repository import fails' do - expect(project.repository) - .to receive(:import_repository) - .and_raise(Gitlab::Git::CommandError, 'Failed to import the repository /a/b/c') + context 'when importer does not support refmap' do + it 'succeeds if repository import is successful' do + expect(project.repository).to receive(:import_repository).and_return(true) + expect_next_instance_of(Gitlab::BitbucketImport::Importer) do |importer| + expect(importer).to receive(:execute).and_return(true) + end - result = subject.execute + expect_next_instance_of(Projects::LfsPointers::LfsImportService) do |service| + expect(service).to receive(:execute).and_return(status: :success) + end - expect(result[:status]).to eq :error - expect(result[:message]).to eq "Error importing repository #{project.safe_import_url} into #{project.full_path} - Failed to import the repository [FILTERED]" + result = subject.execute + + expect(result[:status]).to eq :success + end + + it 'fails if repository import fails' do + expect(project.repository) + .to receive(:import_repository) + .with('https://bitbucket.org/vim/vim.git', resolved_address: '') + .and_raise(Gitlab::Git::CommandError, 'Failed to import the repository /a/b/c') + + result = subject.execute + + expect(result[:status]).to eq :error + expect(result[:message]).to eq "Error importing repository #{project.safe_import_url} into #{project.full_path} - Failed to import the repository [FILTERED]" + end end context 'when lfs import fails' do @@ -287,6 +324,102 @@ RSpec.describe Projects::ImportService do end end + context 'when DNS rebind protection is disabled' do + before do + allow(Gitlab::CurrentSettings).to receive(:dns_rebinding_protection_enabled?).and_return(false) + project.import_url = "https://example.com/group/project" + + allow(Gitlab::UrlBlocker).to receive(:validate!) + .with(project.import_url, ports: Project::VALID_IMPORT_PORTS, schemes: Project::VALID_IMPORT_PROTOCOLS, dns_rebind_protection: false) + .and_return([Addressable::URI.parse("https://example.com/group/project"), nil]) + end + + it 'imports repository with url without additional resolved address' do + expect(project.repository).to receive(:import_repository).with('https://example.com/group/project', resolved_address: '').and_return(true) + + expect_next_instance_of(Projects::LfsPointers::LfsImportService) do |service| + expect(service).to receive(:execute).and_return(status: :success) + end + + result = subject.execute + + expect(result[:status]).to eq(:success) + end + end + + context 'when DNS rebind protection is enabled' do + before do + allow(Gitlab::CurrentSettings).to receive(:http_proxy_env?).and_return(false) + allow(Gitlab::CurrentSettings).to receive(:dns_rebinding_protection_enabled?).and_return(true) + end + + context 'when https url is provided' do + before do + project.import_url = "https://example.com/group/project" + + allow(Gitlab::UrlBlocker).to receive(:validate!) + .with(project.import_url, ports: Project::VALID_IMPORT_PORTS, schemes: Project::VALID_IMPORT_PROTOCOLS, dns_rebind_protection: true) + .and_return([Addressable::URI.parse("https://172.16.123.1/group/project"), 'example.com']) + end + + it 'imports repository with url and additional resolved address' do + expect(project.repository).to receive(:import_repository).with('https://example.com/group/project', resolved_address: '172.16.123.1').and_return(true) + + expect_next_instance_of(Projects::LfsPointers::LfsImportService) do |service| + expect(service).to receive(:execute).and_return(status: :success) + end + + result = subject.execute + + expect(result[:status]).to eq(:success) + end + end + + context 'when http url is provided' do + before do + project.import_url = "http://example.com/group/project" + + allow(Gitlab::UrlBlocker).to receive(:validate!) + .with(project.import_url, ports: Project::VALID_IMPORT_PORTS, schemes: Project::VALID_IMPORT_PROTOCOLS, dns_rebind_protection: true) + .and_return([Addressable::URI.parse("http://172.16.123.1/group/project"), 'example.com']) + end + + it 'imports repository with url and additional resolved address' do + expect(project.repository).to receive(:import_repository).with('http://example.com/group/project', resolved_address: '172.16.123.1').and_return(true) + + expect_next_instance_of(Projects::LfsPointers::LfsImportService) do |service| + expect(service).to receive(:execute).and_return(status: :success) + end + + result = subject.execute + + expect(result[:status]).to eq(:success) + end + end + + context 'when git address is provided' do + before do + project.import_url = "git://example.com/group/project.git" + + allow(Gitlab::UrlBlocker).to receive(:validate!) + .with(project.import_url, ports: Project::VALID_IMPORT_PORTS, schemes: Project::VALID_IMPORT_PROTOCOLS, dns_rebind_protection: true) + .and_return([Addressable::URI.parse("git://172.16.123.1/group/project"), 'example.com']) + end + + it 'imports repository with url and without resolved address' do + expect(project.repository).to receive(:import_repository).with('git://example.com/group/project.git', resolved_address: '').and_return(true) + + expect_next_instance_of(Projects::LfsPointers::LfsImportService) do |service| + expect(service).to receive(:execute).and_return(status: :success) + end + + result = subject.execute + + expect(result[:status]).to eq(:success) + end + end + end + it_behaves_like 'measurable service' do let(:base_log_data) do { diff --git a/spec/services/web_hooks/log_execution_service_spec.rb b/spec/services/web_hooks/log_execution_service_spec.rb index 1b8ff9f2a05..fd97d01fa9f 100644 --- a/spec/services/web_hooks/log_execution_service_spec.rb +++ b/spec/services/web_hooks/log_execution_service_spec.rb @@ -11,14 +11,15 @@ RSpec.describe WebHooks::LogExecutionService do travel_to(Time.current) { example.run } end - let_it_be_with_reload(:project_hook) { create(:project_hook) } + let_it_be_with_reload(:project_hook) { create(:project_hook, :token) } let(:response_category) { :ok } + let(:request_headers) { { 'Header' => 'header value' } } let(:data) do { trigger: 'trigger_name', url: 'https://example.com', - request_headers: { 'Header' => 'header value' }, + request_headers: request_headers, request_data: { 'Request Data' => 'request data value' }, response_body: 'Response body', response_status: '200', @@ -163,5 +164,15 @@ RSpec.describe WebHooks::LogExecutionService do service.execute end end + + context 'with X-Gitlab-Token' do + let(:request_headers) { { 'X-Gitlab-Token' => project_hook.token } } + + it 'redacts the token' do + service.execute + + expect(WebHookLog.recent.first.request_headers).to include('X-Gitlab-Token' => '[REDACTED]') + end + end end end diff --git a/spec/support/shared_examples/features/user_views_tag_shared_examples.rb b/spec/support/shared_examples/features/user_views_tag_shared_examples.rb index 989de1dbfbb..702964a2610 100644 --- a/spec/support/shared_examples/features/user_views_tag_shared_examples.rb +++ b/spec/support/shared_examples/features/user_views_tag_shared_examples.rb @@ -2,33 +2,54 @@ RSpec.shared_examples 'user views tag' do context 'when user views with the tag' do - let(:project) { create(:project, :repository) } + let(:project) { create(:project, :repository, :public) } let(:user) { create(:user) } let(:tag_name) { "stable" } - let!(:release) { create(:release, project: project, tag: tag_name, name: "ReleaseName") } + let(:release_name) { 'ReleaseName' } + let(:release_notes) { 'Release notes' } + let!(:release) do + create(:release, project: project, tag: tag_name, name: release_name, description: release_notes) + end before do - project.add_developer(user) project.repository.add_tag(user, tag_name, project.default_branch_or_main) - sign_in(user) end - shared_examples 'shows tag' do - it do - visit tag_page + context 'and user is authorized to read release' do + before do + project.add_developer(user) + end + + shared_examples 'shows tag' do + it do + visit tag_page + + expect(page).to have_content tag_name + expect(page).to have_link(release_name, href: project_release_path(project, release)) + end + end - expect(page).to have_content tag_name - expect(page).to have_link("ReleaseName", href: project_release_path(project, release)) + it_behaves_like 'shows tag' + + context 'when tag name contains a slash' do + let(:tag_name) { "stable/v0.1" } + + it_behaves_like 'shows tag' end end - it_behaves_like 'shows tag' + context 'and user is not authorized to read release' do + before do + project.project_feature.update!(releases_access_level: Featurable::PRIVATE) + end - context 'when tag name contains a slash' do - let(:tag_name) { "stable/v0.1" } + it 'hides release link and notes', :aggregate_failures do + visit tag_page - it_behaves_like 'shows tag' + expect(page).not_to have_link(release_name, href: project_release_path(project, release)) + expect(page).not_to have_text(release_notes) + end end end end -- cgit v1.2.3