mirror of
https://github.com/gradle/wrapper-validation-action
synced 2024-11-27 10:42:03 +00:00
- introduce new configuration to enable checksum validation only for detected versions
- version is detected from gradle-wrapper.properties - checksum is only fetched for these particular versions - FIX https://github.com/gradle/wrapper-validation-action/issues/96 - update action.yml with new config option - update and introduce testcases for the new configuration option
This commit is contained in:
parent
63d15e7a1e
commit
ba65536530
11 changed files with 249 additions and 18 deletions
|
@ -21,7 +21,7 @@ test('has loaded hardcoded wrapper jars checksums', async () => {
|
|||
})
|
||||
|
||||
test('fetches wrapper jars checksums', async () => {
|
||||
const validChecksums = await checksums.fetchValidChecksums(false)
|
||||
const validChecksums = await checksums.fetchValidChecksums(false, false, [])
|
||||
expect(validChecksums.size).toBeGreaterThan(10)
|
||||
// Verify that checksum of arbitrary version is contained
|
||||
expect(
|
||||
|
@ -32,6 +32,13 @@ test('fetches wrapper jars checksums', async () => {
|
|||
).toBe(true)
|
||||
})
|
||||
|
||||
test('fetches wrapper jars checksums only for detected versions', async () => {
|
||||
const validChecksums = await checksums.fetchValidChecksums(false, true, [
|
||||
'8.2.1'
|
||||
])
|
||||
expect(validChecksums.size).toBe(1)
|
||||
})
|
||||
|
||||
describe('retry', () => {
|
||||
afterEach(() => {
|
||||
nock.cleanAll()
|
||||
|
@ -47,7 +54,11 @@ describe('retry', () => {
|
|||
code: 'ECONNREFUSED'
|
||||
})
|
||||
|
||||
const validChecksums = await checksums.fetchValidChecksums(false)
|
||||
const validChecksums = await checksums.fetchValidChecksums(
|
||||
false,
|
||||
false,
|
||||
[]
|
||||
)
|
||||
expect(validChecksums.size).toBeGreaterThan(10)
|
||||
nock.isDone()
|
||||
})
|
||||
|
|
7
__tests__/data/invalid/gradle-wrapper.properties
Normal file
7
__tests__/data/invalid/gradle-wrapper.properties
Normal file
|
@ -0,0 +1,7 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle8.2.1bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
5
__tests__/data/valid/gradle-wrapper.properties
Normal file
5
__tests__/data/valid/gradle-wrapper.properties
Normal file
|
@ -0,0 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1-milestone-3-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
|
@ -10,3 +10,13 @@ test('finds test data wrapper jars', async () => {
|
|||
expect(wrapperJars).toContain('__tests__/data/invalid/gradle-wrapper.jar')
|
||||
expect(wrapperJars).toContain('__tests__/data/invalid/gradlе-wrapper.jar') // homoglyph
|
||||
})
|
||||
|
||||
test('detect version from `gradle-wrapper.properties` alongside wrappers', async () => {
|
||||
const repoRoot = path.resolve('.')
|
||||
const wrapperJars = await find.findWrapperJars(repoRoot)
|
||||
|
||||
const detectedVersions = await find.detectVersions(wrapperJars)
|
||||
|
||||
expect(detectedVersions.length).toBe(1)
|
||||
expect(detectedVersions).toContain('6.1-milestone-3')
|
||||
})
|
||||
|
|
|
@ -7,9 +7,13 @@ jest.setTimeout(30000)
|
|||
const baseDir = path.resolve('.')
|
||||
|
||||
test('succeeds if all found wrapper jars are valid', async () => {
|
||||
const result = await validate.findInvalidWrapperJars(baseDir, 3, false, [
|
||||
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
|
||||
])
|
||||
const result = await validate.findInvalidWrapperJars(
|
||||
baseDir,
|
||||
3,
|
||||
false,
|
||||
['e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'],
|
||||
false
|
||||
)
|
||||
|
||||
expect(result.isValid()).toBe(true)
|
||||
// Only hardcoded and explicitly allowed checksums should have been used
|
||||
|
@ -30,6 +34,7 @@ test('succeeds if all found wrapper jars are valid (and checksums are fetched fr
|
|||
1,
|
||||
false,
|
||||
['e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'],
|
||||
false,
|
||||
knownValidChecksums
|
||||
)
|
||||
|
||||
|
@ -46,7 +51,51 @@ test('succeeds if all found wrapper jars are valid (and checksums are fetched fr
|
|||
})
|
||||
|
||||
test('fails if invalid wrapper jars are found', async () => {
|
||||
const result = await validate.findInvalidWrapperJars(baseDir, 3, false, [])
|
||||
const result = await validate.findInvalidWrapperJars(
|
||||
baseDir,
|
||||
3,
|
||||
false,
|
||||
[],
|
||||
false
|
||||
)
|
||||
|
||||
expect(result.isValid()).toBe(false)
|
||||
|
||||
expect(result.valid).toEqual([
|
||||
new validate.WrapperJar(
|
||||
'__tests__/data/valid/gradle-wrapper.jar',
|
||||
'3888c76faa032ea8394b8a54e04ce2227ab1f4be64f65d450f8509fe112d38ce'
|
||||
)
|
||||
])
|
||||
|
||||
expect(result.invalid).toEqual([
|
||||
new validate.WrapperJar(
|
||||
'__tests__/data/invalid/gradle-wrapper.jar',
|
||||
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
|
||||
),
|
||||
new validate.WrapperJar(
|
||||
'__tests__/data/invalid/gradlе-wrapper.jar', // homoglyph
|
||||
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
|
||||
)
|
||||
])
|
||||
|
||||
expect(result.toDisplayString()).toBe(
|
||||
'✗ Found unknown Gradle Wrapper JAR files:\n' +
|
||||
' e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 __tests__/data/invalid/gradle-wrapper.jar\n' +
|
||||
' e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 __tests__/data/invalid/gradlе-wrapper.jar\n' + // homoglyph
|
||||
'✓ Found known Gradle Wrapper JAR files:\n' +
|
||||
' 3888c76faa032ea8394b8a54e04ce2227ab1f4be64f65d450f8509fe112d38ce __tests__/data/valid/gradle-wrapper.jar'
|
||||
)
|
||||
})
|
||||
|
||||
test('fails if invalid wrapper jars are found when detection versions from `gradle-wrapper.properties`', async () => {
|
||||
const result = await validate.findInvalidWrapperJars(
|
||||
baseDir,
|
||||
3,
|
||||
false,
|
||||
[],
|
||||
true
|
||||
)
|
||||
|
||||
expect(result.isValid()).toBe(false)
|
||||
|
||||
|
@ -78,7 +127,13 @@ test('fails if invalid wrapper jars are found', async () => {
|
|||
})
|
||||
|
||||
test('fails if not enough wrapper jars are found', async () => {
|
||||
const result = await validate.findInvalidWrapperJars(baseDir, 4, false, [])
|
||||
const result = await validate.findInvalidWrapperJars(
|
||||
baseDir,
|
||||
4,
|
||||
false,
|
||||
[],
|
||||
false
|
||||
)
|
||||
|
||||
expect(result.isValid()).toBe(false)
|
||||
|
||||
|
|
|
@ -15,6 +15,10 @@ inputs:
|
|||
description: 'Accept arbitrary user-defined checksums as valid. Comma separated list of SHA256 checksums (lowercase hex).'
|
||||
required: false
|
||||
default: ''
|
||||
detect-version:
|
||||
description: 'Searches for the Gradle version defined in the gradle-wrapper.properties file to limit checksum verification to these versions. Boolean, true or false.'
|
||||
required: false
|
||||
default: 'false'
|
||||
|
||||
outputs:
|
||||
failed-wrapper:
|
||||
|
|
74
dist/index.js
vendored
74
dist/index.js
vendored
|
@ -27865,14 +27865,15 @@ function getKnownValidChecksums() {
|
|||
* Maps from the checksum to the names of the Gradle versions whose wrapper has this checksum.
|
||||
*/
|
||||
exports.KNOWN_VALID_CHECKSUMS = getKnownValidChecksums();
|
||||
async function fetchValidChecksums(allowSnapshots) {
|
||||
async function fetchValidChecksums(allowSnapshots, detectVersions, detectedVersions) {
|
||||
const all = await httpGetJsonArray('https://services.gradle.org/versions/all');
|
||||
const withChecksum = all.filter(entry => typeof entry === 'object' &&
|
||||
entry != null &&
|
||||
entry.hasOwnProperty('wrapperChecksumUrl'));
|
||||
const allowed = withChecksum.filter(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(entry) => allowSnapshots || !entry.snapshot);
|
||||
(entry) => (allowSnapshots || !entry.snapshot) &&
|
||||
(!detectVersions || detectedVersions.includes(entry.version)));
|
||||
const checksumUrls = allowed.map(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(entry) => entry.wrapperChecksumUrl);
|
||||
|
@ -27923,12 +27924,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.findWrapperJars = void 0;
|
||||
exports.detectVersions = exports.findWrapperJars = void 0;
|
||||
const util = __importStar(__nccwpck_require__(3837));
|
||||
const path = __importStar(__nccwpck_require__(1017));
|
||||
const fs = __importStar(__nccwpck_require__(7147));
|
||||
const readline = __importStar(__nccwpck_require__(4521));
|
||||
const unhomoglyph_1 = __importDefault(__nccwpck_require__(8708));
|
||||
const core = __importStar(__nccwpck_require__(2186));
|
||||
const events_1 = __importDefault(__nccwpck_require__(2361));
|
||||
const readdir = util.promisify(fs.readdir);
|
||||
const versionRegex = new RegExp(/\/gradle-(.+)-/);
|
||||
async function findWrapperJars(baseDir) {
|
||||
const files = await recursivelyListFiles(baseDir);
|
||||
return files
|
||||
|
@ -27937,6 +27942,48 @@ async function findWrapperJars(baseDir) {
|
|||
.sort((a, b) => a.localeCompare(b));
|
||||
}
|
||||
exports.findWrapperJars = findWrapperJars;
|
||||
async function detectVersions(wrapperJars) {
|
||||
return (await Promise.all(wrapperJars.map(async (wrapperJar) => await findWrapperVersion(wrapperJar)))).filter(version => version !== undefined);
|
||||
}
|
||||
exports.detectVersions = detectVersions;
|
||||
async function findWrapperVersion(wrapperJar) {
|
||||
const jar = path.parse(wrapperJar);
|
||||
const properties = path.resolve(jar.dir, 'gradle-wrapper.properties');
|
||||
if (fs.existsSync(properties)) {
|
||||
try {
|
||||
const lineReader = readline.createInterface({
|
||||
input: fs.createReadStream(properties)
|
||||
});
|
||||
let distributionUrl = '';
|
||||
lineReader.on('line', function (line) {
|
||||
if (line.startsWith('distributionUrl=')) {
|
||||
distributionUrl = line;
|
||||
lineReader.close();
|
||||
}
|
||||
});
|
||||
await events_1.default.once(lineReader, 'close');
|
||||
if (distributionUrl) {
|
||||
const matchedVersion = distributionUrl.match(versionRegex);
|
||||
if (matchedVersion && matchedVersion.length >= 1) {
|
||||
return matchedVersion[1];
|
||||
}
|
||||
else {
|
||||
core.debug(`Could not parse version from distributionUrl in gradle-wrapper.properties file: ${properties}`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
core.debug(`Could not identify valid distributionUrl in gradle-wrapper.properties file: ${properties}`);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
core.warning(`Failed to retrieve version from gradle-wrapper.properties file: ${properties} due to ${error}`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
core.debug(`No gradle-wrapper.properties file existed alongside ${wrapperJar}`);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
async function recursivelyListFiles(baseDir) {
|
||||
const childrenNames = await readdir(baseDir);
|
||||
const childrenPaths = await Promise.all(childrenNames.map(async (childName) => {
|
||||
|
@ -28038,7 +28085,7 @@ const core = __importStar(__nccwpck_require__(2186));
|
|||
const validate = __importStar(__nccwpck_require__(4953));
|
||||
async function run() {
|
||||
try {
|
||||
const result = await validate.findInvalidWrapperJars(path.resolve('.'), +core.getInput('min-wrapper-count'), core.getInput('allow-snapshots') === 'true', core.getInput('allow-checksums').split(','));
|
||||
const result = await validate.findInvalidWrapperJars(path.resolve('.'), +core.getInput('min-wrapper-count'), core.getInput('allow-snapshots') === 'true', core.getInput('allow-checksums').split(','), core.getInput('detect-version') === 'true');
|
||||
if (result.isValid()) {
|
||||
core.info(result.toDisplayString());
|
||||
}
|
||||
|
@ -28103,7 +28150,7 @@ exports.WrapperJar = exports.ValidationResult = exports.findInvalidWrapperJars =
|
|||
const find = __importStar(__nccwpck_require__(3288));
|
||||
const checksums = __importStar(__nccwpck_require__(1541));
|
||||
const hash = __importStar(__nccwpck_require__(9778));
|
||||
async function findInvalidWrapperJars(gitRepoRoot, minWrapperCount, allowSnapshots, allowedChecksums, knownValidChecksums = checksums.KNOWN_VALID_CHECKSUMS) {
|
||||
async function findInvalidWrapperJars(gitRepoRoot, minWrapperCount, allowSnapshots, allowedChecksums, detectVersions, knownValidChecksums = checksums.KNOWN_VALID_CHECKSUMS) {
|
||||
const wrapperJars = await find.findWrapperJars(gitRepoRoot);
|
||||
const result = new ValidationResult([], []);
|
||||
if (wrapperJars.length < minWrapperCount) {
|
||||
|
@ -28123,7 +28170,14 @@ async function findInvalidWrapperJars(gitRepoRoot, minWrapperCount, allowSnapsho
|
|||
// Otherwise fall back to fetching checksums from Gradle API and compare against them
|
||||
if (notYetValidatedWrappers.length > 0) {
|
||||
result.fetchedChecksums = true;
|
||||
const fetchedValidChecksums = await checksums.fetchValidChecksums(allowSnapshots);
|
||||
let detectedVersions;
|
||||
if (detectVersions) {
|
||||
detectedVersions = await find.detectVersions(wrapperJars);
|
||||
}
|
||||
else {
|
||||
detectedVersions = [];
|
||||
}
|
||||
const fetchedValidChecksums = await checksums.fetchValidChecksums(allowSnapshots, detectVersions, detectedVersions);
|
||||
for (const wrapperJar of notYetValidatedWrappers) {
|
||||
if (!fetchedValidChecksums.has(wrapperJar.checksum)) {
|
||||
result.invalid.push(wrapperJar);
|
||||
|
@ -28335,6 +28389,14 @@ module.exports = require("querystring");
|
|||
|
||||
/***/ }),
|
||||
|
||||
/***/ 4521:
|
||||
/***/ ((module) => {
|
||||
|
||||
"use strict";
|
||||
module.exports = require("readline");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 2781:
|
||||
/***/ ((module) => {
|
||||
|
||||
|
|
|
@ -33,7 +33,9 @@ function getKnownValidChecksums(): Map<string, Set<string>> {
|
|||
export const KNOWN_VALID_CHECKSUMS = getKnownValidChecksums()
|
||||
|
||||
export async function fetchValidChecksums(
|
||||
allowSnapshots: boolean
|
||||
allowSnapshots: boolean,
|
||||
detectVersions: boolean,
|
||||
detectedVersions: string[]
|
||||
): Promise<Set<string>> {
|
||||
const all = await httpGetJsonArray('https://services.gradle.org/versions/all')
|
||||
const withChecksum = all.filter(
|
||||
|
@ -44,7 +46,9 @@ export async function fetchValidChecksums(
|
|||
)
|
||||
const allowed = withChecksum.filter(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(entry: any) => allowSnapshots || !entry.snapshot
|
||||
(entry: any) =>
|
||||
(allowSnapshots || !entry.snapshot) &&
|
||||
(!detectVersions || detectedVersions.includes(entry.version))
|
||||
)
|
||||
const checksumUrls = allowed.map(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
|
61
src/find.ts
61
src/find.ts
|
@ -1,9 +1,13 @@
|
|||
import * as util from 'util'
|
||||
import * as path from 'path'
|
||||
import * as fs from 'fs'
|
||||
import * as readline from 'readline'
|
||||
import unhomoglyph from 'unhomoglyph'
|
||||
import * as core from '@actions/core'
|
||||
import events from 'events'
|
||||
|
||||
const readdir = util.promisify(fs.readdir)
|
||||
const versionRegex = new RegExp(/\/gradle-(.+)-/)
|
||||
|
||||
export async function findWrapperJars(baseDir: string): Promise<string[]> {
|
||||
const files = await recursivelyListFiles(baseDir)
|
||||
|
@ -13,6 +17,63 @@ export async function findWrapperJars(baseDir: string): Promise<string[]> {
|
|||
.sort((a, b) => a.localeCompare(b))
|
||||
}
|
||||
|
||||
export async function detectVersions(wrapperJars: string[]): Promise<string[]> {
|
||||
return (
|
||||
await Promise.all(
|
||||
wrapperJars.map(async wrapperJar => await findWrapperVersion(wrapperJar))
|
||||
)
|
||||
).filter(version => version !== undefined) as string[]
|
||||
}
|
||||
|
||||
async function findWrapperVersion(
|
||||
wrapperJar: string
|
||||
): Promise<string | undefined> {
|
||||
const jar = path.parse(wrapperJar)
|
||||
const properties = path.resolve(jar.dir, 'gradle-wrapper.properties')
|
||||
|
||||
if (fs.existsSync(properties)) {
|
||||
try {
|
||||
const lineReader = readline.createInterface({
|
||||
input: fs.createReadStream(properties)
|
||||
})
|
||||
|
||||
let distributionUrl = ''
|
||||
lineReader.on('line', function (line) {
|
||||
if (line.startsWith('distributionUrl=')) {
|
||||
distributionUrl = line
|
||||
lineReader.close()
|
||||
}
|
||||
})
|
||||
|
||||
await events.once(lineReader, 'close')
|
||||
|
||||
if (distributionUrl) {
|
||||
const matchedVersion = distributionUrl.match(versionRegex)
|
||||
if (matchedVersion && matchedVersion.length >= 1) {
|
||||
return matchedVersion[1]
|
||||
} else {
|
||||
core.debug(
|
||||
`Could not parse version from distributionUrl in gradle-wrapper.properties file: ${properties}`
|
||||
)
|
||||
}
|
||||
} else {
|
||||
core.debug(
|
||||
`Could not identify valid distributionUrl in gradle-wrapper.properties file: ${properties}`
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
core.warning(
|
||||
`Failed to retrieve version from gradle-wrapper.properties file: ${properties} due to ${error}`
|
||||
)
|
||||
}
|
||||
} else {
|
||||
core.debug(
|
||||
`No gradle-wrapper.properties file existed alongside ${wrapperJar}`
|
||||
)
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
async function recursivelyListFiles(baseDir: string): Promise<string[]> {
|
||||
const childrenNames = await readdir(baseDir)
|
||||
const childrenPaths = await Promise.all(
|
||||
|
|
|
@ -9,7 +9,8 @@ export async function run(): Promise<void> {
|
|||
path.resolve('.'),
|
||||
+core.getInput('min-wrapper-count'),
|
||||
core.getInput('allow-snapshots') === 'true',
|
||||
core.getInput('allow-checksums').split(',')
|
||||
core.getInput('allow-checksums').split(','),
|
||||
core.getInput('detect-version') === 'true'
|
||||
)
|
||||
if (result.isValid()) {
|
||||
core.info(result.toDisplayString())
|
||||
|
|
|
@ -7,6 +7,7 @@ export async function findInvalidWrapperJars(
|
|||
minWrapperCount: number,
|
||||
allowSnapshots: boolean,
|
||||
allowedChecksums: string[],
|
||||
detectVersions: boolean,
|
||||
knownValidChecksums: Map<
|
||||
string,
|
||||
Set<string>
|
||||
|
@ -33,8 +34,18 @@ export async function findInvalidWrapperJars(
|
|||
// Otherwise fall back to fetching checksums from Gradle API and compare against them
|
||||
if (notYetValidatedWrappers.length > 0) {
|
||||
result.fetchedChecksums = true
|
||||
const fetchedValidChecksums =
|
||||
await checksums.fetchValidChecksums(allowSnapshots)
|
||||
|
||||
let detectedVersions: string[]
|
||||
if (detectVersions) {
|
||||
detectedVersions = await find.detectVersions(wrapperJars)
|
||||
} else {
|
||||
detectedVersions = []
|
||||
}
|
||||
const fetchedValidChecksums = await checksums.fetchValidChecksums(
|
||||
allowSnapshots,
|
||||
detectVersions,
|
||||
detectedVersions
|
||||
)
|
||||
|
||||
for (const wrapperJar of notYetValidatedWrappers) {
|
||||
if (!fetchedValidChecksums.has(wrapperJar.checksum)) {
|
||||
|
|
Loading…
Reference in a new issue