dependabot-nuget 0.242.1 → 0.243.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/.editorconfig +37 -28
  3. data/helpers/lib/NuGetUpdater/.gitignore +1 -0
  4. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.CommandLine/AssemblyMetadataExtractor.cs +2 -1
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Program.cs +2 -2
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs +178 -176
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/JsonBuildFile.cs +2 -1
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/PackagesConfigBuildFile.cs +1 -0
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/ProjectBuildFile.cs +5 -4
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/CompatabilityChecker.cs +1 -0
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/FrameworkCompatibilityService.cs +10 -5
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/SupportedFrameworks.cs +16 -12
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectManager.cs +18 -17
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectResolver.cs +7 -7
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/DotNetToolsJsonUpdater.cs +13 -20
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/GlobalJsonUpdater.cs +9 -3
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +32 -16
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs +42 -22
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +32 -13
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/WebApplicationTargetsConditionPatcher.cs +47 -0
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/XmlFilePreAndPostProcessor.cs +55 -0
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/JsonHelper.cs +12 -9
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +49 -42
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs +16 -3
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ProcessExtensions.cs +6 -6
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/XmlExtensions.cs +11 -0
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/ProjectBuildFileTests.cs +18 -9
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/CompatibilityCheckerFacts.cs +2 -2
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/FrameworkCompatibilityServiceFacts.cs +7 -7
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/SupportedFrameworkFacts.cs +1 -1
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/PackagesConfigUpdaterTests.cs +9 -9
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorker.DirsProj.cs +81 -80
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +22 -9
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DotNetTools.cs +140 -104
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.GlobalJson.cs +25 -25
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs +8 -9
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +198 -22
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs +401 -399
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/JsonHelperTests.cs +17 -15
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +111 -42
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/SdkPackageUpdaterTests.cs +103 -87
  42. data/lib/dependabot/nuget/file_parser/project_file_parser.rb +45 -19
  43. data/lib/dependabot/nuget/file_parser.rb +21 -3
  44. data/lib/dependabot/nuget/file_updater.rb +42 -6
  45. data/lib/dependabot/nuget/native_helpers.rb +27 -8
  46. data/lib/dependabot/nuget/nuget_client.rb +66 -23
  47. data/lib/dependabot/nuget/nuget_config_credential_helpers.rb +7 -3
  48. data/lib/dependabot/nuget/update_checker/compatibility_checker.rb +63 -59
  49. data/lib/dependabot/nuget/update_checker/dependency_finder.rb +2 -2
  50. data/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb +1 -1
  51. data/lib/dependabot/nuget/update_checker/nuspec_fetcher.rb +22 -17
  52. data/lib/dependabot/nuget/update_checker/repository_finder.rb +292 -270
  53. data/lib/dependabot/nuget/update_checker/tfm_comparer.rb +11 -13
  54. data/lib/dependabot/nuget/update_checker/tfm_finder.rb +80 -82
  55. data/lib/dependabot/nuget/update_checker/version_finder.rb +3 -2
  56. data/lib/dependabot/nuget/version.rb +18 -7
  57. data/lib/dependabot/nuget.rb +0 -2
  58. metadata +7 -5
@@ -10,321 +10,343 @@ require "dependabot/nuget/cache_manager"
10
10
 
11
11
  module Dependabot
12
12
  module Nuget
13
- class UpdateChecker < Dependabot::UpdateCheckers::Base
14
- class RepositoryFinder
15
- DEFAULT_REPOSITORY_URL = "https://api.nuget.org/v3/index.json"
16
- DEFAULT_REPOSITORY_API_KEY = "nuget.org"
17
-
18
- def initialize(dependency:, credentials:, config_files: [])
19
- @dependency = dependency
20
- @credentials = credentials
21
- @config_files = config_files
22
- end
23
-
24
- def dependency_urls
25
- find_dependency_urls
26
- end
13
+ class RepositoryFinder
14
+ DEFAULT_REPOSITORY_URL = "https://api.nuget.org/v3/index.json"
15
+ DEFAULT_REPOSITORY_API_KEY = "nuget.org"
16
+
17
+ def initialize(dependency:, credentials:, config_files: [])
18
+ @dependency = dependency
19
+ @credentials = credentials
20
+ @config_files = config_files
21
+ end
27
22
 
