Repository: CycloneDX/cyclonedx-core-java (java)

Issue:
Unable to deserialize bom with pedigree
Dependency-Track user attempting to upload a BOM with pedigree, was unable to do so. The sample BOM was pulled directly from the CycloneDX website and is a valid example. The following exception was thrown:

```
org.cyclonedx.exception.ParseException: com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `org.cyclonedx.model.Component$Type` from String "enhancement": not one of the values accepted for Enum class: [application, container, device, library, file, framework, operating-system, firmware]
 at [Source: (byte[])"<?xml version="1.0" encoding="UTF-8"?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.3"
     serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"
     version="1">
    <components>
        <component type="library">
            <group>com.acme</group>
            <name>sample-library</name>
            <version>1.0.0</version>
            <pedigree>
                <ancestors>
                    <!-- The component from which com.acme's modified
                    version of sample-libr"[truncated 4119 bytes]; line: 47, column: 29] (through reference chain: org.cyclonedx.model.Bom["components"]->java.util.ArrayList[1]->org.cyclonedx.model.Component["type"])
	at org.cyclonedx.parsers.XmlParser.parse(XmlParser.java:89)
	at org.dependencytrack.tasks.BomUploadProcessingTask.inform(BomUploadProcessingTask.java:89)
	at alpine.event.framework.BaseEventService.lambda$publish$0(BaseEventService.java:99)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `org.cyclonedx.model.Component$Type` from String "enhancement": not one of the values accepted for Enum class: [application, container, device, library, file, framework, operating-system, firmware]
 at [Source: (byte[])"<?xml version="1.0" encoding="UTF-8"?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.3"
     serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"
     version="1">
    <components>
        <component type="library">
            <group>com.acme</group>
            <name>sample-library</name>
            <version>1.0.0</version>
            <pedigree>
                <ancestors>
                    <!-- The component from which com.acme's modified
                    version of sample-libr"[truncated 4119 bytes]; line: 47, column: 29] (through reference chain: org.cyclonedx.model.Bom["components"]->java.util.ArrayList[1]->org.cyclonedx.model.Component["type"])
	at com.fasterxml.jackson.databind.exc.InvalidFormatException.from(InvalidFormatException.java:67)
	at com.fasterxml.jackson.databind.DeserializationContext.weirdStringException(DeserializationContext.java:1851)
	at com.fasterxml.jackson.databind.DeserializationContext.handleWeirdStringValue(DeserializationContext.java:1079)
	at com.fasterxml.jackson.databind.deser.std.EnumDeserializer._deserializeAltString(EnumDeserializer.java:327)
	at com.fasterxml.jackson.databind.deser.std.EnumDeserializer._fromString(EnumDeserializer.java:214)
	at com.fasterxml.jackson.databind.deser.std.EnumDeserializer.deserialize(EnumDeserializer.java:188)
	at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:324)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:225)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:197)
	at com.fasterxml.jackson.dataformat.xml.deser.WrapperHandlingDeserializer.deserialize(WrapperHandlingDeserializer.java:121)
	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer._deserializeFromArray(CollectionDeserializer.java:347)
	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:244)
	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:28)
	at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:324)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:187)
	at com.fasterxml.jackson.dataformat.xml.deser.WrapperHandlingDeserializer.deserialize(WrapperHandlingDeserializer.java:121)
	at com.fasterxml.jackson.dataformat.xml.deser.XmlDeserializationContext.readRootValue(XmlDeserializationContext.java:91)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4593)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3609)
	at org.cyclonedx.parsers.XmlParser.parse(XmlParser.java:87)
```


Need to rework unit tests to account for all valid examples in the specification repo. This library should be able to deserialize every single valid example without errors.

Pull request:
Implement Patches for Pedigree
This PR adds patches in, and adds a test to ensures they are parsed appropriately (test is fairly bare bones, but can be improved).

This uses the example XML from `cyclonedx.org` for the unit test, and honestly, that's great :)

Fixes #104 

