Skip to content

Commit

Permalink
feat: use /rest/packages best practice Snyk REST API endpoint
Browse files Browse the repository at this point in the history
https://github.com/snyk/maven-deps/pull/1036 added best practice and new route
for searching for packages using sha1 hash as query param. This PR adjusts
the request to point to this new endpoint and expecting a purl string response.
  • Loading branch information
Jas Kuner committed Sep 28, 2022
1 parent b855db9 commit edef32e
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 90 deletions.
33 changes: 5 additions & 28 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@ import * as path from 'path';
import * as subProcess from './sub-process';
import * as tmp from 'tmp';
import * as pMap from 'p-map';
import { MissingSubProjectError } from './errors';
import * as chalk from 'chalk';
import { DepGraph } from '@snyk/dep-graph';
import debugModule = require('debug');
import { legacyCommon, legacyPlugin as api } from '@snyk/cli-interface';
import * as javaCallGraphBuilder from '@snyk/java-call-graph-builder';

import { MissingSubProjectError } from './errors';
import { getGradleAttributesPretty } from './gradle-attributes-pretty';
import debugModule = require('debug');
import { buildGraph, SnykGraph } from './graph';
import type {
CoordinateMap,
PomCoords,
Sha1Map,
SnykHttpClient,
} from './types';
import { getMavenPackageInfo } from './search';

type ScannedProject = legacyCommon.ScannedProject;
type CallGraph = legacyCommon.CallGraph;
Expand All @@ -26,7 +28,7 @@ type CallGraphResult = legacyCommon.CallGraphResult;
// To enable debugging output, use `snyk -d`
let logger: debugModule.Debugger | null = null;

