Skip to content

Commit ee3d0d2

Browse files
jan-stehlikJan Stehlik
authored and
Jan Stehlik
committedMay 6, 2021
fix: fix invalid poetry detection
1 parent c6d1329 commit ee3d0d2

File tree

14 files changed

+1076
-74
lines changed

14 files changed

+1076
-74
lines changed
 

‎package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@
126126
"snyk-nuget-plugin": "1.21.1",
127127
"snyk-php-plugin": "1.9.2",
128128
"snyk-policy": "1.19.0",
129-
"snyk-python-plugin": "1.19.8",
129+
"snyk-python-plugin": "1.19.9",
130130
"snyk-resolve": "1.1.0",
131131
"snyk-resolve-deps": "4.7.2",
132132
"snyk-sbt-plugin": "2.11.0",

‎src/lib/detect.ts

+36-34
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import * as pathLib from 'path';
33
import * as debugLib from 'debug';
44
const endsWith = require('lodash.endswith');
55
import { NoSupportedManifestsFoundError } from './errors';
6-
import { SupportedPackageManagers } from './package-managers';
6+
import {
7+
SupportedPackageManagers,
8+
SUPPORTED_MANIFEST_FILES,
9+
} from './package-managers';
710

811
const debug = debugLib('snyk-detect');
912

