Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: SPDX license values and download location #2007

Merged
merged 3 commits into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 27 additions & 18 deletions syft/formats/common/spdxhelpers/license.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package spdxhelpers

import (
"crypto/sha256"
"fmt"
"strings"

"github.com/anchore/syft/internal/spdxlicense"
Expand All @@ -27,29 +29,18 @@ func License(p pkg.Package) (concluded, declared string) {
// https://spdx.github.io/spdx-spec/v2.3/SPDX-license-expressions/
pc, pd := parseLicenses(p.Licenses.ToSlice())

for i, v := range pc {
if strings.HasPrefix(v, spdxlicense.LicenseRefPrefix) {
pc[i] = SanitizeElementID(v)
}
}

for i, v := range pd {
if strings.HasPrefix(v, spdxlicense.LicenseRefPrefix) {
pd[i] = SanitizeElementID(v)
}
}

return joinLicenses(pc), joinLicenses(pd)
}

func joinLicenses(licenses []string) string {
func joinLicenses(licenses []spdxLicense) string {
if len(licenses) == 0 {
return NOASSERTION
}

var newLicenses []string

for _, v := range licenses {
for _, l := range licenses {
v := l.id
// check if license does not start or end with parens
if !strings.HasPrefix(v, "(") && !strings.HasSuffix(v, ")") {
// if license contains AND, OR, or WITH, then wrap in parens
Expand All @@ -66,14 +57,31 @@ func joinLicenses(licenses []string) string {
return strings.Join(newLicenses, " AND ")
}

func parseLicenses(raw []pkg.License) (concluded, declared []string) {
type spdxLicense struct {
id string
value string
}

func parseLicenses(raw []pkg.License) (concluded, declared []spdxLicense) {
for _, l := range raw {
var candidate string
if l.Value == "" {
continue
}

candidate := spdxLicense{}
if l.SPDXExpression != "" {
candidate = l.SPDXExpression
candidate.id = l.SPDXExpression
} else {
// we did not find a valid SPDX license ID so treat as separate license
candidate = spdxlicense.LicenseRefPrefix + l.Value
if len(l.Value) <= 64 {
// if the license text is less than the size of the hash,
// just use it directly so the id is more readable
candidate.id = spdxlicense.LicenseRefPrefix + SanitizeElementID(l.Value)
} else {
hash := sha256.Sum256([]byte(l.Value))
candidate.id = fmt.Sprintf("%s%x", spdxlicense.LicenseRefPrefix, hash)
}
candidate.value = l.Value
}

switch l.Type {
Expand All @@ -83,5 +91,6 @@ func parseLicenses(raw []pkg.License) (concluded, declared []string) {
declared = append(declared, candidate)
}
}

return concluded, declared
}
89 changes: 88 additions & 1 deletion syft/formats/common/spdxhelpers/license_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package spdxhelpers

import (
"strings"
"testing"

"github.com/spdx/tools-golang/spdx"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/anchore/syft/internal/spdxlicense"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/sbom"
)

func Test_License(t *testing.T) {
Expand Down Expand Up @@ -103,6 +108,77 @@ func Test_License(t *testing.T) {
}
}

func Test_otherLicenses(t *testing.T) {
pkg1 := pkg.Package{
Name: "first-pkg",
Version: "1.1",
Licenses: pkg.NewLicenseSet(
pkg.NewLicense("MIT"),
),
}
pkg2 := pkg.Package{
Name: "second-pkg",
Version: "2.2",
Licenses: pkg.NewLicenseSet(
pkg.NewLicense("non spdx license"),
),
}
bigText := `
Apache License
Version 2.0, January 2004`
pkg3 := pkg.Package{
Name: "third-pkg",
Version: "3.3",
Licenses: pkg.NewLicenseSet(
pkg.NewLicense(bigText),
),
}

tests := []struct {
name string
packages []pkg.Package
expected []*spdx.OtherLicense
}{
{
name: "no other licenses when all valid spdx expressions",
packages: []pkg.Package{pkg1},
expected: nil,
},
{
name: "other licenses includes original text",
packages: []pkg.Package{pkg2},
expected: []*spdx.OtherLicense{
{
LicenseIdentifier: "LicenseRef-non-spdx-license",
ExtractedText: "non spdx license",
},
},
},
{
name: "big licenses get hashed",
packages: []pkg.Package{pkg3},
expected: []*spdx.OtherLicense{
{
LicenseIdentifier: "LicenseRef-e9a1e42833d3e456f147052f4d312101bd171a0798893169fe596ca6b55c049e",
ExtractedText: bigText,
},
},
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
s := sbom.SBOM{
Artifacts: sbom.Artifacts{
Packages: pkg.NewCollection(test.packages...),
},
}
got := ToFormatModel(s)
require.Equal(t, test.expected, got.OtherLicenses)
})
}
}

func Test_joinLicenses(t *testing.T) {
tests := []struct {
name string
Expand All @@ -122,7 +198,18 @@ func Test_joinLicenses(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, joinLicenses(tt.args), "joinLicenses(%v)", tt.args)
assert.Equalf(t, tt.want, joinLicenses(toSpdxLicenses(tt.args)), "joinLicenses(%v)", tt.args)
})
}
}

func toSpdxLicenses(ids []string) (licenses []spdxLicense) {
for _, l := range ids {
license := spdxLicense{id: l}
if strings.HasPrefix(l, spdxlicense.LicenseRefPrefix) {
license.value = l
}
licenses = append(licenses, license)
}
return licenses
}
32 changes: 16 additions & 16 deletions syft/formats/common/spdxhelpers/to_format_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ func toRootPackage(s source.Description) *spdx.Package {
PackageSupplier: &spdx.Supplier{
Supplier: NOASSERTION,
},
PackageDownloadLocation: NOASSERTION,
}

if purl != nil {
Expand Down Expand Up @@ -703,32 +704,31 @@ func toFileTypes(metadata *file.Metadata) (ty []string) {
// other licenses are for licenses from the pkg.Package that do not have an SPDXExpression
// field. The spdxexpression field is only filled given a validated Value field.
func toOtherLicenses(catalog *pkg.Collection) []*spdx.OtherLicense {
licenses := map[string]bool{}
for _, p := range catalog.Sorted() {
licenses := map[string]spdxLicense{}

for p := range catalog.Enumerate() {
declaredLicenses, concludedLicenses := parseLicenses(p.Licenses.ToSlice())
for _, license := range declaredLicenses {
if strings.HasPrefix(license, spdxlicense.LicenseRefPrefix) {
licenses[license] = true
for _, l := range declaredLicenses {
if l.value != "" {
licenses[l.id] = l
}
}
for _, license := range concludedLicenses {
if strings.HasPrefix(license, spdxlicense.LicenseRefPrefix) {
licenses[license] = true
for _, l := range concludedLicenses {
if l.value != "" {
licenses[l.id] = l
}
}
}

var result []*spdx.OtherLicense

sorted := maps.Keys(licenses)
slices.Sort(sorted)
for _, license := range sorted {
// separate the found value from the prefix
// this only contains licenses that are not found on the SPDX License List
name := strings.TrimPrefix(license, spdxlicense.LicenseRefPrefix)
ids := maps.Keys(licenses)
slices.Sort(ids)
for _, id := range ids {
license := licenses[id]
result = append(result, &spdx.OtherLicense{
LicenseIdentifier: SanitizeElementID(license),
ExtractedText: name,
LicenseIdentifier: license.id,
ExtractedText: license.value,
})
}
return result
Expand Down
6 changes: 2 additions & 4 deletions syft/formats/common/spdxhelpers/to_syft_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/anchore/packageurl-go"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/spdxlicense"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/file"
Expand Down Expand Up @@ -495,10 +496,7 @@ func parseSPDXLicenses(p *spdx.Package) []pkg.License {
}

func cleanSPDXID(id string) string {
if strings.HasPrefix(id, "LicenseRef-") {
return strings.TrimPrefix(id, "LicenseRef-")
}
return id
return strings.TrimPrefix(id, spdxlicense.LicenseRefPrefix)
}

//nolint:funlen
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
"name": "some/path",
"SPDXID": "SPDXRef-DocumentRoot-Directory-some-path",
"supplier": "NOASSERTION",
"downloadLocation": "",
"downloadLocation": "NOASSERTION",
"filesAnalyzed": false,
"primaryPackagePurpose": "FILE"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
"SPDXID": "SPDXRef-DocumentRoot-Image-user-image-input",
"versionInfo": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
"supplier": "NOASSERTION",
"downloadLocation": "",
"downloadLocation": "NOASSERTION",
"filesAnalyzed": false,
"checksums": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
"SPDXID": "SPDXRef-DocumentRoot-Image-user-image-input",
"versionInfo": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
"supplier": "NOASSERTION",
"downloadLocation": "",
"downloadLocation": "NOASSERTION",
"filesAnalyzed": false,
"checksums": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Created: redacted
PackageName: foobar/baz
SPDXID: SPDXRef-DocumentRoot-Directory-foobar-baz
PackageSupplier: NOASSERTION
PackageDownloadLocation: NOASSERTION
PrimaryPackagePurpose: FILE
FilesAnalyzed: false

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ PackageName: user-image-input
SPDXID: SPDXRef-DocumentRoot-Image-user-image-input
PackageVersion: sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368
PackageSupplier: NOASSERTION
PackageDownloadLocation: NOASSERTION
PrimaryPackagePurpose: CONTAINER
FilesAnalyzed: false
PackageChecksum: SHA256: 2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Created: redacted
PackageName: some/path
SPDXID: SPDXRef-DocumentRoot-Directory-some-path
PackageSupplier: NOASSERTION
PackageDownloadLocation: NOASSERTION
PrimaryPackagePurpose: FILE
FilesAnalyzed: false

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ PackageName: user-image-input
SPDXID: SPDXRef-DocumentRoot-Image-user-image-input
PackageVersion: sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368
PackageSupplier: NOASSERTION
PackageDownloadLocation: NOASSERTION
PrimaryPackagePurpose: CONTAINER
FilesAnalyzed: false
PackageChecksum: SHA256: 2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368
Expand Down
6 changes: 5 additions & 1 deletion test/cli/spdx_tooling_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,11 @@ func TestSpdxValidationTooling(t *testing.T) {

validateCmd := exec.Command("make", "validate", fileArg, mountArg, imageArg)
validateCmd.Dir = filepath.Join(cwd, "test-fixtures", "image-java-spdx-tools")
runAndShow(t, validateCmd)

stdout, stderr, err := runCommand(validateCmd, map[string]string{})
if err != nil {
t.Fatalf("invalid SPDX document:%v\nSTDOUT:\n%s\nSTDERR:\n%s", err, stdout, stderr)
}
})
}
}
Expand Down
2 changes: 1 addition & 1 deletion test/cli/test-fixtures/image-java-spdx-tools/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
FROM openjdk:11

RUN wget https://github.com/spdx/tools-java/releases/download/v1.1.3/tools-java-1.1.3.zip && \
RUN wget --no-verbose https://github.com/spdx/tools-java/releases/download/v1.1.3/tools-java-1.1.3.zip && \
unzip tools-java-1.1.3.zip && \
rm tools-java-1.1.3.zip

Expand Down
Loading