From 161fa7be4a14855155f3c0f93fa2a12d0cec5585 Mon Sep 17 00:00:00 2001
From: Peter Balogh
Date: Tue, 25 Jan 2022 16:36:15 +0100
Subject: [PATCH] [CycloneDX] Add artifactID and groupID to the cycloneDX
properties (support lower level struct as properties) (#758)
* [CycloneDX] Add artifactID and groupID to the cycloneDX properties
Signed-off-by: Peter Balogh
* update comment
Signed-off-by: Peter Balogh
* additional checks for value
Signed-off-by: Peter Balogh
* fill group filed with groupID in the case of Java
Signed-off-by: Peter Balogh
* fix linter warning
Signed-off-by: Alex Goodman
Co-authored-by: Alex Goodman
---
.../common/cyclonedxhelpers/component.go | 1 +
.../formats/common/cyclonedxhelpers/group.go | 12 +++++
.../common/cyclonedxhelpers/group_test.go | 52 +++++++++++++++++++
.../common/cyclonedxhelpers/properties.go | 22 +++++---
syft/pkg/java_metadata.go | 6 +--
5 files changed, 84 insertions(+), 9 deletions(-)
create mode 100644 internal/formats/common/cyclonedxhelpers/group.go
create mode 100644 internal/formats/common/cyclonedxhelpers/group_test.go
diff --git a/internal/formats/common/cyclonedxhelpers/component.go b/internal/formats/common/cyclonedxhelpers/component.go
index 5ff83aa9d..ed493a552 100644
--- a/internal/formats/common/cyclonedxhelpers/component.go
+++ b/internal/formats/common/cyclonedxhelpers/component.go
@@ -9,6 +9,7 @@ func Component(p pkg.Package) cyclonedx.Component {
return cyclonedx.Component{
Type: cyclonedx.ComponentTypeLibrary,
Name: p.Name,
+ Group: Group(p),
Version: p.Version,
PackageURL: p.PURL,
Licenses: Licenses(p),
diff --git a/internal/formats/common/cyclonedxhelpers/group.go b/internal/formats/common/cyclonedxhelpers/group.go
new file mode 100644
index 000000000..0a2a8b34c
--- /dev/null
+++ b/internal/formats/common/cyclonedxhelpers/group.go
@@ -0,0 +1,12 @@
+package cyclonedxhelpers
+
+import "github.com/anchore/syft/syft/pkg"
+
+func Group(p pkg.Package) string {
+ if hasMetadata(p) {
+ if metadata, ok := p.Metadata.(pkg.JavaMetadata); ok && metadata.PomProperties != nil {
+ return metadata.PomProperties.GroupID
+ }
+ }
+ return ""
+}
diff --git a/internal/formats/common/cyclonedxhelpers/group_test.go b/internal/formats/common/cyclonedxhelpers/group_test.go
new file mode 100644
index 000000000..908fc3b87
--- /dev/null
+++ b/internal/formats/common/cyclonedxhelpers/group_test.go
@@ -0,0 +1,52 @@
+package cyclonedxhelpers
+
+import (
+ "testing"
+
+ "github.com/anchore/syft/syft/pkg"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestGroup(t *testing.T) {
+ tests := []struct {
+ name string
+ input pkg.Package
+ expected string
+ }{
+ {
+ name: "no metadata",
+ input: pkg.Package{},
+ expected: "",
+ },
+ {
+ name: "metadata is not Java",
+ input: pkg.Package{
+ Metadata: pkg.NpmPackageJSONMetadata{},
+ },
+ expected: "",
+ },
+ {
+ name: "metadata is Java but pom properties is empty",
+ input: pkg.Package{
+ Metadata: pkg.JavaMetadata{},
+ },
+ expected: "",
+ },
+ {
+ name: "metadata is Java and contains pomProperties",
+ input: pkg.Package{
+ Metadata: pkg.JavaMetadata{
+ PomProperties: &pkg.PomProperties{
+ GroupID: "test",
+ },
+ },
+ },
+ expected: "test",
+ },
+ }
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ assert.Equal(t, test.expected, Group(test.input))
+ })
+ }
+}
diff --git a/internal/formats/common/cyclonedxhelpers/properties.go b/internal/formats/common/cyclonedxhelpers/properties.go
index 5e5b782ca..17c71e225 100644
--- a/internal/formats/common/cyclonedxhelpers/properties.go
+++ b/internal/formats/common/cyclonedxhelpers/properties.go
@@ -28,17 +28,22 @@ func Properties(p pkg.Package) *[]cyclonedx.Property {
func getCycloneDXProperties(m interface{}) *[]cyclonedx.Property {
props := []cyclonedx.Property{}
structValue := reflect.ValueOf(m)
- // we can only handle top level structs as interfaces for now
if structValue.Kind() != reflect.Struct {
return &props
}
structType := structValue.Type()
for i := 0; i < structValue.NumField(); i++ {
if name, value := getCycloneDXPropertyName(structType.Field(i)), getCycloneDXPropertyValue(structValue.Field(i)); name != "" && value != "" {
- props = append(props, cyclonedx.Property{
- Name: name,
- Value: value,
- })
+ // In the case of the value is a struct and has cyclonedx tag with name "-"
+ // call the getCycloneDXProperties recursively.
+ if name == "-" && reflect.ValueOf(value).Kind() == reflect.Struct {
+ props = append(props, *getCycloneDXProperties(value)...)
+ } else if reflect.ValueOf(value).Kind() == reflect.String {
+ props = append(props, cyclonedx.Property{
+ Name: name,
+ Value: fmt.Sprint(value),
+ })
+ }
}
}
return &props
@@ -51,7 +56,7 @@ func getCycloneDXPropertyName(field reflect.StructField) string {
return ""
}
-func getCycloneDXPropertyValue(field reflect.Value) string {
+func getCycloneDXPropertyValue(field reflect.Value) interface{} {
if field.IsZero() {
return ""
}
@@ -61,6 +66,11 @@ func getCycloneDXPropertyValue(field reflect.Value) string {
return fmt.Sprint(field.Interface())
}
return ""
+ case reflect.Struct:
+ if field.CanInterface() {
+ return field.Interface()
+ }
+ return ""
case reflect.Ptr:
return getCycloneDXPropertyValue(reflect.Indirect(field))
}
diff --git a/syft/pkg/java_metadata.go b/syft/pkg/java_metadata.go
index 9dcd32e1e..a3c5d8e31 100644
--- a/syft/pkg/java_metadata.go
+++ b/syft/pkg/java_metadata.go
@@ -23,7 +23,7 @@ var JenkinsPluginPomPropertiesGroupIDs = []string{
type JavaMetadata struct {
VirtualPath string `json:"virtualPath"`
Manifest *JavaManifest `mapstructure:"Manifest" json:"manifest,omitempty"`
- PomProperties *PomProperties `mapstructure:"PomProperties" json:"pomProperties,omitempty"`
+ PomProperties *PomProperties `mapstructure:"PomProperties" json:"pomProperties,omitempty" cyclonedx:"-"`
PomProject *PomProject `mapstructure:"PomProject" json:"pomProject,omitempty"`
Parent *Package `hash:"ignore" json:"-"` // note: the parent cannot be included in the minimal definition of uniqueness since this field is not reproducible in an encode-decode cycle (is lossy).
}
@@ -32,8 +32,8 @@ type JavaMetadata struct {
type PomProperties struct {
Path string `mapstructure:"path" json:"path"`
Name string `mapstructure:"name" json:"name"`
- GroupID string `mapstructure:"groupId" json:"groupId"`
- ArtifactID string `mapstructure:"artifactId" json:"artifactId"`
+ GroupID string `mapstructure:"groupId" json:"groupId" cyclonedx:"groupID"`
+ ArtifactID string `mapstructure:"artifactId" json:"artifactId" cyclonedx:"artifactID"`
Version string `mapstructure:"version" json:"version"`
Extra map[string]string `mapstructure:",remain" json:"extraFields"`
}