Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,440 changes: 1,319 additions & 121 deletions package-lock.json

Large diffs are not rendered by default.

14 changes: 11 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"lint:fix": "eslint src test --ext js --fix",
"test": "c8 npm run tests",
"localtest": "EXHORT_PIP3_PATH=/home/zgrinber/python3.9/bin/pip3 EXHORT_PYTHON3_PATH=/home/zgrinber/python3.9/bin/python3 c8 npm run tests",
"postlocaltest": " git status | grep src/providers/ | grep rewire | xargs -i git clean -f {}",
"tests": "mocha --grep \"Integration Tests|.*analysis module.*\" --invert",
"tests:rep": "mocha --reporter-option maxDiffSize=0 --reporter json > unit-tests-result.json",
"integration-tests": "mocha --grep \"Integration Tests\"",
Expand All @@ -47,13 +48,18 @@
},
"dependencies": {
"@cyclonedx/cyclonedx-library": "^4.0.0",
"babel-core": "^6.26.3",
"fast-xml-parser": "^4.2.4",
"node-hook": "^1.0.0",
"packageurl-js": "^1.0.2",
"yargs": "^17.7.2"
},
"devDependencies": {
"@babel/cli": "^7.23.0",
"@babel/core": "^7.23.2",
"@openapitools/openapi-generator-cli": "^2.6.0",
"@types/node": "^20.3.1",
"babel-plugin-rewire": "^1.2.0",
"c8": "^8.0.0",
"chai": "^4.3.7",
"eslint": "^8.42.0",
Expand All @@ -65,7 +71,7 @@
"typescript": "^5.1.3"
},
"mocha": {
"check-leaks": true,
"check-leaks": false,
"color": true,
"extension": "js",
"fail-zero": true,
Expand All @@ -82,9 +88,11 @@
"exclude": [
"src/cli.js",
"src/index.js",
"src/analysis.js"
"src/analysis.js",
"src/providers/java_maven.js",
"src/providers/javascript_npm.js"
],
"lines": 85,
"lines": 83,
"reporter": [
"html",
"json",
Expand Down
50 changes: 34 additions & 16 deletions src/providers/javascript_npm.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,31 @@ import path from 'node:path'
import Sbom from '../sbom.js'
import {PackageURL} from 'packageurl-js'

export default { isSupported, provideComponent, provideStack }
export var npmInteractions = {
listing: function runNpmListing(npmListing) {
let npmOutput = execSync(npmListing, err => {
if (err) {
throw new Error('failed to get npmOutput json from npm')
}
});
return npmOutput;
},
version: function checkNpmVersion(npm) {
execSync(`${npm} --version`, err => {
if (err) {
throw new Error('npm is not accessible')
}
})
},
createPackageLock: function createPackageLock(npm, manifestDir) {
execSync(`${npm} i --package-lock-only --prefix ${manifestDir}`, err => {
if (err) {
throw new Error('failed to create npmOutput list')
}
})
}
}
export default { isSupported, provideComponent, provideStack, npmInteractions }

/** @typedef {import('../provider').Provider} */

Expand Down Expand Up @@ -73,6 +97,12 @@ function getNpmListing(npm, allFilter, manifestDir) {
return `${npm} ls${allFilter} --omit=dev --package-lock-only --json --prefix ${manifestDir}`;
}







/**
* Create SBOM json string for npm Package.
* @param {string} manifest - path for package.json
Expand All @@ -84,24 +114,12 @@ function getSBOM(manifest, opts = {}, includeTransitive) {
// get custom npm path
let npm = getCustomPath('npm', opts)
// verify npm is accessible
execSync(`${npm} --version`, err => {
if (err) {
throw new Error('npm is not accessible')
}
})
npmInteractions.version(npm);
let manifestDir = path.dirname(manifest)
execSync(`${npm} i --package-lock-only --prefix ${manifestDir}`, err => {
if (err) {
throw new Error('failed to create npmOutput list')
}
})
npmInteractions.createPackageLock(npm, manifestDir);
let allFilter = includeTransitive? " --all" : ""
let npmListing = getNpmListing(npm, allFilter, manifestDir)
let npmOutput = execSync(npmListing, err => {
if (err) {
throw new Error('failed to get npmOutput json from npm')
}
});
let npmOutput = npmInteractions.listing(npmListing);
let depsObject = JSON.parse(npmOutput);
let rootName = depsObject["name"]
let rootVersion = depsObject["version"]
Expand Down
8 changes: 5 additions & 3 deletions src/providers/python_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export default class Python_controller {
pathToPythonBin
realEnvironment
pathToRequirements
options

/**
* Constructor to create new python controller instance to interact with pip package manager
Expand All @@ -25,12 +26,13 @@ export default class Python_controller {
* @param {string} pathToRequirements
* @
*/
constructor(realEnvironment,pathToPip,pathToPython,pathToRequirements) {
constructor(realEnvironment,pathToPip,pathToPython,pathToRequirements,options={}) {
this.pathToPythonBin = pathToPython
this.pathToPipBin = pathToPip
this.realEnvironment= realEnvironment
this.prepareEnvironment()
this.pathToRequirements = pathToRequirements
this.options = options
}
prepareEnvironment()
{
Expand Down Expand Up @@ -82,7 +84,7 @@ export default class Python_controller {
console.log("Starting time to get requirements.txt dependency tree = " + startingTime)
}
if(!this.realEnvironment) {
let installBestEfforts = getCustom("EXHORT_PYTHON_INSTALL_BEST_EFFORTS","false");
let installBestEfforts = getCustom("EXHORT_PYTHON_INSTALL_BEST_EFFORTS","false",this.options);
if(installBestEfforts === "false")
{
execSync(`${this.pathToPipBin} install -r ${this.pathToRequirements}`, err =>{
Expand All @@ -95,7 +97,7 @@ export default class Python_controller {
// that means that it will install the packages without referring to the versions, but will let pip choose the version
// tailored for version of the python environment( and of pip package manager) for each package.
else {
let matchManifestVersions = getCustom("MATCH_MANIFEST_VERSIONS","true");
let matchManifestVersions = getCustom("MATCH_MANIFEST_VERSIONS","true",this.options);
if(matchManifestVersions === "true")
{
throw new Error("Conflicting settings, EXHORT_PYTHON_INSTALL_BEST_EFFORTS=true can only work with MATCH_MANIFEST_VERSIONS=false")
Expand Down
6 changes: 4 additions & 2 deletions test/it/end-to-end.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,12 @@ suite('Integration Tests', () => {
// // process.env["RHDA_TOKEN"] = "34JKLDS-4234809-66666666666"
// // process.env["RHDA_SOURCE"] = "Zvika Client"
// // let result = await index.stackAnalysis("/tmp/rajan-0410/go.mod", false, opts);
//
// let opts = {
// MATCH_MANIFEST_VERSIONS: 'false'
// }
//
// let pomPath = `/tmp/231023/requirements.txt`
// let providedDataForStack = await index.stackAnalysis(pomPath)
// let providedDataForStack = await index.stackAnalysis(pomPath,opts)
// console.log(JSON.stringify(providedDataForStack.summary,null , 4))
// expect(providedDataForStack.summary.dependencies.scanned).greaterThan(0)
// }).timeout(15000);
Expand Down
46 changes: 41 additions & 5 deletions test/providers/java_maven.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,30 @@ import { expect } from 'chai'
import fs from 'fs'
import sinon from "sinon";
// import exhort from "../../dist/src/index.js"
import javaMvnProvider from '../../src/providers/java_maven.js'
import {rewireProvider} from "./test-utils.js";
let clock

let javaMvnProviderRewire = await rewireProvider("src/providers/java_maven")

/** this function is parsing the outputfile path from the given command, and write that file the providerContent supplied.
*
* @param {string}command - the command string to be executed
* @param {string}providerContent - the content of the mocked data to replace original content in intercepted temp file
* @param {string} outputFileParameter - name of the parameter indicating the output file of the command invocation, including '='.
* @private
*/
function interceptAndOverwriteDataWithMock(command, providerContent, outputFileParameter) {
let length = outputFileParameter.length;
let indexOf = command.indexOf(outputFileParameter);
let outputFileTokenPlusRest = command.substring(indexOf + length);
let endOfOutputFile = outputFileTokenPlusRest.indexOf("-f");
let interceptedFilePath = outputFileTokenPlusRest.substring(0, endOfOutputFile).trim()
fs.writeFileSync(interceptedFilePath, providerContent)
}

import javaMvnProvider from '../../src/providers/java_maven.js'
let clock
suite('testing the java-maven data provider', () => {

[
{name: 'pom.xml', expected: true},
{name: 'some_other.file', expected: false}
Expand All @@ -17,7 +35,8 @@ suite('testing the java-maven data provider', () => {
)
});

[ "poms_deps_with_2_ignore_long",
[
"poms_deps_with_2_ignore_long",
"pom_deps_with_ignore_on_artifact",
"pom_deps_with_ignore_on_dependency",
"pom_deps_with_ignore_on_group",
Expand Down Expand Up @@ -48,15 +67,24 @@ suite('testing the java-maven data provider', () => {
test(`verify maven data provided for stack analysis with scenario ${scenario}`, async () => {
// load the expected graph for the scenario
let expectedSbom = fs.readFileSync(`test/providers/tst_manifests/maven/${testCase}/stack_analysis_expected_sbom.json`,).toString()
let dependencyTreeTextContent = fs.readFileSync(`test/providers/tst_manifests/maven/${testCase}/dep-tree.txt`,).toString()
expectedSbom = JSON.stringify(JSON.parse(expectedSbom))
let mockedExecFunction = function(command){
if(command.includes(":tree")){
interceptAndOverwriteDataWithMock(command,dependencyTreeTextContent,"DoutputFile=")
}
}
javaMvnProviderRewire.__set__('execSync',mockedExecFunction)
// invoke sut stack analysis for scenario manifest
let providedDataForStack = await javaMvnProvider.provideStack(`test/providers/tst_manifests/maven/${testCase}/pom.xml`)
let providedDataForStack = await javaMvnProviderRewire.__get__("provideStack")(`test/providers/tst_manifests/maven/${testCase}/pom.xml`)
javaMvnProviderRewire.__ResetDependency__()
// verify returned data matches expectation
expect(providedDataForStack).to.deep.equal({
ecosystem: 'maven',
contentType: 'application/vnd.cyclonedx+json',
content: expectedSbom
})

// these test cases takes ~2500-2700 ms each pr >10000 in CI (for the first test-case)
}).timeout(process.env.GITHUB_ACTIONS ? 40000 : 10000)

Expand All @@ -65,15 +93,23 @@ suite('testing the java-maven data provider', () => {
let expectedSbom = fs.readFileSync(`test/providers/tst_manifests/maven/${testCase}/component_analysis_expected_sbom.json`,).toString().trim()
// read target manifest file
expectedSbom = JSON.stringify(JSON.parse(expectedSbom))
let effectivePomContent = fs.readFileSync(`test/providers/tst_manifests/maven/${testCase}/effective-pom.xml`,).toString()
let manifestContent = fs.readFileSync(`test/providers/tst_manifests/maven/${testCase}/pom.xml`).toString()
let mockedExecFunction = function(command){
if(command.includes(":effective-pom")){
interceptAndOverwriteDataWithMock(command, effectivePomContent,"Doutput=");
}
}
javaMvnProviderRewire.__set__('execSync',mockedExecFunction)
// invoke sut component analysis for scenario manifest
let providedDataForStack = await javaMvnProvider.provideComponent(manifestContent)
let providedDataForStack = await javaMvnProviderRewire.__get__("provideComponent")(manifestContent)
// verify returned data matches expectation
expect(providedDataForStack).to.deep.equal({
ecosystem: 'maven',
contentType: 'application/vnd.cyclonedx+json',
content: expectedSbom
})
javaMvnProviderRewire.__ResetDependency__()
// these test cases takes ~1400-2000 ms each pr >10000 in CI (for the first test-case)
}).timeout(process.env.GITHUB_ACTIONS ? 15000 : 5000)
// these test cases takes ~1400-2000 ms each pr >10000 in CI (for the first test-case)
Expand Down
49 changes: 40 additions & 9 deletions test/providers/javascript_npm.test.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,52 @@
import { expect } from 'chai'
import fs from 'fs'
import sinon from "sinon";
// import babelCore from 'babel-core'
import javascriptNpmProvider from "../../src/providers/javascript_npm.js"
import {rewireProvider} from "./test-utils.js";


let javascriptNpmProviderRewire = await rewireProvider("src/providers/javascript_npm")

let clock
suite('testing the javascript-npm data provider', () => {


// async function rewireProvider()
// {
// let jsNpmProvider = fs.readFileSync("src/providers/javascript_npm.js")
// let javascriptNpmProviderSource = babelCore.transform(jsNpmProvider, {plugins: ["babel-plugin-rewire"]}).code;
// fs.writeFileSync("src/providers/javascript_npm_rewire.js",javascriptNpmProviderSource)
// javascriptNpmProviderRewire = await import("../../src/providers/javascript_npm_rewire.js")
// }

suite('testing the javascript-npm data provider', async() => {
[

{name: 'package.json', expected: true},
{name: 'some_other.file', expected: false}
].forEach(testCase => {
test(`verify isSupported returns ${testCase.expected} for ${testCase.name}`, () =>
expect(javascriptNpmProvider.isSupported(testCase.name)).to.equal(testCase.expected)
)
});

[
"package_json_deps_without_exhortignore_object",
"package_json_deps_with_exhortignore_object"
].forEach(testCase => {
let scenario = testCase.replace('package_json_deps_', '').replaceAll('_', ' ')
test(`verify package.json data provided for stack analysis with scenario ${scenario}`, async () => {
// javascriptNpmProviderRewire = await rewireProvider("src/providers/javascript_npm")
// load the expected graph for the scenario
let expectedSbom = fs.readFileSync(`test/providers/tst_manifests/npm/${testCase}/stack_expected_sbom.json`,).toString()
let npmListing = fs.readFileSync(`test/providers/tst_manifests/npm/${testCase}/npm_listing_stack.json`,).toString()
expectedSbom = JSON.stringify(JSON.parse(expectedSbom))
let mpmMockedInteractions = {
listing: () => npmListing,
version: () => void (0),
createPackageLock: () => void (0)
}
javascriptNpmProviderRewire.__set__('npmInteractions', mpmMockedInteractions)
// invoke sut stack analysis for scenario manifest

let providedDataForStack = await javascriptNpmProvider.provideStack(`test/providers/tst_manifests/npm/${testCase}/package.json`)
let providedDataForStack = javascriptNpmProviderRewire.__get__("provideStack")(`test/providers/tst_manifests/npm/${testCase}/package.json`)
// new(year: number, month: number, date?: number, hours?: number, minutes?: number, seconds?: number, ms?: number): Date

// providedDataForStack.content = providedDataForStack.content.replaceAll("\"timestamp\":\"[a-zA-Z0-9\\-\\:]+\"","")
Expand All @@ -37,23 +56,35 @@ suite('testing the javascript-npm data provider', () => {
contentType: 'application/vnd.cyclonedx+json',
content: expectedSbom
})
// these test cases takes ~2500-2700 ms each pr >10000 in CI (for the first test-case)
}).timeout(process.env.GITHUB_ACTIONS ? 30000 : 10000)

javascriptNpmProviderRewire.__ResetDependency__()
// javascriptNpmProviderSource.runNpmListing.restore()
// these test cases takes ~2500-2700 ms each pr >10000 in CI (for the first test-case)
}).timeout(process.env.GITHUB_ACTIONS ? 30000 : 10000);
test(`verify package.json data provided for component analysis with scenario ${scenario}`, async () => {
// javascriptNpmProviderRewire = await rewireProvider("src/providers/javascript_npm")
// load the expected list for the scenario
let expectedSbom = fs.readFileSync(`test/providers/tst_manifests/npm/${testCase}/component_expected_sbom.json`,).toString().trim()
expectedSbom = JSON.stringify(JSON.parse(expectedSbom))
let npmListing = fs.readFileSync(`test/providers/tst_manifests/npm/${testCase}/npm_listing_component.json`,).toString()
// read target manifest file
let manifestContent = fs.readFileSync(`test/providers/tst_manifests/npm/${testCase}/package.json`).toString()
// sinon.stub(javascriptNpmProviderSource,'runNpmListing').callsFake(() => npmListing)
let mpmMockedInteractions = {
listing: () => npmListing,
version: () => void (0),
createPackageLock: () => void (0)
}
javascriptNpmProviderRewire.__set__('npmInteractions', mpmMockedInteractions)
// invoke sut stack analysis for scenario manifest
let providedDataForStack = await javascriptNpmProvider.provideComponent(manifestContent)
let providedDataForStack = await javascriptNpmProviderRewire.__get__("provideComponent")(manifestContent)
// verify returned data matches expectation
expect(providedDataForStack).to.deep.equal({
ecosystem: 'npm',
contentType: 'application/vnd.cyclonedx+json',
content: expectedSbom
})
javascriptNpmProviderRewire.__ResetDependency__()
// javascriptNpmProviderSource.runNpmListing.restore()
// these test cases takes ~1400-2000 ms each pr >10000 in CI (for the first test-case)
}).timeout(process.env.GITHUB_ACTIONS ? 15000 : 10000)

Expand Down
19 changes: 19 additions & 0 deletions test/providers/test-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import babelCore from "babel-core";
import fs from "fs";

async function dynamicImportProvider(path) {
return await import(path)
}

/**
*
* @param path
* @return providerInstance - provider instance that exposing private method/functions/properties to be mocked/stubbed
*/
export function rewireProvider(path)
{
let providerBuffeer = fs.readFileSync(path + ".js")
let providerSource = babelCore.transform(providerBuffeer, {plugins: ["babel-plugin-rewire"]}).code;
fs.writeFileSync(path + "_rewire.js",providerSource)
return dynamicImportProvider("../../" + path + "_rewire.js" )
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pom-with-deps-and-ignore:pom-with-dependency-not-ignored-for-tests:jar:0.0.1
Loading