Skip to content

Commit

Permalink
Merge pull request #414 from DependencyTrack/fix-cdx-validation
Browse files Browse the repository at this point in the history
Fix BOM validation failing for spec versions lower than 1.5
  • Loading branch information
VithikaS authored Nov 1, 2023
2 parents 08220b2 + 572ed9a commit a05f0d3
Show file tree
Hide file tree
Showing 12 changed files with 177 additions and 3 deletions.
22 changes: 21 additions & 1 deletion src/main/java/org/dependencytrack/resources/v1/BomResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@
import org.apache.commons.lang3.StringUtils;
import org.cyclonedx.BomParserFactory;
import org.cyclonedx.CycloneDxMediaType;
import org.cyclonedx.CycloneDxSchema;
import org.cyclonedx.exception.GeneratorException;
import org.cyclonedx.exception.ParseException;
import org.cyclonedx.model.Bom;
import org.cyclonedx.parsers.Parser;
import org.dependencytrack.auth.Permissions;
import org.dependencytrack.event.BomUploadEvent;
Expand Down Expand Up @@ -78,6 +80,8 @@
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

import static org.apache.commons.lang3.StringUtils.trimToEmpty;

/**
* JAX-RS resources for processing bill-of-material (bom) documents.
*
Expand Down Expand Up @@ -434,7 +438,23 @@ private File validateAndStoreBom(final byte[] bomBytes, final Project project) t
final List<ParseException> validationErrors;
try {
final Parser parser = BomParserFactory.createParser(bomBytes);
validationErrors = parser.validate(bomBytes);

// Have to parse the entire BOM in order to determine the spec version :c
final Bom bom = parser.parse(bomBytes);
final CycloneDxSchema.Version specVersion = switch (trimToEmpty(bom.getSpecVersion())) {
case "1.0" -> CycloneDxSchema.Version.VERSION_10;
case "1.1" -> CycloneDxSchema.Version.VERSION_11;
case "1.2" -> CycloneDxSchema.Version.VERSION_12;
case "1.3" -> CycloneDxSchema.Version.VERSION_13;
case "1.4" -> CycloneDxSchema.Version.VERSION_14;
case "1.5" -> CycloneDxSchema.Version.VERSION_15;
// Default to latest schema version when the BOM does not specify
// one explicitly. Validation will fail anyway though, as specifying
// a schema version is mandatory across all spec versions.
default -> CycloneDxSchema.VERSION_LATEST;
};

validationErrors = parser.validate(bomBytes, specVersion);
} catch (JsonParseException e) {
throw new IllegalArgumentException("The uploaded file contains malformed JSON or XML", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import alpine.common.util.UuidUtil;
import alpine.server.filters.ApiFilter;
import alpine.server.filters.AuthenticationFilter;
import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpStatus;
Expand All @@ -44,12 +46,23 @@
import org.glassfish.jersey.test.ServletDeploymentContext;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;

import javax.json.JsonObject;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
Expand All @@ -64,6 +77,7 @@
import static org.dependencytrack.model.WorkflowStep.BOM_PROCESSING;
import static org.hamcrest.CoreMatchers.equalTo;

@RunWith(JUnitParamsRunner.class)
public class BomResourceTest extends ResourceTest {

@Override
Expand Down Expand Up @@ -750,7 +764,7 @@ public void uploadInvalidCycloneDxBomTest() {
.header(X_API_KEY, apiKey)
.put(Entity.entity(request, MediaType.APPLICATION_JSON));
Assert.assertEquals(400, response.getStatus(), 0);
Assert.assertEquals("The uploaded CycloneDX BOM is invalid: $.components[0].type: is missing but it is required; $.components[0].name: is missing but it is required", getPlainTextBody(response));
Assert.assertEquals("The uploaded CycloneDX BOM is invalid: $.version: is missing but it is required; $.components[0].type: is missing but it is required; $.components[0].name: is missing but it is required", getPlainTextBody(response));
}

@Test
Expand All @@ -764,7 +778,7 @@ public void uploadInvalidFormatBomTest() throws Exception {
.header(X_API_KEY, apiKey)
.put(Entity.entity(request, MediaType.APPLICATION_JSON));
Assert.assertEquals(400, response.getStatus(), 0);
Assert.assertEquals("The uploaded file contains malformed JSON or XML", getPlainTextBody(response));
Assert.assertEquals("Unable to parse BOM from byte array", getPlainTextBody(response));
}

@Test
Expand Down Expand Up @@ -899,6 +913,39 @@ public void uploadBomInvalidParentTest() throws Exception {
Assert.assertEquals("The parent component could not be found.", body);
}

@SuppressWarnings("unused")
private Object[] uploadBomSchemaValidationTestParameters() throws Exception {
final PathMatcher pathMatcherJson = FileSystems.getDefault().getPathMatcher("glob:**/bom-schema*.json");
final PathMatcher pathMatcherXml = FileSystems.getDefault().getPathMatcher("glob:**/bom-schema*.xml");
final var bomFilePaths = new ArrayList<Path>();

