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
127 changes: 63 additions & 64 deletions src/cyclone_dx_sbom.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,30 +58,31 @@ export default class CycloneDxSbom {
rootComponent
components
dependencies

constructor() {
this.dependencies = new Array()
this.components = new Array()


}

/**
* @param {PackageURL} root - add main/root component for sbom
* @return {CycloneDxSbom} the CycloneDxSbom Sbom Object
*/
addRoot (root) {
addRoot(root) {

this.rootComponent =
getComponent(root,"application")
getComponent(root, "application")
this.components.push(this.rootComponent)
return this
}



/**
* @return {{{"bom-ref": string, name, purl: string, type, version}}} root component of sbom.
*/
getRoot (){
getRoot() {
return this.rootComponent
}

Expand All @@ -90,52 +91,52 @@ export default class CycloneDxSbom {
* @param {PackageURL} targetRef current dependency to add to Dependencies list of component sourceRef
* @return Sbom
*/
addDependency(sourceRef, targetRef){
addDependency(sourceRef, targetRef) {
let componentIndex = this.getComponentIndex(sourceRef);
if(componentIndex < 0)
{
this.components.push(getComponent(sourceRef,"library"))
if (componentIndex < 0) {
this.components.push(getComponent(sourceRef, "library"))
}
let dependencyIndex = this.getDependencyIndex(sourceRef.purl)
if(dependencyIndex < 0)
{
if (dependencyIndex < 0) {
this.dependencies.push(createDependency(sourceRef.purl))
dependencyIndex = this.getDependencyIndex(sourceRef.purl)
}

//Only if the dependency doesn't exists on the dependency list of dependency, then add it to this list.
if(this.dependencies[dependencyIndex].dependsOn.findIndex(dep => dep === targetRef.toString()) === -1)
{
if (this.dependencies[dependencyIndex].dependsOn.findIndex(dep => dep === targetRef.toString()) === -1) {
this.dependencies[dependencyIndex].dependsOn.push(targetRef.toString())
}
if(this.getDependencyIndex(targetRef.toString()) < 0)
{
if (this.getDependencyIndex(targetRef.toString()) < 0) {
this.dependencies.push(createDependency(targetRef.toString()))
}
let newComponent = getComponent(targetRef,"library");
let newComponent = getComponent(targetRef, "library");
// Only if component doesn't exists in component list, add it to the list.
if(this.getComponentIndex(newComponent) < 0)
{
if (this.getComponentIndex(newComponent) < 0) {
this.components.push(newComponent)
}
return this
}

/**
* @return String CycloneDx Sbom json object in a string format
*/
getAsJsonString(){
getAsJsonString() {
this.sbomObject = {
"bomFormat" : "CycloneDX",
"specVersion" : "1.4",
"version" : 1,
"metadata" : {
"timestamp" : new Date(),
"component" : this.rootComponent
"bomFormat": "CycloneDX",
"specVersion": "1.4",
"version": 1,
"metadata": {
"timestamp": new Date(),
"component": this.rootComponent
},
"components" : this.components,
"dependencies" : this.dependencies
"components": this.components,
"dependencies": this.dependencies
}
if (this.rootComponent === undefined)
{
delete this.sbomObject.metadata.component
}
if(process.env["EXHORT_DEBUG"] === "true") {
if (process.env["EXHORT_DEBUG"] === "true") {
console.log("SBOM Generated for manifest, to be sent to exhort service:" + EOL + JSON.stringify(this.sbomObject, null, 4))
}
return JSON.stringify(this.sbomObject)
Expand All @@ -146,7 +147,7 @@ export default class CycloneDxSbom {
* @param {String} dependency - purl string of the component.
* @return {int} - the index of the dependency in dependencies Array, returns -1 if not found.
*/
getDependencyIndex(dependency){
getDependencyIndex(dependency) {
return this.dependencies.findIndex(dep => dep.ref === dependency)
}

Expand All @@ -156,7 +157,7 @@ export default class CycloneDxSbom {
* @return {int} index of the found component entry, if not found returns -1.
* @private
*/
getComponentIndex(theComponent){
getComponentIndex(theComponent) {

return this.components.findIndex(component => component.purl === theComponent.purl)
}
Expand All @@ -165,33 +166,30 @@ export default class CycloneDxSbom {
* @param purl {PackageURL}
* @return component
*/
purlToComponent(purl)
{
return getComponent(purl,"library")
purlToComponent(purl) {
return getComponent(purl, "library")
}

/**
* This method gets an array of dependencies to be ignored, and remove all of them from CycloneDx Sbom
* @param {Array} dependencies to be removed from sbom
* @return {CycloneDxSbom} without ignored dependencies
*/
filterIgnoredDeps(deps){
filterIgnoredDeps(deps) {
deps.forEach(dep => {
let index = this.components.findIndex(component => component.name === dep );
if(index>=0)
{
this.components.splice(index,1)
let index = this.components.findIndex(component => component.name === dep);
if (index >= 0) {
this.components.splice(index, 1)
}
index = this.dependencies.findIndex(dependency => dependency.ref.includes(dep));
if(index>=0)
{
this.dependencies.splice(index,1)
if (index >= 0) {
this.dependencies.splice(index, 1)
}

this.dependencies.forEach(dependency => {
let indexDependsOn = dependency.dependsOn.findIndex(theDep => theDep.includes(dep));
if (indexDependsOn > -1 )
{
dependency.dependsOn.splice(indexDependsOn,1)
if (indexDependsOn > -1) {
dependency.dependsOn.splice(indexDependsOn, 1)
}
})
})
Expand All @@ -203,24 +201,21 @@ export default class CycloneDxSbom {
* @param {Array} dependencies to be removed from sbom
* @return {CycloneDxSbom} without ignored dependencies
*/
filterIgnoredDepsIncludingVersion(deps){
filterIgnoredDepsIncludingVersion(deps) {
deps.forEach(dep => {
let index = this.components.findIndex(component => component.purl === dep );
if(index>=0)
{
this.components.splice(index,1)
let index = this.components.findIndex(component => component.purl === dep);
if (index >= 0) {
this.components.splice(index, 1)
}
index = this.dependencies.findIndex(dependency => dependency.ref === dep);
if(index>=0)
{
this.dependencies.splice(index,1)
if (index >= 0) {
this.dependencies.splice(index, 1)
}

this.dependencies.forEach(dependency => {
let indexDependsOn = dependency.dependsOn.findIndex(theDep => theDep === dep );
if (indexDependsOn > -1 )
{
dependency.dependsOn.splice(indexDependsOn,1)
let indexDependsOn = dependency.dependsOn.findIndex(theDep => theDep === dep);
if (indexDependsOn > -1) {
dependency.dependsOn.splice(indexDependsOn, 1)
}
})
})
Expand All @@ -233,24 +228,28 @@ export default class CycloneDxSbom {
*
* @return {boolean}
*/
checkIfPackageInsideDependsOnList(component, name)
{
checkIfPackageInsideDependsOnList(component, name) {

let dependencyIndex = this.getDependencyIndex(component.purl)
if(dependencyIndex < 0)
{
if (dependencyIndex < 0) {
return false
}

//Only if the dependency doesn't exists on the dependency list of dependency, then add it to this list.
if(this.dependencies[dependencyIndex].dependsOn.findIndex(dep => dep.includes(name) ) >= 0)
{
if (this.dependencies[dependencyIndex].dependsOn.findIndex(dep => dep.includes(name)) >= 0) {
return true;
}
else {
} else {
return false
}
}


/** Removes the root component from the sbom
*/
removeRootComponent() {
let compIndex = this.getComponentIndex(this.rootComponent)
let depIndex = this.getDependencyIndex(this.rootComponent.purl)
this.components.splice(compIndex, 1)
this.dependencies.splice(depIndex, 1)
this.rootComponent = undefined
}
}
4 changes: 4 additions & 0 deletions src/providers/python_pip.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ function createSbomStackAnalysis(manifest, opts = {}) {
})
let requirementTxtContent = fs.readFileSync(manifest).toString();
handleIgnoredDependencies(requirementTxtContent,sbom)
// In python there is no root component, then we must remove the dummy root we added, so the sbom json will be accepted by exhort backend
sbom.removeRootComponent()
return sbom.getAsJsonString()


Expand Down Expand Up @@ -208,6 +210,8 @@ function getSbomForComponentAnalysis(data, opts = {}) {
})
fs.rmSync(tmpDir, { recursive: true, force: true });
handleIgnoredDependencies(data,sbom)
// In python there is no root component, then we must remove the dummy root we added, so the sbom json will be accepted by exhort backend
sbom.removeRootComponent()
return sbom.getAsJsonString()
}

Expand Down
7 changes: 7 additions & 0 deletions src/sbom.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ export default class Sbom {
{
return this.sbomModel.checkIfPackageInsideDependsOnList(component,name)
}

/** Removes the root component from the sbom
*/
removeRootComponent()
{
return this.sbomModel.removeRootComponent()
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,9 @@
"specVersion" : "1.4",
"version" : 1,
"metadata" : {
"timestamp" : "2023-10-01T00:00:00.000Z",
"component" : {
"name" : "root",
"purl" : "pkg:pypi/root",
"type" : "application",
"bom-ref" : "pkg:pypi/root"
}
"timestamp" : "2023-10-01T00:00:00.000Z"
},
"components" : [
{
"name" : "root",
"purl" : "pkg:pypi/root",
"type" : "application",
"bom-ref" : "pkg:pypi/root"
},
{
"name" : "anyio",
"version" : "3.6.2",
Expand Down Expand Up @@ -195,36 +183,6 @@
}
],
"dependencies" : [
{
"ref" : "pkg:pypi/root",
"dependsOn" : [
"pkg:pypi/anyio@3.6.2",
"pkg:pypi/asgiref@3.4.1",
"pkg:pypi/beautifulsoup4@4.12.2",
"pkg:pypi/certifi@2023.7.22",
"pkg:pypi/chardet@4.0.0",
"pkg:pypi/contextlib2@21.6.0",
"pkg:pypi/fastapi@0.75.1",
"pkg:pypi/flask@2.0.3",
"pkg:pypi/h11@0.13.0",
"pkg:pypi/idna@2.10",
"pkg:pypi/immutables@0.19",
"pkg:pypi/importlib-metadata@4.8.3",
"pkg:pypi/itsdangerous@2.0.1",
"pkg:pypi/jinja2@3.0.3",
"pkg:pypi/markupsafe@2.0.1",
"pkg:pypi/requests@2.25.1",
"pkg:pypi/six@1.16.0",
"pkg:pypi/sniffio@1.2.0",
"pkg:pypi/soupsieve@2.3.2.post1",
"pkg:pypi/starlette@0.17.1",
"pkg:pypi/typing-extensions@4.1.1",
"pkg:pypi/urllib3@1.26.16",
"pkg:pypi/uvicorn@0.17.0",
"pkg:pypi/werkzeug@2.0.3",
"pkg:pypi/zipp@3.6.0"
]
},
{
"ref" : "pkg:pypi/anyio@3.6.2",
"dependsOn" : [ ]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,9 @@
"specVersion": "1.4",
"version": 1,
"metadata": {
"timestamp": "2023-10-01T00:00:00.000Z",
"component": {
"name": "root",
"purl": "pkg:pypi/root",
"type": "application",
"bom-ref": "pkg:pypi/root"
}
"timestamp": "2023-10-01T00:00:00.000Z"
},
"components": [
{
"name": "root",
"purl": "pkg:pypi/root",
"type": "application",
"bom-ref": "pkg:pypi/root"
},
{
"name": "anyio",
"version": "3.6.2",
Expand Down Expand Up @@ -195,36 +183,6 @@
}
],
"dependencies": [
{
"ref": "pkg:pypi/root",
"dependsOn": [
"pkg:pypi/anyio@3.6.2",
"pkg:pypi/asgiref@3.4.1",
"pkg:pypi/beautifulsoup4@4.12.2",
"pkg:pypi/certifi@2023.7.22",
"pkg:pypi/chardet@4.0.0",
"pkg:pypi/contextlib2@21.6.0",
"pkg:pypi/fastapi@0.75.1",
"pkg:pypi/flask@2.0.3",
"pkg:pypi/h11@0.13.0",
"pkg:pypi/idna@2.10",
"pkg:pypi/immutables@0.19",
"pkg:pypi/importlib-metadata@4.8.3",
"pkg:pypi/itsdangerous@2.0.1",
"pkg:pypi/jinja2@3.0.3",
"pkg:pypi/markupsafe@2.0.1",
"pkg:pypi/requests@2.25.1",
"pkg:pypi/six@1.16.0",
"pkg:pypi/sniffio@1.2.0",
"pkg:pypi/soupsieve@2.3.2.post1",
"pkg:pypi/starlette@0.17.1",
"pkg:pypi/typing-extensions@4.1.1",
"pkg:pypi/urllib3@1.26.16",
"pkg:pypi/uvicorn@0.17.0",
"pkg:pypi/werkzeug@2.0.3",
"pkg:pypi/zipp@3.6.0"
]
},
{
"ref": "pkg:pypi/anyio@3.6.2",
"dependsOn": [
Expand Down
Loading