Skip to content
This repository was archived by the owner on Feb 17, 2025. It is now read-only.

Commit f60c867

Browse files
authored
feat: Add v2 support for maven_install.json (#6528)
2 parents 08a595b + a6a8f21 commit f60c867

File tree

4 files changed

+2481
-19
lines changed

4 files changed

+2481
-19
lines changed

core/src/main/java/org/owasp/dependencycheck/analyzer/PinnedMavenInstallAnalyzer.java

Lines changed: 134 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@
1919

2020
import com.fasterxml.jackson.annotation.JsonProperty;
2121
import com.fasterxml.jackson.databind.DeserializationFeature;
22+
import com.fasterxml.jackson.databind.JsonNode;
2223
import com.fasterxml.jackson.databind.ObjectMapper;
2324
import com.fasterxml.jackson.databind.ObjectReader;
2425
import com.github.packageurl.MalformedPackageURLException;
2526
import com.github.packageurl.PackageURL;
2627
import com.github.packageurl.PackageURLBuilder;
28+
import java.util.Map;
29+
import java.util.stream.Collectors;
2730
import org.owasp.dependencycheck.Engine;
2831
import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
2932
import org.owasp.dependencycheck.data.nvd.ecosystem.Ecosystem;
@@ -118,26 +121,48 @@ protected void analyzeDependency(Dependency dependency, Engine engine) throws An
118121
}
119122

120123
final DependencyTree tree;
124+
List<MavenDependency> deps;
121125
try {
122-
final InstallFile installFile = INSTALL_FILE_READER.readValue(dependencyFile);
123-
tree = installFile.getDependencyTree();
124-
} catch (IOException e) {
125-
return;
126-
}
126+
JsonNode jsonNode = MAPPER.readTree(dependencyFile);
127+
JsonNode v2Version = jsonNode.path("version");
128+
JsonNode v010Version = jsonNode.path("dependency_tree").path("version");
129+
130+
if (v2Version.isTextual()) {
131+
final InstallFileV2 installFile = INSTALL_FILE_V2_READER.readValue(dependencyFile);
132+
if (!Objects.equals(installFile.getAutogeneratedSentinel(), "THERE_IS_NO_DATA_ONLY_ZUUL")) {
133+
return;
134+
}
135+
if (!Objects.equals(installFile.getVersion(), "2")) {
136+
LOGGER.warn("Unsupported pinned maven_install.json version {}. Continuing optimistically.", installFile.getVersion());
137+
}
138+
deps = installFile.getArtifacts().entrySet().stream().map(entry -> new MavenDependency(
139+
entry.getKey() + ":" + entry.getValue().getVersion()
140+
)).collect(Collectors.toList());
141+
} else if (v010Version.isTextual()) {
142+
final InstallFile installFile = INSTALL_FILE_READER.readValue(dependencyFile);
143+
tree = installFile.getDependencyTree();
144+
if (tree == null) {
145+
return;
146+
} else if (!Objects.equals(tree.getAutogeneratedSentinel(), "THERE_IS_NO_DATA_ONLY_ZUUL")) {
147+
return;
148+
}
149+
if (!Objects.equals(tree.getVersion(), "0.1.0")) {
150+
LOGGER.warn("Unsupported pinned maven_install.json version {}. Continuing optimistically.", tree.getVersion());
151+
}
152+
deps = tree.getDependencies();
153+
} else {
154+
LOGGER.warn("No pinned maven_install.json version found. Cannot Parse");
155+
return;
156+
}
127157

128-
if (tree == null) {
129-
return;
130-
} else if (!Objects.equals(tree.getAutogeneratedSentinel(), "THERE_IS_NO_DATA_ONLY_ZUUL")) {
158+
159+
} catch (IOException e) {
160+
System.out.println("e");
131161
return;
132162
}
133163

134164
engine.removeDependency(dependency);
135165

