From a196e4ba60e2d7b4cd92df358a9b6b913059a62b Mon Sep 17 00:00:00 2001 From: Tom Robinson <tlrobinson@gmail.com> Date: Tue, 17 Nov 2020 15:22:27 -0800 Subject: [PATCH] Check dependency licenses in CI (#9290) --- .circleci/config.yml | 13 ++- bin/check-licenses | 189 +++++++++++++++++++++++++++++++++++++++++++ project.clj | 3 +- 3 files changed, 203 insertions(+), 2 deletions(-) create mode 100755 bin/check-licenses diff --git a/.circleci/config.yml b/.circleci/config.yml index 97aa0da1513..7bcd7aab450 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -716,6 +716,14 @@ jobs: - run: ./bin/deploy-webhook $DEPLOY_WEBHOOK + check-licenses: + executor: node + steps: + - attach-workspace + - restore-be-deps-cache + - restore-fe-deps-cache + - run: ./bin/check-licenses + ######################################################################################################################## # WORKFLOWS # ######################################################################################################################## @@ -992,7 +1000,10 @@ workflows: requires: - be-deps <<: *Matrix - + - check-licenses: + requires: + - be-deps + - fe-deps - fe-tests-cypress: name: fe-tests-cypress-1-<< matrix.edition >> requires: diff --git a/bin/check-licenses b/bin/check-licenses new file mode 100755 index 00000000000..ec9faceb8ce --- /dev/null +++ b/bin/check-licenses @@ -0,0 +1,189 @@ +#!/usr/bin/env node + +const { execSync } = require("child_process"); +const { readFileSync } = require("fs"); +const _ = require("underscore"); + +const DEV_MODE = false; + +function normalizeLicense(license) { + return license + .toLowerCase() + .trim() + .replace(/^the\s+/, ""); +} + +const LICENSE_ALLOWLIST = new Set( + [ + "Apache 2", + "Apache 2.0", + "Apache License 2.0", + "Apache License", + "Apache License, Version 2.0", + "Apache Software License - Version 2.0", + "Apache Software License, Version 2.0", + "Apache v2", + "Apache*", + "Apache-2.0", + "Apache 2.0 License", + "Artistic-2.0", + "BSD 3-Clause License", + "BSD License", + "BSD licence", + "BSD", + "BSD*", + "0BSD", + "BSD-2-Clause", + "BSD-3-Clause", + "BSD 3-clause", + "BSD style", + "Bouncy Castle Licence", + "CC0 1.0 Universal", + "CC0-1.0", + "CC-BY-4.0", + "CC-BY-3.0", + "CDDL + GPLv2 with classpath exception", // this means CDDL or GPLv2 w/ classpath exception + "CDDL 1.1", + "CDDL License", + "CDDL/GPLv2+CE", // this means CDDL or GPLv2 w/ classpath exception + "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0", + "Common Development and Distribution License (CDDL) v1.0", + "Common Public License Version 1.0", + "Common Public License Version", + "Eclipse Public License - v 1.0", + "Eclipse Public License 1.0", + "Eclipse Public License", + "GNU Lesser General Public License 3.0", + "GNU Lesser General Public License, Version 2.1", + "ISC", + "ISC/BSD License", + "LGPL-2.1", + "Lesser GPL", + "MIT (http://mootools.net/license.txt)", + "MIT License", + "MIT Licensed. http://www.opensource.org/licenses/mit-license.php", + "MIT", + "MIT*", + "MIT AND BSD-3-Clause", + "MIT AND Zlib", + "MPL 2.0", + "New BSD License", // same as 3-clause + "Public Domain", + "Revised BSD", + // "The MIT License (MIT)", + "Unlicense", + "WTFPL", + ].map(normalizeLicense), +); + +const PACKAGE_ALLOWLIST = new Set([ + "com.amazon.redshift/redshift-jdbc42-no-awssdk@1.2.45.1069", // Apache 2.0? + "amalloy/ring-gzip-middleware@0.1.4", // MIT https://github.com/clj-commons/ring-gzip-middleware/blob/master/LICENSE + "caniuse-db@1.0.30000789", // CC-BY-4.0: https://github.com/Fyrd/caniuse + "colorize@0.1.1", // Eclipse Public License: https://github.com/ibdknox/colorize + "com.google.re2j/re2j@1.1", // Go License https://github.com/google/re2j/blob/master/LICENSE + "com.unboundid/unboundid-ldapsdk@4.0.4", // dual licensed LGPL/GPL: https://ldap.com/unboundid-ldap-sdk-for-java/ + "cycle@1.0.3", // Public domain: https://github.com/dscape/cycle/blob/master/cycle.js + "expectations@2.2.0-beta2", // BSD 3-Clause: https://github.com/clojure-expectations/expectations + "hiccup@1.0.5", // Eclipse Public License: https://github.com/weavejester/hiccup + "isnumeric@0.2.0", // MIT: https://github.com/leecrossley/isNumeric + "javax.servlet.jsp/jsp-api@2.1", // CDDL/GPL dual license: https://javaee.github.io/javaee-jsp-api/LICENSE + "jdk.tools@1.8", // build tools + "net.jcip/jcip-annotations@1.0", // Public Domain: https://mvnrepository.com/artifact/net.jcip/jcip-annotations + "org.gnu.gettext/libintl@0.18.3", // LGPL: https://www.gnu.org/software/gettext/manual/html_node/Licenses.html + "org.json/json@20090211", // JSON License: https://www.json.org/license.html + "pako@1.0.6", // build tool only + "slingshot@0.10.2", // Eclipse Public License: https://github.com/scgilardi/slingshot + "sntp@1.0.9", // BSD-3-Clause: https://github.com/hueniverse/sntp/blob/master/LICENSE + "spdx-expression-parse@1.0.4", // build tool only + "spdx-license-ids@1.2.2", // build tool only + "stencil@0.5.0", // Eclipse Public License: https://github.com/davidsantiago/stencil/blob/0.5.0/LICENSE +]); + +function parseLicenses(license) { + license = license.replace(/^\((.*)\)$/g, "$1"); // trim parens + if (/\bor\b/i.test(license) && !/\band\b/i.test(license)) { + return license.split(/\bor\b/gi).map(normalizeLicense); + } else { + return [normalizeLicense(license)]; + } +} + +function getJavaScriptPackageLicences() { + return (DEV_MODE + ? readFileSync("licenses.json", "utf-8") + : execSync("yarn licenses list --json", { encoding: "utf-8" }) + ) + .split("\n") + .slice(-2, -1) // 2nd to last row + .map(line => JSON.parse(line))[0] + .data.body.map(row => ({ + name: row[0], + version: row[1], + license: row[2], + licenses: parseLicenses(row[2]), + type: "javascript", + })); +} + +function getClojurePackageLicenses() { + return ( + (DEV_MODE + ? readFileSync("licenses.csv", "utf-8") + : execSync("lein with-profiles +include-all-drivers,-dev licenses :csv", { + encoding: "utf-8", + }) + ) + // rudimentary csv parsing + .split("\n") + .filter(line => /^".*"$/.test(line)) + .map(line => line.replace(/^"(.*)"$/g, "$1").split(/","/g)) + .map(row => ({ + name: row[0], + version: row[1], + license: row[2], + licenses: parseLicenses(row[2]), + type: "clojure", + })) + ); +} + +function checkLicences(packages) { + return packages.filter( + ({ name, version, licenses }) => + // check if any of the licenses are allowed + !_.any(licenses, license => LICENSE_ALLOWLIST.has(license)) && + // check package allowlist + !PACKAGE_ALLOWLIST.has(`${name}@${version}`), + ); +} + +const unknownJavaScriptLicenses = checkLicences(getJavaScriptPackageLicences()); +const unknownClojureLicenses = checkLicences(getClojurePackageLicenses()); + +const unknownPackageLogs = []; +const unknownLicenseCounts = new Map(); + +for (const { name, version, license, type } of [ + ...unknownJavaScriptLicenses, + ...unknownClojureLicenses, +]) { + unknownPackageLogs.push(`[${license}] ${name}@${version} (${type})`); + unknownLicenseCounts.set( + license, + (unknownLicenseCounts.get(license) || 0) + 1, + ); +} + +console.log("Packages\n========\n"); +console.log(unknownPackageLogs.sort().join("\n")); + +console.log("\nLicenses\n========\n"); +console.log( + _.sortBy(Array.from(unknownLicenseCounts), 1) + .reverse() + .map(([license, count]) => `[${count}] ${license}`) + .join("\n"), +); + +process.exit(unknownLicenseCounts.size === 0 ? 0 : 1); diff --git a/project.clj b/project.clj index 836c27ffeda..880882ae7a4 100644 --- a/project.clj +++ b/project.clj @@ -205,7 +205,8 @@ [ring/ring-mock "0.4.0"]] :plugins - [[lein-environ "1.1.0"]] ; easy access to environment variables + [[lein-environ "1.1.0"] ; easy access to environment variables + [lein-licenses "LATEST"]] :injections [(require 'pjstadig.humane-test-output) -- GitLab