dependabot-nuget 0.289.0 → 0.290.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/Directory.Packages.props +1 -1
  3. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/AnalyzeCommand.cs +7 -3
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/RunCommand.cs +1 -1
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Analyze.cs +26 -1
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Discover.cs +2 -1
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Run.cs +0 -6
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalyzeWorker.cs +3 -1
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/CompatabilityChecker.cs +24 -9
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/NuGetContext.cs +0 -13
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/RequirementConverter.cs +17 -0
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/Advisory.cs +13 -0
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/AllowedUpdate.cs +18 -1
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/CommitOptions.cs +8 -0
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/Condition.cs +19 -0
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/DependencyGroup.cs +8 -0
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/GroupPullRequest.cs +9 -0
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/Job.cs +13 -10
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/PullRequest.cs +11 -0
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/RequirementsUpdateStrategy.cs +15 -0
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +24 -4
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/VersionConverter.cs +19 -0
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectManager.cs +2 -1
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +13 -12
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/JsonHelper.cs +2 -0
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ProjectHelper.cs +2 -2
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTestBase.cs +5 -2
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.DotNetToolsJson.cs +45 -1
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.GlobalJson.cs +35 -1
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +0 -4
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/MiscellaneousTests.cs +85 -0
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +7 -31
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +340 -0
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TemporaryDirectory.cs +18 -7
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/PackagesConfigUpdaterTests.cs +24 -0
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +0 -12
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DotNetTools.cs +84 -0
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.GlobalJson.cs +66 -0
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackageReference.cs +55 -0
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +0 -6
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +557 -713
  42. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/PathHelperTests.cs +2 -2
  43. data/lib/dependabot/nuget/analysis/analysis_json_reader.rb +1 -1
  44. data/lib/dependabot/nuget/analysis/dependency_analysis.rb +3 -3
  45. data/lib/dependabot/nuget/discovery/dependency_details.rb +10 -3
  46. data/lib/dependabot/nuget/discovery/dependency_file_discovery.rb +8 -12
  47. data/lib/dependabot/nuget/discovery/discovery_json_reader.rb +214 -29
  48. data/lib/dependabot/nuget/discovery/project_discovery.rb +41 -8
  49. data/lib/dependabot/nuget/discovery/workspace_discovery.rb +14 -19
  50. data/lib/dependabot/nuget/file_fetcher.rb +2 -3
  51. data/lib/dependabot/nuget/file_parser.rb +2 -3
  52. data/lib/dependabot/nuget/file_updater.rb +13 -13
  53. data/lib/dependabot/nuget/native_helpers.rb +14 -5
  54. data/lib/dependabot/nuget/update_checker/requirements_updater.rb +23 -27
  55. data/lib/dependabot/nuget/update_checker.rb +116 -190
  56. metadata +18 -29
  57. data/lib/dependabot/nuget/discovery/directory_packages_props_discovery.rb +0 -43
  58. data/lib/dependabot/nuget/http_response_helpers.rb +0 -19
  59. data/lib/dependabot/nuget/native_discovery/native_dependency_details.rb +0 -102
  60. data/lib/dependabot/nuget/native_discovery/native_dependency_file_discovery.rb +0 -122
  61. data/lib/dependabot/nuget/native_discovery/native_discovery_json_reader.rb +0 -277
  62. data/lib/dependabot/nuget/native_discovery/native_evaluation_details.rb +0 -63
  63. data/lib/dependabot/nuget/native_discovery/native_project_discovery.rb +0 -104
  64. data/lib/dependabot/nuget/native_discovery/native_property_details.rb +0 -43
  65. data/lib/dependabot/nuget/native_discovery/native_workspace_discovery.rb +0 -61
  66. data/lib/dependabot/nuget/native_update_checker/native_requirements_updater.rb +0 -105
  67. data/lib/dependabot/nuget/native_update_checker/native_update_checker.rb +0 -214
  68. data/lib/dependabot/nuget/nuget_client.rb +0 -223
  69. data/lib/dependabot/nuget/update_checker/compatibility_checker.rb +0 -116
  70. data/lib/dependabot/nuget/update_checker/dependency_finder.rb +0 -297
  71. data/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb +0 -221
  72. data/lib/dependabot/nuget/update_checker/nuspec_fetcher.rb +0 -110
  73. data/lib/dependabot/nuget/update_checker/property_updater.rb +0 -196
  74. data/lib/dependabot/nuget/update_checker/repository_finder.rb +0 -466
  75. data/lib/dependabot/nuget/update_checker/tfm_comparer.rb +0 -34
  76. data/lib/dependabot/nuget/update_checker/tfm_finder.rb +0 -30
  77. data/lib/dependabot/nuget/update_checker/version_finder.rb +0 -449
