Skip to content

Commit 3954b0f

Browse files
feat(CSAF2.1): #340 add mandatory test 6.1.54
1 parent c8d301a commit 3954b0f

File tree

7 files changed

+217
-2
lines changed

7 files changed

+217
-2
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,6 @@ The following tests are not yet implemented and therefore missing:
335335
- Mandatory Test 6.1.51
336336
- Mandatory Test 6.1.52
337337
- Mandatory Test 6.1.53
338-
- Mandatory Test 6.1.54
339338
- Mandatory Test 6.1.55
340339
341340
**Recommended Tests**
@@ -435,6 +434,7 @@ export const mandatoryTest_6_1_38: DocumentTest
435434
export const mandatoryTest_6_1_39: DocumentTest
436435
export const mandatoryTest_6_1_40: DocumentTest
437436
export const mandatoryTest_6_1_41: DocumentTest
437+
export const mandatoryTest_6_1_54: DocumentTest
438438
```
439439
440440
[(back to top)](#bsi-csaf-validator-lib)

csaf_2_1/mandatoryTests.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,4 @@ export { mandatoryTest_6_1_38 } from './mandatoryTests/mandatoryTests_6_1_38.js'
4848
export { mandatoryTest_6_1_39 } from './mandatoryTests/mandatoryTest_6_1_39.js'
4949
export { mandatoryTest_6_1_40 } from './mandatoryTests/mandatoryTest_6_1_40.js'
5050
export { mandatoryTest_6_1_41 } from './mandatoryTests/mandatoryTest_6_1_41.js'
51+
export { mandatoryTest_6_1_54 } from './mandatoryTests/mandatoryTest_6_1_54.js'
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import Ajv from 'ajv/dist/jtd.js'
2+
import { validate, parse } from 'license-expressions'
3+
4+
const ajv = new Ajv()
5+
6+
/*
7+
This is the jtd schema that needs to match the input document so that the
8+
test is activated. If this schema doesn't match it normally means that the input
9+
document does not validate against the csaf json schema or optional fields that
10+
the test checks are not present.
11+
*/
12+
const inputSchema = /** @type {const} */ ({
13+
additionalProperties: true,
14+
properties: {
15+
document: {
16+
additionalProperties: true,
17+
properties: {
18+
license_expression: {
19+
type: 'string',
20+
},
21+
},
22+
},
23+
},
24+
})
25+
26+
const validateSchema = ajv.compile(inputSchema)
27+
28+
/**
29+
* Recursively checks if a parsed license expression contains any license references.
30+
*
31+
* @param {import('license-expressions').ParsedSpdxExpression} parsedExpression - The parsed license expression
32+
* @returns {boolean} True if the expression contains any license references, false otherwise
33+
*/
34+
function containsLicenseRef(parsedExpression) {
35+
// If it's a LicenseRef type directly
36+
if ('documentRef' in parsedExpression && parsedExpression.documentRef) {
37+
return true
38+
}
39+
40+
// If it's a conjunction, check both sides
41+
if ('conjunction' in parsedExpression) {
42+
return (
43+
containsLicenseRef(parsedExpression.left) ||
44+
containsLicenseRef(parsedExpression.right)
45+
)
46+
}
47+
48+
// If it's a LicenseInfo type, it doesn't contain a document reference
49+
return false
50+
}
51+
52+
/**
53+
* Checks if a license expression contains any document references.
54+
*
55+
* @param {string} licenseToCheck - The license expression to check
56+
* @returns {boolean} True if the license expression contains any document references, false otherwise
57+
*/
58+
export function hasDocumentRef(licenseToCheck) {
59+
const parseResult = parse(licenseToCheck)
60+
return containsLicenseRef(parseResult)
61+
}
62+
63+
/**
64+
* Checks if a license expression is valid, according to SPDX standards.
65+
*
66+
* @param {string} licenseToCheck - The license expression to check
67+
* @returns {boolean} True if the license is valid, false otherwise
68+
*/
69+
export function isValidLicenseExpression(licenseToCheck) {
70+
return (
71+
!licenseToCheck ||
72+
(validate(licenseToCheck).valid && !hasDocumentRef(licenseToCheck))
73+
)
74+
}
75+
76+
/**
77+
* It MUST be tested that the license expression is valid.
78+
*
79+
* @param {unknown} doc
80+
*/
81+
export function mandatoryTest_6_1_54(doc) {
82+
/*
83+
The `ctx` variable holds the state that is accumulated during the test ran and is
84+
finally returned by the function.
85+
*/
86+
const ctx = {
87+
errors:
88+
/** @type {Array<{ instancePath: string; message: string }>} */ ([]),
89+
isValid: true,
90+
}
91+
92+
if (!validateSchema(doc)) {
93+
return ctx
94+
}
95+
96+
const licenseToCheck = doc.document.license_expression
97+
if (!isValidLicenseExpression(licenseToCheck)) {
98+
ctx.isValid = false
99+
ctx.errors.push({
100+
instancePath: '/document/license_expression',
101+
message: `Invalid license expression: '${licenseToCheck}'`,
102+
})
103+
}
104+
105+
return ctx
106+
}

package-lock.json

Lines changed: 45 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"bcp47": "^1.1.2",
2828
"cvss2js": "^1.1.0",
2929
"json-pointer": "^0.6.1",
30+
"license-expressions": "^0.7.3",
3031
"lodash": "^4.17.21",
3132
"packageurl-js": "^2.0.1",
3233
"semver": "^7.5.4",
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import {
2+
isValidLicenseExpression,
3+
mandatoryTest_6_1_54,
4+
} from '../../csaf_2_1/mandatoryTests/mandatoryTest_6_1_54.js'
5+
import { expect } from 'chai'
6+
7+
describe('mandatoryTest_6_1_54', function () {
8+
it('only runs on relevant documents', function () {
9+
expect(mandatoryTest_6_1_54({ document: 'mydoc' }).isValid).to.be.true
10+
})
11+
12+
it('check license expressions', function () {
13+
expect(isValidLicenseExpression('GPL-3.0+')).to.be.true
14+
expect(isValidLicenseExpression('GPL-3.0-only')).to.be.true
15+
expect(isValidLicenseExpression('MIT OR (Apache-2.0 AND 0BSD)')).to.be.true
16+
expect(isValidLicenseExpression('Invalid-license-expression')).to.be.false
17+
expect(isValidLicenseExpression('GPL-2.0 OR BSD-3-Clause')).to.be.true
18+
expect(isValidLicenseExpression('LGPL-2.1 OR BSD-3-Clause AND MIT')).to.be
19+
.true
20+
expect(isValidLicenseExpression('(MIT AND (LGPL-2.1+ AND BSD-3-Clause))'))
21+
.to.be.true
22+
// Exception associated with unrelated license:
23+
expect(
24+
isValidLicenseExpression('MIT OR Apache-2.0 WITH Autoconf-exception-2.0'),
25+
'Exception associated with unrelated license'
26+
).to.be.false
27+
expect(
28+
isValidLicenseExpression('3dslicer-1.0'),
29+
'SPDX License List matching guidelines'
30+
).to.be.true
31+
32+
expect(
33+
isValidLicenseExpression('LicenseRef-www.example.com-no-work-pd'),
34+
'Valid SPDX expression with License Ref'
35+
).to.be.true
36+
37+
expect(
38+
isValidLicenseExpression(
39+
'LicenseRef-www.example.com-no-work-pd OR BSD-3-Clause AND MIT'
40+
),
41+
'Valid SPDX expression with compound-expression and License Ref'
42+
).to.be.true
43+
44+
expect(isValidLicenseExpression('wxWindows'), 'Deprecated License').to.be
45+
.true
46+
expect(
47+
isValidLicenseExpression('DocumentRef-X:LicenseRef-Y AND MIT'),
48+
'DocumentRef in License with compound-expression '
49+
).to.be.false
50+
expect(
51+
isValidLicenseExpression(
52+
'DocumentRef-some-document-reference:LicenseRef-www.example.org-Example-CSAF-License-2.0'
53+
),
54+
'DocumentRef in License'
55+
).to.be.false
56+
expect(
57+
isValidLicenseExpression(
58+
'LicenseRef-www.example.org-Example-CSAF-License-3.0+'
59+
),
60+
'LicenseRef in License with trailing +'
61+
).to.be.false
62+
})
63+
})

tests/csaf_2_1/oasis.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ const excluded = [
4040
'6.1.51',
4141
'6.1.52',
4242
'6.1.53',
43-
'6.1.54',
4443
'6.1.55',
4544
'6.2.11',
4645
'6.2.19',

0 commit comments

Comments
 (0)