136-
if (!Objects.equals(tree.getVersion(), "0.1.0")) {
137-
LOGGER.warn("Unsupported pinned maven_install.json version {}. Continuing optimistically.", tree.getVersion());
138-
}
139-
140-
List<MavenDependency> deps = tree.getDependencies();
141166
if (deps == null) {
142167
deps = Collections.emptyList();
143168
}
@@ -300,7 +325,12 @@ public String getVersion() {
300325
* {@code .dependency_tree.dependencies}.
301326
*/
302327
private static class MavenDependency {
328+
public MavenDependency(String coord) {
329+
this.coord = coord;
330+
}
303331

332+
public MavenDependency() {
333+
}
304334
/**
305335
* The standard Maven coordinate string
306336
* {@code group:artifact[:optional classifier][:optional packaging]:version}.
@@ -322,10 +352,98 @@ public String getCoord() {
322352
* A reusable reader for {@link InstallFile}.
323353
*/
324354
private static final ObjectReader INSTALL_FILE_READER;
355+
private static final ObjectReader INSTALL_FILE_V2_READER;
356+
private static final ObjectMapper MAPPER;
325357

326358
static {
327-
final ObjectMapper mapper = new ObjectMapper();
328-
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
329-
INSTALL_FILE_READER = mapper.readerFor(InstallFile.class);
359+
MAPPER = new ObjectMapper();
360+
MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
361+
INSTALL_FILE_READER = MAPPER.readerFor(InstallFile.class);
362+
INSTALL_FILE_V2_READER = MAPPER.readerFor(InstallFileV2.class);
330363
}
364+
365+
/**
366+
* Represents the entire pinned Maven dependency set in an install.json
367+
* file.
368+
*
369+
* <p>
370+
* At the time of writing, the latest version is 2, and the dependencies
371+
* are stored in {@code .artifacts}.
372+
*
373+
* <p>
374+
* The top-level keys we care about are {@code .artifacts}. {@code .version}.
375+
*/
376+
private static class InstallFileV2 {
377+
378+
/**
379+
* The file format version.
380+
*/
381+
@JsonProperty("version")
382+
private String version;
383+
384+
/**
385+
* A list of Maven dependencies made available. Note that this map is transitively closed and
386+
* pinned to a specific version of each artifact.
387+
* <p>
388+
* The key is the Maven coordinate string, less the version
389+
* {@code group:artifact[:optional classifier][:optional packaging]}.
390+
* <p>
391+
* The value contains the version of the artifact.
392+
*/
393+
@JsonProperty("artifacts")
394+
private Map<String, Artifactv2> artifacts;
395+
396+
/**
397+
* A sentinel value placed in the file to indicate that it is an auto-generated pinned maven
398+
* install file.
399+
*/
400+
@JsonProperty("__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY")
401+
private String autogeneratedSentinel;
402+
403+
/**
404+
* Returns artifacts.
405+
*
406+
* @return artifacts
407+
*/
408+
public Map<String, Artifactv2> getArtifacts() {
409+
return artifacts;
410+
}
411+
412+
/**
413+
* Returns version.
414+
*
415+
* @return version
416+
*/
417+
public String getVersion() {
418+
return version;
419+
}
420+
421+
/**
422+
* Returns autogeneratedSentinel.
423+
*
424+
* @return autogeneratedSentinel
425+
*/
426+
public String getAutogeneratedSentinel() {
427+
return autogeneratedSentinel;
428+
}
429+
}
430+
private static class Artifactv2 {
431+
432+
/**
433+
* The version of the artifact.
434+
*/
435+
@JsonProperty("version")
436+
private String version;
437+
438+
/**
439+
* Returns the value of version.
440+
*
441+
* @return the value of version
442+
*/
443+
public String getVersion() {
444+
return version;
445+
}
446+
}
447+
448+
331449
}

core/src/test/java/org/owasp/dependencycheck/analyzer/PinnedMavenInstallAnalyzerTest.java

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ public void testGetName() {
8080
public void testSupportsFiles() {
8181
assertTrue(analyzer.accept(new File("install_maven.json")));
8282
assertTrue(analyzer.accept(new File("maven_install.json")));
83+
assertTrue(analyzer.accept(new File("maven_install_v010.json")));
84+
assertTrue(analyzer.accept(new File("maven_install_v2.json")));
8385
assertTrue(analyzer.accept(new File("rules_jvm_external_install.json")));
8486
assertTrue(analyzer.accept(new File("pinned_install_gplonly.json")));
8587
assertFalse("should not accept Cloudflare install.json", analyzer.accept(new File("install.json")));
@@ -89,12 +91,12 @@ public void testSupportsFiles() {
8991
}
9092

9193
/**
92-
* Tests that the analyzer correctly pulls dependencies out of a pinned {@code maven_install.json}.
94+
* Tests that the analyzer correctly pulls dependencies out of a pinned v0.1.0 {@code maven_install.json}.
9395
*/
9496
@Test
95-
public void testAnalyzePinnedInstallJson() throws Exception {
97+
public void testAnalyzePinnedInstallJsonV010() throws Exception {
9698
try (Engine engine = new Engine(getSettings())) {
97-
final Dependency result = new Dependency(BaseTest.getResourceAsFile(this, "maven_install.json"));
99+
final Dependency result = new Dependency(BaseTest.getResourceAsFile(this, "maven_install_v010.json"));
98100
engine.addDependency(result);
99101
analyzer.analyze(result, engine);
100102
assertFalse(ArrayUtils.contains(engine.getDependencies(), result));
@@ -111,6 +113,29 @@ public void testAnalyzePinnedInstallJson() throws Exception {
111113
}
112114
}
113115

116+
/**
117+
* Tests that the analyzer correctly pulls dependencies out of a pinned v2 {@code maven_install.json}.
118+
*/
119+
@Test
120+
public void testAnalyzePinnedInstallJsonV2() throws Exception {
121+
try (Engine engine = new Engine(getSettings())) {
122+
final Dependency result = new Dependency(BaseTest.getResourceAsFile(this, "maven_install_v2.json"));
123+
engine.addDependency(result);
124+
analyzer.analyze(result, engine);
125+
assertFalse(ArrayUtils.contains(engine.getDependencies(), result));
126+
assertEquals(113, engine.getDependencies().length);
127+
boolean found = false;
128+
for (Dependency d : engine.getDependencies()) {
129+
if ("io.grpc:grpc-protobuf".equals(d.getName())) {
130+
found = true;
131+
assertEquals("1.48.1", d.getVersion());
132+
assertEquals(Ecosystem.JAVA, d.getEcosystem());
133+
}
134+
}
135+
assertTrue("Expected to find com.google.errorprone:error_prone_annotations:2.3.4", found);
136+
}
137+
}
138+
114139
/**
115140
* Tests that the analyzer ignores a Cloudflare-style {@code install.json}.
116141
*/

0 commit comments

Comments
 (0)