Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix incorrect (de-) serialization of tools #568

Merged
merged 2 commits into from
Dec 10, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -45,7 +45,7 @@ public ToolInformation deserialize(JsonParser jsonParser, DeserializationContext
return parseToolInformation(node);
}

private ToolInformation parseToolInformation(JsonNode toolsNode) throws IOException {
private ToolInformation parseToolInformation(JsonNode toolsNode) {
ToolInformation toolInformation = new ToolInformation();
if (toolsNode.has("components")) {
parseComponents(toolsNode.get("components"), toolInformation);
@@ -58,24 +58,42 @@ private ToolInformation parseToolInformation(JsonNode toolsNode) throws IOExcept

private void parseComponents(JsonNode componentsNode, ToolInformation toolInformation) {
if (componentsNode != null) {
// Case JSON input where "components" is an array
if (componentsNode.isArray()) {
List<Component> components = mapper.convertValue(componentsNode, new TypeReference<List<Component>>() {});
toolInformation.setComponents(components);
} else if (componentsNode.isObject()) {
Component component = mapper.convertValue(componentsNode, Component.class);
toolInformation.setComponents(Collections.singletonList(component));
}
// Case XML-like input where "components" contains "component"
else if (componentsNode.isObject() && componentsNode.has("component")) {
JsonNode componentNode = componentsNode.get("component");
if (componentNode.isArray()) {
List<Component> components = mapper.convertValue(componentNode, new TypeReference<List<Component>>() {});
toolInformation.setComponents(components);
} else if (componentNode.isObject()) {
Component component = mapper.convertValue(componentNode, Component.class);
toolInformation.setComponents(Collections.singletonList(component));
}
}
}
}

private void parseServices(JsonNode servicesNode, ToolInformation toolInformation) {
if (servicesNode != null) {
// Case JSON input where "services" is an array
if (servicesNode.isArray()) {
List<Service> services = mapper.convertValue(servicesNode, new TypeReference<List<Service>>() {});
toolInformation.setServices(services);
} else if (servicesNode.isObject()) {
Service service = mapper.convertValue(servicesNode, Service.class);
toolInformation.setServices(Collections.singletonList(service));
}
// Case XML-like input where "services" contains "component"
else if (servicesNode.isObject() && servicesNode.has("service")) {
JsonNode serviceNode = servicesNode.get("service");
if (serviceNode.isArray()) {
List<Service> services = mapper.convertValue(servicesNode, new TypeReference<List<Service>>() {});
toolInformation.setServices(services);
} else if (serviceNode.isObject()) {
Service service = mapper.convertValue(servicesNode, Service.class);
toolInformation.setServices(Collections.singletonList(service));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@
import org.cyclonedx.Version;
import org.cyclonedx.model.Metadata;
import org.cyclonedx.model.Property;
import org.cyclonedx.model.component.evidence.Occurrence;
import org.cyclonedx.model.metadata.ToolInformation;

import static org.cyclonedx.util.serializer.SerializerUtils.shouldSerializeField;
@@ -164,15 +165,14 @@ private <T> void writeArrayFieldJSON(JsonGenerator jsonGenerator, String fieldNa
}

private <T> void writeArrayFieldXML(List<T> items, ToXmlGenerator xmlGenerator, String fieldName) throws IOException {
if (items != null) {
if (CollectionUtils.isNotEmpty(items)) {
xmlGenerator.writeFieldName(fieldName + "s");
xmlGenerator.writeStartArray();
xmlGenerator.writeStartObject();
for (T item : items) {
xmlGenerator.writeStartObject();
xmlGenerator.writeObjectField(fieldName, item);
xmlGenerator.writeEndObject();
xmlGenerator.writeFieldName(fieldName);
xmlGenerator.writeObject(item);
}
xmlGenerator.writeEndArray();
xmlGenerator.writeEndObject();
}
}

26 changes: 26 additions & 0 deletions src/test/java/org/cyclonedx/BomJsonGeneratorTest.java
Original file line number Diff line number Diff line change
@@ -545,6 +545,32 @@ public void testIssue408Regression_xmlToJson_externalReferenceBom() throws Excep
assertTrue(parser.isValid(loadedFile, version));
}

@Test
public void testIssue562() throws Exception {
Version version = Version.VERSION_16;
Bom bom = createCommonJsonBom("/regression/issue562.json");


BomJsonGenerator generator = BomGeneratorFactory.createJson(version, bom);
File loadedFile = writeToFile(generator.toJsonString());

JsonParser parser = new JsonParser();
assertTrue(parser.isValid(loadedFile, version));
}

@Test
public void testIssue492() throws Exception {
Version version = Version.VERSION_14;
Bom bom = createCommonJsonBom("/regression/issue492.json");


BomJsonGenerator generator = BomGeneratorFactory.createJson(version, bom);
File loadedFile = writeToFile(generator.toJsonString());

JsonParser parser = new JsonParser();
assertTrue(parser.isValid(loadedFile, version));
}

private void assertExternalReferenceInfo(Bom bom) {
assertEquals(3, bom.getExternalReferences().size());
assertEquals(3, bom.getComponents().get(0).getExternalReferences().size());
64 changes: 62 additions & 2 deletions src/test/java/org/cyclonedx/BomXmlGeneratorTest.java
Original file line number Diff line number Diff line change
@@ -32,8 +32,10 @@
import org.cyclonedx.model.License;
import org.cyclonedx.model.LicenseChoice;
import org.cyclonedx.model.Metadata;
import org.cyclonedx.model.OrganizationalContact;
import org.cyclonedx.model.Service;
import org.cyclonedx.model.license.Expression;
import org.cyclonedx.model.metadata.ToolInformation;
import org.cyclonedx.parsers.JsonParser;
import org.cyclonedx.parsers.XmlParser;
import org.junit.jupiter.api.AfterEach;
@@ -50,7 +52,9 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Stream;
import java.util.Objects;

@@ -672,11 +676,67 @@ public void testXxeProtection() {

@Test
public void testIssue408Regression_extensibleTypes() throws Exception {
Bom bom = new Bom();
bom.setSerialNumber("urn:uuid:" + UUID.randomUUID());

Metadata meta = new Metadata();

// ToolInformation test
Component tool1 = new Component();
tool1.setType(Component.Type.APPLICATION);
tool1.setName("TOOL 1");
tool1.setVersion("v1");

Component tool2 = new Component();
tool2.setType(Component.Type.APPLICATION);
tool2.setName("TOOL 2");
tool2.setVersion("v2");

ToolInformation tools = new ToolInformation();
List<Component> components = new LinkedList<>();
components.add(tool1);
components.add(tool2);
tools.setComponents(components);
meta.setToolChoice(tools);

// Author test
OrganizationalContact auth1 = new OrganizationalContact();
auth1.setName("Author 1");
meta.addAuthor(auth1);

OrganizationalContact auth2 = new OrganizationalContact();
auth2.setName("Author 2");
meta.addAuthor(auth2);

bom.setMetadata(meta);

BomXmlGenerator generator = BomGeneratorFactory.createXml(Version.VERSION_16, bom);
File loadedFile = writeToFile(generator.toXmlString());

XmlParser parser = new XmlParser();
assertTrue(parser.isValid(loadedFile, Version.VERSION_16));
}

@Test
public void testIssue562() throws Exception {
Version version = Version.VERSION_15;
Bom bom = createCommonBomXml("/regression/issue408-extensible-type.xml");
addExtensibleTypes(bom);
Bom bom = createCommonBomXml("/regression/issue562.xml");

BomXmlGenerator generator = BomGeneratorFactory.createXml(version, bom);

File loadedFile = writeToFile(generator.toXmlString());

XmlParser parser = new XmlParser();
assertTrue(parser.isValid(loadedFile, version));
}

@Test
public void testIssue492() throws Exception {
Version version = Version.VERSION_15;
Bom bom = createCommonBomXml("/regression/issue492.xml");

BomXmlGenerator generator = BomGeneratorFactory.createXml(version, bom);

File loadedFile = writeToFile(generator.toXmlString());

XmlParser parser = new XmlParser();
13 changes: 13 additions & 0 deletions src/test/java/org/cyclonedx/parsers/JsonParserTest.java
Original file line number Diff line number Diff line change
@@ -568,4 +568,17 @@ public void schema16_cbom() throws Exception {
.containsAll(Arrays.asList(CryptoFunction.KEYGEN, CryptoFunction.ENCRYPT, CryptoFunction.DECRYPT,
CryptoFunction.TAG)));
}

@Test
public void testIssue562Regression() throws Exception {
final Bom bom = getJsonBom("regression/issue562.json");
assertEquals(2, bom.getMetadata().getToolChoice().getComponents().size());
assertEquals(2, bom.getMetadata().getAuthors().size());
}

@Test
public void testIssue492Regression() throws Exception {
final Bom bom = getJsonBom("regression/issue492.json");
assertEquals(2, bom.getMetadata().getTools().size());
}
}
13 changes: 13 additions & 0 deletions src/test/java/org/cyclonedx/parsers/XmlParserTest.java
Original file line number Diff line number Diff line change
@@ -713,4 +713,17 @@ public void schema16_cbom() throws Exception {
.containsAll(Arrays.asList(CryptoFunction.KEYGEN, CryptoFunction.ENCRYPT, CryptoFunction.DECRYPT,
CryptoFunction.TAG)));
}

@Test
public void testIssue562Regression() throws Exception {
final Bom bom = getXmlBom("regression/issue562.xml");
assertEquals(2, bom.getMetadata().getToolChoice().getComponents().size());
assertEquals(2, bom.getMetadata().getAuthors().size());
}

@Test
public void testIssue492Regression() throws Exception {
final Bom bom = getXmlBom("regression/issue492.xml");
assertEquals(2, bom.getMetadata().getTools().size());
}
}
17 changes: 17 additions & 0 deletions src/test/resources/regression/issue492.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"bomFormat":"CycloneDX",
"specVersion":"1.4",
"serialNumber":"urn:uuid:0c81ff2e-d64e-4897-bfa4-2f0f7d8ab767",
"version" : 1,
"metadata" : {
"timestamp":"2024-12-09T21:56:45Z",
"tools": [
{
"name": "tool-a"
},
{
"name": "tool-b"
}
]
}
}
18 changes: 18 additions & 0 deletions src/test/resources/regression/issue492.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:0c81ff2e-d64e-4897-bfa4-2f0f7d8ab767" version="1">
<metadata>
<timestamp>2024-12-09T21:56:45Z</timestamp>
<tools>
<tool>
<vendor>corp A</vendor>
<name>tool A</name>
<version>1-A</version>
</tool>
<tool>
<vendor>corp B</vendor>
<name>tool B</name>
<version>1-B</version>
</tool>
</tools>
</metadata>
</bom>
25 changes: 25 additions & 0 deletions src/test/resources/regression/issue562.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"bomFormat":"CycloneDX",
"specVersion":"1.6",
"serialNumber":"urn:uuid:0c81ff2e-d64e-4897-bfa4-2f0f7d8ab767",
"version" : 1,
"metadata" : {
"timestamp":"2024-12-09T21:56:45Z",
"tools" : {
"components" : [ {
"type":"application",
"name":"TOOL 1",
"version":"v1"
}, {
"type":"application",
"name":"TOOL 2",
"version":"v2"
} ]
},
"authors" : [ {
"name":"Author 1"
}, {
"name":"Author 2"
} ]
}
}
26 changes: 26 additions & 0 deletions src/test/resources/regression/issue562.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.6" serialNumber="urn:uuid:0c81ff2e-d64e-4897-bfa4-2f0f7d8ab767" version="1">
<metadata>
<timestamp>2024-12-09T21:56:45Z</timestamp>
<tools>
<components>
<component type="application">
<name>TOOL 1</name>
<version>v1</version>
</component>
<component type="application">
<name>TOOL 2</name>
<version>v2</version>
</component>
</components>
</tools>
<authors>
<author>
<name>Author 1</name>
</author>
<author>
<name>Author 2</name>
</author>
</authors>
</metadata>
</bom>
Loading