Skip to content

Commit 6992c2f

Browse files
authored
Merge branch 'main' into rust
Signed-off-by: Adva Oren <aoren@redhat.com>
2 parents e570866 + cb4ae28 commit 6992c2f

3 files changed

Lines changed: 61 additions & 14 deletions

File tree

src/license/licenses_api.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* @see https://github.com/guacsec/trustify-da-api-spec/blob/main/api/v5/openapi.yaml
66
*/
77

8+
import { PackageURL } from 'packageurl-js';
89
import { selectTrustifyDABackend } from '../index.js';
910
import { addProxyAgent, getTokenHeaders } from '../tools.js';
1011

@@ -42,6 +43,11 @@ export async function getLicenseDetails(spdxId, opts = {}) {
4243
}
4344
}
4445

46+
function normalizePurlString(purl) {
47+
const parsed = PackageURL.fromString(purl);
48+
return new PackageURL(parsed.type, parsed.namespace, parsed.name, parsed.version, null, null).toString();
49+
}
50+
4551
/**
4652
* Normalize the LicensesResponse shape (array of LicenseProviderResult) into a map of purl -> license info.
4753
* Each provider result has { status, summary, packages } where packages is { [purl]: { concluded, evidence } }.
@@ -55,6 +61,8 @@ export function normalizeLicensesResponse(data, purls = []) {
5561
const map = new Map();
5662
if (!data || !Array.isArray(data)) {return map;}
5763

64+
const normalizedPurlsSet = purls.length > 0 ? new Set(purls.map(normalizePurlString)) : null;
65+
5866
for (const providerResult of data) {
5967
const packages = providerResult?.packages;
6068
if (!packages || typeof packages !== 'object') {continue;}
@@ -64,8 +72,9 @@ export function normalizeLicensesResponse(data, purls = []) {
6472
const expression = concluded?.expression;
6573
const licenses = identifiers.length > 0 ? identifiers : (expression ? [expression] : []);
6674
const category = concluded?.category; // PERMISSIVE | WEAK_COPYLEFT | STRONG_COPYLEFT | UNKNOWN
67-
if (purls.length === 0 || purls.includes(purl)) {
68-
map.set(purl, { licenses: licenses.filter(Boolean), category });
75+
const normalizedPurl = normalizePurlString(purl);
76+
if (normalizedPurlsSet === null || normalizedPurlsSet.has(normalizedPurl)) {
77+
map.set(normalizedPurl, { licenses: licenses.filter(Boolean), category });
6978
}
7079
}
7180
// Use first provider that has packages; backend may return multiple (e.g. deps.dev)

src/license/project_license.js

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,3 @@ export async function identifyLicense(licenseFilePath, opts = {}) {
7272
return null; // Fallback to local detection on error
7373
}
7474
}
75-
76-
/**
77-
* Get license for SBOM root component with fallback to LICENSE file.
78-
* Priority: manifest license > LICENSE file > null
79-
* This is used by providers to populate the license field in the SBOM.
80-
* @param {string} manifestPath - path to manifest
81-
* @returns {string|null} - SPDX identifier or null
82-
*/
83-
export function getLicenseForSbom(manifestPath) {
84-
const { fromManifest, fromFile } = getProjectLicense(manifestPath);
85-
return fromManifest || fromFile || null;
86-
}

test/providers/license.test.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,56 @@ import Javascript_pnpm from '../../src/providers/javascript_pnpm.js'
1111
import Javascript_yarn from '../../src/providers/javascript_yarn.js'
1212
import pythonPipProvider from '../../src/providers/python_pip.js'
1313
import rustCargoProvider from '../../src/providers/rust_cargo.js'
14+
import { normalizeLicensesResponse } from '../../src/license/licenses_api.js'
15+
16+
suite('normalizeLicensesResponse', () => {
17+
const lgpl = { id: 'LGPL-2.1', name: 'GNU Lesser General Public License v2.1 only', category: 'WEAK_COPYLEFT' }
18+
const apache = { id: 'Apache-2.0', name: 'Apache License 2.0', category: 'PERMISSIVE' }
19+
20+
const backendResponse = [
21+
{
22+
status: { ok: true, name: 'deps.dev' },
23+
packages: {
24+
// backend returns purls with ?scope=compile qualifier
25+
'pkg:maven/org.mariadb.jdbc/mariadb-java-client@3.1.4?scope=compile': {
26+
concluded: { identifiers: [lgpl], expression: 'LGPL-2.1', category: 'WEAK_COPYLEFT' }
27+
},
28+
'pkg:maven/javassist/javassist@3.12.1.GA?scope=compile': {
29+
concluded: { identifiers: [lgpl], expression: 'LGPL-2.1', category: 'WEAK_COPYLEFT' }
30+
},
31+
'pkg:maven/commons-collections/commons-collections@3.2.1': {
32+
concluded: { identifiers: [apache], expression: 'Apache-2.0', category: 'PERMISSIVE' }
33+
}
34+
}
35+
}
36+
]
37+
38+
// SBOM purls have no qualifier
39+
const sbomPurls = [
40+
'pkg:maven/org.mariadb.jdbc/mariadb-java-client@3.1.4',
41+
'pkg:maven/javassist/javassist@3.12.1.GA',
42+
'pkg:maven/commons-collections/commons-collections@3.2.1'
43+
]
44+
45+
test('matches backend purls with ?scope=compile qualifier against plain SBOM purls', () => {
46+
const map = normalizeLicensesResponse(backendResponse, sbomPurls)
47+
expect(map.size).to.equal(3)
48+
})
49+
50+
test('stores purl without qualifier as map key', () => {
51+
const map = normalizeLicensesResponse(backendResponse, sbomPurls)
52+
expect(map.has('pkg:maven/org.mariadb.jdbc/mariadb-java-client@3.1.4')).to.be.true
53+
expect(map.has('pkg:maven/javassist/javassist@3.12.1.GA')).to.be.true
54+
expect(map.has('pkg:maven/org.mariadb.jdbc/mariadb-java-client@3.1.4?scope=compile')).to.be.false
55+
})
56+
57+
test('preserves correct license category for qualifier-stripped purls', () => {
58+
const map = normalizeLicensesResponse(backendResponse, sbomPurls)
59+
expect(map.get('pkg:maven/org.mariadb.jdbc/mariadb-java-client@3.1.4').category).to.equal('WEAK_COPYLEFT')
60+
expect(map.get('pkg:maven/javassist/javassist@3.12.1.GA').category).to.equal('WEAK_COPYLEFT')
61+
expect(map.get('pkg:maven/commons-collections/commons-collections@3.2.1').category).to.equal('PERMISSIVE')
62+
})
63+
})
1464

1565
suite('testing readLicenseFromManifest with existing test manifests', () => {
1666

0 commit comments

Comments
 (0)