dependabot-nuget 0.271.0 → 0.272.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalyzeWorker.cs +19 -4
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +14 -6
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +1 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTests.cs +51 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +83 -0
- data/lib/dependabot/nuget/file_parser.rb +4 -1
- data/lib/dependabot/nuget/file_updater.rb +100 -126
- data/lib/dependabot/nuget/native_discovery/native_dependency_file_discovery.rb +4 -6
- data/lib/dependabot/nuget/native_discovery/native_discovery_json_reader.rb +78 -4
- data/lib/dependabot/nuget/native_discovery/native_project_discovery.rb +1 -0
- data/lib/dependabot/nuget/native_helpers.rb +25 -10
- data/lib/dependabot/nuget/native_update_checker/native_requirements_updater.rb +17 -8
- data/lib/dependabot/nuget/native_update_checker/native_update_checker.rb +45 -10
- data/lib/dependabot/nuget/update_checker/requirements_updater.rb +4 -4
- data/lib/dependabot/nuget/update_checker.rb +2 -1
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b33f6b0a817def66d738b6d998bedf0248881c225ddf6b97fa0a5405d92b9d23
|
4
|
+
data.tar.gz: 9e736306b9d21e95639627ea2fe585d2e47a8979e1096545b4da2ed49e8dbd5f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4dddbe1b9c9cbf4fb9d7876c143cfbd713bf2f6ca501210ae40a7c37bb2076687ea75996042e9ccc6582a6cd535a24eb3b2caa3143b5e8c3ef8ff500455f7827
|
7
|
+
data.tar.gz: f51748be257babb63de285cec8981ca84e580fa48b6d6fec2a89892f485f46ae237af86ab387a584c27c8f207ba1ae16167570bffdc59cf3cd4bb64ac27e9830
|
@@ -108,6 +108,7 @@ public partial class AnalyzeWorker
|
|
108
108
|
discovery,
|
109
109
|
dependenciesToUpdate,
|
110
110
|
updatedVersion,
|
111
|
+
dependencyInfo,
|
111
112
|
nugetContext,
|
112
113
|
_logger,
|
113
114
|
CancellationToken.None);
|
@@ -359,6 +360,7 @@ public partial class AnalyzeWorker
|
|
359
360
|
WorkspaceDiscoveryResult discovery,
|
360
361
|
ImmutableHashSet<string> packageIds,
|
361
362
|
NuGetVersion updatedVersion,
|
363
|
+
DependencyInfo dependencyInfo,
|
362
364
|
NuGetContext nugetContext,
|
363
365
|
Logger logger,
|
364
366
|
CancellationToken cancellationToken)
|
@@ -379,10 +381,23 @@ public partial class AnalyzeWorker
|
|
379
381
|
.Select(NuGetFramework.Parse)
|
380
382
|
.ToImmutableArray();
|
381
383
|
|
382
|
-
// When updating
|
383
|
-
var
|
384
|
-
.
|
385
|
-
|
384
|
+
// When updating dependencies, we only need to consider top-level dependencies _UNLESS_ it's specifically vulnerable
|
385
|
+
var relevantDependencies = projectsWithDependency.SelectMany(p => p.Dependencies)
|
386
|
+
.Where(d =>
|
387
|
+
{
|
388
|
+
if (string.Compare(d.Name, dependencyInfo.Name, StringComparison.OrdinalIgnoreCase) == 0 &&
|
389
|
+
dependencyInfo.IsVulnerable)
|
390
|
+
{
|
391
|
+
// if this dependency is one we're specifically updating _and_ if it's vulnerable, always update it
|
392
|
+
return true;
|
393
|
+
}
|
394
|
+
else
|
395
|
+
{
|
396
|
+
// otherwise only update if it's a top-level dependency
|
397
|
+
return !d.IsTransitive;
|
398
|
+
}
|
399
|
+
});
|
400
|
+
var projectDependencyNames = relevantDependencies
|
386
401
|
.Select(d => d.Name)
|
387
402
|
.ToImmutableHashSet(StringComparer.OrdinalIgnoreCase);
|
388
403
|
|
@@ -199,13 +199,21 @@ public partial class DiscoveryWorker
|
|
199
199
|
}
|
200
200
|
else
|
201
201
|
{
|
202
|
-
|
203
|
-
// keep this project and check for references
|
204
|
-
expandedProjects.Add(candidateEntryPoint);
|
205
|
-
IEnumerable<string> referencedProjects = ExpandItemGroupFilesFromProject(candidateEntryPoint, "ProjectReference");
|
206
|
-
foreach (string referencedProject in referencedProjects)
|
202
|
+
switch (extension)
|
207
203
|
{
|
208
|
-
|
204
|
+
case ".csproj":
|
205
|
+
case ".fsproj":
|
206
|
+
case ".vbproj":
|
207
|
+
// keep this project and check for references
|
208
|
+
expandedProjects.Add(candidateEntryPoint);
|
209
|
+
IEnumerable<string> referencedProjects = ExpandItemGroupFilesFromProject(candidateEntryPoint, "ProjectReference");
|
210
|
+
foreach (string referencedProject in referencedProjects)
|
211
|
+
{
|
212
|
+
filesToExpand.Push(referencedProject);
|
213
|
+
}
|
214
|
+
break;
|
215
|
+
default:
|
216
|
+
continue;
|
209
217
|
}
|
210
218
|
}
|
211
219
|
}
|
@@ -169,7 +169,7 @@ internal static partial class MSBuildHelper
|
|
169
169
|
string.Equals(property.Condition, $"'$({property.Name})' == ''", StringComparison.OrdinalIgnoreCase);
|
170
170
|
if (hasEmptyCondition || conditionIsCheckingForEmptyString)
|
171
171
|
{
|
172
|
-
properties[property.Name] = new(property.Name, property.Value, buildFile.RelativePath);
|
172
|
+
properties[property.Name] = new(property.Name, property.Value, PathHelper.NormalizePathToUnix(buildFile.RelativePath));
|
173
173
|
}
|
174
174
|
}
|
175
175
|
}
|
@@ -347,6 +347,57 @@ public partial class AnalyzeWorkerTests : AnalyzeWorkerTestBase
|
|
347
347
|
);
|
348
348
|
}
|
349
349
|
|
350
|
+
[Fact]
|
351
|
+
public async Task AnalyzeVulnerableTransitiveDependencies()
|
352
|
+
{
|
353
|
+
await TestAnalyzeAsync(
|
354
|
+
packages:
|
355
|
+
[
|
356
|
+
MockNuGetPackage.CreateSimplePackage("Some.Transitive.Dependency", "1.0.0", "net8.0"),
|
357
|
+
MockNuGetPackage.CreateSimplePackage("Some.Transitive.Dependency", "1.0.1", "net8.0"),
|
358
|
+
],
|
359
|
+
discovery: new()
|
360
|
+
{
|
361
|
+
Path = "/",
|
362
|
+
Projects = [
|
363
|
+
new()
|
364
|
+
{
|
365
|
+
FilePath = "project.csproj",
|
366
|
+
TargetFrameworks = ["net8.0"],
|
367
|
+
Dependencies = [
|
368
|
+
new("Some.Transitive.Dependency", "1.0.0", DependencyType.Unknown, TargetFrameworks: ["net8.0"], IsTransitive: true),
|
369
|
+
]
|
370
|
+
}
|
371
|
+
]
|
372
|
+
},
|
373
|
+
dependencyInfo: new()
|
374
|
+
{
|
375
|
+
Name = "Some.Transitive.Dependency",
|
376
|
+
Version = "1.0.0",
|
377
|
+
IsVulnerable = true,
|
378
|
+
IgnoredVersions = [],
|
379
|
+
Vulnerabilities = [
|
380
|
+
new()
|
381
|
+
{
|
382
|
+
DependencyName = "Some.Transitive.Dependency",
|
383
|
+
PackageManager = "nuget",
|
384
|
+
VulnerableVersions = [Requirement.Parse("<= 1.0.0")],
|
385
|
+
SafeVersions = [Requirement.Parse("= 1.0.1")],
|
386
|
+
}
|
387
|
+
]
|
388
|
+
},
|
389
|
+
expectedResult: new()
|
390
|
+
{
|
391
|
+
UpdatedVersion = "1.0.1",
|
392
|
+
CanUpdate = true,
|
393
|
+
VersionComesFromMultiDependencyProperty = false,
|
394
|
+
UpdatedDependencies = [
|
395
|
+
new("Some.Transitive.Dependency", "1.0.1", DependencyType.Unknown, TargetFrameworks: ["net8.0"]),
|
396
|
+
],
|
397
|
+
}
|
398
|
+
);
|
399
|
+
}
|
400
|
+
|
350
401
|
[Fact]
|
351
402
|
public async Task IgnoredVersionsCanHandleWildcardSpecification()
|
352
403
|
{
|
@@ -375,6 +375,89 @@ public partial class DiscoveryWorkerTests : DiscoveryWorkerTestBase
|
|
375
375
|
);
|
376
376
|
}
|
377
377
|
|
378
|
+
[Fact]
|
379
|
+
public async Task NonSupportedProjectExtensionsAreSkipped()
|
380
|
+
{
|
381
|
+
await TestDiscoveryAsync(
|
382
|
+
packages:
|
383
|
+
[
|
384
|
+
MockNuGetPackage.CreateSimplePackage("Some.Package", "1.0.0", "net8.0"),
|
385
|
+
],
|
386
|
+
workspacePath: "/",
|
387
|
+
files: new[]
|
388
|
+
{
|
389
|
+
("solution.sln", """
|
390
|
+
Microsoft Visual Studio Solution File, Format Version 12.00
|
391
|
+
# Visual Studio Version 17
|
392
|
+
VisualStudioVersion = 17.10.35027.167
|
393
|
+
MinimumVisualStudioVersion = 10.0.40219.1
|
394
|
+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "supported", "src\supported.csproj", "{4A3B8D8A-A585-4593-8AF3-DED05AE3C40F}"
|
395
|
+
EndProject
|
396
|
+
Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "unsupported", "src\unsupported.vdproj", "{271E533C-8A44-4572-8C18-CD65A79F8658}"
|
397
|
+
EndProject
|
398
|
+
Global
|
399
|
+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
400
|
+
Debug|Any CPU = Debug|Any CPU
|
401
|
+
Release|Any CPU = Release|Any CPU
|
402
|
+
EndGlobalSection
|
403
|
+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
404
|
+
{4A3B8D8A-A585-4593-8AF3-DED05AE3C40F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
405
|
+
{4A3B8D8A-A585-4593-8AF3-DED05AE3C40F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
406
|
+
{4A3B8D8A-A585-4593-8AF3-DED05AE3C40F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
407
|
+
{4A3B8D8A-A585-4593-8AF3-DED05AE3C40F}.Release|Any CPU.Build.0 = Release|Any CPU
|
408
|
+
{271E533C-8A44-4572-8C18-CD65A79F8658}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
409
|
+
{271E533C-8A44-4572-8C18-CD65A79F8658}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
410
|
+
{271E533C-8A44-4572-8C18-CD65A79F8658}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
411
|
+
{271E533C-8A44-4572-8C18-CD65A79F8658}.Release|Any CPU.Build.0 = Release|Any CPU
|
412
|
+
EndGlobalSection
|
413
|
+
GlobalSection(SolutionProperties) = preSolution
|
414
|
+
HideSolutionNode = FALSE
|
415
|
+
EndGlobalSection
|
416
|
+
GlobalSection(ExtensibilityGlobals) = postSolution
|
417
|
+
SolutionGuid = {EE5BDEF7-1D4D-4773-9659-FC4A3846CD6D}
|
418
|
+
EndGlobalSection
|
419
|
+
EndGlobal
|
420
|
+
"""),
|
421
|
+
("src/supported.csproj", """
|
422
|
+
<Project Sdk="Microsoft.NET.Sdk">
|
423
|
+
<PropertyGroup>
|
424
|
+
<TargetFramework>net8.0</TargetFramework>
|
425
|
+
</PropertyGroup>
|
426
|
+
<ItemGroup>
|
427
|
+
<PackageReference Include="Some.Package" Version="1.0.0" />
|
428
|
+
</ItemGroup>
|
429
|
+
</Project>
|
430
|
+
"""),
|
431
|
+
("src/unsupported.vdproj", """
|
432
|
+
"DeployProject"
|
433
|
+
{
|
434
|
+
"SomeKey" = "SomeValue"
|
435
|
+
}
|
436
|
+
"""),
|
437
|
+
},
|
438
|
+
expectedResult: new()
|
439
|
+
{
|
440
|
+
Path = "",
|
441
|
+
Projects = [
|
442
|
+
new()
|
443
|
+
{
|
444
|
+
FilePath = "src/supported.csproj",
|
445
|
+
TargetFrameworks = ["net8.0"],
|
446
|
+
ReferencedProjectPaths = [],
|
447
|
+
ExpectedDependencyCount = 2,
|
448
|
+
Dependencies = [
|
449
|
+
new("Microsoft.NET.Sdk", null, DependencyType.MSBuildSdk),
|
450
|
+
new("Some.Package", "1.0.0", DependencyType.PackageReference, TargetFrameworks: ["net8.0"], IsDirect: true)
|
451
|
+
],
|
452
|
+
Properties = [
|
453
|
+
new("TargetFramework", "net8.0", @"src/supported.csproj"),
|
454
|
+
]
|
455
|
+
}
|
456
|
+
]
|
457
|
+
}
|
458
|
+
);
|
459
|
+
}
|
460
|
+
|
378
461
|
[Fact]
|
379
462
|
public async Task ResultFileHasCorrectShapeForAuthenticationFailure()
|
380
463
|
{
|
@@ -50,7 +50,10 @@ module Dependabot
|
|
50
50
|
# cache discovery results
|
51
51
|
NativeDiscoveryJsonReader.set_discovery_from_dependency_files(dependency_files: dependency_files,
|
52
52
|
discovery: discovery_json_reader)
|
53
|
-
|
53
|
+
# we only return top-level dependencies and requirements here
|
54
|
+
dependency_set = discovery_json_reader.dependency_set(dependency_files: dependency_files,
|
55
|
+
top_level_only: true)
|
56
|
+
dependency_set.dependencies
|
54
57
|
end
|
55
58
|
|
56
59
|
T.must(self.class.file_dependency_cache[key])
|
@@ -16,35 +16,32 @@ module Dependabot
|
|
16
16
|
class FileUpdater < Dependabot::FileUpdaters::Base
|
17
17
|
extend T::Sig
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
]
|
33
|
-
|
34
|
-
#
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
/^Packages\.props$/i
|
46
|
-
]
|
47
|
-
end
|
19
|
+
DependencyDetails = T.type_alias do
|
20
|
+
{
|
21
|
+
file: String,
|
22
|
+
name: String,
|
23
|
+
version: String,
|
24
|
+
previous_version: String,
|
25
|
+
is_transitive: T::Boolean
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
sig { override.returns(T::Array[Regexp]) }
|
30
|
+
def self.updated_files_regex
|
31
|
+
[
|
32
|
+
/.*\.([a-z]{2})?proj$/, # Matches files with any extension like .csproj, .vbproj, etc., in any directory
|
33
|
+
/packages\.config$/i, # Matches packages.config in any directory
|
34
|
+
/app\.config$/i, # Matches app.config in any directory
|
35
|
+
/web\.config$/i, # Matches web.config in any directory
|
36
|
+
/global\.json$/i, # Matches global.json in any directory
|
37
|
+
/dotnet-tools\.json$/i, # Matches dotnet-tools.json in any directory
|
38
|
+
/Directory\.Build\.props$/i, # Matches Directory.Build.props in any directory
|
39
|
+
/Directory\.Build\.targets$/i, # Matches Directory.Build.targets in any directory
|
40
|
+
/Directory\.targets$/i, # Matches Directory.targets in any directory or root directory
|
41
|
+
/Packages\.props$/i, # Matches Packages.props in any directory
|
42
|
+
/.*\.nuspec$/, # Matches any .nuspec files in any directory
|
43
|
+
%r{^\.config/dotnet-tools\.json$} # Matches .config/dotnet-tools.json in only root directory
|
44
|
+
]
|
48
45
|
end
|
49
46
|
|
50
47
|
sig { params(original_content: T.nilable(String), updated_content: String).returns(T::Boolean) }
|
@@ -65,9 +62,21 @@ module Dependabot
|
|
65
62
|
def updated_dependency_files
|
66
63
|
base_dir = "/"
|
67
64
|
SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do
|
68
|
-
|
69
|
-
|
65
|
+
expanded_dependency_details.each do |dep_details|
|
66
|
+
file = T.let(dep_details.fetch(:file), String)
|
67
|
+
name = T.let(dep_details.fetch(:name), String)
|
68
|
+
version = T.let(dep_details.fetch(:version), String)
|
69
|
+
previous_version = T.let(dep_details.fetch(:previous_version), String)
|
70
|
+
is_transitive = T.let(dep_details.fetch(:is_transitive), T::Boolean)
|
71
|
+
NativeHelpers.run_nuget_updater_tool(repo_root: T.must(repo_contents_path),
|
72
|
+
proj_path: file,
|
73
|
+
dependency_name: name,
|
74
|
+
version: version,
|
75
|
+
previous_version: previous_version,
|
76
|
+
is_transitive: is_transitive,
|
77
|
+
credentials: credentials)
|
70
78
|
end
|
79
|
+
|
71
80
|
updated_files = dependency_files.filter_map do |f|
|
72
81
|
updated_content = File.read(dependency_file_path(f))
|
73
82
|
next if updated_content == f.content
|
@@ -87,104 +96,69 @@ module Dependabot
|
|
87
96
|
|
88
97
|
private
|
89
98
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
checked_key = "#{project_file.name}-#{dependency.name}#{dependency.version}"
|
105
|
-
call_nuget_updater_tool(dependency, proj_path) unless checked_files.include?(checked_key)
|
106
|
-
|
107
|
-
checked_files.add(checked_key)
|
108
|
-
# We need to check the downstream references even though we're already evaluated the file
|
109
|
-
downstream_files = referenced_project_paths(project_file)
|
110
|
-
downstream_files.each do |downstream_file|
|
111
|
-
checked_files.add("#{downstream_file}-#{dependency.name}#{dependency.version}")
|
99
|
+
# rubocop:disable Metrics/AbcSize
|
100
|
+
sig { returns(T::Array[DependencyDetails]) }
|
101
|
+
def expanded_dependency_details
|
102
|
+
discovery_json_reader = NativeDiscoveryJsonReader.get_discovery_from_dependency_files(dependency_files)
|
103
|
+
dependency_set = discovery_json_reader.dependency_set(dependency_files: dependency_files, top_level_only: false)
|
104
|
+
all_dependencies = dependency_set.dependencies
|
105
|
+
dependencies.map do |dep|
|
106
|
+
# if vulnerable metadata is set, re-fetch all requirements from discovery
|
107
|
+
is_vulnerable = T.let(dep.metadata.fetch(:is_vulnerable, false), T::Boolean)
|
108
|
+
relevant_dependencies = all_dependencies.filter { |d| d.name.casecmp?(dep.name) }
|
109
|
+
candidate_vulnerable_dependency = T.must(relevant_dependencies.first)
|
110
|
+
relevant_dependency = is_vulnerable ? candidate_vulnerable_dependency : dep
|
111
|
+
relevant_details = relevant_dependency.requirements.filter_map do |req|
|
112
|
+
dependency_details_from_requirement(dep.name, req, is_vulnerable: is_vulnerable)
|
112
113
|
end
|
113
|
-
update_ran = true
|
114
|
-
end
|
115
|
-
update_ran
|
116
|
-
end
|
117
|
-
|
118
|
-
sig { params(dependency: Dependabot::Dependency).returns(T::Boolean) }
|
119
|
-
def try_update_json(dependency)
|
120
|
-
if dotnet_tools_json_dependencies.any? { |dep| dep.name.casecmp?(dependency.name) } ||
|
121
|
-
global_json_dependencies.any? { |dep| dep.name.casecmp?(dependency.name) }
|
122
|
-
|
123
|
-
# We just need to feed the updater a project file, grab the first
|
124
|
-
project_file = T.must(project_files.first)
|
125
|
-
proj_path = dependency_file_path(project_file)
|
126
|
-
|
127
|
-
return false unless repo_contents_path
|
128
|
-
|
129
|
-
call_nuget_updater_tool(dependency, proj_path)
|
130
|
-
return true
|
131
|
-
end
|
132
114
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
T.must(@update_tooling_calls[key]) + 1
|
150
|
-
else
|
151
|
-
1
|
115
|
+
next relevant_details if relevant_details.any?
|
116
|
+
|
117
|
+
# If we didn't find anything to update, we're in a very specific corner case: we were explicitly asked to
|
118
|
+
# (1) update a certain dependency, (2) it wasn't listed as a security update, but (3) it only exists as a
|
119
|
+
# transitive dependency. In this case, we need to rebuild the dependency requirements as if this were a
|
120
|
+
# security update so that we can perform the appropriate update.
|
121
|
+
candidate_vulnerable_dependency.requirements.filter_map do |req|
|
122
|
+
rebuilt_req = {
|
123
|
+
file: req[:file], # simple copy
|
124
|
+
requirement: relevant_dependency.version, # the newly available version
|
125
|
+
metadata: {
|
126
|
+
is_transitive: T.let(req[:metadata], T::Hash[Symbol, T.untyped])[:is_transitive], # simple copy
|
127
|
+
previous_requirement: req[:requirement] # the old requirement's "current" version is now the "previous"
|
128
|
+
}
|
129
|
+
}
|
130
|
+
dependency_details_from_requirement(dep.name, rebuilt_req, is_vulnerable: true)
|
152
131
|
end
|
153
|
-
|
154
|
-
|
155
|
-
#
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
end
|
184
|
-
|
185
|
-
sig { returns(T::Array[NativeDependencyDetails]) }
|
186
|
-
def dotnet_tools_json_dependencies
|
187
|
-
workspace&.dotnet_tools_json&.dependencies || []
|
132
|
+
end.flatten
|
133
|
+
end
|
134
|
+
# rubocop:enable Metrics/AbcSize
|
135
|
+
|
136
|
+
sig do
|
137
|
+
params(
|
138
|
+
name: String,
|
139
|
+
requirement: T::Hash[Symbol, T.untyped],
|
140
|
+
is_vulnerable: T::Boolean
|
141
|
+
).returns(T.nilable(DependencyDetails))
|
142
|
+
end
|
143
|
+
def dependency_details_from_requirement(name, requirement, is_vulnerable:)
|
144
|
+
metadata = T.let(requirement.fetch(:metadata), T::Hash[Symbol, T.untyped])
|
145
|
+
current_file = T.let(requirement.fetch(:file), String)
|
146
|
+
return nil unless current_file.match?(/\.(cs|vb|fs)proj$/)
|
147
|
+
|
148
|
+
is_transitive = T.let(metadata.fetch(:is_transitive), T::Boolean)
|
149
|
+
return nil if !is_vulnerable && is_transitive
|
150
|
+
|
151
|
+
version = T.let(requirement.fetch(:requirement), String)
|
152
|
+
previous_version = T.let(metadata[:previous_requirement], String)
|
153
|
+
return nil if version == previous_version
|
154
|
+
|
155
|
+
{
|
156
|
+
file: T.let(requirement.fetch(:file), String),
|
157
|
+
name: name,
|
158
|
+
version: version,
|
159
|
+
previous_version: previous_version,
|
160
|
+
is_transitive: is_transitive
|
161
|
+
}
|
188
162
|
end
|
189
163
|
|
190
164
|
# rubocop:disable Metrics/PerceivedComplexity
|
@@ -106,22 +106,20 @@ module Dependabot
|
|
106
106
|
.returns(T.nilable(T::Hash[Symbol, T.untyped]))
|
107
107
|
end
|
108
108
|
def build_requirement(file_name, dependency_details)
|
109
|
-
return if dependency_details.is_transitive
|
110
|
-
|
111
109
|
version = dependency_details.version
|
112
110
|
version = nil if version&.empty?
|
111
|
+
metadata = { is_transitive: dependency_details.is_transitive }
|
113
112
|
|
114
113
|
requirement = {
|
115
114
|
requirement: version,
|
116
115
|
file: file_name,
|
117
116
|
groups: [dependency_details.is_dev_dependency ? "devDependencies" : "dependencies"],
|
118
|
-
source: nil
|
117
|
+
source: nil,
|
118
|
+
metadata: metadata
|
119
119
|
}
|
120
120
|
|
121
121
|
property_name = dependency_details.evaluation&.root_property_name
|
122
|
-
|
123
|
-
|
124
|
-
requirement[:metadata] = { property_name: property_name }
|
122
|
+
metadata[:property_name] = property_name if property_name
|
125
123
|
requirement
|
126
124
|
end
|
127
125
|
end
|
@@ -125,16 +125,90 @@ module Dependabot
|
|
125
125
|
sig { returns(T.nilable(NativeWorkspaceDiscovery)) }
|
126
126
|
attr_reader :workspace_discovery
|
127
127
|
|
128
|
-
sig { returns(Dependabot::FileParsers::Base::DependencySet) }
|
129
|
-
attr_reader :dependency_set
|
130
|
-
|
131
128
|
sig { params(discovery_json: DependencyFile).void }
|
132
129
|
def initialize(discovery_json:)
|
133
130
|
@discovery_json = discovery_json
|
134
131
|
@workspace_discovery = T.let(read_workspace_discovery, T.nilable(Dependabot::Nuget::NativeWorkspaceDiscovery))
|
135
|
-
@dependency_set = T.let(read_dependency_set, Dependabot::FileParsers::Base::DependencySet)
|
136
132
|
end
|
137
133
|
|
134
|
+
# rubocop:disable Metrics/AbcSize
|
135
|
+
# rubocop:disable Metrics/MethodLength
|
136
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
137
|
+
sig do
|
138
|
+
params(
|
139
|
+
dependency_files: T::Array[Dependabot::DependencyFile],
|
140
|
+
top_level_only: T::Boolean
|
141
|
+
).returns(Dependabot::FileParsers::Base::DependencySet)
|
142
|
+
end
|
143
|
+
def dependency_set(dependency_files:, top_level_only:)
|
144
|
+
# dependencies must be recalculated so that we:
|
145
|
+
# 1. only return dependencies that are in the file set we reported earlier
|
146
|
+
# see https://github.com/dependabot/dependabot-core/issues/10303
|
147
|
+
# 2. the reported version is the minimum across all requirements; this ensures that we get the opportunity
|
148
|
+
# to update everything later
|
149
|
+
dependency_file_set = T.let(Set.new(dependency_files.map do |df|
|
150
|
+
Pathname.new(File.join(df.directory, df.name)).cleanpath.to_path
|
151
|
+
end), T::Set[String])
|
152
|
+
|
153
|
+
rebuilt_dependencies = read_dependency_set.dependencies.filter_map do |dep|
|
154
|
+
# only report requirements in files we know about
|
155
|
+
matching_requirements = dep.requirements.filter do |req|
|
156
|
+
file = T.let(req.fetch(:file), String)
|
157
|
+
dependency_file_set.include?(file)
|
158
|
+
end
|
159
|
+
|
160
|
+
# find the minimum version across all requirements
|
161
|
+
min_version = matching_requirements.filter_map do |req|
|
162
|
+
v = T.let(req.fetch(:requirement), T.nilable(String))
|
163
|
+
next unless v
|
164
|
+
|
165
|
+
Dependabot::Nuget::Version.new(v)
|
166
|
+
end.min
|
167
|
+
next unless min_version
|
168
|
+
|
169
|
+
# only return dependency requirements that are top-level
|
170
|
+
if top_level_only
|
171
|
+
matching_requirements.reject! do |req|
|
172
|
+
metadata = T.let(req.fetch(:metadata), T::Hash[Symbol, T.untyped])
|
173
|
+
T.let(metadata.fetch(:is_transitive), T::Boolean)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# we might need to return a dependency like this
|
178
|
+
dep_without_reqs = Dependabot::Dependency.new(
|
179
|
+
name: dep.name,
|
180
|
+
version: min_version.to_s,
|
181
|
+
package_manager: "nuget",
|
182
|
+
requirements: []
|
183
|
+
)
|
184
|
+
|
185
|
+
dep_with_reqs = matching_requirements.filter_map do |req|
|
186
|
+
version = T.let(req.fetch(:requirement, nil), T.nilable(String))
|
187
|
+
next unless version
|
188
|
+
|
189
|
+
Dependabot::Dependency.new(
|
190
|
+
name: dep.name,
|
191
|
+
version: min_version.to_s,
|
192
|
+
package_manager: "nuget",
|
193
|
+
requirements: [req]
|
194
|
+
)
|
195
|
+
end
|
196
|
+
|
197
|
+
# if only returning top-level dependencies and we had no non-transitive requirements, return an empty
|
198
|
+
# dependency so it can be tracked for security updates
|
199
|
+
matching_requirements.empty? && top_level_only ? [dep_without_reqs] : dep_with_reqs
|
200
|
+
end.flatten
|
201
|
+
|
202
|
+
final_dependency_set = Dependabot::FileParsers::Base::DependencySet.new
|
203
|
+
rebuilt_dependencies.each do |dep|
|
204
|
+
final_dependency_set << dep
|
205
|
+
end
|
206
|
+
final_dependency_set
|
207
|
+
end
|
208
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
209
|
+
# rubocop:enable Metrics/MethodLength
|
210
|
+
# rubocop:enable Metrics/AbcSize
|
211
|
+
|
138
212
|
private
|
139
213
|
|
140
214
|
sig { returns(DependencyFile) }
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# typed: strong
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require "dependabot/file_parsers/base/dependency_set"
|
4
5
|
require "dependabot/nuget/native_discovery/native_dependency_details"
|
5
6
|
require "dependabot/nuget/native_discovery/native_property_details"
|
6
7
|
require "sorbet-runtime"
|
@@ -171,10 +171,18 @@ module Dependabot
|
|
171
171
|
|
172
172
|
# rubocop:disable Metrics/MethodLength
|
173
173
|
sig do
|
174
|
-
params(
|
175
|
-
|
174
|
+
params(
|
175
|
+
repo_root: String,
|
176
|
+
proj_path: String,
|
177
|
+
dependency_name: String,
|
178
|
+
version: String,
|
179
|
+
previous_version: String,
|
180
|
+
is_transitive: T::Boolean,
|
181
|
+
result_output_path: String
|
182
|
+
).returns([String, String])
|
176
183
|
end
|
177
|
-
def self.get_nuget_updater_tool_command(repo_root:, proj_path:,
|
184
|
+
def self.get_nuget_updater_tool_command(repo_root:, proj_path:, dependency_name:, version:, previous_version:,
|
185
|
+
is_transitive:, result_output_path:)
|
178
186
|
exe_path = File.join(native_helpers_root, "NuGetUpdater", "NuGetUpdater.Cli")
|
179
187
|
command_parts = [
|
180
188
|
exe_path,
|
@@ -184,11 +192,11 @@ module Dependabot
|
|
184
192
|
"--solution-or-project",
|
185
193
|
proj_path,
|
186
194
|
"--dependency",
|
187
|
-
|
195
|
+
dependency_name,
|
188
196
|
"--new-version",
|
189
|
-
|
197
|
+
version,
|
190
198
|
"--previous-version",
|
191
|
-
|
199
|
+
previous_version,
|
192
200
|
is_transitive ? "--transitive" : nil,
|
193
201
|
"--result-output-path",
|
194
202
|
result_output_path,
|
@@ -229,14 +237,21 @@ module Dependabot
|
|
229
237
|
params(
|
230
238
|
repo_root: String,
|
231
239
|
proj_path: String,
|
232
|
-
|
240
|
+
dependency_name: String,
|
241
|
+
version: String,
|
242
|
+
previous_version: String,
|
233
243
|
is_transitive: T::Boolean,
|
234
244
|
credentials: T::Array[Dependabot::Credential]
|
235
245
|
).void
|
236
246
|
end
|
237
|
-
def self.run_nuget_updater_tool(repo_root:, proj_path:,
|
238
|
-
|
239
|
-
|
247
|
+
def self.run_nuget_updater_tool(repo_root:, proj_path:, dependency_name:, version:, previous_version:,
|
248
|
+
is_transitive:, credentials:)
|
249
|
+
(command, fingerprint) = get_nuget_updater_tool_command(repo_root: repo_root,
|
250
|
+
proj_path: proj_path,
|
251
|
+
dependency_name: dependency_name,
|
252
|
+
version: version,
|
253
|
+
previous_version: previous_version,
|
254
|
+
is_transitive: is_transitive,
|
240
255
|
result_output_path: update_result_file_path)
|
241
256
|
|
242
257
|
puts "running NuGet updater:\n" + command
|
@@ -21,15 +21,18 @@ module Dependabot
|
|
21
21
|
sig do
|
22
22
|
params(
|
23
23
|
requirements: T::Array[T::Hash[Symbol, T.untyped]],
|
24
|
-
dependency_details: T.nilable(Dependabot::Nuget::NativeDependencyDetails)
|
24
|
+
dependency_details: T.nilable(Dependabot::Nuget::NativeDependencyDetails),
|
25
|
+
vulnerable: T::Boolean
|
25
26
|
)
|
26
27
|
.void
|
27
28
|
end
|
28
|
-
def initialize(requirements:, dependency_details:)
|
29
|
+
def initialize(requirements:, dependency_details:, vulnerable:)
|
29
30
|
@requirements = requirements
|
30
31
|
@dependency_details = dependency_details
|
32
|
+
@vulnerable = vulnerable
|
31
33
|
end
|
32
34
|
|
35
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
33
36
|
sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
|
34
37
|
def updated_requirements
|
35
38
|
return requirements unless clean_version
|
@@ -37,13 +40,18 @@ module Dependabot
|
|
37
40
|
# NOTE: Order is important here. The FileUpdater needs the updated
|
38
41
|
# requirement at index `i` to correspond to the previous requirement
|
39
42
|
# at the same index.
|
40
|
-
requirements.
|
41
|
-
next
|
42
|
-
|
43
|
+
requirements.filter_map do |req|
|
44
|
+
next if !@vulnerable && req[:metadata][:is_transitive]
|
45
|
+
|
46
|
+
previous_requirement = req.fetch(:requirement)
|
47
|
+
req[:metadata][:previous_requirement] = previous_requirement
|
48
|
+
|
49
|
+
next req if previous_requirement.nil?
|
50
|
+
next req if previous_requirement.include?(",")
|
43
51
|
|
44
52
|
new_req =
|
45
|
-
if
|
46
|
-
update_wildcard_requirement(
|
53
|
+
if previous_requirement.include?("*")
|
54
|
+
update_wildcard_requirement(previous_requirement)
|
47
55
|
else
|
48
56
|
# Since range requirements are excluded by the line above we can
|
49
57
|
# replace anything that looks like a version with the new
|
@@ -54,7 +62,7 @@ module Dependabot
|
|
54
62
|
)
|
55
63
|
end
|
56
64
|
|
57
|
-
next req if new_req ==
|
65
|
+
next req if new_req == previous_requirement
|
58
66
|
|
59
67
|
new_source = req[:source]&.dup
|
60
68
|
unless @dependency_details.nil?
|
@@ -67,6 +75,7 @@ module Dependabot
|
|
67
75
|
req.merge({ requirement: new_req, source: new_source })
|
68
76
|
end
|
69
77
|
end
|
78
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
70
79
|
|
71
80
|
private
|
72
81
|
|
@@ -56,7 +56,8 @@ module Dependabot
|
|
56
56
|
dep_details = updated_dependency_details.find { |d| d.name.casecmp?(dependency.name) }
|
57
57
|
NativeRequirementsUpdater.new(
|
58
58
|
requirements: dependency.requirements,
|
59
|
-
dependency_details: dep_details
|
59
|
+
dependency_details: dep_details,
|
60
|
+
vulnerable: vulnerable?
|
60
61
|
).updated_requirements
|
61
62
|
end
|
62
63
|
|
@@ -112,9 +113,10 @@ module Dependabot
|
|
112
113
|
|
113
114
|
sig { void }
|
114
115
|
def write_dependency_info
|
116
|
+
dependency_version = T.let(dependency.requirements.first&.fetch(:requirement, nil), T.nilable(String))
|
115
117
|
dependency_info = {
|
116
118
|
Name: dependency.name,
|
117
|
-
Version: dependency.version.to_s,
|
119
|
+
Version: dependency_version || dependency.version.to_s,
|
118
120
|
IsVulnerable: vulnerable?,
|
119
121
|
IgnoredVersions: ignored_versions,
|
120
122
|
Vulnerabilities: security_advisories.map do |vulnerability|
|
@@ -141,7 +143,7 @@ module Dependabot
|
|
141
143
|
sig { returns(Dependabot::FileParsers::Base::DependencySet) }
|
142
144
|
def discovered_dependencies
|
143
145
|
discovery_json_reader = NativeDiscoveryJsonReader.get_discovery_from_dependency_files(dependency_files)
|
144
|
-
discovery_json_reader.dependency_set
|
146
|
+
discovery_json_reader.dependency_set(dependency_files: dependency_files, top_level_only: false)
|
145
147
|
end
|
146
148
|
|
147
149
|
sig { override.returns(T::Boolean) }
|
@@ -150,6 +152,10 @@ module Dependabot
|
|
150
152
|
true
|
151
153
|
end
|
152
154
|
|
155
|
+
# rubocop:disable Metrics/AbcSize
|
156
|
+
# rubocop:disable Metrics/BlockLength
|
157
|
+
# rubocop:disable Metrics/MethodLength
|
158
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
153
159
|
sig { override.returns(T::Array[Dependabot::Dependency]) }
|
154
160
|
def updated_dependencies_after_full_unlock
|
155
161
|
dependencies = discovered_dependencies.dependencies
|
@@ -157,14 +163,16 @@ module Dependabot
|
|
157
163
|
dep = dependencies.find { |d| d.name.casecmp(dependency_details.name)&.zero? }
|
158
164
|
next unless dep
|
159
165
|
|
160
|
-
|
166
|
+
dep_metadata = T.let({}, T::Hash[Symbol, T.untyped])
|
161
167
|
# For peer dependencies, instruct updater to not directly update this dependency
|
162
|
-
|
168
|
+
dep_metadata[:information_only] = true unless dependency.name.casecmp(dependency_details.name)&.zero?
|
169
|
+
dep_metadata[:is_vulnerable] = vulnerable?
|
163
170
|
|
164
171
|
# rebuild the new requirements with the updated dependency details
|
165
172
|
updated_reqs = dep.requirements.map do |r|
|
166
173
|
r = r.clone
|
167
|
-
r[:
|
174
|
+
T.let(r[:metadata], T::Hash[Symbol, T.untyped])[:previous_requirement] = r[:requirement] # keep old version
|
175
|
+
r[:requirement] = dependency_details.version # set new version
|
168
176
|
r[:source] = {
|
169
177
|
type: "nuget_repo",
|
170
178
|
source_url: dependency_details.info_url
|
@@ -172,17 +180,44 @@ module Dependabot
|
|
172
180
|
r
|
173
181
|
end
|
174
182
|
|
183
|
+
reqs = dep.requirements
|
184
|
+
unless vulnerable?
|
185
|
+
updated_reqs = updated_reqs.filter do |r|
|
186
|
+
req_metadata = T.let(r.fetch(:metadata, {}), T::Hash[Symbol, T.untyped])
|
187
|
+
!T.let(req_metadata[:is_transitive], T::Boolean)
|
188
|
+
end
|
189
|
+
reqs = reqs.filter do |r|
|
190
|
+
req_metadata = T.let(r.fetch(:metadata, {}), T::Hash[Symbol, T.untyped])
|
191
|
+
!T.let(req_metadata[:is_transitive], T::Boolean)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# report back the highest version that all of these dependencies can be updated to
|
196
|
+
# this will ensure that we get a chance to update all relevant dependencies
|
197
|
+
max_updatable_version = updated_reqs.filter_map do |r|
|
198
|
+
v = T.let(r.fetch(:requirement, nil), T.nilable(String))
|
199
|
+
next unless v
|
200
|
+
|
201
|
+
Dependabot::Nuget::Version.new(v)
|
202
|
+
end.max
|
203
|
+
next unless max_updatable_version
|
204
|
+
|
205
|
+
previous_version = T.let(dep.requirements.first&.fetch(:requirement, nil), T.nilable(String))
|
175
206
|
Dependency.new(
|
176
207
|
name: dep.name,
|
177
|
-
version:
|
208
|
+
version: max_updatable_version.to_s,
|
178
209
|
requirements: updated_reqs,
|
179
|
-
previous_version:
|
180
|
-
previous_requirements:
|
210
|
+
previous_version: previous_version,
|
211
|
+
previous_requirements: reqs,
|
181
212
|
package_manager: dep.package_manager,
|
182
|
-
metadata:
|
213
|
+
metadata: dep_metadata
|
183
214
|
)
|
184
215
|
end
|
185
216
|
end
|
217
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
218
|
+
# rubocop:enable Metrics/MethodLength
|
219
|
+
# rubocop:enable Metrics/BlockLength
|
220
|
+
# rubocop:enable Metrics/AbcSize
|
186
221
|
|
187
222
|
sig { returns(T::Array[Dependabot::Nuget::NativeDependencyDetails]) }
|
188
223
|
def updated_dependency_details
|
@@ -37,10 +37,11 @@ module Dependabot
|
|
37
37
|
def updated_requirements
|
38
38
|
return requirements unless latest_version
|
39
39
|
|
40
|
-
# NOTE: Order is important here. The FileUpdater needs the updated
|
41
|
-
# requirement at index `i` to correspond to the previous requirement
|
42
|
-
# at the same index.
|
43
40
|
requirements.map do |req|
|
41
|
+
req[:metadata] ||= {}
|
42
|
+
req[:metadata][:is_transitive] = false
|
43
|
+
req[:metadata][:previous_requirement] = req[:requirement]
|
44
|
+
|
44
45
|
next req if req.fetch(:requirement).nil?
|
45
46
|
next req if req.fetch(:requirement).include?(",")
|
46
47
|
|
@@ -56,7 +57,6 @@ module Dependabot
|
|
56
57
|
latest_version.to_s
|
57
58
|
)
|
58
59
|
end
|
59
|
-
|
60
60
|
next req if new_req == req.fetch(:requirement)
|
61
61
|
|
62
62
|
req.merge(requirement: new_req, source: updated_source)
|
@@ -166,7 +166,8 @@ module Dependabot
|
|
166
166
|
requirements: updated_requirements,
|
167
167
|
previous_version: dependency.version,
|
168
168
|
previous_requirements: dependency.requirements,
|
169
|
-
package_manager: dependency.package_manager
|
169
|
+
package_manager: dependency.package_manager,
|
170
|
+
metadata: { is_vulnerable: vulnerable? }
|
170
171
|
)
|
171
172
|
updated_dependencies = [updated_dependency]
|
172
173
|
updated_dependencies += DependencyFinder.new(
|
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.
|
4
|
+
version: 0.272.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dependabot
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-08-
|
11
|
+
date: 2024-08-22 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.
|
19
|
+
version: 0.272.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.
|
26
|
+
version: 0.272.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rubyzip
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -463,7 +463,7 @@ licenses:
|
|
463
463
|
- MIT
|
464
464
|
metadata:
|
465
465
|
bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
|
466
|
-
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.
|
466
|
+
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.272.0
|
467
467
|
post_install_message:
|
468
468
|
rdoc_options: []
|
469
469
|
require_paths:
|