diff --git a/syft/pkg/cataloger/cpe.go b/syft/pkg/cataloger/cpe.go index b53bdfb99..ad5ee4e04 100644 --- a/syft/pkg/cataloger/cpe.go +++ b/syft/pkg/cataloger/cpe.go @@ -54,9 +54,6 @@ func (s candidateStore) getCandidates(t pkg.Type, key string) []string { return value } -const pomPropertiesGroupIDJenkinsPlugins = "com.cloudbees.jenkins.plugins" -const pomPropertiesGroupIDJiraPlugins = "com.atlassian.jira.plugins" - func newCPE(product, vendor, version, targetSW string) wfn.Attributes { cpe := *(wfn.NewAttributesWithAny()) cpe.Part = "a" @@ -115,22 +112,9 @@ func candidateTargetSoftwareAttrs(p pkg.Package) []string { return targetSw } -func isJenkinsPlugin(p pkg.Package) bool { - if p.Type == pkg.JenkinsPluginPkg { - return true - } - - if groupID := groupIDFromPomProperties(p); groupID == pomPropertiesGroupIDJenkinsPlugins { - return true - } - - return false -} - func candidateTargetSoftwareAttrsForJava(p pkg.Package) []string { // Use the more specific indicator if available - - if isJenkinsPlugin(p) { + if p.Type == pkg.JenkinsPluginPkg { return []string{"jenkins", "cloudbees_jenkins"} } @@ -217,8 +201,8 @@ func shouldConsiderGroupID(groupID string) bool { } excludedGroupIDs := []string{ - pomPropertiesGroupIDJiraPlugins, - pomPropertiesGroupIDJenkinsPlugins, + pkg.PomPropertiesGroupIDJiraPlugins, + pkg.PomPropertiesGroupIDJenkinsPlugins, } for _, excludedGroupID := range excludedGroupIDs { diff --git a/syft/pkg/cataloger/cpe_test.go b/syft/pkg/cataloger/cpe_test.go index 560c574b0..678eed7aa 100644 --- a/syft/pkg/cataloger/cpe_test.go +++ b/syft/pkg/cataloger/cpe_test.go @@ -156,7 +156,7 @@ func TestGeneratePackageCPEs(t *testing.T) { Version: "3.2", FoundBy: "some-analyzer", Language: pkg.Java, - Type: pkg.JavaPkg, + Type: pkg.JenkinsPluginPkg, Metadata: pkg.JavaMetadata{ PomProperties: &pkg.PomProperties{ GroupID: "com.cloudbees.jenkins.plugins", diff --git a/syft/pkg/cataloger/java/archive_parser.go b/syft/pkg/cataloger/java/archive_parser.go index 201e44c93..591f0d85f 100644 --- a/syft/pkg/cataloger/java/archive_parser.go +++ b/syft/pkg/cataloger/java/archive_parser.go @@ -186,14 +186,14 @@ func (j *archiveParser) discoverPkgsFromAllPomProperties(parentPkg *pkg.Package) continue } - pkgs = append(pkgs, j.packagesFromPomProperties(pomProperties, parentPkg)...) + pkgs = append(pkgs, j.packagesFromPomProperties(*pomProperties, parentPkg)...) } return pkgs, nil } // packagesFromPomProperties processes a single Maven POM properties for a given parent package, returning all listed Java packages found and // associating each discovered package to the given parent package. -func (j *archiveParser) packagesFromPomProperties(pomProperties *pkg.PomProperties, parentPkg *pkg.Package) []pkg.Package { +func (j *archiveParser) packagesFromPomProperties(pomProperties pkg.PomProperties, parentPkg *pkg.Package) []pkg.Package { // keep the artifact name within the virtual path if this package does not match the parent package vPathSuffix := "" if !strings.HasPrefix(pomProperties.ArtifactID, parentPkg.Name) { @@ -206,11 +206,11 @@ func (j *archiveParser) packagesFromPomProperties(pomProperties *pkg.PomProperti Name: pomProperties.ArtifactID, Version: pomProperties.Version, Language: pkg.Java, - Type: pkg.JavaPkg, + Type: pomProperties.PkgTypeIndicated(), MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaMetadata{ VirtualPath: virtualPath, - PomProperties: pomProperties, + PomProperties: &pomProperties, Parent: parentPkg, }, } @@ -243,10 +243,13 @@ func (j *archiveParser) packagesFromPomProperties(pomProperties *pkg.PomProperti parentPkg.Version = p.Version } + // We may have learned more about the type via data in the pom properties + parentPkg.Type = p.Type + // keep the pom properties, but don't overwrite existing pom properties parentMetadata, ok := parentPkg.Metadata.(pkg.JavaMetadata) if ok && parentMetadata.PomProperties == nil { - parentMetadata.PomProperties = pomProperties + parentMetadata.PomProperties = &pomProperties parentPkg.Metadata = parentMetadata } diff --git a/syft/pkg/cataloger/java/archive_parser_test.go b/syft/pkg/cataloger/java/archive_parser_test.go index e044c4e8e..8988bdaa5 100644 --- a/syft/pkg/cataloger/java/archive_parser_test.go +++ b/syft/pkg/cataloger/java/archive_parser_test.go @@ -629,12 +629,12 @@ func TestPackagesFromPomProperties(t *testing.T) { }, }, { - name: "child matches parent by key", + name: "single package from pom properties that's a Jenkins plugin", props: &pkg.PomProperties{ Name: "some-name", - GroupID: "some-group-id", - ArtifactID: "some-parent-name", // note: matches parent package - Version: "2.0", // note: matches parent package + GroupID: "com.cloudbees.jenkins.plugins", + ArtifactID: "some-artifact-id", + Version: "1.0", }, parent: &pkg.Package{ Name: "some-parent-name", @@ -650,6 +650,66 @@ func TestPackagesFromPomProperties(t *testing.T) { expectedParent: pkg.Package{ Name: "some-parent-name", Version: "2.0", + Metadata: pkg.JavaMetadata{ + VirtualPath: "some-parent-virtual-path", + Manifest: nil, + PomProperties: nil, + Parent: nil, + }, + }, + expectedPackages: []pkg.Package{ + { + Name: "some-artifact-id", + Version: "1.0", + Language: pkg.Java, + Type: pkg.JenkinsPluginPkg, + MetadataType: pkg.JavaMetadataType, + Metadata: pkg.JavaMetadata{ + VirtualPath: virtualPath + ":" + "some-artifact-id", + PomProperties: &pkg.PomProperties{ + Name: "some-name", + GroupID: "com.cloudbees.jenkins.plugins", + ArtifactID: "some-artifact-id", + Version: "1.0", + }, + Parent: &pkg.Package{ + Name: "some-parent-name", + Version: "2.0", + Metadata: pkg.JavaMetadata{ + VirtualPath: "some-parent-virtual-path", + Manifest: nil, + PomProperties: nil, + Parent: nil, + }, + }, + }, + }, + }, + }, + { + name: "child matches parent by key", + props: &pkg.PomProperties{ + Name: "some-name", + GroupID: "some-group-id", + ArtifactID: "some-parent-name", // note: matches parent package + Version: "2.0", // note: matches parent package + }, + parent: &pkg.Package{ + Name: "some-parent-name", + Version: "2.0", + Type: pkg.JavaPkg, + Metadata: pkg.JavaMetadata{ + VirtualPath: "some-parent-virtual-path", + Manifest: nil, + PomProperties: nil, + Parent: nil, + }, + }, + // note: the SAME as the original parent values + expectedParent: pkg.Package{ + Name: "some-parent-name", + Version: "2.0", + Type: pkg.JavaPkg, Metadata: pkg.JavaMetadata{ VirtualPath: "some-parent-virtual-path", Manifest: nil, @@ -665,6 +725,44 @@ func TestPackagesFromPomProperties(t *testing.T) { }, expectedPackages: nil, }, + { + name: "child matches parent by key and is Jenkins plugin", + props: &pkg.PomProperties{ + Name: "some-name", + GroupID: "com.cloudbees.jenkins.plugins", + ArtifactID: "some-parent-name", // note: matches parent package + Version: "2.0", // note: matches parent package + }, + parent: &pkg.Package{ + Name: "some-parent-name", + Version: "2.0", + Type: pkg.JavaPkg, + Metadata: pkg.JavaMetadata{ + VirtualPath: "some-parent-virtual-path", + Manifest: nil, + PomProperties: nil, + Parent: nil, + }, + }, + expectedParent: pkg.Package{ + Name: "some-parent-name", + Version: "2.0", + Type: pkg.JenkinsPluginPkg, + Metadata: pkg.JavaMetadata{ + VirtualPath: "some-parent-virtual-path", + Manifest: nil, + // note: we attach the discovered pom properties data + PomProperties: &pkg.PomProperties{ + Name: "some-name", + GroupID: "com.cloudbees.jenkins.plugins", + ArtifactID: "some-parent-name", // note: matches parent package + Version: "2.0", // note: matches parent package + }, + Parent: nil, + }, + }, + expectedPackages: nil, + }, { name: "child matches parent by virtual path", props: &pkg.PomProperties{ @@ -676,6 +774,7 @@ func TestPackagesFromPomProperties(t *testing.T) { parent: &pkg.Package{ Name: "some-parent-name", Version: "2.0", + Type: pkg.JavaPkg, Metadata: pkg.JavaMetadata{ VirtualPath: virtualPath + ":some-parent-name", // note: matching virtual path Manifest: nil, @@ -686,6 +785,7 @@ func TestPackagesFromPomProperties(t *testing.T) { expectedParent: pkg.Package{ Name: "some-parent-name", Version: "2.0", + Type: pkg.JavaPkg, Metadata: pkg.JavaMetadata{ VirtualPath: virtualPath + ":some-parent-name", // note: matching virtual path Manifest: nil, @@ -712,6 +812,7 @@ func TestPackagesFromPomProperties(t *testing.T) { parent: &pkg.Package{ Name: "", // note: empty Version: "", // note: empty + Type: pkg.JavaPkg, Metadata: pkg.JavaMetadata{ VirtualPath: virtualPath + ":some-parent-name", // note: matching virtual path Manifest: nil, @@ -722,6 +823,7 @@ func TestPackagesFromPomProperties(t *testing.T) { expectedParent: pkg.Package{ Name: "some-parent-name", Version: "3.0", + Type: pkg.JavaPkg, Metadata: pkg.JavaMetadata{ VirtualPath: virtualPath + ":some-parent-name", // note: matching virtual path Manifest: nil, @@ -748,11 +850,12 @@ func TestPackagesFromPomProperties(t *testing.T) { parent: &pkg.Package{ Name: "some-parent-name", Version: "2.0", + Type: pkg.JavaPkg, Metadata: pkg.JavaMetadata{ VirtualPath: virtualPath + ":some-parent-name", // note: matching virtual path Manifest: nil, PomProperties: &pkg.PomProperties{ - Name: "EXISTS", //note: this already exists and should not be overridden + Name: "EXISTS", // note: this already exists and should not be overridden }, Parent: nil, }, @@ -760,12 +863,13 @@ func TestPackagesFromPomProperties(t *testing.T) { expectedParent: pkg.Package{ Name: "some-parent-name", Version: "2.0", + Type: pkg.JavaPkg, Metadata: pkg.JavaMetadata{ VirtualPath: virtualPath + ":some-parent-name", // note: matching virtual path Manifest: nil, // note: we attach the discovered pom properties data PomProperties: &pkg.PomProperties{ - Name: "EXISTS", //note: this already exists and should not be overridden + Name: "EXISTS", // note: this already exists and should not be overridden }, Parent: nil, }, @@ -783,6 +887,7 @@ func TestPackagesFromPomProperties(t *testing.T) { parent: &pkg.Package{ Name: "some-parent-name", Version: "2.0", + Type: pkg.JavaPkg, Metadata: pkg.JavaMetadata{ VirtualPath: virtualPath + ":NEW_VIRTUAL_PATH", // note: DOES NOT match the existing virtual path Manifest: nil, @@ -794,6 +899,7 @@ func TestPackagesFromPomProperties(t *testing.T) { expectedParent: pkg.Package{ Name: "some-parent-name", Version: "2.0", + Type: pkg.JavaPkg, Metadata: pkg.JavaMetadata{ VirtualPath: virtualPath + ":NEW_VIRTUAL_PATH", Manifest: nil, @@ -823,7 +929,7 @@ func TestPackagesFromPomProperties(t *testing.T) { t.Cleanup(cleanup) // get the test data - actualPackages := parser.packagesFromPomProperties(test.props, test.parent) + actualPackages := parser.packagesFromPomProperties(*test.props, test.parent) assert.Equal(t, test.expectedPackages, actualPackages) assert.Equal(t, test.expectedParent, *test.parent) }) diff --git a/syft/pkg/java_metadata.go b/syft/pkg/java_metadata.go index beb03463f..8969b7c36 100644 --- a/syft/pkg/java_metadata.go +++ b/syft/pkg/java_metadata.go @@ -20,6 +20,15 @@ type PomProperties struct { Extra map[string]string `mapstructure:",remain" json:"extraFields"` } +// PkgTypeIndicated returns the package Type indicated by the data contained in the PomProperties. +func (p PomProperties) PkgTypeIndicated() Type { + if p.GroupID == PomPropertiesGroupIDJenkinsPlugins { + return JenkinsPluginPkg + } + + return JavaPkg +} + // JavaManifest represents the fields of interest extracted from a Java archive's META-INF/MANIFEST.MF file. type JavaManifest struct { Main map[string]string `json:"main,omitempty"` @@ -43,3 +52,6 @@ func (m JavaMetadata) PackageURL() string { return "" } + +const PomPropertiesGroupIDJenkinsPlugins = "com.cloudbees.jenkins.plugins" +const PomPropertiesGroupIDJiraPlugins = "com.atlassian.jira.plugins" diff --git a/syft/pkg/java_metadata_test.go b/syft/pkg/java_metadata_test.go index 446d30820..866686f79 100644 --- a/syft/pkg/java_metadata_test.go +++ b/syft/pkg/java_metadata_test.go @@ -2,9 +2,48 @@ package pkg import ( "github.com/sergi/go-diff/diffmatchpatch" + "github.com/stretchr/testify/assert" "testing" ) +func TestPomProperties_PkgTypeIndicated(t *testing.T) { + cases := []struct { + name string + pomProperties PomProperties + expectedType Type + }{ + { + name: "regular Java package", + pomProperties: PomProperties{ + Path: "some path", + Name: "some name", + GroupID: "some group ID", + ArtifactID: "some artifact ID", + Version: "1", + }, + expectedType: JavaPkg, + }, + { + name: "jenkins plugin", + pomProperties: PomProperties{ + Path: "some path", + Name: "some name", + GroupID: "com.cloudbees.jenkins.plugins", + ArtifactID: "some artifact ID", + Version: "1", + }, + expectedType: JenkinsPluginPkg, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + actual := tc.pomProperties.PkgTypeIndicated() + assert.Equal(t, tc.expectedType, actual) + }) + } +} + func TestJavaMetadata_pURL(t *testing.T) { tests := []struct { metadata JavaMetadata