Files.walkFileTree(Paths.get("./src/test/resources"), new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
if (pathMatcherJson.matches(file) || pathMatcherXml.matches(file)) {
bomFilePaths.add(file);
}
return FileVisitResult.CONTINUE;
}
});

return bomFilePaths.stream().sorted().toArray();
}

@Test
@Parameters(method = "uploadBomSchemaValidationTestParameters")
public void uploadBomSchemaValidationTest(final Path filePath) throws Exception {
initializeWithPermissions(Permissions.BOM_UPLOAD);
Project project = qm.createProject("Acme Example", null, "1.0", null, null, null, true, false);
File file = filePath.toFile();
String bomString = Base64.getEncoder().encodeToString(FileUtils.readFileToByteArray(file));
BomSubmitRequest request = new BomSubmitRequest(project.getUuid().toString(), null, null, false, bomString);
Response response = target(V1_BOM).request()
.header(X_API_KEY, apiKey)
.put(Entity.entity(request, MediaType.APPLICATION_JSON));
assertThat(response.getStatus()).isEqualTo(200);
}

@Test
public void isTokenBeingProcessedTrueTest() {
UUID uuid = UUID.randomUUID();
Expand Down
10 changes: 10 additions & 0 deletions src/test/resources/unit/bom-schema1.0.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0"?>
<bom version="1" xmlns="http://cyclonedx.org/schema/bom/1.0">
<components>
<component type="library">
<name>acme-lib</name>
<version>1.0.0</version>
<modified>false</modified>
</component>
</components>
</bom>
9 changes: 9 additions & 0 deletions src/test/resources/unit/bom-schema1.1.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<bom serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79" version="1" xmlns="http://cyclonedx.org/schema/bom/1.1">
<components>
<component type="library">
<name>acme-lib</name>
<version>1.0.0</version>
</component>
</components>
</bom>
13 changes: 13 additions & 0 deletions src/test/resources/unit/bom-schema1.2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "http://cyclonedx.org/schema/bom-1.2a.schema.json",
"bomFormat": "CycloneDX",
"specVersion": "1.2",
"version": 1,
"components": [
{
"type": "library",
"name": "acme-lib",
"version": "1.0.0"
}
]
}
9 changes: 9 additions & 0 deletions src/test/resources/unit/bom-schema1.2.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<bom serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79" version="1" xmlns="http://cyclonedx.org/schema/bom/1.2">
<components>
<component type="library">
<name>acme-lib</name>
<version>1.0.0</version>
</component>
</components>
</bom>
13 changes: 13 additions & 0 deletions src/test/resources/unit/bom-schema1.3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "http://cyclonedx.org/schema/bom-1.3.schema.json",
"bomFormat": "CycloneDX",
"specVersion": "1.3",
"version": 1,
"components": [
{
"type": "library",
"name": "acme-lib",
"version": "1.0.0"
}
]
}
9 changes: 9 additions & 0 deletions src/test/resources/unit/bom-schema1.3.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<bom serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79" version="1" xmlns="http://cyclonedx.org/schema/bom/1.3">
<components>
<component type="library">
<name>acme-lib</name>
<version>1.0.0</version>
</component>
</components>
</bom>
13 changes: 13 additions & 0 deletions src/test/resources/unit/bom-schema1.4.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json",
"bomFormat": "CycloneDX",
"specVersion": "1.4",
"version": 1,
"components": [
{
"type": "library",
"name": "acme-lib",
"version": "1.0.0"
}
]
}
9 changes: 9 additions & 0 deletions src/test/resources/unit/bom-schema1.4.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<bom serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79" version="1" xmlns="http://cyclonedx.org/schema/bom/1.4">
<components>
<component type="library">
<name>acme-lib</name>
<version>1.0.0</version>
</component>
</components>
</bom>
13 changes: 13 additions & 0 deletions src/test/resources/unit/bom-schema1.5.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"version": 1,
"components": [
{
"type": "library",
"name": "acme-lib",
"version": "1.0.0"
}
]
}
9 changes: 9 additions & 0 deletions src/test/resources/unit/bom-schema1.5.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<bom serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79" version="1" xmlns="http://cyclonedx.org/schema/bom/1.5">
<components>
<component type="library">
<name>acme-lib</name>
<version>1.0.0</version>
</component>
</components>
</bom>

0 comments on commit a05f0d3

Please sign in to comment.