Skip to content

Commit 3a2b687

Browse files
committed
Handle complex licenses (e.g. X AND Y)
There are many packages that are dual-licensed, offering a choice of licenses (e.g. `MIT OR Apache-2.0`). There are some that include code from multiple sources and require multiple licenses (e.g. `MIT AND Apache-2.0`). There are also complex combinations that can exist for a variety of reasons, such as `MIT AND (Apache-2.0 OR BSD-3-Clause)`. The most straightforward approach to handle these is to have an allow list. As long as the licenses on the allow list can satisfy the license expression of the package in question, it should pass. To implement this, I the newest release of spdx-satisfies which changed the interface to be exactly as described `satisfies(license, allowList)` (see jslicense/spdx-satisfies.js#17). Fixes #263
1 parent a87294d commit 3a2b687

File tree

9 files changed

+258
-34
lines changed

9 files changed

+258
-34
lines changed

__tests__/licenses.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,32 @@ const pipChange: Change = {
7474
]
7575
}
7676

77+
const complexLicenseChange: Change = {
78+
change_type: 'added',
79+
manifest: 'requirements.txt',
80+
ecosystem: 'pip',
81+
name: 'package-1',
82+
version: '1.1.1',
83+
package_url: 'pkg:pypi/[email protected]',
84+
license: 'MIT AND Apache-2.0',
85+
source_repository_url: 'github.com/some-repo',
86+
scope: 'runtime',
87+
vulnerabilities: [
88+
{
89+
severity: 'moderate',
90+
advisory_ghsa_id: 'second-random_string',
91+
advisory_summary: 'not so dangerous',
92+
advisory_url: 'github.com/future-funk'
93+
},
94+
{
95+
severity: 'low',
96+
advisory_ghsa_id: 'third-random_string',
97+
advisory_summary: 'dont page me',
98+
advisory_url: 'github.com/future-funk'
99+
}
100+
]
101+
}
102+
77103
jest.mock('@actions/core')
78104

79105
const mockOctokit = {
@@ -129,6 +155,30 @@ test('it adds license inside the deny list to forbidden changes', async () => {
129155
expect(forbidden.length).toEqual(1)
130156
})
131157

158+
test('it handles allowed complex licenses', async () => {
159+
const changes: Changes = [
160+
complexLicenseChange // MIT AND Apache-2.0 license
161+
]
162+
163+
const {forbidden} = await getInvalidLicenseChanges(changes, {
164+
allow: ['MIT', 'Apache-2.0']
165+
})
166+
167+
expect(forbidden.length).toEqual(0)
168+
})
169+
170+
test('it handles complex licenses not all on the allow list', async () => {
171+
const changes: Changes = [
172+
complexLicenseChange // MIT AND Apache-2.0 license
173+
]
174+
175+
const {forbidden} = await getInvalidLicenseChanges(changes, {
176+
allow: ['MIT']
177+
})
178+
179+
expect(forbidden.length).toEqual(1)
180+
})
181+
132182
test('it does not add license outside the allow list to forbidden changes if it is in removed changes', async () => {
133183
const changes: Changes = [
134184
{...npmChange, change_type: 'removed'},

__tests__/spdx.test.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -145,77 +145,77 @@ describe('satisfies', () => {
145145
const units = [
146146
{
147147
candidate: 'MIT',
148-
constraint: 'MIT',
148+
allowList: ['MIT'],
149149
expected: true
150150
},
151151
{
152152
candidate: 'Apache-2.0',
153-
constraint: 'MIT',
153+
allowList: ['MIT'],
154154
expected: false
155155
},
156156
{
157157
candidate: 'MIT OR Apache-2.0',
158-
constraint: 'MIT',
158+
allowList: ['MIT'],
159159
expected: true
160160
},
161161
{
162162
candidate: 'MIT OR Apache-2.0',
163-
constraint: 'Apache-2.0',
163+
allowList: ['Apache-2.0'],
164164
expected: true
165165
},
166166
{
167167
candidate: 'MIT OR Apache-2.0',
168-
constraint: 'BSD-3-Clause',
168+
allowList: ['BSD-3-Clause'],
169169
expected: false
170170
},
171171
{
172172
candidate: 'MIT OR Apache-2.0',
173-
constraint: 'Apache-2.0 OR BSD-3-Clause',
173+
allowList: ['Apache-2.0', 'BSD-3-Clause'],
174174
expected: true
175175
},
176176
{
177177
candidate: 'MIT AND Apache-2.0',
178-
constraint: 'MIT AND Apache-2.0',
178+
allowList: ['MIT', 'Apache-2.0'],
179179
expected: true
180180
},
181181
{
182182
candidate: 'MIT OR Apache-2.0',
183-
constraint: 'MIT AND Apache-2.0',
184-
expected: false
183+
allowList: ['MIT', 'Apache-2.0'],
184+
expected: true
185185
},
186186
{
187187
candidate: 'ISC OR (MIT AND Apache-2.0)',
188-
constraint: 'MIT AND Apache-2.0',
188+
allowList: ['MIT', 'Apache-2.0'],
189189
expected: true
190190
},
191191

192192
// missing params, case sensitivity, syntax problems,
193193
// or unknown licenses will return 'false'
194194
{
195195
candidate: 'MIT',
196-
constraint: 'MiT',
196+
allowList: ['MiT'],
197197
expected: false
198198
},
199199
{
200200
candidate: 'MIT AND (ISC OR',
201-
constraint: 'MIT',
201+
allowList: ['MIT'],
202202
expected: false
203203
},
204204
{
205205
candidate: 'MIT OR ISC OR Apache-2.0',
206-
constraint: '',
206+
allowList: [],
207207
expected: false
208208
},
209209
{
210210
candidate: '',
211-
constraint: '(BSD-3-Clause AND ISC) OR MIT',
211+
allowList: ['BSD-3-Clause', 'ISC', 'MIT'],
212212
expected: false
213213
}
214214
]
215215

216216
for (const unit of units) {
217-
const got: boolean = spdx.satisfies(unit.candidate, unit.constraint)
218-
test(`should return ${unit.expected} for ("${unit.candidate}", "${unit.constraint}")`, () => {
217+
const got: boolean = spdx.satisfies(unit.candidate, unit.allowList)
218+
test(`should return ${unit.expected} for ("${unit.candidate}", "${unit.allowList}")`, () => {
219219
expect(got).toBe(unit.expected)
220220
})
221221
}

dist/index.js

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

dist/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/licenses.txt

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

0 commit comments

Comments
 (0)