dependabot-nuget 0.239.0 → 0.240.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2bf5bdfc4f365f5d04c30193e1b44e7a82ddb5816461148e4172cabc1a86bea3
4
- data.tar.gz: 0ca7483d0fdc3d3a7dc2af7c8f475e7b02a121d4a8a5c58f495ff857d32c3dad
3
+ metadata.gz: 7d8152c074be84d639b86fe67da074f558ca1509102a5a1e9e97c3411178a747
4
+ data.tar.gz: 6562552b5b89ddfc8c521aeb7a52cf17e2d918c230439f70281663d5e8e45811
5
5
  SHA512:
6
- metadata.gz: a0bd5448386d99ac0fa6a27ea6f5ec137885cd8a208762602d69284c1cdea6bb9e521a885b9a50cb42eb45b2de9ec760def87940c15023a910f7f9455a612fc9
7
- data.tar.gz: 4096cfc58f861ec2f4f5d7a4022fd06f001d0f4421130bf0708e877a93ce2e12e722259d60b29f8303375d2e3cdc5f595e396cb4273d5c1e03a34e23a3a154ec
6
+ metadata.gz: 6d91dec9cd6d5db739507bfc91d6454b6f21426a60a2086bd7b944922d4fa123886dbb1037a94b712471deeb89d175c1b0f10a2b84aeb7f6810f3aeddb92eecb
7
+ data.tar.gz: 7dbe0cacc3ffaa2d9290e09da5026a94432b8f45b839c6db49905b40636188c0201b61f6650aa1a01fa36fe590672b3e0359b8d4139d1bfe26d98dac344b494a
@@ -13,6 +13,8 @@ module Dependabot
13
13
  end
14
14
 
15
15
  def self.cache(name)
16
+ return {} if caching_disabled?
17
+
16
18
  @cache ||= {}
17
19
  @cache[name] ||= {}
18
20
  @cache[name]
@@ -3,6 +3,7 @@
3
3
 
4
4
  require "dependabot/file_fetchers"
5
5
  require "dependabot/file_fetchers/base"
6
+ require "dependabot/nuget/cache_manager"
6
7
  require "set"
7
8
  require "sorbet-runtime"
8
9
 
@@ -23,7 +24,7 @@ module Dependabot
23
24
  return true if filenames.any? { |f| f.match?("^src$") }
24
25
  return true if filenames.any? { |f| f.end_with?(".proj") }
25
26
 
26
- filenames.any? { |name| name.match?(%r{^[^/]*\.[a-z]{2}proj$}) }
27
+ filenames.any? { |name| name.match?(/\.[a-z]{2}proj$/) }
27
28
  end
28
29
 
29
30
  def self.required_files_message
@@ -219,29 +220,32 @@ module Dependabot
219
220
  def nuget_config_files
220
221
  return @nuget_config_files if @nuget_config_files
221
222
 
222
- @nuget_config_files = []
223
- candidate_paths = [*project_files.map { |f| File.dirname(f.name) }, "."].uniq
224
- visited_directories = Set.new
225
- candidate_paths.each do |dir|
226
- search_in_directory_and_parents(dir, visited_directories)
227
- end
223
+ @nuget_config_files = [*project_files.map do |f|
224
+ named_file_up_tree_from_project_file(f, "nuget.config")
225
+ end].compact.uniq
228
226
  @nuget_config_files
229
227
  end
230
228
 
231
- def search_in_directory_and_parents(dir, visited_directories)
232
- loop do
233
- break if visited_directories.include?(dir)
234
-
235
- visited_directories << dir
236
- file = repo_contents(dir: dir)
237
- .find { |f| f.name.casecmp("nuget.config").zero? }
238
- if file
239
- file = fetch_file_from_host(File.join(dir, file.name))
240
- file&.tap { |f| f.support_file = true }
241
- @nuget_config_files << file
229
+ def named_file_up_tree_from_project_file(project_file, expected_file_name)
230
+ found_expected_file = nil
231
+ directory_path = Pathname.new(directory)
232
+ full_project_dir = Pathname.new(project_file.directory).join(project_file.name).dirname
233
+ full_project_dir.ascend.each do |base|
234
+ break if found_expected_file
235
+
236
+ candidate_file_path = Pathname.new(base).join(expected_file_name).cleanpath.to_path
237
+ candidate_directory = Pathname.new(File.dirname(candidate_file_path))
238
+ relative_candidate_directory = candidate_directory.relative_path_from(directory_path)
239
+ candidate_file = repo_contents(dir: relative_candidate_directory).find do |f|
240
+ f.name.casecmp?(expected_file_name)
241
+ end
242
+ if candidate_file
243
+ found_expected_file = fetch_file_from_host(File.join(relative_candidate_directory,
244
+ candidate_file.name))
242
245
  end
