diff --git a/ui/components/sbom/SpdxSummaryCard.tsx b/ui/components/sbom/SpdxSummaryCard.tsx index a9ed73e..b77b7ba 100644 --- a/ui/components/sbom/SpdxSummaryCard.tsx +++ b/ui/components/sbom/SpdxSummaryCard.tsx @@ -6,7 +6,6 @@ import { ZeroData } from 'azure-devops-ui/ZeroData'; import { createTheme, Theme, ThemeProvider } from '@mui/material'; -import { SecurityAdvisoryIdentifierType } from '../../../shared/ghsa/ISecurityAdvisory'; import { ISecurityVulnerability } from '../../../shared/ghsa/ISecurityVulnerability'; import { ISeverity } from '../../../shared/models/severity/ISeverity'; import { DEFAULT_SEVERITY, getSeverityByName, SEVERITIES } from '../../../shared/models/severity/Severities'; @@ -21,6 +20,13 @@ import { BarChart, BarChartSeries } from '../charts/BarChart'; import { PieChart, PieChartValue } from '../charts/PieChart'; import { Tile } from '../charts/Tile'; +interface Recommendation { + severity: ISeverity; + title: string; + target: string; + action: string; +} + interface Props { document: IDocument; files: IFile[]; @@ -39,26 +45,29 @@ interface State { packages?: { total: number; totalVulnerable: number; - packageManagersChartData: PieChartValue[]; packageTypesChartData: PieChartValue[]; + packageManagersChartData: PieChartValue[]; }; securityAdvisories?: { total: number; weaknesses: string[]; + vulnPackageNames: string[]; vulnHighestSeverity: ISeverity; vulnByPackageManagerChartData: BarChartSeries[]; + vulnByPackageNameChartData: BarChartSeries[]; vulnByWeaknessChartData: BarChartSeries[]; - vulnAgeInDaysChartData?: PieChartValue[]; - vulnPackagesChartData?: PieChartValue[]; - vulnFixableChartData?: PieChartValue[]; - vulnCvssScoresChartData?: PieChartValue[]; + // vulnSeverityAndCvssHeatMapChartData: HeatMapValue[]; + // vulnPublishedTimelineChartData: TimelineValue[]; }; licenses?: { total: number; + // licensesChartData: PieChartValue[]; }; suppliers?: { total: number; + // suppliersChartData: PieChartValue[]; }; + // recommendations?: Recommendation[]; } export class SpdxSummaryCard extends React.Component { @@ -75,6 +84,13 @@ export class SpdxSummaryCard extends React.Component { ) .orderBy((pm: string) => pm, false) .distinct(); + const vulnPackageNames = reduceAsMap( + props.securityAdvisories.map((v) => v.package), + (p) => p.name, + (k, v) => k, + ) + .orderBy((pn: string) => pn, false) + .distinct(); const weaknesses = props.securityAdvisories .flatMap((v) => v.advisory.cwes || []) .map((w) => w.id) @@ -91,14 +107,14 @@ export class SpdxSummaryCard extends React.Component { totalVulnerable: props.packages.filter((p) => p.externalRefs?.some((ref) => spdxConstantsAreEqual(ref.referenceCategory, ExternalRefCategory.Security)), ).length, - packageManagersChartData: reduceAsMap( + packageTypesChartData: reduceAsMap( props.packages, - (p) => getExternalRefPackageManagerName(p.externalRefs) || 'Other', + (p) => (isPackageTopLevel(props.document, p.SPDXID) ? 'Top Level' : 'Transitive'), (k, v) => ({ label: k, value: v }), ), - packageTypesChartData: reduceAsMap( + packageManagersChartData: reduceAsMap( props.packages, - (p) => (isPackageTopLevel(props.document, p.SPDXID) ? 'Top Level' : 'Transitive'), + (p) => getExternalRefPackageManagerName(p.externalRefs) || 'Other', (k, v) => ({ label: k, value: v }), ), } @@ -107,6 +123,7 @@ export class SpdxSummaryCard extends React.Component { ? { total: props.securityAdvisories.length, weaknesses: weaknesses, + vulnPackageNames: vulnPackageNames, vulnHighestSeverity: props.securityAdvisories .map((s) => getSeverityByName(s.advisory.severity)) .reduce((max, s) => (s.weight > max.weight ? s : max), DEFAULT_SEVERITY), @@ -115,31 +132,14 @@ export class SpdxSummaryCard extends React.Component { packageManagers, props.securityAdvisories, ), - vulnByWeaknessChartData: SpdxSummaryCard.getVulnerabiilityWeaknessesChartData( - weaknesses, - props.securityAdvisories, - ), - vulnAgeInDaysChartData: summariseAsMap( - props.securityAdvisories, - (v) => - v.advisory.identifiers?.find((id) => id.type == SecurityAdvisoryIdentifierType.Ghsa)?.value || - 'Unknown', - (v) => Math.floor((Date.now() - new Date(v.advisory.publishedAt).getTime()) / (1000 * 60 * 60 * 24)), - ), - vulnPackagesChartData: reduceAsMap( - props.securityAdvisories, - (v) => v.package.name, - (k, v) => ({ label: k, value: v }), - ), - vulnFixableChartData: reduceAsMap( + vulnByPackageNameChartData: SpdxSummaryCard.getVulnerabilitiesByPackageNameChartData( + props.packages, + vulnPackageNames, props.securityAdvisories, - (v) => (v.firstPatchedVersion ? 'Fixable' : 'Not Fixable'), - (k, v) => ({ label: k, value: v }), ), - vulnCvssScoresChartData: reduceAsMap( + vulnByWeaknessChartData: SpdxSummaryCard.getVulnerabiilityWeaknessesChartData( + weaknesses, props.securityAdvisories, - (v) => Math.floor(v.advisory.cvss?.score || 0).toString(), - (k, v) => ({ label: k, value: v }), ), } : undefined, @@ -180,6 +180,29 @@ export class SpdxSummaryCard extends React.Component { }); } + static getVulnerabilitiesByPackageNameChartData( + packages: IPackage[], + packageNames: string[], + securityAdvisories: ISecurityVulnerability[], + ): BarChartSeries[] { + return SEVERITIES.filter((s: ISeverity) => s.id > 0) + .orderBy((s: ISeverity) => s.weight, false) + .map((s: ISeverity) => { + return { + color: rgbToHex(s.color), + label: s.name, + data: packageNames.map( + (pn) => + securityAdvisories.filter((v) => { + const pkg = packages.find((p) => p.name == v.package.name); + return pkg && pkg.name == pn && v.advisory.severity.toUpperCase() === s.name.toUpperCase(); + }).length, + ), + stack: 'severity', + }; + }); + } + static getVulnerabiilityWeaknessesChartData( weaknesses: string[], securityAdvisories: ISecurityVulnerability[], @@ -221,6 +244,24 @@ export class SpdxSummaryCard extends React.Component {
+
+ + + + +
{ } title="Vulnerable Packages" header={`${this.state.packages?.totalVulnerable || 0} of ${this.state.packages?.total || 0} packages are vulnerable`} - size={200} /> +
@@ -255,7 +301,7 @@ export class SpdxSummaryCard extends React.Component { data={this.state.securityAdvisories?.vulnByWeaknessChartData || []} layout="horizontal" title="Weaknesses" - height={120 + (this.state.securityAdvisories?.weaknesses?.length || 0) * 30} + height={100 + (this.state.securityAdvisories?.weaknesses?.length || 0) * 30} />