Skip to content

Commit

Permalink
Show all possible ancestor dependency paths for a package
Browse files Browse the repository at this point in the history
  • Loading branch information
rhyskoedijk committed Jan 8, 2025
1 parent c92066e commit 8f45569
Show file tree
Hide file tree
Showing 6 changed files with 1,430 additions and 39 deletions.
27 changes: 27 additions & 0 deletions shared/models/spdx/2.3/IDocument.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as fs from 'fs';

import { expect } from '@jest/globals';

import { getPackageAncestorPaths, IDocument } from './IDocument';

describe('getPackageAncestorPaths', () => {
const document = JSON.parse(fs.readFileSync('tests/spdx/manifest.spdx.json', 'utf-8')) as IDocument;

it('should return an empty array if the package is not found', () => {
const result = getPackageAncestorPaths(document, 'SPDXRef-NonExistentPackage');
expect(result).toEqual([]);
});

it('should return an empty array if the package has no ancestors', () => {
const result = getPackageAncestorPaths(document, 'SPDXRef-RootPackage');
expect(result).toEqual([]);
});

it('should return the correct dependency path for a package with ancestors', () => {
const targetPackage = document.packages.find((p) => p.name === 'Microsoft.Identity.Web')?.SPDXID || '';
const result = getPackageAncestorPaths(document, targetPackage);
expect(result).toMatchObject([
{ dependencyPath: [{ name: 'Microsoft.Identity.Web.UI' }, { name: 'Microsoft.Identity.Web' }] },
]);
});
});
50 changes: 38 additions & 12 deletions shared/models/spdx/2.3/IDocument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ export enum DocumentDataLicense {
CC0_1_0 = 'CC0-1.0',
}

export interface IPackageDependencyPath {
dependencyPath: IPackage[];
}

export function getDisplayNameForDocument(document: IDocument): string | undefined {
const describesPackageIds = document.documentDescribes;
const packages = document.packages?.filter((p) => describesPackageIds.includes(p.SPDXID))?.map((p) => p.name);
Expand All @@ -40,7 +44,7 @@ export function getDisplayNameForDocument(document: IDocument): string | undefin
}
}

export function getPackageDependsOnChain(document: IDocument, packageId: string): IPackage[] {
export function getPackageAncestorPaths(document: IDocument, packageId: string): IPackageDependencyPath[] {
const hasMultipleRootPackages = document.documentDescribes.length > 1;
const rootPackageIds = document.documentDescribes;
const relationships = document.relationships || [];
Expand All @@ -51,21 +55,43 @@ export function getPackageDependsOnChain(document: IDocument, packageId: string)
return hasMultipleRootPackages || !rootPackageIds.includes(p.SPDXID);
});

// Walk the chain of "DependsOn" relationships for the package to discover the dependencies
const packageChain: IPackage[] = [];
let currentElementId = packageId;
while (currentElementId) {
const relationship = dependsOnRelationships.find((r) => r.relatedSpdxElement === currentElementId);
if (!relationship) break;
const currentPackage = packages.find((p) => p.SPDXID === packageId);
if (!currentPackage) {
return [];
}

// Walk the dependency tree via all "DependsOn" relationships with the package to discover all possible package dependency paths
const ancestorDependencyReleationships = dependsOnRelationships.filter((r) => r.relatedSpdxElement === packageId);
return ancestorDependencyReleationships.flatMap((relationship) =>
getPackageAncestorPathsRecursive(dependsOnRelationships, packages, relationship.spdxElementId, [currentPackage]),
);
}

const pkg = packages.find((p) => p.SPDXID === relationship.spdxElementId);
if (!pkg) break;
function getPackageAncestorPathsRecursive(
relationships: IRelationship[],
packages: IPackage[],
packageId: string,
pathSoFar: IPackage[],
): IPackageDependencyPath[] {
const currentPackage = packages.find((p) => p.SPDXID === packageId);
if (!currentPackage) {
return [{ dependencyPath: pathSoFar }];
}

packageChain.unshift(pkg);
currentElementId = relationship.spdxElementId;
const newPath = [currentPackage, ...pathSoFar].filter((p) => p !== undefined);
const ancestorDependencyPaths: IPackageDependencyPath[] = [];
const ancestorDependencyReleationships = relationships.filter((r) => r.relatedSpdxElement === packageId);
if (ancestorDependencyReleationships.length > 0) {
for (const relationship of ancestorDependencyReleationships) {
ancestorDependencyPaths.push(
...getPackageAncestorPathsRecursive(relationships, packages, relationship.spdxElementId, newPath),
);
}
} else {
ancestorDependencyPaths.push({ dependencyPath: newPath });
}

return packageChain;
return ancestorDependencyPaths;
}

export function getPackageLevelName(document: IDocument, packageId: string): string {
Expand Down
14 changes: 7 additions & 7 deletions shared/spdx/convertSpdxToXlsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { IJsonSheet } from 'json-as-xlsx';
import { getSeverityByName } from '../models/severity/Severities';
import { ChecksumAlgorithm, getChecksum } from '../models/spdx/2.3/IChecksum';
import { getCreatorOrganization, getCreatorTool } from '../models/spdx/2.3/ICreationInfo';
import { getPackageDependsOnChain, getPackageLevelName, IDocument } from '../models/spdx/2.3/IDocument';
import { getPackageAncestorPaths, getPackageLevelName, IDocument } from '../models/spdx/2.3/IDocument';
import {
ExternalRefCategory,
ExternalRefSecurityType,
Expand Down Expand Up @@ -163,9 +163,9 @@ export async function convertSpdxToXlsxAsync(spdx: IDocument): Promise<Buffer> {
packageManagerUrl: getExternalRefPackageManagerUrl(pkg.externalRefs) || '',
type: getPackageLevelName(spdx, pkg.SPDXID) || '',
introducedThrough:
getPackageDependsOnChain(spdx, pkg.SPDXID)
.map((x) => x.name)
.join(' > ') || '',
getPackageAncestorPaths(spdx, pkg.SPDXID)
.map((p) => p.dependencyPath.map((p) => p.name).join(' > '))
.join(', ') || '',
license: getPackageLicenseExpression(pkg) || '',
supplier: getPackageSupplierOrganization(pkg) || '',
totalVulnerabilities: securityAdvisories.length,
Expand Down Expand Up @@ -227,9 +227,9 @@ export async function convertSpdxToXlsxAsync(spdx: IDocument): Promise<Buffer> {
firstPatchedVersion: vuln.firstPatchedVersion,
introducedThrough:
(packageSpdxId &&
getPackageDependsOnChain(spdx, packageSpdxId)
.map((p) => p.name)
.join(' > ')) ||
getPackageAncestorPaths(spdx, packageSpdxId)
.map((p) => p.dependencyPath.map((p) => p.name).join(' > '))
.join(', ')) ||
'',
severity: vuln.advisory.severity?.toPascalCase(),
cvssScore: vuln.advisory.cvss?.score || '',
Expand Down
Loading

0 comments on commit 8f45569

Please sign in to comment.