28
- def self.get_default_repository_details(dependency_name)
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",
32
- repository_url: DEFAULT_REPOSITORY_URL,
33
- versions_url: "https://api.nuget.org/v3-flatcontainer/" \
34
- "#{dependency_name.downcase}/index.json",
35
- search_url: "https://azuresearch-usnc.nuget.org/query" \
36
- "?q=#{dependency_name.downcase}&prerelease=true&semVerLevel=2.0.0",
37
- auth_header: {},
38
- repository_type: "v3"
39
- }
40
- end
23
+ def dependency_urls
24
+ find_dependency_urls
25
+ end
41
26
 
42
- private
27
+ def known_repositories
28
+ return @known_repositories if @known_repositories
43
29
 
44
- attr_reader :dependency, :credentials, :config_files
30
+ @known_repositories = []
31
+ @known_repositories += credential_repositories
32
+ @known_repositories += config_file_repositories
45
33
 
46
- def find_dependency_urls
47
- @find_dependency_urls ||=
48
- known_repositories.flat_map do |details|
49
- if details.fetch(:url) == DEFAULT_REPOSITORY_URL
50
- # Save a request for the default URL, since we already know how
51
- # it addresses packages
52
- next default_repository_details
53
- end
34
+ @known_repositories << { url: DEFAULT_REPOSITORY_URL, token: nil } if @known_repositories.empty?
54
35
 
55
- build_url_for_details(details)
56
- end.compact.uniq
36
+ @known_repositories = @known_repositories.map do |repo|
37
+ { url: URI::DEFAULT_PARSER.escape(repo[:url]), token: repo[:token] }
57
38
  end
39
+ @known_repositories.uniq
40
+ end
58
41
 
59
- def build_url_for_details(repo_details)
60
- response = get_repo_metadata(repo_details)
61
- check_repo_response(response, repo_details)
62
- return unless response.status == 200
63
-
64
- body = remove_wrapping_zero_width_chars(response.body)
65
- parsed_json = JSON.parse(body)
66
- base_url = base_url_from_v3_metadata(parsed_json)
67
- resolved_base_url = base_url || repo_details.fetch(:url).gsub("/index.json", "-flatcontainer")
68
- search_url = search_url_from_v3_metadata(parsed_json)
69
- registration_url = registration_url_from_v3_metadata(parsed_json)
70
-
71
- details = {
72
- base_url: resolved_base_url,
73
- repository_url: repo_details.fetch(:url),
74
- auth_header: auth_header_for_token(repo_details.fetch(:token)),
75
- repository_type: "v3"
76
- }
77
- if base_url
78
- details[:versions_url] =
79
- File.join(base_url, dependency.name.downcase, "index.json")
80
- end
81
- if search_url
82
- details[:search_url] =
83
- search_url + "?q=#{dependency.name.downcase}&prerelease=true&semVerLevel=2.0.0"
84
- end
42
+ def self.get_default_repository_details(dependency_name)
43
+ {
44
+ base_url: "https://api.nuget.org/v3-flatcontainer/",
45
+ registration_url: "https://api.nuget.org/v3/registration5-gz-semver2/#{dependency_name.downcase}/index.json",
46
+ repository_url: DEFAULT_REPOSITORY_URL,
47
+ versions_url: "https://api.nuget.org/v3-flatcontainer/" \
48
+ "#{dependency_name.downcase}/index.json",
49
+ search_url: "https://azuresearch-usnc.nuget.org/query" \
50
+ "?q=#{dependency_name.downcase}&prerelease=true&semVerLevel=2.0.0",
51
+ auth_header: {},
52
+ repository_type: "v3"
53
+ }
54
+ end
85
55
 
86
- if registration_url
87
- details[:registration_url] = File.join(registration_url, dependency.name.downcase, "index.json")
88
- end
56
+ private
89
57
 
90
- details
91
- rescue JSON::ParserError
92
- build_v2_url(response, repo_details)
93
- rescue Excon::Error::Timeout, Excon::Error::Socket
94
- handle_timeout(repo_metadata_url: repo_details.fetch(:url))
95
- end
58
+ attr_reader :dependency, :credentials, :config_files
96
59
 
