feat: update syft license concept to complex struct (#1743)

this PR makes the following changes to update the underlying license model to have more expressive capabilities
it also provides some guarantee's surrounding the license values themselves

- Licenses are updated from string -> pkg.LicenseSet which contain pkg.License with the following fields:
- original `Value` read by syft
- If it's possible to construct licenses will always have a valid SPDX expression for downstream consumption
- the above is run against a generated list of SPDX license ID to try and find the correct ID
- SPDX concluded vs declared is added to the new struct
- URL source for license is added to the new struct
- Location source is added to the new struct to show where the expression was pulled from
This commit is contained in:
Christopher Angelo Phillips
2023-05-15 16:23:39 -04:00
committed by GitHub
parent 8046f09562
commit 42fa9e4965
126 changed files with 5165 additions and 1503 deletions
+5 -4
View File
@@ -8,23 +8,24 @@ import (
"github.com/anchore/syft/syft/source"
)
func newComposerLockPackage(m pkg.PhpComposerJSONMetadata, location ...source.Location) pkg.Package {
func newComposerLockPackage(m parsedData, indexLocation source.Location) pkg.Package {
p := pkg.Package{
Name: m.Name,
Version: m.Version,
Locations: source.NewLocationSet(location...),
Locations: source.NewLocationSet(indexLocation),
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(indexLocation, m.License...)...),
PURL: packageURL(m),
Language: pkg.PHP,
Type: pkg.PhpComposerPkg,
MetadataType: pkg.PhpComposerJSONMetadataType,
Metadata: m,
Metadata: m.PhpComposerJSONMetadata,
}
p.SetID()
return p
}
func packageURL(m pkg.PhpComposerJSONMetadata) string {
func packageURL(m parsedData) string {
var name, vendor string
fields := strings.Split(m.Name, "/")
switch len(fields) {
+19 -10
View File
@@ -11,30 +11,39 @@ import (
func Test_packageURL(t *testing.T) {
tests := []struct {
name string
metadata pkg.PhpComposerJSONMetadata
metadata parsedData
expected string
}{
{
name: "with extractable vendor",
metadata: pkg.PhpComposerJSONMetadata{
Name: "ven/name",
Version: "1.0.1",
metadata: parsedData{
[]string{},
pkg.PhpComposerJSONMetadata{
Version: "1.0.1",
Name: "ven/name",
},
},
expected: "pkg:composer/ven/name@1.0.1",
},
{
name: "name with slashes (invalid)",
metadata: pkg.PhpComposerJSONMetadata{
Name: "ven/name/component",
Version: "1.0.1",
metadata: parsedData{
[]string{},
pkg.PhpComposerJSONMetadata{
Name: "ven/name/component",
Version: "1.0.1",
},
},
expected: "pkg:composer/ven/name-component@1.0.1",
},
{
name: "unknown vendor",
metadata: pkg.PhpComposerJSONMetadata{
Name: "name",
Version: "1.0.1",
metadata: parsedData{
[]string{},
pkg.PhpComposerJSONMetadata{
Name: "name",
Version: "1.0.1",
},
},
expected: "pkg:composer/name@1.0.1",
},
+12 -2
View File
@@ -14,9 +14,14 @@ import (
var _ generic.Parser = parseComposerLock
type parsedData struct {
License []string `json:"license"`
pkg.PhpComposerJSONMetadata
}
type composerLock struct {
Packages []pkg.PhpComposerJSONMetadata `json:"packages"`
PackageDev []pkg.PhpComposerJSONMetadata `json:"packages-dev"`
Packages []parsedData `json:"packages"`
PackageDev []parsedData `json:"packages-dev"`
}
// parseComposerLock is a parser function for Composer.lock contents, returning "Default" php packages discovered.
@@ -40,6 +45,11 @@ func parseComposerLock(_ source.FileResolver, _ *generic.Environment, reader sou
),
)
}
// TODO: did we omit this on purpose?
// for _, m := range lock.PackageDev {
// pkgs = append(pkgs, newComposerLockPackage(m, reader.Location))
//}
}
return pkgs, nil, nil
@@ -15,10 +15,13 @@ func TestParseComposerFileLock(t *testing.T) {
locations := source.NewLocationSet(source.NewLocation(fixture))
expectedPkgs := []pkg.Package{
{
Name: "adoy/fastcgi-client",
Version: "1.0.2",
PURL: "pkg:composer/adoy/fastcgi-client@1.0.2",
Locations: locations,
Name: "adoy/fastcgi-client",
Version: "1.0.2",
PURL: "pkg:composer/adoy/fastcgi-client@1.0.2",
Locations: locations,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("MIT", source.NewLocation(fixture)),
),
Language: pkg.PHP,
Type: pkg.PhpComposerPkg,
MetadataType: pkg.PhpComposerJSONMetadataType,
@@ -37,9 +40,6 @@ func TestParseComposerFileLock(t *testing.T) {
},
Type: "library",
NotificationURL: "https://packagist.org/downloads/",
License: []string{
"MIT",
},
Authors: []pkg.PhpComposerAuthors{
{
Name: "Pierrick Charron",
@@ -55,11 +55,14 @@ func TestParseComposerFileLock(t *testing.T) {
},
},
{
Name: "alcaeus/mongo-php-adapter",
Version: "1.1.11",
Locations: locations,
PURL: "pkg:composer/alcaeus/mongo-php-adapter@1.1.11",
Language: pkg.PHP,
Name: "alcaeus/mongo-php-adapter",
Version: "1.1.11",
Locations: locations,
PURL: "pkg:composer/alcaeus/mongo-php-adapter@1.1.11",
Language: pkg.PHP,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("MIT", source.NewLocation(fixture)),
),
Type: pkg.PhpComposerPkg,
MetadataType: pkg.PhpComposerJSONMetadataType,
Metadata: pkg.PhpComposerJSONMetadata{
@@ -91,9 +94,6 @@ func TestParseComposerFileLock(t *testing.T) {
},
Type: "library",
NotificationURL: "https://packagist.org/downloads/",
License: []string{
"MIT",
},
Authors: []pkg.PhpComposerAuthors{
{
Name: "alcaeus",
@@ -16,19 +16,19 @@ var _ generic.Parser = parseComposerLock
// Note: composer version 2 introduced a new structure for the installed.json file, so we support both
type installedJSONComposerV2 struct {
Packages []pkg.PhpComposerJSONMetadata `json:"packages"`
Packages []parsedData `json:"packages"`
}
func (w *installedJSONComposerV2) UnmarshalJSON(data []byte) error {
type compv2 struct {
Packages []pkg.PhpComposerJSONMetadata `json:"packages"`
Packages []parsedData `json:"packages"`
}
compv2er := new(compv2)
err := json.Unmarshal(data, &compv2er)
if err != nil {
// If we had an err or, we may be dealing with a composer v.1 installed.json
// which should be all arrays
var packages []pkg.PhpComposerJSONMetadata
var packages []parsedData
err := json.Unmarshal(data, &packages)
if err != nil {
return err
@@ -24,6 +24,9 @@ func TestParseInstalledJsonComposerV1(t *testing.T) {
Language: pkg.PHP,
Type: pkg.PhpComposerPkg,
MetadataType: pkg.PhpComposerJSONMetadataType,
Licenses: pkg.NewLicenseSet(
pkg.NewLicense("MIT"),
),
Metadata: pkg.PhpComposerJSONMetadata{
Name: "asm89/stack-cors",
Version: "1.3.0",
@@ -49,9 +52,6 @@ func TestParseInstalledJsonComposerV1(t *testing.T) {
Time: "2019-12-24T22:41:47+00:00",
Type: "library",
NotificationURL: "https://packagist.org/downloads/",
License: []string{
"MIT",
},
Authors: []pkg.PhpComposerAuthors{
{
Name: "Alexander",
@@ -68,11 +68,14 @@ func TestParseInstalledJsonComposerV1(t *testing.T) {
},
},
{
Name: "behat/mink",
Version: "v1.8.1",
PURL: "pkg:composer/behat/mink@v1.8.1",
Language: pkg.PHP,
Type: pkg.PhpComposerPkg,
Name: "behat/mink",
Version: "v1.8.1",
PURL: "pkg:composer/behat/mink@v1.8.1",
Language: pkg.PHP,
Type: pkg.PhpComposerPkg,
Licenses: pkg.NewLicenseSet(
pkg.NewLicense("MIT"),
),
MetadataType: pkg.PhpComposerJSONMetadataType,
Metadata: pkg.PhpComposerJSONMetadata{
Name: "behat/mink",
@@ -106,9 +109,6 @@ func TestParseInstalledJsonComposerV1(t *testing.T) {
Time: "2020-03-11T15:45:53+00:00",
Type: "library",
NotificationURL: "https://packagist.org/downloads/",
License: []string{
"MIT",
},
Authors: []pkg.PhpComposerAuthors{
{
Name: "Konstantin Kudryashov",
@@ -133,9 +133,14 @@ func TestParseInstalledJsonComposerV1(t *testing.T) {
locations := source.NewLocationSet(source.NewLocation(fixture))
for i := range expectedPkgs {
expectedPkgs[i].Locations = locations
locationLicenses := pkg.NewLicenseSet()
for _, license := range expectedPkgs[i].Licenses.ToSlice() {
license.Location = locations
locationLicenses.Add(license)
}
expectedPkgs[i].Licenses = locationLicenses
}
pkgtest.TestFileParser(t, fixture, parseInstalledJSON, expectedPkgs, expectedRelationships)
})
}
}