243
- dir = File.dirname(dir)
244
246
  end
247
+
248
+ found_expected_file
245
249
  end
246
250
 
247
251
  def global_json
@@ -280,27 +284,34 @@ module Dependabot
280
284
  end
281
285
 
282
286
  def fetch_imported_property_files(file:, previously_fetched_files:)
283
- paths =
284
- ImportPathsFinder.new(project_file: file).import_paths +
285
- ImportPathsFinder.new(project_file: file).project_reference_paths +
286
- ImportPathsFinder.new(project_file: file).project_file_paths
287
-
288
- paths.flat_map do |path|
289
- next if previously_fetched_files.map(&:name).include?(path)
290
- next if file.name == path
291
- next if path.include?("$(")
292
-
293
- fetched_file = fetch_file_from_host(path)
294
- grandchild_property_files = fetch_imported_property_files(
295
- file: fetched_file,
296
- previously_fetched_files: previously_fetched_files + [file]
297
- )
298
- [fetched_file, *grandchild_property_files]
299
- rescue Dependabot::DependencyFileNotFound
300
- # Don't worry about missing files too much for now (at least
301
- # until we start resolving properties)
302
- nil
303
- end.compact
287
+ file_id = file.directory + "/" + file.name
288
+ @fetched_files ||= {}
289
+ if @fetched_files[file_id]
290
+ @fetched_files[file_id]
291
+ else
292
+ paths =
293
+ ImportPathsFinder.new(project_file: file).import_paths +
294
+ ImportPathsFinder.new(project_file: file).project_reference_paths +
295
+ ImportPathsFinder.new(project_file: file).project_file_paths
296
+
297
+ paths.flat_map do |path|
298
+ next if previously_fetched_files.map(&:name).include?(path)
299
+ next if file.name == path
300
+ next if path.include?("$(")
301
+
302
+ fetched_file = fetch_file_from_host(path)
303
+ grandchild_property_files = fetch_imported_property_files(
304
+ file: fetched_file,
305
+ previously_fetched_files: previously_fetched_files + [file]
306
+ )
307
+ @fetched_files[file_id] = [fetched_file, *grandchild_property_files]
308
+ @fetched_files[file_id]
309
+ rescue Dependabot::DependencyFileNotFound
310
+ # Don't worry about missing files too much for now (at least
311
+ # until we start resolving properties)
312
+ nil
313
+ end.compact
314
+ end
304
315
  end
305
316
  end
306
317
  end
@@ -26,16 +26,10 @@ module Dependabot
26
26
  end
27
27
 
28
28
  def dependency_set
29
- return parse_dependencies if CacheManager.caching_disabled?
30
-
31
29
  key = "#{packages_config.name.downcase}::#{packages_config.content.hash}"
32
30
  cache = PackagesConfigParser.dependency_set_cache
33
31
 
34
32
  cache[key] ||= parse_dependencies
35
-
36
- dependency_set = Dependabot::FileParsers::Base::DependencySet.new
37
- dependency_set += cache[key]
38
- dependency_set
39
33
  end
40
34
 
41
35
  private
@@ -7,13 +7,13 @@ require "dependabot/dependency"
7
7
  require "dependabot/nuget/file_parser"
8
8
  require "dependabot/nuget/update_checker"
9
9
  require "dependabot/nuget/cache_manager"
10
+ require "dependabot/nuget/nuget_client"
10
11
 
11
12
  # For details on how dotnet handles version constraints, see:
12
13
  # https://docs.microsoft.com/en-us/nuget/reference/package-versioning
13
14
  module Dependabot
14
15
  module Nuget
15
16
  class FileParser
16
- # rubocop:disable Metrics/ClassLength
17
17
  class ProjectFileParser
18
18
  require "dependabot/file_parsers/base/dependency_set"
19
19
  require_relative "property_value_finder"
@@ -50,16 +50,10 @@ module Dependabot
50
50
  end
51
51
 
52
52
  def dependency_set(project_file:)
53
- return parse_dependencies(project_file) if CacheManager.caching_disabled?
54
-
55
53
  key = "#{project_file.name.downcase}::#{project_file.content.hash}"
56
54
  cache = ProjectFileParser.dependency_set_cache
57
55
 
58
56
  cache[key] ||= parse_dependencies(project_file)
59
-
60
- dependency_set = Dependabot::FileParsers::Base::DependencySet.new
61
- dependency_set += cache[key]
62
- dependency_set
63
57
  end
64
58
 
65
59
  def target_frameworks(project_file:)