@@ -29,7 +32,6 @@ const DETECTABLE_FILES: string[] = [
2932
'composer.lock',
3033
'Podfile',
3134
'Podfile.lock',
32-
'pyproject.toml',
3335
'poetry.lock',
3436
'mix.exs',
3537
'mix.lock',
@@ -57,46 +59,45 @@ export const AUTO_DETECTABLE_FILES: string[] = [
5759
'build.sbt',
5860
'build.gradle',
5961
'build.gradle.kts',
60-
'pyproject.toml',
6162
'poetry.lock',
6263
'mix.exs',
6364
'mix.lock',
6465
];
6566

6667
// when file is specified with --file, we look it up here
68+
// this is also used when --all-projects flag is enabled and auto detection plugin is triggered
6769
const DETECTABLE_PACKAGE_MANAGERS: {
68-
[name: string]: SupportedPackageManagers;
70+
[key in SUPPORTED_MANIFEST_FILES]: SupportedPackageManagers;
6971
} = {
70-
Gemfile: 'rubygems',
71-
'Gemfile.lock': 'rubygems',
72-
'.gemspec': 'rubygems',
73-
'package-lock.json': 'npm',
74-
'pom.xml': 'maven',
75-
'.jar': 'maven',
76-
'.war': 'maven',
77-
'build.gradle': 'gradle',
78-
'build.gradle.kts': 'gradle',
79-
'build.sbt': 'sbt',
80-
'yarn.lock': 'yarn',
81-
'package.json': 'npm',
82-
Pipfile: 'pip',
83-
'setup.py': 'pip',
84-
'requirements.txt': 'pip',
85-
'Gopkg.lock': 'golangdep',
86-
'go.mod': 'gomodules',
87-
'vendor.json': 'govendor',
88-
'project.assets.json': 'nuget',
89-
'packages.config': 'nuget',
90-
'project.json': 'nuget',
91-
'paket.dependencies': 'paket',
92-
'composer.lock': 'composer',
93-
'Podfile.lock': 'cocoapods',
94-
'CocoaPods.podfile.yaml': 'cocoapods',
95-
'CocoaPods.podfile': 'cocoapods',
96-
Podfile: 'cocoapods',
97-
'pyproject.toml': 'poetry',
98-
'poetry.lock': 'poetry',
99-
'mix.exs': 'hex',
72+
[SUPPORTED_MANIFEST_FILES.GEMFILE]: 'rubygems',
73+
[SUPPORTED_MANIFEST_FILES.GEMFILE_LOCK]: 'rubygems',
74+
[SUPPORTED_MANIFEST_FILES.GEMSPEC]: 'rubygems',
75+
[SUPPORTED_MANIFEST_FILES.PACKAGE_LOCK_JSON]: 'npm',
76+
[SUPPORTED_MANIFEST_FILES.POM_XML]: 'maven',
77+
[SUPPORTED_MANIFEST_FILES.JAR]: 'maven',
78+
[SUPPORTED_MANIFEST_FILES.WAR]: 'maven',
79+
[SUPPORTED_MANIFEST_FILES.BUILD_GRADLE]: 'gradle',
80+
[SUPPORTED_MANIFEST_FILES.BUILD_GRADLE_KTS]: 'gradle',
81+
[SUPPORTED_MANIFEST_FILES.BUILD_SBT]: 'sbt',
82+
[SUPPORTED_MANIFEST_FILES.YARN_LOCK]: 'yarn',
83+
[SUPPORTED_MANIFEST_FILES.PACKAGE_JSON]: 'npm',
84+
[SUPPORTED_MANIFEST_FILES.PIPFILE]: 'pip',
85+
[SUPPORTED_MANIFEST_FILES.SETUP_PY]: 'pip',
86+
[SUPPORTED_MANIFEST_FILES.REQUIREMENTS_TXT]: 'pip',
87+
[SUPPORTED_MANIFEST_FILES.GOPKG_LOCK]: 'golangdep',
88+
[SUPPORTED_MANIFEST_FILES.GO_MOD]: 'gomodules',
89+
[SUPPORTED_MANIFEST_FILES.VENDOR_JSON]: 'govendor',
90+
[SUPPORTED_MANIFEST_FILES.PROJECT_ASSETS_JSON]: 'nuget',
91+
[SUPPORTED_MANIFEST_FILES.PACKAGES_CONFIG]: 'nuget',
92+
[SUPPORTED_MANIFEST_FILES.PROJECT_JSON]: 'nuget',
93+
[SUPPORTED_MANIFEST_FILES.PAKET_DEPENDENCIES]: 'paket',
94+
[SUPPORTED_MANIFEST_FILES.COMPOSER_LOCK]: 'composer',
95+
[SUPPORTED_MANIFEST_FILES.PODFILE_LOCK]: 'cocoapods',
96+
[SUPPORTED_MANIFEST_FILES.COCOAPODS_PODFILE_YAML]: 'cocoapods',
97+
[SUPPORTED_MANIFEST_FILES.COCOAPODS_PODFILE]: 'cocoapods',
98+
[SUPPORTED_MANIFEST_FILES.PODFILE]: 'cocoapods',
99+
[SUPPORTED_MANIFEST_FILES.POETRY_LOCK]: 'poetry',
100+
[SUPPORTED_MANIFEST_FILES.MIX_EXS]: 'hex',
100101
};
101102

102103
export function isPathToPackageFile(path) {
@@ -200,6 +201,7 @@ export function detectPackageManagerFromFile(
200201
// we throw and error here because the file was specified by the user
201202
throw new Error('Could not detect package manager for file: ' + file);
202203
}
204+
203205
return DETECTABLE_PACKAGE_MANAGERS[key];
204206
}
205207

‎src/lib/package-managers.ts

+32
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,38 @@ export type SupportedPackageManagers =
1616
| 'poetry'
1717
| 'hex';
1818

19+
export enum SUPPORTED_MANIFEST_FILES {
20+
GEMFILE = 'Gemfile',
21+
GEMFILE_LOCK = 'Gemfile.lock',
22+
GEMSPEC = '.gemspec',
23+
PACKAGE_LOCK_JSON = 'package-lock.json',
24+
POM_XML = 'pom.xml',
25+
JAR = '.jar',
26+
WAR = '.war',
27+
BUILD_GRADLE = 'build.gradle',
28+
BUILD_GRADLE_KTS = 'build.gradle.kts',
29+
BUILD_SBT = 'build.sbt',
30+
YARN_LOCK = 'yarn.lock',
31+
PACKAGE_JSON = 'package.json',
32+
PIPFILE = 'Pipfile',
33+
SETUP_PY = 'setup.py',
34+
REQUIREMENTS_TXT = 'requirements.txt',
35+
GOPKG_LOCK = 'Gopkg.lock',
36+
GO_MOD = 'go.mod',
37+
VENDOR_JSON = 'vendor.json',
38+
PROJECT_ASSETS_JSON = 'project.assets.json',
39+
PACKAGES_CONFIG = 'packages.config',
40+
PROJECT_JSON = 'project.json',
41+
PAKET_DEPENDENCIES = 'paket.dependencies',
42+
COMPOSER_LOCK = 'composer.lock',
43+
PODFILE_LOCK = 'Podfile.lock',
44+
COCOAPODS_PODFILE_YAML = 'CocoaPods.podfile.yaml',
45+
COCOAPODS_PODFILE = 'CocoaPods.podfile',
46+
PODFILE = 'Podfile',
47+
POETRY_LOCK = 'poetry.lock',
48+
MIX_EXS = 'mix.exs',
49+
}
50+
1951
export const SUPPORTED_PACKAGE_MANAGER_NAME: {
2052
readonly [packageManager in SupportedPackageManagers]: string;
2153
} = {

‎test/acceptance/cli-monitor/cli-monitor.acceptance.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -933,7 +933,7 @@ if (!isWindows) {
933933
'sends version number',
934934
);
935935
t.match(req.url, '/monitor/poetry/graph', 'puts at correct url');
936-
t.equal(req.body.targetFile, 'pyproject.toml', 'sends targetFile');
936+
t.equal(req.body.targetFile, 'poetry.lock', 'sends targetFile');
937937
const depGraphJSON = req.body.depGraphJSON;
938938
t.ok(depGraphJSON);
939939
});