97
- def get_repo_metadata(repo_details)
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
110
- end
60
+ def find_dependency_urls
61
+ @find_dependency_urls ||=
62
+ known_repositories.flat_map do |details|
63
+ if details.fetch(:url) == DEFAULT_REPOSITORY_URL
64
+ # Save a request for the default URL, since we already know how
65
+ # it addresses packages
66
+ next default_repository_details
67
+ end
111
68
 
112
- def base_url_from_v3_metadata(metadata)
113
- metadata
114
- .fetch("resources", [])
115
- .find { |r| r.fetch("@type") == "PackageBaseAddress/3.0.0" }
116
- &.fetch("@id")
117
- end
69
+ build_url_for_details(details)
70
+ end.compact.uniq
71
+ end
118
72
 
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")
73
+ def build_url_for_details(repo_details)
74
+ response = get_repo_metadata(repo_details)
75
+ check_repo_response(response, repo_details)
76
+ return unless response.status == 200
77
+
78
+ body = remove_wrapping_zero_width_chars(response.body)
79
+ parsed_json = JSON.parse(body)
80
+ base_url = base_url_from_v3_metadata(parsed_json)
81
+ resolved_base_url = base_url || repo_details.fetch(:url).gsub("/index.json", "-flatcontainer")
82
+ search_url = search_url_from_v3_metadata(parsed_json)
83
+ registration_url = registration_url_from_v3_metadata(parsed_json)
84
+
85
+ details = {
86
+ base_url: resolved_base_url,
87
+ repository_url: repo_details.fetch(:url),
88
+ auth_header: auth_header_for_token(repo_details.fetch(:token)),
89
+ repository_type: "v3"
90
+ }
91
+ if base_url
92
+ details[:versions_url] =
93
+ File.join(base_url, dependency.name.downcase, "index.json")
131
94
  end
132
-
133
- def search_url_from_v3_metadata(metadata)
134
- # allowable values from here: https://learn.microsoft.com/en-us/nuget/api/search-query-service-resource#versioning
135
- allowed_search_types = %w(
136
- SearchQueryService
137
- SearchQueryService/3.0.0-beta
138
- SearchQueryService/3.0.0-rc
139
- SearchQueryService/3.5.0
140
- )
141
- metadata
142
- .fetch("resources", [])
143
- .find { |r| allowed_search_types.find { |s| r.fetch("@type") == s } }
144
- &.fetch("@id")
95
+ if search_url
96
+ details[:search_url] =
97
+ search_url + "?q=#{dependency.name.downcase}&prerelease=true&semVerLevel=2.0.0"
145
98
  end
146
99
 
147
- def build_v2_url(response, repo_details)
148
- doc = Nokogiri::XML(response.body)
149
-
150
- doc.remove_namespaces!
151
- base_url = doc.at_xpath("service")&.attributes
152
- &.fetch("base", nil)&.value
153
-
154
- base_url ||= repo_details.fetch(:url)
155
-
156
- {
157
- base_url: base_url,
158
- repository_url: base_url,
159
- versions_url: File.join(
160
- base_url,
161
- "FindPackagesById()?id='#{dependency.name}'"
162
- ),
163
- auth_header: auth_header_for_token(repo_details.fetch(:token)),
164
- repository_type: "v2"
165
- }
100
+ if registration_url
101
+ details[:registration_url] = File.join(registration_url, dependency.name.downcase, "index.json")
166
102
  end
167
103
 
168
- def check_repo_response(response, details)
169
- return unless [401, 402, 403].include?(response.status)
170
- raise if details.fetch(:url) == DEFAULT_REPOSITORY_URL
104
+ details
105
+ rescue JSON::ParserError
106
+ build_v2_url(response, repo_details)
107
+ rescue Excon::Error::Timeout, Excon::Error::Socket
108
+ handle_timeout(repo_metadata_url: repo_details.fetch(:url))
109
+ end
171
110
 
172
- raise PrivateSourceAuthenticationFailure, details.fetch(:url)
111
+ def get_repo_metadata(repo_details)
112
+ url = repo_details.fetch(:url)
113
+ cache = CacheManager.cache("repo_finder_metadatacache")
114
+ if cache[url]
115
+ cache[url]
116
+ else
117
+ result = Dependabot::RegistryClient.get(
118
+ url: url,
119
+ headers: auth_header_for_token(repo_details.fetch(:token))
120
+ )
121
+ cache[url] = result
122
+ result
173
123
  end
124
+ end
174
125
 