@@ -78,6 +72,10 @@ module Dependabot
78
72
  ["net#{value[1..-1].delete('.')}"]
79
73
  end
80
74
 
75
+ def nuget_configs
76
+ dependency_files.select { |f| f.name.match?(%r{(^|/)nuget\.config$}i) }
77
+ end
78
+
81
79
  private
82
80
 
83
81
  attr_reader :dependency_files, :credentials
@@ -281,7 +279,6 @@ module Dependabot
281
279
  end
282
280
 
283
281
  def dependency_has_search_results?(dependency)
284
- nuget_configs = dependency_files.select { |f| f.name.casecmp?("nuget.config") }
285
282
  dependency_urls = UpdateChecker::RepositoryFinder.new(
286
283
  dependency: dependency,
287
284
  credentials: credentials,
@@ -307,16 +304,9 @@ module Dependabot
307
304
  end
308
305
 
309
306
  def dependency_url_has_matching_result_v3?(dependency_name, dependency_url)
310
- url = dependency_url.fetch(:search_url)
311
- auth_header = dependency_url.fetch(:auth_header)
312
- response = execute_search_for_dependency_url(url, auth_header)
313
- return false unless response.status == 200
314
-
315
- body = JSON.parse(response.body)
316
- data = body["data"]
317
- return false unless data.length.positive?
307
+ versions = NugetClient.get_package_versions_v3(dependency_name, dependency_url)
318
308
 
319
- data.any? { |result| result["id"].casecmp?(dependency_name) }
309
+ versions != nil
320
310
  end
321
311
 
322
312
  def dependency_url_has_matching_result_v2?(dependency_name, dependency_url)
@@ -490,10 +480,6 @@ module Dependabot
490
480
  end
491
481
  end
492
482
 
493
- def nuget_configs
494
- dependency_files.select { |f| f.name.match?(/nuget\.config$/i) }
495
- end
496
-
497
483
  def global_json
498
484
  dependency_files.find { |f| f.name.casecmp("global.json").zero? }
499
485
  end
@@ -502,7 +488,6 @@ module Dependabot
502
488
  dependency_files.find { |f| f.name.casecmp(".config/dotnet-tools.json").zero? }
503
489
  end
504
490
  end
505
- # rubocop:enable Metrics/ClassLength
506
491
  end
507
492
  end
508
493
  end
@@ -74,7 +74,7 @@ module Dependabot
74
74
  end
75
75
 
76
76
  def project_files
77
- projfile = /\.[a-z]{2}proj$/
77
+ projfile = /\.([a-z]{2})?proj$/
78
78
  packageprops = /[Dd]irectory.[Pp]ackages.props/
79
79
 
80
80
  dependency_files.select do |df|
@@ -62,7 +62,9 @@ module Dependabot
62
62
 
63
63
  next unless project_dependencies.any? { |dep| dep.name.casecmp(dependency.name).zero? }
64
64
 
65
- NativeHelpers.run_nuget_updater_tool(repo_contents_path, proj_path, dependency, !dependency.top_level?)
65
+ NativeHelpers.run_nuget_updater_tool(repo_root: repo_contents_path, proj_path: proj_path,
66
+ dependency: dependency, is_transitive: !dependency.top_level?,
67
+ credentials: credentials)
66
68
  update_ran = true
67
69
  end
68
70
 
@@ -77,7 +79,9 @@ module Dependabot
77
79
  project_file = project_files.first
78
80
  proj_path = dependency_file_path(project_file)
79
81
 
80
- NativeHelpers.run_nuget_updater_tool(repo_contents_path, proj_path, dependency, !dependency.top_level?)
82
+ NativeHelpers.run_nuget_updater_tool(repo_root: repo_contents_path, proj_path: proj_path,
83
+ dependency: dependency, is_transitive: !dependency.top_level?,
84
+ credentials: credentials)
81
85
  return true
82
86
  end
83
87
 
@@ -1,6 +1,8 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
+ require_relative "nuget_config_credential_helpers"
5
+
4
6
  module Dependabot
5
7
  module Nuget
6
8
  module NativeHelpers
@@ -46,7 +48,7 @@ module Dependabot
46
48
  end
47
49
 
48
50
  # rubocop:disable Metrics/MethodLength
49
- def self.run_nuget_updater_tool(repo_root, proj_path, dependency, is_transitive)
51
+ def self.run_nuget_updater_tool(repo_root:, proj_path:, dependency:, is_transitive:, credentials:)
50
52
  exe_path = File.join(native_helpers_root, "NuGetUpdater", "NuGetUpdater.Cli")
51
53
  command = [
52
54
  exe_path,
@@ -84,9 +86,10 @@ module Dependabot
84
86
 
85
87
  puts "running NuGet updater:\n" + command
86
88
 
87
- output = SharedHelpers.run_shell_command(command, fingerprint: fingerprint)
88
-
89
- puts output
89
+ NuGetConfigCredentialHelpers.patch_nuget_config_for_action(credentials) do
90
+ output = SharedHelpers.run_shell_command(command, fingerprint: fingerprint)
91
+ puts output
92
+ end
90
93
  end
91
94
  # rubocop:enable Metrics/MethodLength
92
95
  end
@@ -0,0 +1,99 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/nuget/cache_manager"
5
+ require "dependabot/nuget/update_checker/repository_finder"
6
+
7
+ module Dependabot
8
+ module Nuget
9
+ class NugetClient
10
+ def self.get_package_versions_v3(dependency_name, repository_details)
11
+ # Use the registration URL if possible because it is fast and correct
12
+ if repository_details[:registration_url]
13
+ get_versions_from_registration_v3(repository_details)
14
+ # use the search API if not because it is slow but correct
15
+ elsif repository_details[:search_url]
16
+ get_versions_from_search_url_v3(repository_details, dependency_name)
17
+ # Otherwise, use the versions URL (fast but wrong because it includes unlisted versions)
18
+ elsif repository_details[:versions_url]
19
+ get_versions_from_versions_url_v3(repository_details)
20
+ end
21
+ end
22
+
23
+ private_class_method def self.get_versions_from_versions_url_v3(repository_details)
24
+ body = execute_search_for_dependency_url(repository_details[:versions_url], repository_details)
25
+ body&.fetch("versions")
26
+ end
27
+
28
+ private_class_method def self.get_versions_from_registration_v3(repository_details)
29
+ url = repository_details[:registration_url]
30
+ body = execute_search_for_dependency_url(url, repository_details)
31
+
32
+ return unless body
33
+
34
+ pages = body.fetch("items")
35
+ versions = Set.new
36
+ pages.each do |page|
37
+ items = page["items"]
38
+ if items
39
+ # inlined entries
40
+ items.each do |item|
41
+ catalog_entry = item["catalogEntry"]
42
+ if catalog_entry["listed"] == true
43
+ vers = catalog_entry["version"]
44
+ versions << vers
45
+ end
46
+ end
47
+ else
48
+ # paged entries
49
+ page_url = page["@id"]
50
+ page_body = execute_search_for_dependency_url(page_url, repository_details)
51
+ items = page_body.fetch("items")
52
+ items.each do |item|
53
+ catalog_entry = item.fetch("catalogEntry")
54
+ versions << catalog_entry.fetch("version") if catalog_entry["listed"] == true
55
+ end
56
+ end
57
+ end
58
+
59
+ versions
60
+ end
61
+
62
+ private_class_method def self.get_versions_from_search_url_v3(repository_details, dependency_name)
63
+ search_url = repository_details[:search_url]
64
+ body = execute_search_for_dependency_url(search_url, repository_details)
65
+
66
+ body&.fetch("data")
67
+ &.find { |d| d.fetch("id").casecmp(dependency_name.downcase).zero? }
68
+ &.fetch("versions")
69
+ &.map { |d| d.fetch("version") }
70
+ end
71
+
72
+ private_class_method def self.execute_search_for_dependency_url(url, repository_details)
73
+ cache = CacheManager.cache("dependency_url_search_cache")
74
+ cache[url] ||= Dependabot::RegistryClient.get(
75
+ url: url,
76
+ headers: repository_details[:auth_header]
77
+ )
78
+
79
+ response = cache[url]
80
+
81
+ return unless response.status == 200
82
+
83
+ body = remove_wrapping_zero_width_chars(response.body)
84
+ JSON.parse(body)
85
+ rescue Excon::Error::Timeout, Excon::Error::Socket
86
+ repo_url = repository_details[:repository_url]
87
+ raise if repo_url == Dependabot::Nuget::UpdateChecker::RepositoryFinder::DEFAULT_REPOSITORY_URL
88
+
89
+ raise PrivateSourceTimedOut, repo_url
90
+ end
91
+
92
+ private_class_method def self.remove_wrapping_zero_width_chars(string)
93
+ string.force_encoding("UTF-8").encode
94
+ .gsub(/\A[\u200B-\u200D\uFEFF]/, "")
95
+ .gsub(/[\u200B-\u200D\uFEFF]\Z/, "")
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,71 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Dependabot
5
+ module Nuget
6
+ module NuGetConfigCredentialHelpers
7
+ def self.user_nuget_config_path
8
+ home_directory = Dir.home
9
+ File.join(home_directory, ".nuget", "NuGet", "NuGet.Config")
10
+ end
11
+
12
+ def self.temporary_nuget_config_path
13
+ user_nuget_config_path + "_ORIGINAL"
14
+ end
15
+
16
+ def self.add_credentials_to_nuget_config(credentials)
17
+ return unless File.exist?(user_nuget_config_path)
18
+
19
+ nuget_credentials = credentials.select { |cred| cred["type"] == "nuget_feed" }
20
+ return if nuget_credentials.empty?
21
+
22
+ File.rename(user_nuget_config_path, temporary_nuget_config_path)
23
+
24
+ package_sources = []
25
+ package_source_credentials = []
26
+ nuget_credentials.each_with_index do |c, i|
27
+ source_name = "nuget_source_#{i + 1}"
28
+ package_sources << " <add key=\"#{source_name}\" value=\"#{c['url']}\" />"
29
+ next unless c["token"]
30
+
31
+ package_source_credentials << " <#{source_name}>"
32
+ package_source_credentials << " <add key=\"Username\" value=\"user\" />"
33
+ package_source_credentials << " <add key=\"ClearTextPassword\" value=\"#{c['token']}\" />"
34
+ package_source_credentials << " </#{source_name}>"
35
+ end
36
+
37
+ nuget_config = <<~NUGET_XML
38
+ <?xml version="1.0" encoding="utf-8"?>
39
+ <configuration>
40
+ <packageSources>
41
+ #{package_sources.join("\n")}
42
+ </packageSources>
43
+ <packageSourceCredentials>
44
+ #{package_source_credentials.join("\n")}
45
+ </packageSourceCredentials>
46
+ </configuration>
47
+ NUGET_XML
48
+ File.write(user_nuget_config_path, nuget_config)
49
+ end
50
+
51
+ def self.restore_user_nuget_config
52
+ return unless File.exist?(temporary_nuget_config_path)
53
+
54
+ File.delete(user_nuget_config_path)
55
+ File.rename(temporary_nuget_config_path, user_nuget_config_path)
56
+ end
57
+
58
+ # rubocop:disable Lint/SuppressedException
59
+ def self.patch_nuget_config_for_action(credentials, &_block)
60
+ add_credentials_to_nuget_config(credentials)
61
+ begin
62
+ yield
63
+ rescue StandardError
64
+ ensure
65
+ restore_user_nuget_config
66
+ end
67
+ end
68
+ # rubocop:enable Lint/SuppressedException
69
+ end
70
+ end
71
+ end
@@ -22,7 +22,7 @@ module Dependabot
22
22
 
23
23
  return DefaultRequirement if matches[1] == ">=" && matches[2] == "0"
24
24
 
25
- [matches[1] || "=", Nuget::Version.new(matches[2])]
25
+ [matches[1] || "=", Nuget::Version.new(T.must(matches[2]))]
26
26
  end
27
27
 
28
28
  # For consistency with other languages, we define a requirements array.
@@ -1,11 +1,11 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
- require "dependabot/nuget/update_checker"
4
+ require "dependabot/update_checkers/base"
5
5
 
6
6
  module Dependabot
7
7
  module Nuget
8
- class UpdateChecker
8
+ class UpdateChecker < Dependabot::UpdateCheckers::Base
9
9
  class CompatibilityChecker
10
10
  require_relative "nuspec_fetcher"
11
11
  require_relative "nupkg_fetcher"
@@ -4,12 +4,12 @@
4
4
  require "nokogiri"
5
5
  require "zip"
6
6
  require "stringio"
7
- require "dependabot/nuget/update_checker"
7
+ require "dependabot/update_checkers/base"
8
8
  require "dependabot/nuget/version"
9
9
 
10
10
  module Dependabot
11
11
  module Nuget
12
- class UpdateChecker
12
+ class UpdateChecker < Dependabot::UpdateCheckers::Base
13
13
  class DependencyFinder
14
14
  require_relative "requirements_updater"
15
15
  require_relative "nuspec_fetcher"
@@ -23,10 +23,7 @@ module Dependabot
23
23
  feed_url = repository_details[:repository_url]
24
24
  repository_type = repository_details[:repository_type]
25
25
 
26
- azure_devops_match = try_match_azure_url(feed_url)
27
- package_url = if azure_devops_match
28
- get_azure_package_url(azure_devops_match, package_id, package_version)
29
- elsif repository_type == "v2"
26
+ package_url = if repository_type == "v2"
30
27
  get_nuget_v2_package_url(feed_url, package_id, package_version)
31
28
  elsif repository_type == "v3"
32
29
  get_nuget_v3_package_url(repository_details, package_id, package_version)
@@ -45,31 +42,6 @@ module Dependabot
45
42
  fetch_stream(package_url, auth_header)
46
43
  end
47
44
 
48
- def self.try_match_azure_url(feed_url)
49
- # if url is azure devops
50
- azure_devops_regexs = [
51
- %r{https://pkgs\.dev\.azure\.com/(?<organization>[^/]+)/(?<project>[^/]+)/_packaging/(?<feedId>[^/]+)/nuget/v3/index\.json},
52
- %r{https://pkgs\.dev\.azure\.com/(?<organization>[^/]+)/_packaging/(?<feedId>[^/]+)/nuget/v3/index\.json(?<project>)},
53
- %r{https://(?<organization>[^\.\/]+)\.pkgs\.visualstudio\.com/_packaging/(?<feedId>[^/]+)/nuget/v3/index\.json(?<project>)}
54
- ]
55
- regex = azure_devops_regexs.find { |reg| reg.match(feed_url) }
56
- return unless regex
57
-
58
- regex.match(feed_url)
59
- end
60
-
61
- def self.get_azure_package_url(azure_devops_match, package_id, package_version)
62
- organization = azure_devops_match[:organization]
63
- project = azure_devops_match[:project]
64
- feed_id = azure_devops_match[:feedId]
65
-
66
- if project.empty?
67
- "https://pkgs.dev.azure.com/#{organization}/_apis/packaging/feeds/#{feed_id}/nuget/packages/#{package_id}/versions/#{package_version}/content?sourceProtocolVersion=nuget&api-version=7.0-preview"
68
- else
69
- "https://pkgs.dev.azure.com/#{organization}/#{project}/_apis/packaging/feeds/#{feed_id}/nuget/packages/#{package_id}/versions/#{package_version}/content?sourceProtocolVersion=nuget&api-version=7.0-preview"
70
- end
71
- end
72
-
73
45
  def self.get_nuget_v3_package_url(repository_details, package_id, package_version)
74
46
  base_url = repository_details[:base_url].delete_suffix("/")
75
47
  package_id_downcased = package_id.downcase
@@ -1,12 +1,12 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "dependabot/update_checkers/base"
4
5
  require "dependabot/nuget/file_parser"
5
- require "dependabot/nuget/update_checker"
6
6
 
7
7
  module Dependabot
8
8
  module Nuget
9
- class UpdateChecker
9
+ class UpdateChecker < Dependabot::UpdateCheckers::Base
10
10
  class PropertyUpdater
11
11
  require_relative "version_finder"
12
12
  require_relative "requirements_updater"
@@ -4,12 +4,13 @@
4
4
  require "excon"
5
5
  require "nokogiri"
6
6
  require "dependabot/errors"
7
- require "dependabot/nuget/update_checker"
7
+ require "dependabot/update_checkers/base"
8
8
  require "dependabot/registry_client"
9
+ require "dependabot/nuget/cache_manager"
9
10
 
10
11
  module Dependabot
11
12
  module Nuget
12
- class UpdateChecker
13
+ class UpdateChecker < Dependabot::UpdateCheckers::Base
13
14
  class RepositoryFinder
14
15
  DEFAULT_REPOSITORY_URL = "https://api.nuget.org/v3/index.json"
15
16
  DEFAULT_REPOSITORY_API_KEY = "nuget.org"
@@ -27,6 +28,7 @@ module Dependabot
27
28
  def self.get_default_repository_details(dependency_name)
28
29
  {
29
30
  base_url: "https://api.nuget.org/v3-flatcontainer/",
31
+ registration_url: "https://api.nuget.org/v3/registration5-gz-semver2/#{dependency_name.downcase}/index.json",
30
32
  repository_url: DEFAULT_REPOSITORY_URL,
31
33
  versions_url: "https://api.nuget.org/v3-flatcontainer/" \
32
34
  "#{dependency_name.downcase}/index.json",
@@ -60,9 +62,11 @@ module Dependabot
60
62
  return unless response.status == 200
61
63
 
62
64
  body = remove_wrapping_zero_width_chars(response.body)
63
- base_url = base_url_from_v3_metadata(JSON.parse(body))
65
+ parsed_json = JSON.parse(body)
66
+ base_url = base_url_from_v3_metadata(parsed_json)
64
67
  resolved_base_url = base_url || repo_details.fetch(:url).gsub("/index.json", "-flatcontainer")
65
- search_url = search_url_from_v3_metadata(JSON.parse(body))
68
+ search_url = search_url_from_v3_metadata(parsed_json)
69
+ registration_url = registration_url_from_v3_metadata(parsed_json)
66
70
 
67
71
  details = {
68
72
  base_url: resolved_base_url,
@@ -78,6 +82,11 @@ module Dependabot
78
82
  details[:search_url] =
79
83
  search_url + "?q=#{dependency.name.downcase}&prerelease=true&semVerLevel=2.0.0"
80
84
  end
85
+
86
+ if registration_url
87
+ details[:registration_url] = File.join(registration_url, dependency.name.downcase, "index.json")
88
+ end
89
+
81
90
  details
82
91
  rescue JSON::ParserError
83
92
  build_v2_url(response, repo_details)
@@ -86,10 +95,18 @@ module Dependabot
86
95
  end
87
96
 
88
97
  def get_repo_metadata(repo_details)
89
- Dependabot::RegistryClient.get(
90
- url: repo_details.fetch(:url),
91
- headers: auth_header_for_token(repo_details.fetch(:token))
92
- )
98
+ url = repo_details.fetch(:url)
99
+ cache = CacheManager.cache("repo_finder_metadatacache")
100
+ if cache[url]
101
+ cache[url]
102
+ else
103
+ result = Dependabot::RegistryClient.get(
104
+ url: url,
105
+ headers: auth_header_for_token(repo_details.fetch(:token))
106
+ )
107
+ cache[url] = result
108
+ result
109
+ end
93
110
  end
94
111
 
95
112
  def base_url_from_v3_metadata(metadata)
@@ -99,6 +116,20 @@ module Dependabot
99
116
  &.fetch("@id")
100
117
  end
101
118
 
119
+ def registration_url_from_v3_metadata(metadata)
120
+ allowed_registration_types = %w(
121
+ RegistrationsBaseUrl
122
+ RegistrationsBaseUrl/3.0.0-beta
123
+ RegistrationsBaseUrl/3.0.0-rc
124
+ RegistrationsBaseUrl/3.4.0
125
+ RegistrationsBaseUrl/3.6.0
126
+ )
127
+ metadata
128
+ .fetch("resources", [])
129
+ .find { |r| allowed_registration_types.find { |s| r.fetch("@type") == s } }
130
+ &.fetch("@id")
131
+ end
132
+
102
133
  def search_url_from_v3_metadata(metadata)
103
134
  # allowable values from here: https://learn.microsoft.com/en-us/nuget/api/search-query-service-resource#versioning
104
135
  allowed_search_types = %w(
@@ -6,12 +6,12 @@
6
6
  # https://docs.microsoft.com/en-us/nuget/reference/package-versioning #
7
7
  #######################################################################
8
8
 
9
- require "dependabot/nuget/update_checker"
9
+ require "dependabot/update_checkers/base"
10
10
  require "dependabot/nuget/version"
11
11
 
12
12
  module Dependabot
13
13
  module Nuget
14
- class UpdateChecker
14
+ class UpdateChecker < Dependabot::UpdateCheckers::Base
15
15
  class RequirementsUpdater
16
16
  def initialize(requirements:, latest_version:, source_details:)
17
17
  @requirements = requirements
@@ -1,15 +1,15 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "dependabot/update_checkers/base"
4
5
  require "dependabot/nuget/version"
5
6
  require "dependabot/nuget/requirement"
6
7
  require "dependabot/nuget/native_helpers"
7
- require "dependabot/nuget/update_checker"
8
8
  require "dependabot/shared_helpers"
9
9
 
10
10
  module Dependabot
11
11
  module Nuget
12
- class UpdateChecker
12
+ class UpdateChecker < Dependabot::UpdateCheckers::Base
13
13
  class TfmComparer
14
14
  def self.are_frameworks_compatible?(project_tfms, package_tfms)
15
15
  return false if package_tfms.empty?
@@ -4,15 +4,15 @@
4
4
  require "excon"
5
5
  require "nokogiri"
6
6
 
7
+ require "dependabot/update_checkers/base"
7
8
  require "dependabot/nuget/version"
8
9
  require "dependabot/nuget/requirement"
9
10
  require "dependabot/nuget/native_helpers"
10
- require "dependabot/nuget/update_checker"
11
11
  require "dependabot/shared_helpers"
12
12
 
13
13
  module Dependabot
14
14
  module Nuget
15
- class UpdateChecker
15
+ class UpdateChecker < Dependabot::UpdateCheckers::Base
16
16
  class TfmFinder
17
17
  require "dependabot/nuget/file_parser/packages_config_parser"
18
18
  require "dependabot/nuget/file_parser/project_file_parser"
@@ -3,12 +3,13 @@
3
3
 
4
4
  require "dependabot/nuget/version"
5
5
  require "dependabot/nuget/requirement"
6
+ require "dependabot/update_checkers/base"
6
7
  require "dependabot/update_checkers/version_filters"
7
- require "dependabot/nuget/update_checker"
8
+ require "dependabot/nuget/nuget_client"
8
9
 
9
10
  module Dependabot
10
11
  module Nuget
11
- class UpdateChecker
12
+ class UpdateChecker < Dependabot::UpdateCheckers::Base
12
13
  class VersionFinder
13
14
  require_relative "compatibility_checker"
14
15
  require_relative "repository_finder"
@@ -294,40 +295,7 @@ module Dependabot
294
295
  end
295
296
 
296
297
  def versions_for_v3_repository(repository_details)
297
- # If we have a search URL that returns results we use it
298
- # (since it will exclude unlisted versions)
299
- if repository_details[:search_url]
300
- fetch_versions_from_search_url(repository_details)
301
- # Otherwise, use the versions URL
302
- elsif repository_details[:versions_url]
303
- response = Dependabot::RegistryClient.get(
304
- url: repository_details[:versions_url],
305
- headers: repository_details[:auth_header]
306
- )
307
- return unless response.status == 200
308
-
309
- body = remove_wrapping_zero_width_chars(response.body)
310
- JSON.parse(body).fetch("versions")
311
- end
312
- end
313
-
314
- def fetch_versions_from_search_url(repository_details)
315
- response = Dependabot::RegistryClient.get(
316
- url: repository_details[:search_url],
317
- headers: repository_details[:auth_header]
318
- )
319
- return unless response.status == 200
320
-
321
- body = remove_wrapping_zero_width_chars(response.body)
322
- JSON.parse(body).fetch("data")
323
- .find { |d| d.fetch("id").casecmp(sanitized_name).zero? }
324
- &.fetch("versions")
325
- &.map { |d| d.fetch("version") }
326
- rescue Excon::Error::Timeout, Excon::Error::Socket
327
- repo_url = repository_details[:repository_url]
328
- raise if repo_url == RepositoryFinder::DEFAULT_REPOSITORY_URL
329
-
330
- raise PrivateSourceTimedOut, repo_url
298
+ NugetClient.get_package_versions_v3(dependency.name, repository_details)
331
299
  end
332
300
 
333
301
  def dependency_urls
@@ -356,12 +324,6 @@ module Dependabot
356
324
  dependency.requirement_class
357
325
  end
358
326
 
359
- def remove_wrapping_zero_width_chars(string)
360
- string.force_encoding("UTF-8").encode
361
- .gsub(/\A[\u200B-\u200D\uFEFF]/, "")
362
- .gsub(/[\u200B-\u200D\uFEFF]\Z/, "")
363
- end
364
-
365
327
  def excon_options
366
328
  # For large JSON files we sometimes need a little longer than for
367
329
  # other languages. For example, see:
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-nuget
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.239.0
4
+ version: 0.240.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-12-28 00:00:00.000000000 Z
11
+ date: 2024-01-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dependabot-common
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 0.239.0
19
+ version: 0.240.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 0.239.0
26
+ version: 0.240.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rubyzip
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -226,6 +226,20 @@ dependencies:
226
226
  - - "~>"
227
227
  - !ruby/object:Gem::Version
228
228
  version: '3.18'
229
+ - !ruby/object:Gem::Dependency
230
+ name: webrick
231
+ requirement: !ruby/object:Gem::Requirement
232
+ requirements:
233
+ - - ">="
234
+ - !ruby/object:Gem::Version
235
+ version: '1.7'
236
+ type: :development
237
+ prerelease: false
238
+ version_requirements: !ruby/object:Gem::Requirement
239
+ requirements:
240
+ - - ">="
241
+ - !ruby/object:Gem::Version
242
+ version: '1.7'
229
243
  description: Dependabot-Nuget provides support for bumping .NET (NuGet) packages via
230
244
  Dependabot. If you want support for multiple package managers, you probably want
231
245
  the meta-gem dependabot-omnibus.
@@ -249,6 +263,8 @@ files:
249
263
  - lib/dependabot/nuget/file_updater/property_value_updater.rb
250
264
  - lib/dependabot/nuget/metadata_finder.rb
251
265
  - lib/dependabot/nuget/native_helpers.rb
266
+ - lib/dependabot/nuget/nuget_client.rb
267
+ - lib/dependabot/nuget/nuget_config_credential_helpers.rb
252
268
  - lib/dependabot/nuget/requirement.rb
253
269
  - lib/dependabot/nuget/update_checker.rb
254
270
  - lib/dependabot/nuget/update_checker/compatibility_checker.rb
@@ -267,7 +283,7 @@ licenses:
267
283
  - Nonstandard
268
284
  metadata:
269
285
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
270
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.239.0
286
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.240.0
271
287
  post_install_message:
272
288
  rdoc_options: []
273
289
  require_paths: