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

fix: maven sa wrongly excluding provided scoped artifacts #90

Merged
merged 2 commits into from
Dec 20, 2023
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
75 changes: 36 additions & 39 deletions src/providers/java_maven.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { XMLParser } from 'fast-xml-parser'
import { execSync } from "node:child_process"
import {XMLParser} from 'fast-xml-parser'
import {execSync} from "node:child_process"
import fs from 'node:fs'
import { getCustomPath } from "../tools.js";
import {getCustomPath} from "../tools.js";
import os from 'node:os'
import path from 'node:path'
import Sbom from '../sbom.js'

Check warning on line 7 in src/providers/java_maven.js

View workflow job for this annotation

GitHub Actions / Lint and test project (18)

Imports should be sorted alphabetically

Check warning on line 7 in src/providers/java_maven.js

View workflow job for this annotation

GitHub Actions / Lint and test project (latest)

Imports should be sorted alphabetically
import {PackageURL} from 'packageurl-js'

Check warning on line 8 in src/providers/java_maven.js

View workflow job for this annotation

GitHub Actions / Lint and test project (18)

Imports should be sorted alphabetically

Check warning on line 8 in src/providers/java_maven.js

View workflow job for this annotation

GitHub Actions / Lint and test project (latest)

Imports should be sorted alphabetically
import {EOL} from 'os'
import {EOL} from 'os'

Check warning on line 9 in src/providers/java_maven.js

View workflow job for this annotation

GitHub Actions / Lint and test project (18)

Imports should be sorted alphabetically

Check warning on line 9 in src/providers/java_maven.js

View workflow job for this annotation

GitHub Actions / Lint and test project (latest)

Imports should be sorted alphabetically

export default { isSupported, provideComponent, provideStack }
export default {isSupported, provideComponent, provideStack}

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

Expand Down Expand Up @@ -77,6 +77,7 @@
parseDependencyTree(root, 0, lines.slice(1), sbom);
return sbom.filterIgnoredDepsIncludingVersion(ignoredDeps).getAsJsonString();
}

