Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce configuration option to detect gradle versions from gradle-wrapper.properties #145

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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