175
- def handle_timeout(repo_metadata_url:)
176
- raise if repo_metadata_url == DEFAULT_REPOSITORY_URL
177
-
178
- raise PrivateSourceTimedOut, repo_metadata_url
179
- end
126
+ def base_url_from_v3_metadata(metadata)
127
+ metadata
128
+ .fetch("resources", [])
129
+ .find { |r| r.fetch("@type") == "PackageBaseAddress/3.0.0" }
130
+ &.fetch("@id")
131
+ end
180
132
 
181
- def known_repositories
182
- return @known_repositories if @known_repositories
133
+ def registration_url_from_v3_metadata(metadata)
134
+ allowed_registration_types = %w(
135
+ RegistrationsBaseUrl
136
+ RegistrationsBaseUrl/3.0.0-beta
137
+ RegistrationsBaseUrl/3.0.0-rc
138
+ RegistrationsBaseUrl/3.4.0
139
+ RegistrationsBaseUrl/3.6.0
140
+ )
141
+ metadata
142
+ .fetch("resources", [])
143
+ .find { |r| allowed_registration_types.find { |s| r.fetch("@type") == s } }
144
+ &.fetch("@id")
145
+ end
183
146
 
184
- @known_repositories = []
185
- @known_repositories += credential_repositories
186
- @known_repositories += config_file_repositories
147
+ def search_url_from_v3_metadata(metadata)
148
+ # allowable values from here: https://learn.microsoft.com/en-us/nuget/api/search-query-service-resource#versioning
149
+ allowed_search_types = %w(
150
+ SearchQueryService
151
+ SearchQueryService/3.0.0-beta
152
+ SearchQueryService/3.0.0-rc
153
+ SearchQueryService/3.5.0
154
+ )
155
+ metadata
156
+ .fetch("resources", [])
157
+ .find { |r| allowed_search_types.find { |s| r.fetch("@type") == s } }
158
+ &.fetch("@id")
159
+ end
187
160
 
188
- @known_repositories << { url: DEFAULT_REPOSITORY_URL, token: nil } if @known_repositories.empty?
161
+ def build_v2_url(response, repo_details)
162
+ doc = Nokogiri::XML(response.body)
163
+
164
+ doc.remove_namespaces!
165
+ base_url = doc.at_xpath("service")&.attributes
166
+ &.fetch("base", nil)&.value
167
+
168
+ base_url ||= repo_details.fetch(:url)
169
+
170
+ {
171
+ base_url: base_url,
172
+ repository_url: base_url,
173
+ versions_url: File.join(
174
+ base_url,
175
+ "FindPackagesById()?id='#{dependency.name}'"
176
+ ),
177
+ auth_header: auth_header_for_token(repo_details.fetch(:token)),
178
+ repository_type: "v2"
179
+ }
180
+ end
189
181
 
190
- @known_repositories.uniq
191
- end
182
+ def check_repo_response(response, details)
183
+ return unless [401, 402, 403].include?(response.status)
184
+ raise if details.fetch(:url) == DEFAULT_REPOSITORY_URL
192
185
 
193
- def credential_repositories
194
- @credential_repositories ||=
195
- credentials
196
- .select { |cred| cred["type"] == "nuget_feed" }
197
- .map { |c| { url: c.fetch("url"), token: c["token"] } }
198
- end
186
+ raise PrivateSourceAuthenticationFailure, details.fetch(:url)
187
+ end
199
188
 
200
- def config_file_repositories
201
- config_files.flat_map { |file| repos_from_config_file(file) }
202
- end
189
+ def handle_timeout(repo_metadata_url:)
190
+ raise if repo_metadata_url == DEFAULT_REPOSITORY_URL
203
191
 
204
- # rubocop:disable Metrics/CyclomaticComplexity
205
- # rubocop:disable Metrics/PerceivedComplexity
206
- # rubocop:disable Metrics/AbcSize
207
- def repos_from_config_file(config_file)
208
- doc = Nokogiri::XML(config_file.content)
209
- doc.remove_namespaces!
210
- # analogous to having a root config with the default repository
211
- base_sources = [{ url: DEFAULT_REPOSITORY_URL, key: "nuget.org" }]
212
-
213
- sources = []
214
-
215
- # regular package sources
216
- doc.css("configuration > packageSources").children.each do |node|
217
- if node.name == "clear"
218
- sources.clear
219
- base_sources.clear
220
- else
221
- key = node.attribute("key")&.value&.strip || node.at_xpath("./key")&.content&.strip
222
- url = node.attribute("value")&.value&.strip || node.at_xpath("./value")&.content&.strip
223
- sources << { url: url, key: key }
224
- end
225
- end
192
+ raise PrivateSourceTimedOut, repo_metadata_url
193
+ end
226
194
 