‎test/acceptance/cli-test/cli-test.all-projects.spec.ts

-21
Original file line numberDiff line numberDiff line change
@@ -1154,26 +1154,5 @@ export const AllProjectsTests: AcceptanceTests = {
11541154
'Go dep package manager',
11551155
);
11561156
},
1157-
'`test mono-repo-poetry --all-projects`': (params, utils) => async (t) => {
1158-
utils.chdirWorkspaces();
1159-
const res: CommandResult = await params.cli.test('mono-repo-poetry', {
1160-
allProjects: true,
1161-
});
1162-
t.match(
1163-
res.getDisplayResults(),
1164-
/Tested 2 projects, no vulnerable paths were found./,
1165-
'Two projects tested',
1166-
);
1167-
t.match(
1168-
res.getDisplayResults(),
1169-
'Package manager: npm',
1170-
'Npm package manager',
1171-
);
1172-
t.match(
1173-
res.getDisplayResults(),
1174-
'Package manager: poetry',
1175-
'Poetry package manager',
1176-
);
1177-
},
11781157
},
11791158
};

‎test/acceptance/cli-test/cli-test.python.spec.ts

-17
Original file line numberDiff line numberDiff line change
@@ -279,22 +279,5 @@ export const PythonTests: AcceptanceTests = {
279279
'calls python plugin',
280280
);
281281
},
282-
'`test poetry-app with pyproject.toml and poetry.lock`': (
283-
params,
284-
utils,
285-
) => async (t) => {
286-
utils.chdirWorkspaces();
287-
await params.cli.test('poetry-app', {});
288-
const req = params.server.popRequest();
289-
t.equal(req.method, 'POST', 'makes POST request');
290-
t.equal(
291-
req.headers['x-snyk-cli-version'],
292-
params.versionNumber,
293-
'sends version number',
294-
);
295-
t.match(req.url, '/test-dep-graph', 'posts to correct url');
296-
t.equal(req.body.targetFile, 'pyproject.toml', 'specifies target');
297-
t.equal(req.body.depGraph.pkgManager.name, 'poetry');
298-
},
299282
},
300283
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[[source]]
2+
url = "https://pypi.org/simple"
3+
verify_ssl = true
4+
name = "pypi"
5+
6+
[packages]
7+
flask = "*"
8+
9+
[dev-packages]
10+
11+
[requires]
12+
python_version = "3.8"