function debugLog(s: string) {
export function debugLog(s: string) {
if (logger === null) {
// Lazy init: Snyk CLI needs to process the CLI argument "-d" first.
// TODO(BST-648): more robust handling of the debug settings
Expand Down Expand Up @@ -493,30 +495,6 @@ async function getAllDepsWithPlugin(
return extractedJSON;
}

export async function getMavenPackageInfo(
sha1: string,
depCoords: Partial<PomCoords>,
snykHttpClient: SnykHttpClient,
): Promise<string> {
const { res, body } = await snykHttpClient({
method: 'get',
path: `/maven/coordinates/sha1/${sha1}`,
qs: depCoords,
});
if (res?.statusCode >= 400 || !body || !body.ok || body.code >= 400) {
debugLog(
body.message ||
`Failed to resolve ${JSON.stringify(depCoords)} using sha1 '${sha1}.`,
);
}
const {
groupId = depCoords.groupId || 'unknown',
artifactId = depCoords.artifactId || 'unknown',
version = depCoords.version || 'unknown',
} = body.coordinate || {};
return `${groupId}:${artifactId}@${version}`;
}

function splitCoordinate(coordinate: string): Partial<PomCoords> {
const coordTest = /^[\w.-]+:[\w.-]+@[\w.-]+$/.test(coordinate);
if (!coordTest) return {};
Expand Down Expand Up @@ -849,5 +827,4 @@ export const exportsForTests = {
toCamelCase,
getGradleAttributesPretty,
splitCoordinate,
getMavenPackageInfo,
};
56 changes: 56 additions & 0 deletions lib/search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { PackageURL } from 'packageurl-js';

import { debugLog } from '.';
import type { GetPackageData, PomCoords, SnykHttpClient } from './types';

const PACKAGE_SEARCH_TYPE = 'maven';
const PACKAGE_SEARCH_VERSION = '2022-09-21~beta';

export async function getMavenPackageInfo(
sha1: string,
depCoords: Partial<PomCoords>,
snykHttpClient: SnykHttpClient,
): Promise<string> {
const { res, body } = await snykHttpClient({
method: 'get',
path: `/rest/packages`,
qs: {
version: PACKAGE_SEARCH_VERSION,
/* eslint-disable @typescript-eslint/camelcase */
package_type: PACKAGE_SEARCH_TYPE,
package_sha1: sha1,
package_namespace: depCoords.groupId,
package_name: depCoords.artifactId,
package_version: depCoords.version,
/* eslint-enable @typescript-eslint/camelcase */
},
});

if (res?.statusCode >= 400 || !body) {
debugLog(
`Failed to resolve ${JSON.stringify(depCoords)} using sha1 '${sha1}.`,
);
}

let groupId: string;
let artifactId: string;
let version: string;
try {
const purlString = (body as GetPackageData)?.data[0]?.id;
const pkg = PackageURL.fromString(purlString);
const { namespace, name, version: pkgVersion } = pkg;
groupId = namespace;
artifactId = name;
version = pkgVersion;
} catch (_error) {
debugLog(
`Failed to parse purl components for ${JSON.stringify(
depCoords,
)} using sha1 '${sha1}.`,
);
}

return `${groupId || 'unknown'}:${artifactId || 'unknown'}@${
version || 'unknown'
}`;
}
23 changes: 23 additions & 0 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,26 @@ export interface PomCoords {
artifactId: string;
version: string;
}

interface PackageResource {
id: string;
type: 'package';
}

type GetPackageResponseData = Array<PackageResource>;

interface GetPackageLinks {
self?:
| string
| {
href: string;
meta?: {
[key: string]: any;
};
};
}

export interface GetPackageData {
data: GetPackageResponseData;
links: GetPackageLinks;
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"chalk": "^3.0.0",
"debug": "^4.1.1",
"p-map": "^4.0.0",
"packageurl-js": "^1.0.0",
"tmp": "0.2.1",
"tslib": "^2.0.0"
},
Expand Down
74 changes: 74 additions & 0 deletions test/functional/search.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import type { NeedleResponse } from 'needle';

import { getMavenPackageInfo } from '../../lib/search';

describe('should get Maven package info', () => {
it('returns maven coordinate string when purl string returned', async () => {
const received = await getMavenPackageInfo(
'ba55c13d7ac2fd44df9cc8074455719a33f375b9', // log4j-core
{},
async () => ({
res: {} as NeedleResponse,
body: {
data: [
{
id: 'pkg:maven/org.apache.logging.log4j/log4j-core@2.15.0',
type: 'package',
},
],
},
}),
);
expect(received).toBe('org.apache.logging.log4j:log4j-core@2.15.0');
});

it("should return 'unknown' values when partial purl string returned", async () => {
const received = await getMavenPackageInfo(
'ba55c13d7ac2fd44df9cc8074455719a33f375b9', // log4j-core
{},
async () => ({
res: {} as NeedleResponse,
body: {
data: [
{
id: 'pkg:maven/log4j-core@2.15.0',
type: 'package',
},
],
},
}),
);
expect(received).toBe('unknown:log4j-core@2.15.0');
});

it("should return all 'unknown' values when no/non purl string returned", async () => {
const noPurlStringReceived = await getMavenPackageInfo(
'ba55c13d7ac2fd44df9cc8074455719a33f375b9', // log4j-core
{},
async () => ({
res: {} as NeedleResponse,
body: {
data: [],
},
}),
);
expect(noPurlStringReceived).toBe('unknown:unknown@unknown');

const nonPurlStringReceived = await getMavenPackageInfo(
'ba55c13d7ac2fd44df9cc8074455719a33f375b9', // log4j-core
{},
async () => ({
res: {} as NeedleResponse,
body: {
data: [
{
id: 'corruptedId',
type: 'package',
},
],
},
}),
);
expect(nonPurlStringReceived).toBe('unknown:unknown@unknown');
});
});
62 changes: 0 additions & 62 deletions test/functional/utilFuncs.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import type { NeedleResponse } from 'needle';

import { exportsForTests as testableMethods } from '../../lib';

describe('should convert strings to camel case', () => {
Expand Down Expand Up @@ -30,63 +28,3 @@ describe('should split coordinate strings', () => {
expect(testableMethods.splitCoordinate(input)).toEqual(output);
});
});

describe('should get Maven package info', () => {
it('returns maven coordinate string when ok:true response with coordinate property', async () => {
const received = await testableMethods.getMavenPackageInfo(
'c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2',
{},
async () => ({
res: {} as NeedleResponse,
body: {
ok: true,
code: 200,
coordinate: {
groupId: 'group1',
artifactId: 'artifact1',
version: 'version1',
},
},
}),
);
expect(received).toBe('group1:artifact1@version1');
});

it("should return all 'unknown' values when no dep coordinates passed, 200, ok:true and NO coordinate property on response", async () => {
const received = await testableMethods.getMavenPackageInfo(
'c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2',
{},
async () => ({
res: {} as NeedleResponse,
body: {
ok: true,
code: 200,
},
}),
);
expect(received).toBe('unknown:unknown@unknown');
});

it('should return passed dep coordinate value when 200, ok:true and partial coordinate property on response', async () => {
const received = await testableMethods.getMavenPackageInfo(
'c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2',
{
groupId: 'group',
artifactId: 'artifact',
version: '2',
},
async () => ({
res: {} as NeedleResponse,
body: {
ok: true,
code: 200,
coordinate: {
groupId: 'group2',
artifactId: 'artifact',
},
},
}),
);
expect(received).toBe('group2:artifact@2');
});
});

0 comments on commit edef32e

Please sign in to comment.