Skip to content

Commit

Permalink
- introduce new configuration to enable checksum validation only for …
Browse files Browse the repository at this point in the history
…detected versions

  - version is detected from gradle-wrapper.properties
  - checksum is only fetched for these particular versions
- FIX #96
- update action.yml with new config option
- update and introduce testcases for the new configuration option
  • Loading branch information
mikepenz authored Feb 23, 2024
1 parent 63d15e7 commit ba65536
Show file tree
Hide file tree
Showing 11 changed files with 249 additions and 18 deletions.
15 changes: 13 additions & 2 deletions __tests__/checksums.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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()
Expand All @@ -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()
})
Expand Down
7 changes: 7 additions & 0 deletions __tests__/data/invalid/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -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 changes: 5 additions & 0 deletions __tests__/data/valid/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -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 changes: 10 additions & 0 deletions __tests__/find.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
})
65 changes: 60 additions & 5 deletions __tests__/validate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -30,6 +34,7 @@ test('succeeds if all found wrapper jars are valid (and checksums are fetched fr
1,
false,
['e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'],
false,
knownValidChecksums
)

Expand All @@ -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)

Expand Down Expand Up @@ -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)

Expand Down
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
74 changes: 68 additions & 6 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand All @@ -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) => {
Expand Down Expand Up @@ -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());
}
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
Expand Down Expand Up @@ -28335,6 +28389,14 @@ module.exports = require("querystring");

/***/ }),

/***/ 4521:
/***/ ((module) => {

"use strict";
module.exports = require("readline");

/***/ }),

/***/ 2781:
/***/ ((module) => {

Expand Down
8 changes: 6 additions & 2 deletions src/checksums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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
Expand Down
Loading

0 comments on commit ba65536

Please sign in to comment.