‎test/jest/system/snyk-test/python/fixtures/pyproject-with-poetry/Pipfile.lock

+119
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎test/jest/system/snyk-test/python/fixtures/pyproject-with-poetry/poetry.lock

+391
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[tool.poetry]
2+
name = "poetry-demo"
3+
version = "0.1.0"
4+
description = ""
5+
authors = []
6+
7+
[tool.poetry.dependencies]
8+
flask = "*"
9+
10+
[tool.poetry.dev-dependencies]
11+
pytest = "^3.4"
12+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[[source]]
2+
url = "https://pypi.org/simple"
3+
verify_ssl = true
4+
name = "pypi"
5+
6+
[packages]
7+
flask = "*"
8+
9+
[dev-packages]
10+
11+
[requires]
12+
python_version = "3.8"

‎test/jest/system/snyk-test/python/fixtures/pyproject-without-poetry/Pipfile.lock

+119
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[tool.black]
2+
line-length = 119
3+
target-version = ['py38']
4+
include = '\.pyi?$'
5+
exclude = '''
6+
(
7+
/(
8+
\.eggs # exclude a few common directories in the
9+
| \.git # root of the project
10+
| \.hg
11+
| \.mypy_cache
12+
| \.tox
13+
| \.venv
14+
| _build
15+
| buck-out
16+
| build
17+
| dist
18+
)
19+
)
20+
'''
21+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
import { join } from 'path';
2+
import { mocked } from 'ts-jest/utils';
3+
import { NeedleResponse } from 'needle';
4+
import { test } from '../../../../../src/cli/commands';
5+
import { loadPlugin } from '../../../../../src/lib/plugins/index';
6+
import { CommandResult } from '../../../../../src/cli/commands/types';
7+
import makeRequest = require('../../../../../src/lib/request/request');
8+
9+
jest.mock('../../../../../src/lib/plugins/index');
10+
jest.mock('../../../../../src/lib/request/request');
11+
12+
const mockedLoadPlugin = mocked(loadPlugin, true);
13+
const mockedMakeRequest = mocked(makeRequest);
14+
15+
describe('snyk test for python project', () => {
16+
afterEach(() => {
17+
jest.clearAllMocks();
18+
});
19+
20+
describe('no flag is used', () => {
21+
describe('project contains poetry.lock file', () => {
22+
it('should scan poetry vulnerabilities', async (done) => {
23+
const fixturePath = join(
24+
__dirname,
25+
'../../../../acceptance/workspaces',
26+
'poetry-app',
27+
);
28+
29+
const plugin = {
30+
async inspect() {
31+
return {
32+
plugin: {
33+
targetFile: 'poetry.lock',
34+
name: 'snyk-python-plugin',
35+
runtime: 'Python',
36+
},
37+
package: {},
38+
};
39+
},
40+
};
41+
mockedLoadPlugin.mockImplementationOnce(() => {
42+
return plugin;
43+
});
44+
mockedMakeRequest.mockImplementationOnce(() => {
45+
return Promise.resolve({
46+
res: { statusCode: 200 } as NeedleResponse,
47+
body: {
48+
result: { issuesData: {}, affectedPkgs: {} },
49+
meta: { org: 'test-org', isPublic: false },
50+
filesystemPolicy: false,
51+
},
52+
});
53+
});
54+
55+
const result: CommandResult = await test(fixturePath, {
56+
json: true,
57+
});
58+
59+
expect(mockedLoadPlugin).toHaveBeenCalledTimes(1);
60+
expect(mockedLoadPlugin).toHaveBeenCalledWith('poetry');
61+
62+
expect(mockedMakeRequest).toHaveBeenCalledTimes(1);
63+
expect(mockedMakeRequest).toHaveBeenCalledWith(
64+
expect.objectContaining({
65+
body: expect.objectContaining({
66+
displayTargetFile: 'poetry.lock',
67+
}),
68+
}),
69+
);
70+
71+
const expectedResultObject = {
72+
vulnerabilities: [],
73+
ok: true,
74+
dependencyCount: 0,
75+
org: 'test-org',
76+
policy: undefined,
77+
isPrivate: true,
78+
licensesPolicy: null,
79+
packageManager: 'poetry',
80+
projectId: undefined,
81+
ignoreSettings: null,
82+
docker: undefined,
83+
summary: 'No known vulnerabilities',
84+
severityThreshold: undefined,
85+
remediation: undefined,
86+
filesystemPolicy: false,
87+
uniqueCount: 0,
88+
targetFile: 'poetry.lock',
89+
projectName: undefined,
90+
foundProjectCount: undefined,
91+
displayTargetFile: 'poetry.lock',
92+
platform: undefined,
93+
path: fixturePath,
94+
};
95+
expect(result).toMatchObject({
96+
result: JSON.stringify(expectedResultObject, null, 2),
97+
});
98+
99+
done();
100+
});
101+
});
102+
});
103+
104+
describe('--all-projects flag is used to scan the project', () => {
105+
describe('project does not contain poetry.lock file', () => {
106+
it('should not attempt to scan poetry vulnerabilities', async (done) => {
107+
const fixturePath = join(
108+
__dirname,
109+
'fixtures',
110+
'pyproject-without-poetry',
111+
);
112+
const plugin = {
113+
async inspect() {
114+
return {
115+
plugin: {
116+
targetFile: 'Pipfile',
117+
name: 'snyk-python-plugin',
118+
runtime: 'Python',
119+
},
120+
package: {},
121+
};
122+
},
123+
};
124+
mockedLoadPlugin.mockImplementationOnce(() => {
125+
return plugin;
126+
});
127+
mockedMakeRequest.mockImplementationOnce(() => {
128+
return Promise.resolve({
129+
res: { statusCode: 200 } as NeedleResponse,
130+
body: {
131+
result: { issuesData: {}, affectedPkgs: {} },
132+
meta: { org: 'test-org', isPublic: false },
133+
filesystemPolicy: false,
134+
},
135+
});
136+
});
137+
138+
const result: CommandResult = await test(fixturePath, {
139+
allProjects: true,
140+
json: true,
141+
});
142+
143+
expect(mockedLoadPlugin).toHaveBeenCalledTimes(1);
144+
expect(mockedLoadPlugin).toHaveBeenCalledWith('pip');
145+
146+
expect(mockedMakeRequest).toHaveBeenCalledTimes(1);
147+
expect(mockedMakeRequest).toHaveBeenCalledWith(
148+
expect.objectContaining({
149+
body: expect.objectContaining({
150+
displayTargetFile: 'Pipfile',
151+
}),
152+
}),
153+
);
154+
155+
const expectedResultObject = {
156+
vulnerabilities: [],
157+
ok: true,
158+
dependencyCount: 0,
159+
org: 'test-org',
160+
policy: undefined,
161+
isPrivate: true,
162+
licensesPolicy: null,
163+
packageManager: 'pip',
164+
projectId: undefined,
165+
ignoreSettings: null,
166+
docker: undefined,
167+
summary: 'No known vulnerabilities',
168+
severityThreshold: undefined,
169+
remediation: undefined,
170+
filesystemPolicy: false,
171+
uniqueCount: 0,
172+
targetFile: 'Pipfile',
173+
projectName: undefined,
174+
foundProjectCount: undefined,
175+
displayTargetFile: 'Pipfile',
176+
platform: undefined,
177+
path: fixturePath,
178+
};
179+
expect(result).toMatchObject({
180+
result: JSON.stringify(expectedResultObject, null, 2),
181+
});
182+
183+
done();
184+
});
185+
});
186+
187+
describe('project does contain poetry.lock file', () => {
188+
it('should scan poetry vulnerabilities', async (done) => {
189+
const fixturePath = join(
190+
__dirname,
191+
'fixtures',
192+
'pyproject-with-poetry',
193+
);
194+
195+
const pipfilePythonPluginResponse = {
196+
async inspect() {
197+
return {
198+
plugin: {
199+
targetFile: 'Pipfile',
200+
name: 'snyk-python-plugin',
201+
runtime: 'Python',
202+
},
203+
package: {},
204+
};
205+
},
206+
};
207+
const pyprojectPythonPluginResponse = {
208+
async inspect() {
209+
return {
210+
plugin: {
211+
targetFile: 'poetry.lock',
212+
name: 'snyk-python-plugin',
213+
runtime: 'Python',
214+
},
215+
package: {},
216+
};
217+
},
218+
};
219+
mockedLoadPlugin
220+
.mockImplementationOnce(() => pipfilePythonPluginResponse)
221+
.mockImplementationOnce(() => pyprojectPythonPluginResponse);
222+
mockedMakeRequest.mockImplementation(() => {
223+
return Promise.resolve({
224+
res: { statusCode: 200 } as NeedleResponse,
225+
body: {
226+
result: { issuesData: {}, affectedPkgs: {} },
227+
meta: { org: 'test-org', isPublic: false },
228+
filesystemPolicy: false,
229+
},
230+
});
231+
});
232+
233+
const result: CommandResult = await test(fixturePath, {
234+
allProjects: true,
235+
json: true,
236+
});
237+
238+
expect(mockedLoadPlugin).toHaveBeenCalledTimes(2);
239+
expect(mockedLoadPlugin).toHaveBeenCalledWith('pip');
240+
expect(mockedLoadPlugin).toHaveBeenCalledWith('poetry');
241+
242+
expect(mockedMakeRequest).toHaveBeenCalledTimes(2);
243+
expect(mockedMakeRequest).toHaveBeenCalledWith(
244+
expect.objectContaining({
245+
body: expect.objectContaining({
246+
displayTargetFile: 'Pipfile',
247+
foundProjectCount: 1,
248+
}),
249+
}),
250+
);
251+
expect(mockedMakeRequest).toHaveBeenCalledWith(
252+
expect.objectContaining({
253+
body: expect.objectContaining({
254+
displayTargetFile: 'poetry.lock',
255+
foundProjectCount: 1,
256+
}),
257+
}),
258+
);
259+
260+
const expectedPipfileResultObject = {
261+
vulnerabilities: [],
262+
ok: true,
263+
dependencyCount: 0,
264+
org: 'test-org',
265+
policy: undefined,
266+
isPrivate: true,
267+
licensesPolicy: null,
268+
packageManager: 'pip',
269+
projectId: undefined,
270+
ignoreSettings: null,
271+
docker: undefined,
272+
summary: 'No known vulnerabilities',
273+
severityThreshold: undefined,
274+
remediation: undefined,
275+
filesystemPolicy: false,
276+
uniqueCount: 0,
277+
targetFile: 'Pipfile',
278+
foundProjectCount: 1,
279+
projectName: undefined,
280+
displayTargetFile: 'Pipfile',
281+
platform: undefined,
282+
path: fixturePath,
283+
};
284+
const expectedPyprojectResultObject = {
285+
vulnerabilities: [],
286+
ok: true,
287+
dependencyCount: 0,
288+
org: 'test-org',
289+
policy: undefined,
290+
isPrivate: true,
291+
licensesPolicy: null,
292+
packageManager: 'poetry',
293+
projectId: undefined,
294+
ignoreSettings: null,
295+
docker: undefined,
296+
summary: 'No known vulnerabilities',
297+
severityThreshold: undefined,
298+
remediation: undefined,
299+
filesystemPolicy: false,
300+
uniqueCount: 0,
301+
targetFile: 'poetry.lock',
302+
foundProjectCount: 1,
303+
projectName: undefined,
304+
displayTargetFile: 'poetry.lock',
305+
platform: undefined,
306+
path: fixturePath,
307+
};
308+
expect(result).toMatchObject({
309+
result: JSON.stringify(
310+
[expectedPipfileResultObject, expectedPyprojectResultObject],
311+
null,
312+
2,
313+
),
314+
});
315+
316+
done();
317+
});
318+
});
319+
});
320+
});

0 commit comments

Comments
 (0)
Please sign in to comment.