diff --git a/syft/formats/common/spdxhelpers/to_syft_model.go b/syft/formats/common/spdxhelpers/to_syft_model.go index c17b6367e..f61f723c8 100644 --- a/syft/formats/common/spdxhelpers/to_syft_model.go +++ b/syft/formats/common/spdxhelpers/to_syft_model.go @@ -286,6 +286,16 @@ func collectSyftPackages(s *sbom.SBOM, spdxIDMap map[string]any, packages []*spd } func collectSyftFiles(s *sbom.SBOM, spdxIDMap map[string]any, doc *spdx.Document) { + for _, p := range doc.Packages { + for _, f := range p.Files { + l := toSyftLocation(f) + spdxIDMap[string(f.FileSPDXIdentifier)] = l + + s.Artifacts.FileMetadata[l.Coordinates] = toFileMetadata(f) + s.Artifacts.FileDigests[l.Coordinates] = toFileDigests(f) + } + } + for _, f := range doc.Files { l := toSyftLocation(f) spdxIDMap[string(f.FileSPDXIdentifier)] = l @@ -332,7 +342,14 @@ func toFileMetadata(f *spdx.File) (meta file.Metadata) { } func toSyftRelationships(spdxIDMap map[string]any, doc *spdx.Document) []artifact.Relationship { - var out []artifact.Relationship + out := collectDocRelationships(spdxIDMap, doc) + + out = append(out, collectPackageFileRelationships(spdxIDMap, doc)...) + + return out +} + +func collectDocRelationships(spdxIDMap map[string]any, doc *spdx.Document) (out []artifact.Relationship) { for _, r := range doc.Relationships { // FIXME what to do with r.RefA.DocumentRefID and r.RefA.SpecialID if r.RefA.DocumentRefID != "" && requireAndTrimPrefix(r.RefA.DocumentRefID, "DocumentRef-") != string(doc.SPDXIdentifier) { @@ -386,6 +403,30 @@ func toSyftRelationships(spdxIDMap map[string]any, doc *spdx.Document) []artifac return out } +// collectPackageFileRelationships add relationships for direct files +func collectPackageFileRelationships(spdxIDMap map[string]any, doc *spdx.Document) (out []artifact.Relationship) { + for _, p := range doc.Packages { + a := spdxIDMap[string(p.PackageSPDXIdentifier)] + from, fromOk := a.(pkg.Package) + if !fromOk { + continue + } + for _, f := range p.Files { + b := spdxIDMap[string(f.FileSPDXIdentifier)] + to, toLocationOk := b.(file.Location) + if !toLocationOk { + continue + } + out = append(out, artifact.Relationship{ + From: from, + To: to, + Type: artifact.ContainsRelationship, + }) + } + } + return out +} + func toSyftCoordinates(f *spdx.File) file.Coordinates { const layerIDPrefix = "layerID: " var fileSystemID string diff --git a/syft/formats/common/spdxhelpers/to_syft_model_test.go b/syft/formats/common/spdxhelpers/to_syft_model_test.go index f6023f662..eb997eb35 100644 --- a/syft/formats/common/spdxhelpers/to_syft_model_test.go +++ b/syft/formats/common/spdxhelpers/to_syft_model_test.go @@ -608,3 +608,77 @@ func Test_purlValue(t *testing.T) { }) } } + +func Test_directPackageFiles(t *testing.T) { + doc := &spdx.Document{ + SPDXVersion: "SPDX-2.3", + Packages: []*spdx.Package{ + { + PackageName: "some-package", + PackageSPDXIdentifier: "1", + PackageVersion: "1.0.5", + Files: []*spdx.File{ + { + FileName: "some-file", + FileSPDXIdentifier: "2", + Checksums: []spdx.Checksum{ + { + Algorithm: "SHA1", + Value: "a8d733c64f9123", + }, + }, + }, + }, + }, + }, + } + + got, err := ToSyftModel(doc) + require.NoError(t, err) + + p := pkg.Package{ + Name: "some-package", + Version: "1.0.5", + MetadataType: pkg.UnknownMetadataType, + } + p.SetID() + f := file.Location{ + LocationData: file.LocationData{ + Coordinates: file.Coordinates{ + RealPath: "some-file", + FileSystemID: "", + }, + VirtualPath: "some-file", + }, + LocationMetadata: file.LocationMetadata{ + Annotations: map[string]string{}, + }, + } + s := &sbom.SBOM{ + Artifacts: sbom.Artifacts{ + Packages: pkg.NewCollection(p), + FileMetadata: map[file.Coordinates]file.Metadata{ + f.Coordinates: {}, + }, + FileDigests: map[file.Coordinates][]file.Digest{ + f.Coordinates: { + { + Algorithm: "sha1", + Value: "a8d733c64f9123", + }, + }, + }, + }, + Relationships: []artifact.Relationship{ + { + From: p, + To: f, + Type: artifact.ContainsRelationship, + }, + }, + Source: source.Description{}, + Descriptor: sbom.Descriptor{}, + } + + require.Equal(t, s, got) +} diff --git a/syft/formats/spdxtagvalue/decoder_test.go b/syft/formats/spdxtagvalue/decoder_test.go index 85f0b01c3..6802e1b5c 100644 --- a/syft/formats/spdxtagvalue/decoder_test.go +++ b/syft/formats/spdxtagvalue/decoder_test.go @@ -2,9 +2,13 @@ package spdxtagvalue import ( "os" + "strings" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/anchore/syft/syft/file" ) // TODO: this is a temporary coverage see below @@ -34,3 +38,59 @@ func TestSPDXTagValueDecoder(t *testing.T) { }) } } + +func Test_packageDirectFiles(t *testing.T) { + contents := ` +SPDXVersion: SPDX-2.2 +DataLicense: CC0-1.0 +SPDXID: SPDXRef-DOCUMENT +DocumentName: Some-SBOM +DocumentNamespace: https://example.org/some/namespace +Creator: Organization: Some-organization +Creator: Tool: Some-tool Version: 1.0 +Created: 2021-12-29T17:02:21Z +PackageName: Some-package +PackageVersion: 5.1.2 +SPDXID: SPDXRef-Package-43c51b08-cc7e-406d-8ad9-34aa292d1157 +PackageSupplier: Organization: Some-organization +PackageDownloadLocation: https://example.org/download/location +FilesAnalyzed: true +PackageLicenseInfoFromFiles: NOASSERTION +PackageVerificationCode: 23460C5559C8D4DE3F6504E0E84E844CAC8B1D95 +PackageLicenseConcluded: NOASSERTION +PackageLicenseDeclared: NOASSERTION +PackageCopyrightText: NOASSERTION +PackageChecksum: SHA1: 23460C5559C8D4DE3F6504E0E84E844CAC8B1D95 +FileName: Some-file-name +SPDXID: SPDXRef-99545d55-933d-4e08-9eb5-9d826111cb79 +FileContributor: Some-file-contributor +FileType: BINARY +FileChecksum: SHA1: 23460C5559C8D4DE3F6504E0E84E844CAC8B1D95 +LicenseConcluded: NOASSERTION +LicenseInfoInFile: NOASSERTION +FileCopyrightText: NOASSERTION +` + + s, err := decoder(strings.NewReader(contents)) + require.NoError(t, err) + + pkgs := s.Artifacts.Packages.Sorted() + assert.Len(t, pkgs, 1) + assert.Len(t, s.Artifacts.FileMetadata, 1) + assert.Len(t, s.Relationships, 1) + p := pkgs[0] + r := s.Relationships[0] + f := file.Location{} + for c := range s.Artifacts.FileMetadata { + f = file.Location{ + LocationData: file.LocationData{ + Coordinates: c, + VirtualPath: "", + }, + LocationMetadata: file.LocationMetadata{}, + } + break // there should only be 1 + } + assert.Equal(t, p.ID(), r.From.ID()) + assert.Equal(t, f.ID(), r.To.ID()) +}