227
- # signed package sources
228
- # https://learn.microsoft.com/en-us/nuget/reference/nuget-config-file#trustedsigners-section
229
- doc.xpath("/configuration/trustedSigners/repository").each do |node|
230
- name = node.attribute("name")&.value&.strip
231
- service_index = node.attribute("serviceIndex")&.value&.strip
232
- sources << { url: service_index, key: name }
233
- end
195
+ def credential_repositories
196
+ @credential_repositories ||=
197
+ credentials
198
+ .select { |cred| cred["type"] == "nuget_feed" && cred["url"] }
199
+ .map { |c| { url: c.fetch("url"), token: c["token"] } }
200
+ end
234
201
 
235
- sources += base_sources # TODO: quirky overwrite behavior
236
- disabled_sources = disabled_sources(doc)
237
- sources.reject! do |s|
238
- disabled_sources.include?(s[:key])
239
- end
202
+ def config_file_repositories
203
+ config_files.flat_map { |file| repos_from_config_file(file) }
204
+ end
240
205
 
241
- sources.reject! do |s|
242
- known_urls = credential_repositories.map { |cr| cr.fetch(:url) }
243
- known_urls.include?(s.fetch(:url))
206
+ # rubocop:disable Metrics/CyclomaticComplexity
207
+ # rubocop:disable Metrics/PerceivedComplexity
208
+ # rubocop:disable Metrics/AbcSize
209
+ def repos_from_config_file(config_file)
210
+ doc = Nokogiri::XML(config_file.content)
211
+ doc.remove_namespaces!
212
+ # analogous to having a root config with the default repository
213
+ base_sources = [{ url: DEFAULT_REPOSITORY_URL, key: "nuget.org" }]
214
+
215
+ sources = []
216
+
217
+ # regular package sources
218
+ doc.css("configuration > packageSources").children.each do |node|
219
+ if node.name == "clear"
220
+ sources.clear
221
+ base_sources.clear
222
+ else
223
+ key = node.attribute("key")&.value&.strip || node.at_xpath("./key")&.content&.strip
224
+ url = node.attribute("value")&.value&.strip || node.at_xpath("./value")&.content&.strip
225
+ url = expand_windows_style_environment_variables(url) if url
226
+ sources << { url: url, key: key }
244
227
  end
228
+ end
245
229
 
246
- sources.select! { |s| s.fetch(:url)&.include?("://") }
247
-
248
- add_config_file_credentials(sources: sources, doc: doc)
249
- sources.each { |details| details.delete(:key) }
230
+ # signed package sources
231
+ # https://learn.microsoft.com/en-us/nuget/reference/nuget-config-file#trustedsigners-section
232
+ doc.xpath("/configuration/trustedSigners/repository").each do |node|
233
+ name = node.attribute("name")&.value&.strip
234
+ service_index = node.attribute("serviceIndex")&.value&.strip
235
+ sources << { url: service_index, key: name }
236
+ end
250
237
 
251
- sources
238
+ sources += base_sources # TODO: quirky overwrite behavior
239
+ disabled_sources = disabled_sources(doc)
240
+ sources.reject! do |s|
241
+ disabled_sources.include?(s[:key])
252
242
  end
253
- # rubocop:enable Metrics/AbcSize
254
- # rubocop:enable Metrics/PerceivedComplexity
255
- # rubocop:enable Metrics/CyclomaticComplexity
256
243
 
257
- def default_repository_details
258
- RepositoryFinder.get_default_repository_details(dependency.name)
244
+ sources.reject! do |s|
245
+ known_urls = credential_repositories.map { |cr| cr.fetch(:url) }
246
+ known_urls.include?(s.fetch(:url))
259
247
  end
260
248
 
261
- # rubocop:disable Metrics/PerceivedComplexity
262
- def disabled_sources(doc)
263
- doc.css("configuration > disabledPackageSources > add").filter_map do |node|
264
- value = node.attribute("value")&.value ||
265
- node.at_xpath("./value")&.content
249
+ sources.select! { |s| s.fetch(:url)&.include?("://") }
266
250
 
