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"` }