Patch:
diff --git a/src/main/java/org/cyclonedx/model/Diff.java b/src/main/java/org/cyclonedx/model/Diff.java
new file mode 100644
index 000000000..0c13860bb
--- /dev/null
+++ b/src/main/java/org/cyclonedx/model/Diff.java
@@ -0,0 +1,41 @@
+/*
+ * This file is part of CycloneDX Core (Java).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) Steve Springett. All Rights Reserved.
+ */
+package org.cyclonedx.model;
+
+public class Diff
+{
+  private AttachmentText text;
+  private String url;
+
+  public AttachmentText getText() {
+    return text;
+  }
+
+  public void setText(final AttachmentText text) {
+    this.text = text;
+  }
+
+  public String getUrl() {
+    return url;
+  }
+
+  public void setUrl(final String url) {
+    this.url = url;
+  }
+}
diff --git a/src/main/java/org/cyclonedx/model/Issue.java b/src/main/java/org/cyclonedx/model/Issue.java
new file mode 100644
index 000000000..394ba7d9f
--- /dev/null
+++ b/src/main/java/org/cyclonedx/model/Issue.java
@@ -0,0 +1,101 @@
+/*
+ * This file is part of CycloneDX Core (Java).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) Steve Springett. All Rights Reserved.
+ */
+package org.cyclonedx.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import org.cyclonedx.model.Source;
+
+@JsonPropertyOrder({"id", "name", "description", "source", "reference"})
+public class Issue
+{
+
+  public enum Type {
+    @JsonProperty("enhancement")
+    ENHANCEMENT("enhancement"),
+    @JsonProperty("security")
+    SECURITY("security"),
+    @JsonProperty("defect")
+    DEFECT("defect");
+
+    private final String name;
+
+    public String getTypeName() {
+      return this.name;
+    }
+
+    Type(String name) {
+      this.name = name;
+    }
+  }
+
+  private String id;
+
+  private String name;
+
+  private String description;
+
+  private Source source;
+
+  private ExternalReference references;
+
+  @JacksonXmlProperty(isAttribute = true)
+  private Type type;
+
+  public String getId() {
+    return id;
+  }
+
+  public void setId(final String id) {
+    this.id = id;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public void setName(final String name) {
+    this.name = name;
+  }
+
+  public String getDescription() {
+    return description;
+  }
+
+  public void setDescription(final String description) {
+    this.description = description;
+  }
+
+  public Source getSource() {
+    return source;
+  }
+
+  public void setSource(final Source source) {
+    this.source = source;
+  }
+
+  public ExternalReference getReferences() {
+    return references;
+  }
+
+  public void setReferences(final ExternalReference references) {
+    this.references = references;
+  }
+}
diff --git a/src/main/java/org/cyclonedx/model/Patch.java b/src/main/java/org/cyclonedx/model/Patch.java
new file mode 100644
index 000000000..00eab51bb
--- /dev/null
+++ b/src/main/java/org/cyclonedx/model/Patch.java
@@ -0,0 +1,73 @@
+/*
+ * This file is part of CycloneDX Core (Java).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) Steve Springett. All Rights Reserved.
+ */
+package org.cyclonedx.model;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+
+@SuppressWarnings("unused")
+@JsonPropertyOrder({"diff", "resolves"})
+public class Patch {
+
+  public enum Type {
+    @JsonProperty("backport")
+    BACKPORT("backport"),
+    @JsonProperty("unofficial")
+    UNOFFICIAL("unofficial");
+
+    private final String name;
+
+    public String getTypeName() {
+      return this.name;
+    }
+
+    Type(final String name) {
+      this.name = name;
+    }
+  }
+
+  private Diff diff;
+
+  private List<Issue> resolves;
+
+  @JacksonXmlElementWrapper(localName = "resolves")
+  @JacksonXmlProperty(localName = "issue")
+  public List<Issue> getResolves() {
+    return resolves;
+  }
+
+  public void setResolves(final List<Issue> resolves) {
+    this.resolves = resolves;
+  }
+
+  @JacksonXmlProperty(isAttribute = true, localName = "type")
+  private Type type;
+
+  public Diff getDiff() {
+    return diff;
+  }
+
+  public void setDiff(final Diff diff) {
+    this.diff = diff;
+  }
+}
diff --git a/src/main/java/org/cyclonedx/model/Pedigree.java b/src/main/java/org/cyclonedx/model/Pedigree.java
index b50223da5..1a66a0813 100644
--- a/src/main/java/org/cyclonedx/model/Pedigree.java
+++ b/src/main/java/org/cyclonedx/model/Pedigree.java
@@ -28,7 +28,7 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
 import org.cyclonedx.util.ComponentWrapperDeserializer;
 
 @SuppressWarnings("unused")
-@JsonPropertyOrder({"ancestors", "descendants", "variants", "commits", "notes"})
+@JsonPropertyOrder({"ancestors", "descendants", "variants", "commits", "patches", "notes"})
 public class Pedigree extends ExtensibleElement {
 
     @JsonDeserialize(using = ComponentWrapperDeserializer.class)
@@ -40,6 +40,7 @@ public class Pedigree extends ExtensibleElement {
     @JsonDeserialize(using = ComponentWrapperDeserializer.class)
     private Variants variants;
     private List<Commit> commits;
+    private List<Patch> patches;
     private String notes;
 
     public Ancestors getAncestors() {
@@ -73,6 +74,16 @@ public class Pedigree extends ExtensibleElement {
         this.commits = commits;
     }
 
+    @JacksonXmlElementWrapper(localName = "patches")
+    @JacksonXmlProperty(localName = "patch")
+    public List<Patch> getPatches() {
+        return patches;
+    }
+
+    public void setPatches(List<Patch> patches) {
+        this.patches = patches;
+    }
+
     public String getNotes() {
         return notes;
     }
diff --git a/src/main/java/org/cyclonedx/model/Source.java b/src/main/java/org/cyclonedx/model/Source.java
new file mode 100644
index 000000000..41cece15a
--- /dev/null
+++ b/src/main/java/org/cyclonedx/model/Source.java
@@ -0,0 +1,45 @@
+/*
+ * This file is part of CycloneDX Core (Java).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) Steve Springett. All Rights Reserved.
+ */
+package org.cyclonedx.model;
+
+import java.net.URL;
+
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+
+public class Source {
+  @JacksonXmlProperty(localName = "name", isAttribute = true)
+  private String name;
+  private URL url;
+
+  public URL getUrl() {
+    return url;
+  }
+
+  public void setUrl(final URL url) {
+    this.url = url;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public void setName(final String name) {
+    this.name = name;
+  }
+}
diff --git a/src/main/java/org/cyclonedx/model/vulnerability/Vulnerability10.java b/src/main/java/org/cyclonedx/model/vulnerability/Vulnerability10.java
index 036b4e73e..cd62a19a9 100644
--- a/src/main/java/org/cyclonedx/model/vulnerability/Vulnerability10.java
+++ b/src/main/java/org/cyclonedx/model/vulnerability/Vulnerability10.java
@@ -18,7 +18,6 @@
  */
 package org.cyclonedx.model.vulnerability;
 
-import java.net.URL;
 import java.util.List;
 import java.io.Serializable;
 
@@ -27,6 +26,7 @@ import com.fasterxml.jackson.annotation.JsonRootName;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
 import org.cyclonedx.model.ExtensibleType;
+import org.cyclonedx.model.Source;
 
 @JsonRootName(Vulnerability10.NAME)
 public class Vulnerability10
@@ -88,6 +88,7 @@ public class Vulnerability10
       for (Severity s : Severity.values()) {
         if (s.name.equals(text)) {
           return s;
+
         }
       }
       return null;
@@ -197,28 +198,6 @@ public class Vulnerability10
     this.id = id;
   }
 
-  public static class Source {
-    @JacksonXmlProperty(localName = "name", isAttribute = true)
-    private String name;
-    private URL url;
-
-    public URL getUrl() {
-      return url;
-    }
-
-    public void setUrl(final URL url) {
-      this.url = url;
-    }
-
-    public String getName() {
-      return name;
-    }
-
-    public void setName(final String name) {
-      this.name = name;
-    }
-  }
-
   public static class Score {
     private Double base;
     private Double impact;
diff --git a/src/main/java/org/cyclonedx/util/ExtensionDeserializer.java b/src/main/java/org/cyclonedx/util/ExtensionDeserializer.java
index 9266812d7..9bfcc4713 100644
--- a/src/main/java/org/cyclonedx/util/ExtensionDeserializer.java
+++ b/src/main/java/org/cyclonedx/util/ExtensionDeserializer.java
@@ -43,7 +43,7 @@ import org.cyclonedx.model.vulnerability.Vulnerability10.Recommendation;
 import org.cyclonedx.model.vulnerability.Vulnerability10.Score;
 import org.cyclonedx.model.vulnerability.Vulnerability10.ScoreSource;
 import org.cyclonedx.model.vulnerability.Vulnerability10.Severity;
-import org.cyclonedx.model.vulnerability.Vulnerability10.Source;
+import org.cyclonedx.model.Source;
 
 public class ExtensionDeserializer extends StdDeserializer<Extension>
 {
@@ -190,7 +190,7 @@ public class ExtensionDeserializer extends StdDeserializer<Extension>
     return c;
   }
 
-  private Vulnerability10.Source processSource(final JsonNode sourceNode) {
+  private Source processSource(final JsonNode sourceNode) {
     Source source = new Source();
     source.setName(getAsString("name", sourceNode));
     if (sourceNode.get(Vulnerability10.URL) != null) {


Test patch:
diff --git a/src/test/java/org/cyclonedx/parsers/XmlParserTest.java b/src/test/java/org/cyclonedx/parsers/XmlParserTest.java
index 326cfcc1a..42227d1e0 100644
--- a/src/test/java/org/cyclonedx/parsers/XmlParserTest.java
+++ b/src/test/java/org/cyclonedx/parsers/XmlParserTest.java
@@ -94,6 +94,22 @@ public class XmlParserTest {
         Assert.assertEquals("sample-library-variant", pedigree.getVariants().getComponents().get(0).getName());
     }
 
+    @Test
+    public void testValid12BomWithPedigreeWithPatches() throws Exception {
+        final File file = new File(this.getClass().getResource("/bom-1.2-pedigree-example.xml").getFile());
+        final XmlParser parser = new XmlParser();
+        final boolean valid = parser.isValid(file, CycloneDxSchema.Version.VERSION_12);
+        Assert.assertTrue(valid);
+
+        final Bom bom = parser.parse(file);
+        testPedigreeFromExample(bom.getComponents().get(0).getPedigree());
+    }
+
+    private void testPedigreeFromExample(final Pedigree pedigree) {
+        Assert.assertEquals(2, pedigree.getPatches().size());
+        Assert.assertEquals(2, pedigree.getPatches().get(1).getResolves().size());
+    }
+
     @Test
     public void testParsedObjects10Bom() throws Exception {
         final byte[] bomBytes = IOUtils.toByteArray(this.getClass().getResourceAsStream("/bom-1.0.xml"));
diff --git a/src/test/resources/bom-1.2-pedigree-example.xml b/src/test/resources/bom-1.2-pedigree-example.xml
new file mode 100644
index 000000000..0235ae23c
--- /dev/null
+++ b/src/test/resources/bom-1.2-pedigree-example.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<bom xmlns="http://cyclonedx.org/schema/bom/1.2"
+     serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"
+     version="1">
+  <components>
+    <component type="library">
+      <group>com.acme</group>
+      <name>sample-library</name>
+      <version>1.0.0</version>
+      <pedigree>
+        <ancestors>
+          <component type="library">
+            <group>org.example</group>
+            <name>sample-library</name>
+            <version>1.0.0</version>
+          </component>
+        </ancestors>
+        <commits>
+          <commit>
+            <uid>7638417db6d59f3c431d3e1f261cc637155684cd</uid>
+            <url>https://location/to/7638417db6d59f3c431d3e1f261cc637155684cd</url>
+            <author>
+              <timestamp>2018-11-07T22:01:45Z</timestamp>
+              <name>John Doe</name>
+              <email>john.doe@example.com</email>
+            </author>
+            <committer>
+              <timestamp>2018-11-07T22:01:45Z</timestamp>
+              <name>Jane Doe</name>
+              <email>jane.doe@example.com</email>
+            </committer>
+            <message>Initial commit</message>
+          </commit>
+        </commits>
+        <patches>
+          <patch type="unofficial">
+            <diff>
+              <text content-type="text/plain" encoding="base64">ZXhhbXBsZSBkaWZmIGhlcmU=</text>
+              <url>uri/to/changes.diff</url>
+            </diff>
+            <resolves>
+              <issue type="enhancement">
+                <id>JIRA-17240</id>
+                <description>Great new feature that does something</description>
+                <source>
+                  <name>Acme Org</name>
+                  <url>https://issues.acme.org/17240</url>
+                </source>
+              </issue>
+            </resolves>
+          </patch>
+          <patch type="backport">
+            <diff>
+              <text content-type="text/plain" encoding="base64">ZXhhbXBsZSBkaWZmIGhlcmU=</text>
+              <url>uri/to/changes.diff</url>
+            </diff>
+            <resolves>
+              <issue type="security">
+                <id>CVE-2019-9997</id>
+                <name>CVE-2019-9997</name>
+                <description>Issue description here</description>
+                <source>
+                  <name>NVD</name>
+                  <url>https://nvd.nist.gov/vuln/detail/CVE-2019-9997</url>
+                </source>
+                <references>
+                  <url>http://some/other/site-1</url>
+                  <url>http://some/other/site-2</url>
+                </references>
+              </issue>
+              <issue type="defect">
+                <id>JIRA-874319</id>
+                <description>Description of fix here</description>
+                <source>
+                  <name>Example Org</name>
+                  <url>https://issues.example.org/874319</url>
+                </source>
+                <references>
+                  <url>http://some/other/site-1</url>
+                  <url>http://some/other/site-2</url>
+                </references>
+              </issue>
+            </resolves>
+          </patch>
+        </patches>
+      </pedigree>
+    </component>
+  </components>
+</bom>