const DEP_REGEX = /(([-a-zA-Z0-9._]{2,})|[0-9])/g
// const DEP_REGEX = /(?:([-a-zA-Z0-9._]+):([-a-zA-Z0-9._]+):[-a-zA-Z0-9._]+:([-a-zA-Z0-9._]+):[-a-zA-Z]+)/
// const ROOT_REGEX = /(?:([-a-zA-Z0-9._]+):([-a-zA-Z0-9._]+):[-a-zA-Z0-9._]+:([-a-zA-Z0-9._]+))/
Expand All @@ -91,22 +92,27 @@
* @private
*/
function parseDependencyTree(src, srcDepth, lines, sbom) {
if(lines.length === 0) {
if (lines.length === 0) {
return;
}
if((lines.length === 1 && lines[0].trim() === "")) {
if ((lines.length === 1 && lines[0].trim() === "")) {
return;
}
let index = 0;
let target = lines[index];
let targetDepth = getDepth(target);
while(targetDepth > srcDepth && index < lines.length) {
if(targetDepth === srcDepth + 1) {
while (targetDepth > srcDepth && index < lines.length) {
if (targetDepth === srcDepth + 1) {
let from = parseDep(src);
let to = parseDep(target);
sbom.addDependency(sbom.purlToComponent(from), to)
let matchedScope = target.match(/:compile|:provided|:runtime|:test|:system/g)
let matchedScopeSrc = src.match(/:compile|:provided|:runtime|:test|:system/g)
// only add dependency to sbom if it's not with test scope or if it's root
if ((matchedScope && matchedScope[0] !== ":test" && (matchedScopeSrc && matchedScopeSrc[0] !== ":test")) || (srcDepth == 0 && matchedScope && matchedScope[0] !== ":test")) {
sbom.addDependency(sbom.purlToComponent(from), to)
}
} else {
parseDependencyTree(lines[index-1], getDepth(lines[index-1]), lines.slice(index), sbom)
parseDependencyTree(lines[index - 1], getDepth(lines[index - 1]), lines.slice(index), sbom)
}
target = lines[++index];
targetDepth = getDepth(target);
Expand All @@ -120,7 +126,7 @@
* @private
*/
function getDepth(line) {
if(line === undefined) {
if (line === undefined) {
return -1;
}
return ((line.indexOf('-') - 1) / 3) + 1;
Expand All @@ -135,14 +141,13 @@
function parseDep(line) {

let match = line.match(DEP_REGEX);
if(!match) {
if (!match) {
throw new Error(`Unable generate SBOM from dependency tree. Line: ${line} cannot be parsed into a PackageURL`);
}
let version
if(match.length >=5 && ['compile','provided','runtime'].includes(match[5])) {
if (match.length >= 5 && ['compile', 'provided', 'runtime'].includes(match[5])) {
version = `${match[4]}-${match[3]}`
}
else {
} else {
version = match[3]
}
let override = line.match(CONFLICT_REGEX);
Expand Down Expand Up @@ -178,14 +183,14 @@
let tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'exhort_'))
let tmpDepTree = path.join(tmpDir, 'mvn_deptree.txt')
// build initial command (dot outputType is not available for verbose mode)
let depTreeCmd = `${mvn} -q org.apache.maven.plugins:maven-dependency-plugin:3.6.0:tree -Dverbose -DoutputType=text -Dscope=compile -Dscope=runtime -DoutputFile=${tmpDepTree} -f ${manifest}`
let depTreeCmd = `${mvn} -q org.apache.maven.plugins:maven-dependency-plugin:3.6.0:tree -Dverbose -DoutputType=text -DoutputFile=${tmpDepTree} -f ${manifest}`
// exclude ignored dependencies, exclude format is groupId:artifactId:scope:version.
// version and scope are marked as '*' if not specified (we do not use scope yet)
let ignoredDeps = new Array()
getDependencies(manifest).forEach(dep => {
if (dep.ignore) {
depTreeCmd += ` -Dexcludes=${dep['groupId']}:${dep['artifactId']}:${dep['scope']}:${dep['version']}`
ignoredDeps.push(toPurl(dep.groupId,dep.artifactId,dep.version).toString())
ignoredDeps.push(toPurl(dep.groupId, dep.artifactId, dep.version).toString())
}
})
// execute dependency tree command
Expand All @@ -195,11 +200,11 @@
}
})
// read dependency tree from temp file
let content= fs.readFileSync(`${tmpDepTree}`)
if(process.env["EXHORT_DEBUG"] === "true") {
let content = fs.readFileSync(`${tmpDepTree}`)
if (process.env["EXHORT_DEBUG"] === "true") {
console.log("Dependency tree that will be used as input for creating the BOM =>" + EOL + EOL + content.toString())
}
let sbom = createSbomFileFromTextFormat(content.toString(),ignoredDeps);
let sbom = createSbomFileFromTextFormat(content.toString(), ignoredDeps);
// delete temp file and directory
fs.rmSync(tmpDir, {recursive: true, force: true})
// return dependency graph as string
Expand Down Expand Up @@ -242,12 +247,12 @@
.filter(d => !(dependencyIn(d, ignored)) && !(dependencyInExcludingVersion(d, ignored)))
let sbom = new Sbom();
let rootDependency = getRootFromPom(tmpTargetPom);
let purlRoot = toPurl(rootDependency.groupId,rootDependency.artifactId,rootDependency.version)
let purlRoot = toPurl(rootDependency.groupId, rootDependency.artifactId, rootDependency.version)
sbom.addRoot(purlRoot)
let rootComponent = sbom.getRoot();
dependencies.forEach(dep => {
let currentPurl = toPurl(dep.groupId,dep.artifactId,dep.version)
sbom.addDependency(rootComponent,currentPurl)
let currentPurl = toPurl(dep.groupId, dep.artifactId, dep.version)
sbom.addDependency(rootComponent, currentPurl)
})
// delete temp files and directory
fs.rmSync(tmpDir, {recursive: true, force: true})
Expand All @@ -256,8 +261,6 @@
}




/**
*
* @param pom.xml manifest path
Expand Down Expand Up @@ -288,13 +291,11 @@
* @param version
* @return {PackageURL}
*/
function toPurl(group,artifact,version)
{
if(typeof version === "number")
{
function toPurl(group, artifact, version) {
if (typeof version === "number") {
version = version.toString()
}
return new PackageURL('maven',group,artifact,version,undefined,undefined);
return new PackageURL('maven', group, artifact, version, undefined, undefined);
}

/**
Expand All @@ -321,7 +322,7 @@
if (dep['#comment'] && dep['#comment'].includes('exhortignore')) { // #comment is an array or a string
ignore = true
}
if(dep['scope'] !== 'test') {
if (dep['scope'] !== 'test') {
ignored.push({
groupId: dep['groupId'],
artifactId: dep['artifactId'],
Expand All @@ -344,13 +345,9 @@
* @private
*/
function dependencyIn(dep, deps) {
return deps.filter(d => dep.artifactId === d.artifactId &&
dep.groupId === d.groupId &&
dep.version === d.version &&
dep.scope === d.scope) .length > 0
return deps.filter(d => dep.artifactId === d.artifactId && dep.groupId === d.groupId && dep.version === d.version && dep.scope === d.scope).length > 0
}

function dependencyInExcludingVersion(dep, deps) {
return deps.filter(d => dep.artifactId === d.artifactId &&
dep.groupId === d.groupId &&
dep.scope === d.scope) .length > 0
return deps.filter(d => dep.artifactId === d.artifactId && dep.groupId === d.groupId && dep.scope === d.scope).length > 0
}
16 changes: 9 additions & 7 deletions test/providers/java_maven.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ 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 expectedSbom = fs.readFileSync(`test/providers/tst_manifests/maven/${testCase}/stack_analysis_expected_sbom.json`,).toString().trim()
let dependencyTreeTextContent = fs.readFileSync(`test/providers/tst_manifests/maven/${testCase}/dep-tree.txt`,).toString()
expectedSbom = JSON.stringify(JSON.parse(expectedSbom))
expectedSbom = JSON.stringify(JSON.parse(expectedSbom),null, 4)
let mockedExecFunction = function(command){
if(command.includes(":tree")){
interceptAndOverwriteDataWithMock(command,dependencyTreeTextContent,"DoutputFile=")
Expand All @@ -79,11 +79,13 @@ suite('testing the java-maven data provider', () => {
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
})
// expect(providedDataForStack).to.deep.equal({
// ecosystem: 'maven',
// contentType: 'application/vnd.cyclonedx+json',
// content: expectedSbom
// })
let beautifiedOutput = JSON.stringify(JSON.parse(providedDataForStack.content),null, 4);
expect(beautifiedOutput).to.deep.equal(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 Down
Loading