267
- if value&.strip&.downcase == "true"
268
- node.attribute("key")&.value&.strip ||
269
- node.at_xpath("./key")&.content&.strip
270
- end
251
+ add_config_file_credentials(sources: sources, doc: doc)
252
+ sources.each { |details| details.delete(:key) }
253
+
254
+ sources
255
+ end
256
+ # rubocop:enable Metrics/AbcSize
257
+ # rubocop:enable Metrics/PerceivedComplexity
258
+ # rubocop:enable Metrics/CyclomaticComplexity
259
+
260
+ def default_repository_details
261
+ RepositoryFinder.get_default_repository_details(dependency.name)
262
+ end
263
+
264
+ # rubocop:disable Metrics/PerceivedComplexity
265
+ def disabled_sources(doc)
266
+ doc.css("configuration > disabledPackageSources > add").filter_map do |node|
267
+ value = node.attribute("value")&.value ||
268
+ node.at_xpath("./value")&.content
269
+
270
+ if value&.strip&.downcase == "true"
271
+ node.attribute("key")&.value&.strip ||
272
+ node.at_xpath("./key")&.content&.strip
271
273
  end
272
274
  end
273
- # rubocop:enable Metrics/PerceivedComplexity
274
-
275
- # rubocop:disable Metrics/PerceivedComplexity
276
- def add_config_file_credentials(sources:, doc:)
277
- sources.each do |source_details|
278
- key = source_details.fetch(:key)
279
- next source_details[:token] = nil unless key
280
- next source_details[:token] = nil if key.match?(/^\d/)
281
-
282
- tag = key.gsub(" ", "_x0020_")
283
- creds_nodes = doc.css("configuration > packageSourceCredentials " \
284
- "> #{tag} > add")
285
-
286
- username =
287
- creds_nodes
288
- .find { |n| n.attribute("key")&.value == "Username" }
289
- &.attribute("value")&.value
290
- password =
291
- creds_nodes
292
- .find { |n| n.attribute("key")&.value == "ClearTextPassword" }
293
- &.attribute("value")&.value
294
-
295
- # NOTE: We have to look for plain text passwords, as we have no
296
- # way of decrypting encrypted passwords. For the same reason we
297
- # don't fetch API keys from the nuget.config at all.
298
- next source_details[:token] = nil unless username && password
299
-
300
- source_details[:token] = "#{username}:#{password}"
301
- rescue Nokogiri::XML::XPath::SyntaxError
302
- # Any non-ascii characters in the tag with cause a syntax error
303
- next source_details[:token] = nil
304
- end
305
-
306
- sources
275
+ end
276
+ # rubocop:enable Metrics/PerceivedComplexity
277
+
278
+ # rubocop:disable Metrics/PerceivedComplexity
279
+ def add_config_file_credentials(sources:, doc:)
280
+ sources.each do |source_details|
281
+ key = source_details.fetch(:key)
282
+ next source_details[:token] = nil unless key
283
+ next source_details[:token] = nil if key.match?(/^\d/)
284
+
285
+ tag = key.gsub(" ", "_x0020_")
286
+ creds_nodes = doc.css("configuration > packageSourceCredentials " \
287
+ "> #{tag} > add")
288
+
289
+ username =
290
+ creds_nodes
291
+ .find { |n| n.attribute("key")&.value == "Username" }
292
+ &.attribute("value")&.value
293
+ password =
294
+ creds_nodes
295
+ .find { |n| n.attribute("key")&.value == "ClearTextPassword" }
296
+ &.attribute("value")&.value
297
+
298
+ # NOTE: We have to look for plain text passwords, as we have no
299
+ # way of decrypting encrypted passwords. For the same reason we
300
+ # don't fetch API keys from the nuget.config at all.
301
+ next source_details[:token] = nil unless username && password
302
+
303
+ expanded_username = expand_windows_style_environment_variables(username)
304
+ expanded_password = expand_windows_style_environment_variables(password)
305
+ source_details[:token] = "#{expanded_username}:#{expanded_password}"
306
+ rescue Nokogiri::XML::XPath::SyntaxError
307
+ # Any non-ascii characters in the tag with cause a syntax error
308
+ next source_details[:token] = nil
307
309
  end