@@ -1,196 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- require "sorbet-runtime"
5
-
6
- require "dependabot/update_checkers/base"
7
- require "dependabot/nuget/file_parser"
8
-
9
- module Dependabot
10
- module Nuget
11
- class UpdateChecker < Dependabot::UpdateCheckers::Base
12
- class PropertyUpdater
13
- extend T::Sig
14
-
15
- require_relative "version_finder"
16
- require_relative "requirements_updater"
17
- require_relative "dependency_finder"
18
-
19
- sig do
20
- params(
21
- dependency: Dependabot::Dependency,
22
- dependency_files: T::Array[Dependabot::DependencyFile],
23
- credentials: T::Array[Dependabot::Credential],
24
- target_version_details: T.nilable(T::Hash[Symbol, String]),
25
- ignored_versions: T::Array[String],
26
- repo_contents_path: T.nilable(String),
27
- raise_on_ignored: T::Boolean
28
- ).void
29
- end
30
- def initialize(dependency:, dependency_files:, credentials:,
31
- target_version_details:, ignored_versions:,
32
- repo_contents_path:, raise_on_ignored: false)
33
- @dependency = dependency
34
- @dependency_files = dependency_files
35
- @credentials = credentials
36
- @ignored_versions = ignored_versions
37
- @raise_on_ignored = raise_on_ignored
38
- @target_version = T.let(
39
- target_version_details&.fetch(:version),
40
- T.nilable(T.any(String, Dependabot::Nuget::Version))
41
- )
42
- @source_details = T.let(
43
- target_version_details&.slice(:nuspec_url, :repo_url, :source_url),
44
- T.nilable(T::Hash[Symbol, String])
45
- )
46
- @repo_contents_path = repo_contents_path
47
- end
48
-
49
- sig { returns(T::Boolean) }
50
- def update_possible?
51
- return false unless target_version
52
-
53
- @update_possible ||= T.let(
54
- dependencies_using_property.all? do |dep|
55
- versions = VersionFinder.new(
56
- dependency: dep,
57
- dependency_files: dependency_files,
58
- credentials: credentials,
59
- ignored_versions: ignored_versions,
60
- raise_on_ignored: @raise_on_ignored,
61
- security_advisories: [],
62
- repo_contents_path: repo_contents_path
63
- ).versions.map { |v| v.fetch(:version) }
64
-
65
- versions.include?(target_version) || versions.none?
66
- end,
67
- T.nilable(T::Boolean)
68
- )
69
- end
70
-
71
- sig { returns(T::Array[Dependabot::Dependency]) }
72
- def updated_dependencies
73
- raise "Update not possible!" unless update_possible?
74
-
75
- @updated_dependencies ||= T.let(
76
- begin
77
- dependencies = T.let({}, T::Hash[String, Dependabot::Dependency])
78
-
79
- dependencies_using_property.each do |dep|
80
- # Only keep one copy of each dependency, the one with the highest target version.
81
- visited_dependency = dependencies[dep.name.downcase]
82
- next unless visited_dependency.nil? || T.must(visited_dependency.numeric_version) < target_version
83
-
84
- updated_dependency = Dependency.new(
85
- name: dep.name,
86
- version: target_version.to_s,
87
- requirements: updated_requirements(dep),
88
- previous_version: dep.version,
89
- previous_requirements: dep.requirements,
90
- package_manager: dep.package_manager
91
- )
92
- dependencies[updated_dependency.name.downcase] = updated_dependency
93
- # Add peer dependencies to the list of updated dependencies.
94
- process_updated_peer_dependencies(updated_dependency, dependencies)
95
- end
96
-
97
- dependencies.map { |_, dependency| dependency }
98
- end,
99
- T.nilable(T::Array[Dependabot::Dependency])
100
- )
101
- end
102
-
103
- private
104
-
105
- sig { returns(Dependabot::Dependency) }
106
- attr_reader :dependency
107
-
108
- sig { returns(T::Array[Dependabot::DependencyFile]) }
109
- attr_reader :dependency_files
110
-
111
- sig { returns(T.nilable(T.any(String, Dependabot::Nuget::Version))) }
112
- attr_reader :target_version
113
-
114
- sig { returns(T.nilable(T::Hash[Symbol, String])) }
115
- attr_reader :source_details
116
-
117
- sig { returns(T::Array[Dependabot::Credential]) }
118
- attr_reader :credentials
119
-
120
- sig { returns(T::Array[String]) }
121
- attr_reader :ignored_versions
122
-
123
- sig { returns(T.nilable(String)) }
124
- attr_reader :repo_contents_path
125
-
126
- sig do
127
- params(
128
- dependency: Dependabot::Dependency,
129
- dependencies: T::Hash[String, Dependabot::Dependency]
130
- )
131
- .returns(T::Array[Dependabot::Dependency])
132
- end
133
- def process_updated_peer_dependencies(dependency, dependencies)
134
- DependencyFinder.new(
135
- dependency: dependency,
136
- dependency_files: dependency_files,
137
- ignored_versions: ignored_versions,
138
- credentials: credentials,
139
- repo_contents_path: repo_contents_path
140
- ).updated_peer_dependencies.each do |peer_dependency|
141
- # Only keep one copy of each dependency, the one with the highest target version.
142
- visited_dependency = dependencies[peer_dependency.name.downcase]
143
- unless visited_dependency.nil? ||
144
- T.must(visited_dependency.numeric_version) < peer_dependency.numeric_version
145
- next
146
- end
147
-
148
- dependencies[peer_dependency.name.downcase] = peer_dependency
149
- end
150
- end
151
-
152
- sig { returns(T::Array[Dependabot::Dependency]) }
153
- def dependencies_using_property
154
- @dependencies_using_property ||=
155
- T.let(
156
- Nuget::FileParser.new(
157
- dependency_files: dependency_files,
158
- repo_contents_path: repo_contents_path,
159
- source: nil
160
- ).parse.select do |dep|
161
- dep.requirements.any? do |r|
162
- r.dig(:metadata, :property_name) == property_name
163
- end
164
- end,
165
- T.nilable(T::Array[Dependabot::Dependency])
166
- )
167
- end
168
-
169
- sig { returns(String) }
170
- def property_name
171
- @property_name ||= T.let(
172
- dependency.requirements
173
- .find { |r| r.dig(:metadata, :property_name) }
174
- &.dig(:metadata, :property_name),
175
- T.nilable(String)
176
- )
177
-
178
- raise "No requirement with a property name!" unless @property_name
179
-
180
- @property_name
181
- end
182
-
183
- sig { params(dep: Dependabot::Dependency).returns(T::Array[T::Hash[Symbol, T.untyped]]) }
184
- def updated_requirements(dep)
185
- @updated_requirements ||= T.let({}, T.nilable(T::Hash[String, T::Array[T::Hash[Symbol, T.untyped]]]))
186
- @updated_requirements[dep.name] ||=
187
- RequirementsUpdater.new(
188
- requirements: dep.requirements,
189
- latest_version: target_version,
190
- source_details: source_details
191
- ).updated_requirements
192
- end
193
- end
194
- end
195
- end
196
- end
@@ -1,466 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- require "excon"
5
- require "nokogiri"
6
- require "sorbet-runtime"
7
-
8
- require "dependabot/errors"
9
- require "dependabot/update_checkers/base"
10
- require "dependabot/registry_client"
11
- require "dependabot/nuget/cache_manager"
12
- require "dependabot/nuget/http_response_helpers"
13
-
14
- module Dependabot
15
- module Nuget
16
- # rubocop:disable Metrics/ClassLength
17
- class RepositoryFinder
18
- extend T::Sig
19
-
20
- DEFAULT_REPOSITORY_URL = "https://api.nuget.org/v3/index.json"
21
- DEFAULT_REPOSITORY_API_KEY = "nuget.org"
22
-
23
- sig do
24
- params(
25
- dependency: Dependabot::Dependency,
26
- credentials: T::Array[Dependabot::Credential],
27
- config_files: T::Array[Dependabot::DependencyFile]
28
- ).void
29
- end
30
- def initialize(dependency:, credentials:, config_files: [])
31
- @dependency = dependency
32
- @credentials = credentials
33
- @config_files = config_files
34
- end
35
-
36
- sig { returns(T::Array[T::Hash[Symbol, String]]) }
37
- def dependency_urls
38
- find_dependency_urls
39
- end
40
-
41
- sig { returns(T::Array[T::Hash[Symbol, String]]) }
42
- def known_repositories
43
- return @known_repositories if @known_repositories
44
-
45
- @known_repositories ||= T.let([], T.nilable(T::Array[T::Hash[Symbol, String]]))
46
- @known_repositories += credential_repositories
47
- @known_repositories += config_file_repositories
48
-
49
- @known_repositories << { url: DEFAULT_REPOSITORY_URL, token: nil } if @known_repositories.empty?
50
-
51
- @known_repositories = @known_repositories.map do |repo|
52
- url = repo[:url]
53
- begin
54
- url = URI::DEFAULT_PARSER.parse(url).to_s
55
- rescue URI::InvalidURIError
56
- # e.g., the url has spaces or unacceptable symbols
57
- url = URI::DEFAULT_PARSER.escape(url)
58
- end
59
-
60
- { url: url, token: repo[:token] }
61
- end
62
- @known_repositories.uniq
63
- end
64
-
65
- sig { params(dependency_name: String).returns(T::Hash[Symbol, T.untyped]) }
66
- def self.get_default_repository_details(dependency_name)
67
- {
68
- base_url: "https://api.nuget.org/v3-flatcontainer/",
69
- registration_url: "https://api.nuget.org/v3/registration5-gz-semver2/#{dependency_name.downcase}/index.json",
70
- repository_url: DEFAULT_REPOSITORY_URL,
71
- versions_url: "https://api.nuget.org/v3-flatcontainer/" \
72
- "#{dependency_name.downcase}/index.json",
73
- search_url: "https://azuresearch-usnc.nuget.org/query" \
74
- "?q=#{dependency_name.downcase}&prerelease=true&semVerLevel=2.0.0",
75
- auth_header: {},
76
- repository_type: "v3"
77
- }
78
- end
79
-
80
- sig { params(source_name: String).returns(String) }
81
- def self.escape_source_name_to_element_name(source_name)
82
- source_name.chars.map do |c|
83
- case c
84
- when /[A-Za-z0-9\-_.]/
85
- # letters, digits, hyphens, underscores, and periods are all directly allowed
86
- c
87
- else
88
- # otherwise it needs to be escaped as a 4 digit hex value
89
- "_x#{c.ord.to_s(16).rjust(4, '0')}_"
90
- end
91
- end.join
92
- end
93
-
94
- private
95
-
96
- sig { returns(Dependabot::Dependency) }
97
- attr_reader :dependency
98
-
99
- sig { returns(T::Array[Dependabot::Credential]) }
100
- attr_reader :credentials
101
-
102
- sig { returns(T::Array[Dependabot::DependencyFile]) }
103
- attr_reader :config_files
104
-
105
- sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
106
- def find_dependency_urls
107
- @find_dependency_urls ||=
108
- T.let(
109
- known_repositories.flat_map do |details|
110
- if details.fetch(:url) == DEFAULT_REPOSITORY_URL
111
- # Save a request for the default URL, since we already know how
112
- # it addresses packages
113
- next default_repository_details
114
- end
115
-
116
- build_url_for_details(details)
117
- end.compact.uniq,
118
- T.nilable(T::Array[T::Hash[Symbol, T.untyped]])
119
- )
120
- end
121
-
122
- sig { params(repo_details: T::Hash[Symbol, T.untyped]).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
123
- def build_url_for_details(repo_details)
124
- url = repo_details.fetch(:url)
125
- url_obj = URI.parse(url)
126
- if url_obj.is_a?(URI::HTTP)
127
- details = build_url_for_details_remote(repo_details)
128
- elsif url_obj.is_a?(URI::File)
129
- details = {
130
- base_url: url,
131
- repository_type: "local"
132
- }
133
- end
134
-
135
- details
136
- end
137
-
138
- sig { params(repo_details: T::Hash[Symbol, T.untyped]).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
139
- def build_url_for_details_remote(repo_details)
140
- response = get_repo_metadata(repo_details)
141
- check_repo_response(response, repo_details)
142
- return unless response.status == 200
143
-
144
- body = HttpResponseHelpers.remove_wrapping_zero_width_chars(response.body)
145
- parsed_json = JSON.parse(body)
146
- base_url = base_url_from_v3_metadata(parsed_json)
147
- search_url = search_url_from_v3_metadata(parsed_json)
148
- registration_url = registration_url_from_v3_metadata(parsed_json)
149
-
150
- details = {
151
- base_url: base_url,
152
- repository_url: repo_details.fetch(:url),
153
- auth_header: auth_header_for_token(repo_details.fetch(:token)),
154
- repository_type: "v3"
155
- }
156
- if base_url
157
- details[:versions_url] =
158
- File.join(base_url, dependency.name.downcase, "index.json")
159
- end
160
- if search_url
161
- details[:search_url] =
162
- search_url + "?q=#{dependency.name.downcase}&prerelease=true&semVerLevel=2.0.0"
163
- end
164
-
165
- if registration_url
166
- details[:registration_url] = File.join(registration_url, dependency.name.downcase, "index.json")
167
- end
168
-
169
- details
170
- rescue JSON::ParserError
171
- build_v2_url(T.must(response), repo_details)
172
- rescue Excon::Error::Timeout, Excon::Error::Socket
173
- handle_timeout(repo_metadata_url: repo_details.fetch(:url))
174
- end
175
-
176
- sig { params(repo_details: T::Hash[Symbol, T.untyped]).returns(Excon::Response) }
177
- def get_repo_metadata(repo_details)
178
- url = repo_details.fetch(:url)
179
- cache = CacheManager.cache("repo_finder_metadatacache")
180
- if cache[url]
181
- cache[url]
182
- else
183
- result = Dependabot::RegistryClient.get(
184
- url: url,
185
- headers: auth_header_for_token(repo_details.fetch(:token))
186
- )
187
- cache[url] = result
188
- result
189
- end
190
- end
191
-
192
- sig { params(metadata: T::Hash[String, T::Array[T::Hash[String, T.untyped]]]).returns(T.nilable(String)) }
193
- def base_url_from_v3_metadata(metadata)
194
- metadata
195
- .fetch("resources", [])
196
- .find { |r| r.fetch("@type") == "PackageBaseAddress/3.0.0" }
197
- &.fetch("@id")
198
- end
199
-
200
- sig { params(metadata: T::Hash[String, T::Array[T::Hash[String, T.untyped]]]).returns(T.nilable(String)) }
201
- def registration_url_from_v3_metadata(metadata)
202
- allowed_registration_types = %w(
203
- RegistrationsBaseUrl
204
- RegistrationsBaseUrl/3.0.0-beta
205
- RegistrationsBaseUrl/3.0.0-rc
206
- RegistrationsBaseUrl/3.4.0
207
- RegistrationsBaseUrl/3.6.0
208
- )
209
- metadata
210
- .fetch("resources", [])
211
- .find { |r| allowed_registration_types.find { |s| r.fetch("@type") == s } }
212
- &.fetch("@id")
213
- end
214
-
215
- sig { params(metadata: T::Hash[String, T::Array[T::Hash[String, T.untyped]]]).returns(T.nilable(String)) }
216
- def search_url_from_v3_metadata(metadata)
217
- # allowable values from here: https://learn.microsoft.com/en-us/nuget/api/search-query-service-resource#versioning
218
- allowed_search_types = %w(
219
- SearchQueryService
220
- SearchQueryService/3.0.0-beta
221
- SearchQueryService/3.0.0-rc
222
- SearchQueryService/3.5.0
223
- )
224
- metadata
225
- .fetch("resources", [])
226
- .find { |r| allowed_search_types.find { |s| r.fetch("@type") == s } }
227
- &.fetch("@id")
228
- end
229
-
230
- sig do
231
- params(
232
- response: Excon::Response,
233
- repo_details: T::Hash[Symbol, T.untyped]
234
- )
235
- .returns(T::Hash[Symbol, T.untyped])
236
- end
237
- def build_v2_url(response, repo_details)
238
- doc = Nokogiri::XML(response.body)
239
-
240
- doc.remove_namespaces!
241
- base_url = doc.at_xpath("service")&.attributes
242
- &.fetch("base", nil)&.value
243
-
244
- base_url ||= repo_details.fetch(:url)
245
-
246
- {
247
- base_url: base_url,
248
- repository_url: base_url,
249
- versions_url: File.join(
250
- base_url.delete_suffix("/"),
251
- "FindPackagesById()?id='#{dependency.name}'"
252
- ),
253
- auth_header: auth_header_for_token(repo_details.fetch(:token)),
254
- repository_type: "v2"
255
- }
256
- end
257
-
258
- sig { params(response: Excon::Response, details: T::Hash[Symbol, T.untyped]).void }
259
- def check_repo_response(response, details)
260
- return unless [401, 402, 403].include?(response.status)
261
- raise if details.fetch(:url) == DEFAULT_REPOSITORY_URL
262
-
263
- raise PrivateSourceAuthenticationFailure, details.fetch(:url)
264
- end
265
-
266
- sig { params(repo_metadata_url: String).returns(T.noreturn) }
267
- def handle_timeout(repo_metadata_url:)
268
- raise if repo_metadata_url == DEFAULT_REPOSITORY_URL
269
-
270
- raise PrivateSourceTimedOut, repo_metadata_url
271
- end
272
-
273
- sig { returns(T::Array[T::Hash[Symbol, String]]) }
274
- def credential_repositories
275
- @credential_repositories ||=
276
- T.let(
277
- credentials
278
- .select { |cred| cred["type"] == "nuget_feed" && cred["url"] }
279
- .map { |c| { url: c.fetch("url"), token: c["token"] } },
280
- T.nilable(T::Array[T::Hash[Symbol, String]])
281
- )
282
- end
283
-
284
- sig { returns(T::Array[T::Hash[Symbol, String]]) }
285
- def config_file_repositories
286
- config_files.flat_map { |file| repos_from_config_file(file) }
287
- end
288
-
289
- # rubocop:disable Metrics/CyclomaticComplexity
290
- # rubocop:disable Metrics/PerceivedComplexity
291
- # rubocop:disable Metrics/MethodLength
292
- # rubocop:disable Metrics/AbcSize
293
- sig { params(config_file: Dependabot::DependencyFile).returns(T::Array[T::Hash[Symbol, String]]) }
294
- def repos_from_config_file(config_file)
295
- doc = Nokogiri::XML(config_file.content)
296
- doc.remove_namespaces!
297
- # analogous to having a root config with the default repository
298
- base_sources = [{ url: DEFAULT_REPOSITORY_URL, key: "nuget.org" }]
299
-
300
- sources = T.let([], T::Array[T::Hash[Symbol, String]])
301
-
302
- # regular package sources
303
- doc.css("configuration > packageSources").children.each do |node|
304
- if node.name == "clear"
305
- sources.clear
306
- base_sources.clear
307
- else
308
- key = node.attribute("key")&.value&.strip || node.at_xpath("./key")&.content&.strip
309
- url = node.attribute("value")&.value&.strip || node.at_xpath("./value")&.content&.strip
310
- url = expand_windows_style_environment_variables(url) if url
311
-
312
- # if the path isn't absolute it's relative to the nuget.config file
313
- if url
314
- unless url.include?("://") || Pathname.new(url).absolute?
315
- url = Pathname(config_file.directory).join(url).to_path
316
- end
317
- sources << { url: url, key: key }
318
- end
319
- end
320
- end
321
-
322
- # signed package sources
323
- # https://learn.microsoft.com/en-us/nuget/reference/nuget-config-file#trustedsigners-section
324
- doc.xpath("/configuration/trustedSigners/repository").each do |node|
325
- name = node.attribute("name")&.value&.strip
326
- service_index = node.attribute("serviceIndex")&.value&.strip
327
- sources << { url: service_index, key: name }
328
- end
329
-
330
- sources += base_sources # TODO: quirky overwrite behavior
331
- disabled_sources = disabled_sources(doc)
332
- sources.reject! do |s|
333
- disabled_sources.include?(s[:key])
334
- end
335
-
336
- sources.reject! do |s|
337
- known_urls = credential_repositories.map { |cr| cr.fetch(:url) }
338
- known_urls.include?(s.fetch(:url))
339
- end
340
-
341
- # filter out based on packageSourceMapping
342
- package_mapping_elements = doc.xpath("/configuration/packageSourceMapping/packageSource/package[@pattern]")
343
- matching_package_elements = package_mapping_elements.select do |package_element|
344
- pattern = package_element.attribute("pattern").value
345
-
346
- # reusing this function for a case insensitive GLOB pattern patch (e.g., "Microsoft.Azure.*")
347
- File.fnmatch(pattern, @dependency.name, File::FNM_CASEFOLD)
348
- end
349
- longest_matching_package_element = matching_package_elements.max_by do |package_element|
350
- package_element.attribute("pattern").value.length
351
- end
352
- matching_key = longest_matching_package_element&.parent&.attribute("key")&.value
353
- if matching_key
354
- # found a matching source, only keep that one
355
- sources.select! { |s| s.fetch(:key) == matching_key }
356
- end
357
-
358
- add_config_file_credentials(sources: sources, doc: doc)
359
- sources.each { |details| details.delete(:key) }
360
-
361
- sources
362
- end
363
- # rubocop:enable Metrics/AbcSize
364
- # rubocop:enable Metrics/MethodLength
365
- # rubocop:enable Metrics/PerceivedComplexity
366
- # rubocop:enable Metrics/CyclomaticComplexity
367
-
368
- sig { returns(T::Hash[Symbol, T.untyped]) }
369
- def default_repository_details
370
- RepositoryFinder.get_default_repository_details(dependency.name)
371
- end
372
-
373
- # rubocop:disable Metrics/PerceivedComplexity
374
- sig { params(doc: Nokogiri::XML::Document).returns(T::Array[String]) }
375
- def disabled_sources(doc)
376
- doc.css("configuration > disabledPackageSources > add").filter_map do |node|
377
- value = node.attribute("value")&.value ||
378
- node.at_xpath("./value")&.content
379
-
380
- if value&.strip&.downcase == "true"
381
- node.attribute("key")&.value&.strip ||
382
- node.at_xpath("./key")&.content&.strip
383
- end
384
- end
385
- end
386
- # rubocop:enable Metrics/PerceivedComplexity
387
-
388
- # rubocop:disable Metrics/PerceivedComplexity
389
- sig do
390
- params(
391
- sources: T::Array[T::Hash[Symbol, T.nilable(String)]],
392
- doc: Nokogiri::XML::Document
393
- )
394
- .void
395
- end
396
- def add_config_file_credentials(sources:, doc:)
397
- sources.each do |source_details|
398
- key = source_details.fetch(:key)
399
- next source_details[:token] = nil unless key
400
- next source_details[:token] = nil if key.match?(/^\d/)
401
-
402
- tag = RepositoryFinder.escape_source_name_to_element_name(key)
403
- creds_nodes = doc.css("configuration > packageSourceCredentials " \
404
- "> #{tag} > add")
405
-
406
- username =
407
- creds_nodes
408
- .find { |n| n.attribute("key")&.value == "Username" }
409
- &.attribute("value")&.value
410
- password =
411
- creds_nodes
412
- .find { |n| n.attribute("key")&.value == "ClearTextPassword" }
413
- &.attribute("value")&.value
414
-
415
- # NOTE: We have to look for plain text passwords, as we have no
416
- # way of decrypting encrypted passwords. For the same reason we
417
- # don't fetch API keys from the nuget.config at all.
418
- next source_details[:token] = nil unless username && password
419
-
420
- expanded_username = expand_windows_style_environment_variables(username)
421
- expanded_password = expand_windows_style_environment_variables(password)
422
- source_details[:token] = "#{expanded_username}:#{expanded_password}"
423
- rescue Nokogiri::XML::XPath::SyntaxError
424
- # Any non-ascii characters in the tag with cause a syntax error
425
- next source_details[:token] = nil
426
- end
427
- end
428
- # rubocop:enable Metrics/PerceivedComplexity
429
-
430
- sig { params(string: String).returns(String) }
431
- def expand_windows_style_environment_variables(string)
432
- # NuGet.Config files can have Windows-style environment variables that need to be replaced
433
- # https://learn.microsoft.com/en-us/nuget/reference/nuget-config-file#using-environment-variables
434
- string.gsub(/%([^%]+)%/) do
435
- environment_variable_name = T.must(::Regexp.last_match(1))
436
- environment_variable_value = ENV.fetch(environment_variable_name, nil)
437
- if environment_variable_value
438
- environment_variable_value
439
- else
440
- # report that the variable couldn't be expanded, then replace it as-is
441
- Dependabot.logger.warn <<~WARN
442
- The variable '%#{environment_variable_name}%' could not be expanded in NuGet.Config
443
- WARN
444
- "%#{environment_variable_name}%"
445
- end
446
- end
447
- end
448
-
449
- sig { params(token: T.nilable(String)).returns(T::Hash[String, String]) }
450
- def auth_header_for_token(token)
451
- return {} unless token
452
-
453
- if token.include?(":")
454
- encoded_token = Base64.encode64(token).delete("\n")
455
- { "Authorization" => "Basic #{encoded_token}" }
456
- elsif Base64.decode64(token).ascii_only? &&
457
- Base64.decode64(token).include?(":")
458
- { "Authorization" => "Basic #{token.delete("\n")}" }
459
- else
460
- { "Authorization" => "Bearer #{token}" }
461
- end
462
- end
463
- end
464
- # rubocop:enable Metrics/ClassLength
465
- end
466
- end
@@ -1,34 +0,0 @@
1
- # typed: strong
2
- # frozen_string_literal: true
3
-
4
- require "sorbet-runtime"
5
-
6
- require "dependabot/update_checkers/base"
7
- require "dependabot/nuget/version"
8
- require "dependabot/nuget/requirement"
9
- require "dependabot/nuget/native_helpers"
10
- require "dependabot/shared_helpers"
11
-
12
- module Dependabot
13
- module Nuget
14
- class TfmComparer
15
- extend T::Sig
16
-
17
- sig { params(project_tfms: T::Array[String], package_tfms: T::Array[String]).returns(T::Boolean) }
18
- def self.are_frameworks_compatible?(project_tfms, package_tfms)
19
- return false if package_tfms.empty?
20
- return false if project_tfms.empty?
21
-
22
- key = "project_ftms:#{project_tfms.sort.join(',')}:package_tfms:#{package_tfms.sort.join(',')}".downcase
23
-
24
- @cached_framework_check ||= T.let({}, T.nilable(T::Hash[String, T::Boolean]))
25
- unless @cached_framework_check.key?(key)
26
- @cached_framework_check[key] =
27
- NativeHelpers.run_nuget_framework_check(project_tfms,
28
- package_tfms)
29
- end
30
- T.must(@cached_framework_check[key])
31
- end
32
- end
33
- end
34
- end