From c56d3b5eef9d5de4617953ac9647b97165df9725 Mon Sep 17 00:00:00 2001 From: cpendery <35637443+cpendery@users.noreply.github.com> Date: Mon, 22 Aug 2022 08:23:44 -0700 Subject: [PATCH] feat: add support for licenses in package-lock json v2 (#1164) --- syft/pkg/cataloger/javascript/cataloger.go | 2 +- .../javascript/parse_package_lock.go | 26 ++++++- .../javascript/parse_package_lock_test.go | 44 ++++++++++++ .../pkg-lock/package-lock-2.json | 71 +++++++++++++++++++ 4 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 syft/pkg/cataloger/javascript/test-fixtures/pkg-lock/package-lock-2.json diff --git a/syft/pkg/cataloger/javascript/cataloger.go b/syft/pkg/cataloger/javascript/cataloger.go index 3853c51f8905..cefbaccb5703 100644 --- a/syft/pkg/cataloger/javascript/cataloger.go +++ b/syft/pkg/cataloger/javascript/cataloger.go @@ -77,7 +77,7 @@ func addLicenses(resolver source.FileResolver, location source.Location, p *pkg. return nil } - p.Licenses = licenses + p.Licenses = append(p.Licenses, licenses...) } return nil diff --git a/syft/pkg/cataloger/javascript/parse_package_lock.go b/syft/pkg/cataloger/javascript/parse_package_lock.go index 30c2897b02ce..5b98135bc6aa 100644 --- a/syft/pkg/cataloger/javascript/parse_package_lock.go +++ b/syft/pkg/cataloger/javascript/parse_package_lock.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "io" + "strings" "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/pkg" @@ -18,6 +19,7 @@ type PackageLock struct { Requires bool `json:"requires"` LockfileVersion int `json:"lockfileVersion"` Dependencies map[string]Dependency + Packages map[string]Package } // Dependency represents a single package dependency listed in the package.lock json file @@ -25,7 +27,13 @@ type Dependency struct { Version string `json:"version"` Resolved string `json:"resolved"` Integrity string `json:"integrity"` - Requires map[string]string +} + +type Package struct { + Version string `json:"version"` + Resolved string `json:"resolved"` + Integrity string `json:"integrity"` + License string `json:""` } // parsePackageLock parses a package-lock.json and returns the discovered JavaScript packages. @@ -46,12 +54,28 @@ func parsePackageLock(path string, reader io.Reader) ([]*pkg.Package, []artifact } else if err != nil { return nil, nil, fmt.Errorf("failed to parse package-lock.json file: %w", err) } + licenseMap := make(map[string]string) + for _, pkgMeta := range lock.Packages { + var sb strings.Builder + sb.WriteString(pkgMeta.Resolved) + sb.WriteString(pkgMeta.Integrity) + licenseMap[sb.String()] = pkgMeta.License + } + for name, pkgMeta := range lock.Dependencies { + var sb strings.Builder + sb.WriteString(pkgMeta.Resolved) + sb.WriteString(pkgMeta.Integrity) + var licenses []string + if license, exists := licenseMap[sb.String()]; exists { + licenses = append(licenses, license) + } packages = append(packages, &pkg.Package{ Name: name, Version: pkgMeta.Version, Language: pkg.JavaScript, Type: pkg.NpmPkg, + Licenses: licenses, }) } } diff --git a/syft/pkg/cataloger/javascript/parse_package_lock_test.go b/syft/pkg/cataloger/javascript/parse_package_lock_test.go index a809441d9a29..c280a61cb517 100644 --- a/syft/pkg/cataloger/javascript/parse_package_lock_test.go +++ b/syft/pkg/cataloger/javascript/parse_package_lock_test.go @@ -111,3 +111,47 @@ func TestParsePackageLock(t *testing.T) { assertPkgsEqual(t, actual, expected) } + +func TestParsePackageLockV2(t *testing.T) { + expected := map[string]pkg.Package{ + "@types/prop-types": { + Name: "@types/prop-types", + Version: "15.7.5", + Language: pkg.JavaScript, + Type: pkg.NpmPkg, + Licenses: []string{"MIT"}, + }, + "@types/react": { + Name: "@types/prop-types", + Version: "18.0.17", + Language: pkg.JavaScript, + Type: pkg.NpmPkg, + Licenses: []string{"MIT"}, + }, + "@types/scheduler": { + Name: "@types/scheduler", + Version: "0.16.2", + Language: pkg.JavaScript, + Type: pkg.NpmPkg, + Licenses: []string{"MIT"}, + }, + "csstype": { + Name: "csstype", + Version: "3.1.0", + Language: pkg.JavaScript, + Type: pkg.NpmPkg, + Licenses: []string{"MIT"}, + }, + } + fixture, err := os.Open("test-fixtures/pkg-lock/package-lock-2.json") + if err != nil { + t.Fatalf("failed to open fixture: %+v", err) + } + + actual, _, err := parsePackageLock(fixture.Name(), fixture) + if err != nil { + t.Fatalf("failed to parse package-lock.json: %+v", err) + } + + assertPkgsEqual(t, actual, expected) +} diff --git a/syft/pkg/cataloger/javascript/test-fixtures/pkg-lock/package-lock-2.json b/syft/pkg/cataloger/javascript/test-fixtures/pkg-lock/package-lock-2.json new file mode 100644 index 000000000000..2373f27ca0ac --- /dev/null +++ b/syft/pkg/cataloger/javascript/test-fixtures/pkg-lock/package-lock-2.json @@ -0,0 +1,71 @@ +{ + "name": "npm", + "version": "6.14.6", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "npm", + "version": "6.14.6", + "dependencies": { + "@types/react": "^18.0.9" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha1-XxnSuFqY6VWANvajysyIGUIPBc8=", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.0.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.17.tgz", + "integrity": "sha1-RYPZwyLWfv5LOak10iPtzHBQzPQ=", + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha1-GmL4lSVyPd4kuhsBsJK/XfitTTk=", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", + "integrity": "sha1-TdysNxjXh8+d8NG30VAzklyPKfI=", + "license": "MIT" + } + }, + "dependencies": { + "@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha1-XxnSuFqY6VWANvajysyIGUIPBc8=" + }, + "@types/react": { + "version": "18.0.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.17.tgz", + "integrity": "sha1-RYPZwyLWfv5LOak10iPtzHBQzPQ=", + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha1-GmL4lSVyPd4kuhsBsJK/XfitTTk=" + }, + "csstype": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", + "integrity": "sha1-TdysNxjXh8+d8NG30VAzklyPKfI=" + } + } +}