308
- # rubocop:enable Metrics/PerceivedComplexity
309
310
 
310
- def remove_wrapping_zero_width_chars(string)
311
- string.force_encoding("UTF-8").encode
312
- .gsub(/\A[\u200B-\u200D\uFEFF]/, "")
313
- .gsub(/[\u200B-\u200D\uFEFF]\Z/, "")
311
+ sources
312
+ end
313
+ # rubocop:enable Metrics/PerceivedComplexity
314
+
315
+ def expand_windows_style_environment_variables(string)
316
+ # NuGet.Config files can have Windows-style environment variables that need to be replaced
317
+ # https://learn.microsoft.com/en-us/nuget/reference/nuget-config-file#using-environment-variables
318
+ string.gsub(/%([^%]+)%/) do
319
+ environment_variable_name = T.must(::Regexp.last_match(1))
320
+ environment_variable_value = ENV.fetch(environment_variable_name, nil)
321
+ if environment_variable_value
322
+ environment_variable_value
323
+ else
324
+ # report that the variable couldn't be expanded, then replace it as-is
325
+ Dependabot.logger.warn <<~WARN
326
+ The variable '%#{environment_variable_name}%' could not be expanded in NuGet.Config
327
+ WARN
328
+ "%#{environment_variable_name}%"
329
+ end
314
330
  end
331
+ end
315
332
 
316
- def auth_header_for_token(token)
317
- return {} unless token
333
+ def remove_wrapping_zero_width_chars(string)
334
+ string.force_encoding("UTF-8").encode
335
+ .gsub(/\A[\u200B-\u200D\uFEFF]/, "")
336
+ .gsub(/[\u200B-\u200D\uFEFF]\Z/, "")
337
+ end
318
338
 
319
- if token.include?(":")
320
- encoded_token = Base64.encode64(token).delete("\n")
321
- { "Authorization" => "Basic #{encoded_token}" }
322
- elsif Base64.decode64(token).ascii_only? &&
323
- Base64.decode64(token).include?(":")
324
- { "Authorization" => "Basic #{token.delete("\n")}" }
325
- else
326
- { "Authorization" => "Bearer #{token}" }
327
- end
339
+ def auth_header_for_token(token)
340
+ return {} unless token
341
+
342
+ if token.include?(":")
343
+ encoded_token = Base64.encode64(token).delete("\n")
344
+ { "Authorization" => "Basic #{encoded_token}" }
345
+ elsif Base64.decode64(token).ascii_only? &&
346
+ Base64.decode64(token).include?(":")
347
+ { "Authorization" => "Basic #{token.delete("\n")}" }
348
+ else
349
+ { "Authorization" => "Bearer #{token}" }
328
350
  end
329
351
  end
330
352
  end
@@ -9,22 +9,20 @@ require "dependabot/shared_helpers"
9
9
 
10
10
  module Dependabot
11
11
  module Nuget
12
- class UpdateChecker < Dependabot::UpdateCheckers::Base
13
- class TfmComparer
14
- def self.are_frameworks_compatible?(project_tfms, package_tfms)
15
- return false if package_tfms.empty?
16
- return false if project_tfms.empty?
12
+ class TfmComparer
13
+ def self.are_frameworks_compatible?(project_tfms, package_tfms)
14
+ return false if package_tfms.empty?
15
+ return false if project_tfms.empty?
17
16
 
18
- key = "project_ftms:#{project_tfms.sort.join(',')}:package_tfms:#{package_tfms.sort.join(',')}".downcase
17
+ key = "project_ftms:#{project_tfms.sort.join(',')}:package_tfms:#{package_tfms.sort.join(',')}".downcase
19
18
 
20
- @cached_framework_check ||= {}
21
- unless @cached_framework_check.key?(key)
22
- @cached_framework_check[key] =
23
- NativeHelpers.run_nuget_framework_check(project_tfms,
24
- package_tfms)
25
- end
26
- @cached_framework_check[key]
19
+ @cached_framework_check ||= {}
20
+ unless @cached_framework_check.key?(key)
21
+ @cached_framework_check[key] =
22
+ NativeHelpers.run_nuget_framework_check(project_tfms,
23
+ package_tfms)
27
24
  end
25
+ @cached_framework_check[key]
28
26
  end
29
27
  end
30
28
  end