From bccacf97087f335a55937b51333cc2e0b0d7e189 Mon Sep 17 00:00:00 2001 From: Federico Builes Date: Mon, 6 Jun 2022 17:07:26 +0200 Subject: [PATCH] Skeleton for license validation. --- __tests__/config.test.ts | 13 ++++++ __tests__/fixtures/conflictive-config.yml | 2 + __tests__/fixtures/no-licenses-config.yml | 1 + __tests__/licenses.test.ts | 53 +++++++++++++++++++++++ src/licenses.ts | 34 +++++++++++++++ src/main.ts | 12 +++++ 6 files changed, 115 insertions(+) create mode 100644 __tests__/fixtures/conflictive-config.yml create mode 100644 __tests__/fixtures/no-licenses-config.yml create mode 100644 __tests__/licenses.test.ts create mode 100644 src/licenses.ts diff --git a/__tests__/config.test.ts b/__tests__/config.test.ts index a45dc7abb..c668d7247 100644 --- a/__tests__/config.test.ts +++ b/__tests__/config.test.ts @@ -16,3 +16,16 @@ test('returns a default config when the config file was not found', async () => expect(options.fail_on_severity).toEqual('low') expect(options.allow_licenses).toEqual([]) }) + +test('it reads config files with empty options', async () => { + let options = readConfigFile('./__tests__/fixtures/no-licenses-config.yml') + expect(options.fail_on_severity).toEqual('critical') + expect(options.allow_licenses).toEqual(undefined) + expect(options.deny_licenses).toEqual(undefined) +}) + +test('it raises an error if both an allow and denylist are specified', async () => { + expect(() => + readConfigFile('./__tests__/fixtures/conflictive-config.yml') + ).toThrow() +}) diff --git a/__tests__/fixtures/conflictive-config.yml b/__tests__/fixtures/conflictive-config.yml new file mode 100644 index 000000000..29d216194 --- /dev/null +++ b/__tests__/fixtures/conflictive-config.yml @@ -0,0 +1,2 @@ +allow_licenses: [] +deny_licenses: [] diff --git a/__tests__/fixtures/no-licenses-config.yml b/__tests__/fixtures/no-licenses-config.yml new file mode 100644 index 000000000..a8d18d910 --- /dev/null +++ b/__tests__/fixtures/no-licenses-config.yml @@ -0,0 +1 @@ +fail_on_severity: critical diff --git a/__tests__/licenses.test.ts b/__tests__/licenses.test.ts new file mode 100644 index 000000000..b8b519339 --- /dev/null +++ b/__tests__/licenses.test.ts @@ -0,0 +1,53 @@ +import {expect, test} from '@jest/globals' +import {Change, Changes} from '../src/schemas' +import {hasInvalidLicenses} from '../src/licenses' + +let npmChange: Change = { + manifest: 'package.json', + change_type: 'added', + ecosystem: 'npm', + name: 'Reeuhq', + version: '1.0.2', + package_url: 'somepurl', + license: 'MIT', + source_repository_url: 'github.com/some-repo', + vulnerabilities: [ + { + severity: 'critical', + advisory_ghsa_id: 'first-random_string', + advisory_summary: 'very dangerouns', + advisory_url: 'github.com/future-funk' + } + ] +} + +let rubyChange: Change = { + change_type: 'added', + manifest: 'Gemfile.lock', + ecosystem: 'rubygems', + name: 'actionsomething', + version: '3.2.0', + package_url: 'somerubypurl', + license: 'BSD', + source_repository_url: 'github.com/some-repo', + vulnerabilities: [ + { + severity: 'moderate', + advisory_ghsa_id: 'second-random_string', + advisory_summary: 'not so dangerouns', + advisory_url: 'github.com/future-funk' + }, + { + severity: 'low', + advisory_ghsa_id: 'third-random_string', + advisory_summary: 'dont page me', + advisory_url: 'github.com/future-funk' + } + ] +} + +test('hasInvalidLicenses fails if an unallowed license is found', async () => { + const changes: Changes = [npmChange, rubyChange] + const result = hasInvalidLicenses(changes, ['BSD'], []) + expect(result.length).toBe(1) +}) diff --git a/src/licenses.ts b/src/licenses.ts new file mode 100644 index 000000000..ee38a4672 --- /dev/null +++ b/src/licenses.ts @@ -0,0 +1,34 @@ +import {Change, ChangeSchema} from './schemas' + +export function hasInvalidLicenses( + changes: Array, + allowLicenses: Array | undefined, + failLicenses: Array | undefined +): Array { + let disallowed: Change[] = [] + + if (allowLicenses === undefined) { + allowLicenses = [] + } + if (failLicenses === undefined) { + failLicenses = [] + } + + for (const change of changes) { + let license = change.license + // TODO: be loud about unknown licenses + if (license === null) { + continue + } + + if (allowLicenses.includes(license)) { + disallowed.push(change) + } + } + + return disallowed +} + +export function printLicensesError(changes: Array): void { + return +} diff --git a/src/main.ts b/src/main.ts index 16ff56120..8160b3b6d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,6 +6,7 @@ import {RequestError} from '@octokit/request-error' import {Change, PullRequestSchema, Severity} from './schemas' import {readConfigFile} from '../src/config' import {filterChangesBySeverity} from '../src/filter' +import {hasInvalidLicenses, printLicensesError} from './licenses' async function run(): Promise { try { @@ -35,6 +36,17 @@ async function run(): Promise { changes ) + let licenseErrors = hasInvalidLicenses( + changes, + config.allow_licenses, + config.deny_licenses + ) + + if (licenseErrors.length > 0) { + printLicensesError(licenseErrors) + throw new Error('Dependency review detected incompatible licenses.') + } + for (const change of filteredChanges) { if ( change.change_type === 'added' &&