Skip to content

Commit

Permalink
Correctly handle license objects in SBOM generation
Browse files Browse the repository at this point in the history
As a means to resolve #6966, we can tweak the way we handle licenses,
where receiving a license object, instead of license string, results in
a malformed SPDX JSON SBOM.

While working on this, it was noted that CycloneDX also needed to be
amended, as it was omitting any license objects.

Closes #6966.
  • Loading branch information
jamietanna committed Nov 6, 2023
1 parent 002be85 commit e2134d5
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 5 deletions.
11 changes: 9 additions & 2 deletions lib/utils/sbom-cyclonedx.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,14 @@ const toCyclonedxItem = (node, { packageType }) => {

let parsedLicense
try {
parsedLicense = parseLicense(node.package?.license)
let license = node.package?.license
if (license) {
if (typeof license === 'object') {
license = license.type
}
}

parsedLicense = parseLicense(license)
} catch (err) {
parsedLicense = null
}
Expand Down Expand Up @@ -152,7 +159,7 @@ const toCyclonedxItem = (node, { packageType }) => {
// If license is a single SPDX license, use the license field
if (parsedLicense?.license) {
component.licenses = [{ license: { id: parsedLicense.license } }]
// If license is a conjunction, use the expression field
// If license is a conjunction, use the expression field
} else if (parsedLicense?.conjunction) {
component.licenses = [{ expression: node.package.license }]
}
Expand Down
9 changes: 8 additions & 1 deletion lib/utils/sbom-spdx.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ const toSpdxItem = (node, { packageType }) => {
location = node.linksIn.values().next().value.location
}

let license = node.package?.license
if (license) {
if (typeof license === 'object') {
license = license.type
}
}

const pkg = {
name: node.packageName,
SPDXID: toSpdxID(node),
Expand All @@ -103,7 +110,7 @@ const toSpdxItem = (node, { packageType }) => {
downloadLocation: (node.isLink ? undefined : node.resolved) || NO_ASSERTION,
filesAnalyzed: false,
homepage: node.package?.homepage || NO_ASSERTION,
licenseDeclared: node.package?.license || NO_ASSERTION,
licenseDeclared: license || NO_ASSERTION,
externalRefs: [
{
referenceCategory: REF_CAT_PACKAGE_MANAGER,
Expand Down
55 changes: 55 additions & 0 deletions tap-snapshots/test/lib/utils/sbom-cyclonedx.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -912,6 +912,61 @@ exports[`test/lib/utils/sbom-cyclonedx.js TAP single node - with license express
}
`

exports[`test/lib/utils/sbom-cyclonedx.js TAP single node - with license object > must match snapshot 1`] = `
{
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"serialNumber": "urn:uuid:00000000-0000-0000-0000-000000000000",
"version": 1,
"metadata": {
"timestamp": "2020-01-01T00:00:00.000Z",
"lifecycles": [
{
"phase": "build"
}
],
"tools": [
{
"vendor": "npm",
"name": "cli",
"version": "10.0.0 "
}
],
"component": {
"bom-ref": "root@1.0.0",
"type": "library",
"name": "root",
"version": "1.0.0",
"scope": "required",
"author": "Author",
"purl": "pkg:npm/root@1.0.0",
"properties": [
{
"name": "cdx:npm:package:path",
"value": ""
}
],
"externalReferences": [],
"licenses": [
{
"license": {
"id": "MIT"
}
}
]
}
},
"components": [],
"dependencies": [
{
"ref": "root@1.0.0",
"dependsOn": []
}
]
}
`

exports[`test/lib/utils/sbom-cyclonedx.js TAP single node - with repository url > must match snapshot 1`] = `
{
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
Expand Down
45 changes: 45 additions & 0 deletions tap-snapshots/test/lib/utils/sbom-spdx.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,51 @@ exports[`test/lib/utils/sbom-spdx.js TAP single node - with license expression >
}
`

exports[`test/lib/utils/sbom-spdx.js TAP single node - with license object > must match snapshot 1`] = `
{
"spdxVersion": "SPDX-2.3",
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "root@1.0.0",
"documentNamespace": "docns",
"creationInfo": {
"created": "2020-01-01T00:00:00.000Z",
"creators": [
"Tool: npm/cli-10.0.0 "
]
},
"documentDescribes": [
"SPDXRef-Package-root-1.0.0"
],
"packages": [
{
"name": "root",
"SPDXID": "SPDXRef-Package-root-1.0.0",
"versionInfo": "1.0.0",
"packageFileName": "",
"downloadLocation": "NOASSERTION",
"filesAnalyzed": false,
"homepage": "NOASSERTION",
"licenseDeclared": "MIT",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:npm/root@1.0.0"
}
]
}
],
"relationships": [
{
"spdxElementId": "SPDXRef-DOCUMENT",
"relatedSpdxElement": "SPDXRef-Package-root-1.0.0",
"relationshipType": "DESCRIBES"
}
]
}
`

exports[`test/lib/utils/sbom-spdx.js TAP single node - with single license > must match snapshot 1`] = `
{
"spdxVersion": "SPDX-2.3",
Expand Down
20 changes: 18 additions & 2 deletions test/lib/utils/sbom-cyclonedx.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,20 @@ t.test('single node - with license expression', t => {
t.end()
})

t.test('single node - with license object', t => {
const pkg = {
...rootPkg,
license: {
type: 'MIT',
url: 'http://github.com/kriskowal/q/raw/master/LICENSE',
},
}
const node = { ...root, package: pkg }
const res = cyclonedxOutput({ npm, nodes: [node] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})

t.test('single node - from git url', t => {
const node = { ...root, type: 'git', resolved: 'https://github.com/foo/bar#1234' }
const res = cyclonedxOutput({ npm, nodes: [node] })
Expand All @@ -205,13 +219,15 @@ t.test('single node - no package info', t => {
})

t.test('node - with deps', t => {
const node = { ...root,
const node = {
...root,
edgesOut: [
{ to: dep1 },
{ to: dep2 },
{ to: undefined },
{ to: { pkgid: 'foo' } },
] }
]

Check failure on line 229 in test/lib/utils/sbom-cyclonedx.js

View workflow job for this annotation

GitHub Actions / Lint

Missing trailing comma
}
const res = cyclonedxOutput({ npm, nodes: [node, dep1, dep2, dep2Link] })
t.matchSnapshot(JSON.stringify(res))
t.end()
Expand Down
14 changes: 14 additions & 0 deletions test/lib/utils/sbom-spdx.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,20 @@ t.test('single node - with single license', t => {
t.end()
})

t.test('single node - with license object', t => {
const pkg = {
...rootPkg,
license: {
type: 'MIT',
url: 'http://github.com/kriskowal/q/raw/master/LICENSE',
},
}
const node = { ...root, package: pkg }
const res = spdxOutput({ npm, nodes: [node] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})

t.test('single node - with license expression', t => {
const pkg = { ...rootPkg, license: '(MIT OR Apache-2.0)' }
const node = { ...root, package: pkg }
Expand Down

0 comments on commit e2134d5

Please sign in to comment.