diff --git a/lib/utils/sbom-cyclonedx.js b/lib/utils/sbom-cyclonedx.js index 0cd170fbbcbee..f8283397989d5 100644 --- a/lib/utils/sbom-cyclonedx.js +++ b/lib/utils/sbom-cyclonedx.js @@ -84,15 +84,20 @@ const toCyclonedxItem = (node, { packageType }) => { node.package = toNormalize.content } - let parsedLicense - try { - let license = node.package?.license - if (license) { - if (typeof license === 'object') { - license = license.type - } + let license = node.package?.license + if (license) { + if (typeof license === 'object') { + license = license.type } + } else if (Array.isArray(node.package?.licenses)) { + license = node.package.licenses + .map(l => (typeof l === 'object' ? l.type : l)) + .filter(Boolean) + .join(' OR ') + } + let parsedLicense + try { parsedLicense = parseLicense(license) } catch { parsedLicense = null @@ -158,7 +163,7 @@ const toCyclonedxItem = (node, { packageType }) => { component.licenses = [{ license: { id: parsedLicense.license } }] // If license is a conjunction, use the expression field } else if (parsedLicense?.conjunction) { - component.licenses = [{ expression: node.package.license }] + component.licenses = [{ expression: license }] } return component diff --git a/lib/utils/sbom-spdx.js b/lib/utils/sbom-spdx.js index 074a6f57f98bf..38824f263681d 100644 --- a/lib/utils/sbom-spdx.js +++ b/lib/utils/sbom-spdx.js @@ -110,6 +110,11 @@ const toSpdxItem = (node, { packageType }) => { if (typeof license === 'object') { license = license.type } + } else if (Array.isArray(node.package?.licenses)) { + license = node.package.licenses + .map(l => (typeof l === 'object' ? l.type : l)) + .filter(Boolean) + .join(' OR ') } const pkg = { diff --git a/tap-snapshots/test/lib/utils/sbom-cyclonedx.js.test.cjs b/tap-snapshots/test/lib/utils/sbom-cyclonedx.js.test.cjs index 2f0af32f7f501..8bc81cc4f69c1 100644 --- a/tap-snapshots/test/lib/utils/sbom-cyclonedx.js.test.cjs +++ b/tap-snapshots/test/lib/utils/sbom-cyclonedx.js.test.cjs @@ -833,6 +833,154 @@ exports[`test/lib/utils/sbom-cyclonedx.js TAP single node - with issue tracker > } ` +exports[`test/lib/utils/sbom-cyclonedx.js TAP single node - with legacy licenses array (multiple) > 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": [], + "externalReferences": [], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ] + } + }, + "components": [], + "dependencies": [ + { + "ref": "root@1.0.0", + "dependsOn": [] + } + ] +} +` + +exports[`test/lib/utils/sbom-cyclonedx.js TAP single node - with legacy licenses array (single) > 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": [], + "externalReferences": [], + "licenses": [ + { + "license": { + "id": "MIT" + } + } + ] + } + }, + "components": [], + "dependencies": [ + { + "ref": "root@1.0.0", + "dependsOn": [] + } + ] +} +` + +exports[`test/lib/utils/sbom-cyclonedx.js TAP single node - with legacy licenses array (string entries) > 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": [], + "externalReferences": [], + "licenses": [ + { + "license": { + "id": "MIT" + } + } + ] + } + }, + "components": [], + "dependencies": [ + { + "ref": "root@1.0.0", + "dependsOn": [] + } + ] +} +` + exports[`test/lib/utils/sbom-cyclonedx.js TAP single node - with license expression > must match snapshot 1`] = ` { "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", diff --git a/tap-snapshots/test/lib/utils/sbom-spdx.js.test.cjs b/tap-snapshots/test/lib/utils/sbom-spdx.js.test.cjs index 3583c0bc83577..26931d78124a7 100644 --- a/tap-snapshots/test/lib/utils/sbom-spdx.js.test.cjs +++ b/tap-snapshots/test/lib/utils/sbom-spdx.js.test.cjs @@ -594,6 +594,141 @@ exports[`test/lib/utils/sbom-spdx.js TAP single node - with integrity > must mat } ` +exports[`test/lib/utils/sbom-spdx.js TAP single node - with legacy licenses array (multiple) > 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 OR Apache-2.0", + "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 legacy licenses array (single) > 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 legacy licenses array (string entries) > 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 license expression > must match snapshot 1`] = ` { "spdxVersion": "SPDX-2.3", diff --git a/test/lib/utils/sbom-cyclonedx.js b/test/lib/utils/sbom-cyclonedx.js index a40831c9fa05a..fc30130f1fae7 100644 --- a/test/lib/utils/sbom-cyclonedx.js +++ b/test/lib/utils/sbom-cyclonedx.js @@ -181,6 +181,53 @@ t.test('single node - with single license', t => { t.end() }) +t.test('single node - with legacy licenses array (single)', t => { + const pkg = { + ...rootPkg, + licenses: [ + { + type: 'MIT', + url: 'http://opensource.org/licenses/mit-license.php', + }, + ], + } + const node = { ...root, package: pkg } + const res = cyclonedxOutput({ npm, nodes: [node] }) + t.matchSnapshot(JSON.stringify(res)) + t.end() +}) + +t.test('single node - with legacy licenses array (multiple)', t => { + const pkg = { + ...rootPkg, + licenses: [ + { + type: 'MIT', + url: 'http://opensource.org/licenses/mit-license.php', + }, + { + type: 'Apache-2.0', + url: 'http://opensource.org/licenses/apache2.0.php', + }, + ], + } + const node = { ...root, package: pkg } + const res = cyclonedxOutput({ npm, nodes: [node] }) + t.matchSnapshot(JSON.stringify(res)) + t.end() +}) + +t.test('single node - with legacy licenses array (string entries)', t => { + const pkg = { + ...rootPkg, + licenses: ['MIT'], + } + const node = { ...root, package: pkg } + const res = cyclonedxOutput({ 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 } diff --git a/test/lib/utils/sbom-spdx.js b/test/lib/utils/sbom-spdx.js index 68b102854315e..cdeb68218ee33 100644 --- a/test/lib/utils/sbom-spdx.js +++ b/test/lib/utils/sbom-spdx.js @@ -131,6 +131,53 @@ t.test('single node - with license object', t => { t.end() }) +t.test('single node - with legacy licenses array (single)', t => { + const pkg = { + ...rootPkg, + licenses: [ + { + type: 'MIT', + url: 'http://opensource.org/licenses/mit-license.php', + }, + ], + } + const node = { ...root, package: pkg } + const res = spdxOutput({ npm, nodes: [node] }) + t.matchSnapshot(JSON.stringify(res)) + t.end() +}) + +t.test('single node - with legacy licenses array (multiple)', t => { + const pkg = { + ...rootPkg, + licenses: [ + { + type: 'MIT', + url: 'http://opensource.org/licenses/mit-license.php', + }, + { + type: 'Apache-2.0', + url: 'http://opensource.org/licenses/apache2.0.php', + }, + ], + } + const node = { ...root, package: pkg } + const res = spdxOutput({ npm, nodes: [node] }) + t.matchSnapshot(JSON.stringify(res)) + t.end() +}) + +t.test('single node - with legacy licenses array (string entries)', t => { + const pkg = { + ...rootPkg, + licenses: ['MIT'], + } + 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 }