diff --git a/package-lock.json b/package-lock.json index a79e0db..d4452e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@soos-io/api-client", - "version": "0.2.2", + "version": "0.2.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@soos-io/api-client", - "version": "0.2.2", + "version": "0.2.3", "license": "MIT", "dependencies": { "axios": "^1.6.2", diff --git a/package.json b/package.json index bb31465..c00d25a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@soos-io/api-client", - "version": "0.2.2", + "version": "0.2.3", "description": "This is the SOOS API Client for registered clients leveraging the various integrations to the SOOS platform.", "main": "dist/index.js", "scripts": { diff --git a/src/api/SOOSAnalysisApiClient.ts b/src/api/SOOSAnalysisApiClient.ts index ecfe8da..d136289 100644 --- a/src/api/SOOSAnalysisApiClient.ts +++ b/src/api/SOOSAnalysisApiClient.ts @@ -78,16 +78,13 @@ interface IScanStatusRequest { interface IScanStatusResponse extends Pick { isComplete: boolean; isSuccess: boolean; - hasIssues: boolean; - violations: number; - vulnerabilities: number; + issues: IIssuesModel; errors: ICodedMessageModel[]; } interface IScanStatusApiResponse { status: ScanStatus; - violations: { count: number } | null; - vulnerabilities: { count: number } | null; + issues: IIssuesModel; clientHash: string; projectHash: string; branchHash: string; @@ -99,6 +96,15 @@ interface IScanStatusApiResponse { errors: ICodedMessageModel[] | null; } +interface IIssuesModel { + Violation: { count: number; maxSeverity: string } | null; + Vulnerability: { count: number; maxSeverity: string } | null; + DependencyTypo: { count: number; maxSeverity: string } | null; + DependencySubstitution: { count: number; maxSeverity: string } | null; + Dast: { count: number; maxSeverity: string } | null; + Sast: { count: number; maxSeverity: string } | null; +} + interface IStartScanRequest { clientId: string; projectHash: string; @@ -259,15 +265,11 @@ class SOOSAnalysisApiClient { async getScanStatus({ scanStatusUrl }: IScanStatusRequest): Promise { const response = await this.client.get(scanStatusUrl); - const violationCount = response.data.violations?.count ?? 0; - const vulnerabilityCount = response.data.vulnerabilities?.count ?? 0; return { status: response.data.status, isComplete: CompletedScanStatuses.includes(response.data.status), isSuccess: response.data.status === ScanStatus.Finished, - hasIssues: violationCount > 0 || vulnerabilityCount > 0, - violations: violationCount, - vulnerabilities: vulnerabilityCount, + issues: response.data.issues, errors: response.data.errors ?? [], }; } @@ -322,6 +324,7 @@ export { IUploadManifestFilesResponse, IGetFormattedScanRequest as IFormattedScanRequest, IUploadScanToolResultRequest, + IIssuesModel, }; export default SOOSAnalysisApiClient; diff --git a/src/services/AnalysisService.ts b/src/services/AnalysisService.ts index fe0b930..87dd4c6 100644 --- a/src/services/AnalysisService.ts +++ b/src/services/AnalysisService.ts @@ -15,7 +15,7 @@ import { SeverityEnum, } from "../enums"; import { soosLogger } from "../logging"; -import { sleep } from "../utilities"; +import { getVulnerabilitiesByScanType, sleep } from "../utilities"; import * as FileSystem from "fs"; import * as Path from "path"; @@ -42,6 +42,7 @@ interface IStartScanParams { interface IWaitForScanToFinishParams { scanStatusUrl: string; scanUrl: string; + scanType: ScanType; } interface ISetupScanParams { @@ -88,6 +89,8 @@ const integrationNameToEnvVariable: Record = { [IntegrationName.TravisCI]: "TRAVIS_COMMIT", }; +const GeneratedScanTypes = [ScanType.CSA, ScanType.SBOM, ScanType.SCA]; + class AnalysisService { public analysisApiClient: SOOSAnalysisApiClient; public projectsApiClient: SOOSProjectsApiClient; @@ -227,6 +230,7 @@ class AnalysisService { async waitForScanToFinish({ scanStatusUrl, scanUrl, + scanType, }: IWaitForScanToFinishParams): Promise { const scanStatus = await this.analysisApiClient.getScanStatus({ scanStatusUrl: scanStatusUrl, @@ -235,7 +239,7 @@ class AnalysisService { if (!scanStatus.isComplete) { soosLogger.info(`${StringUtilities.fromCamelToTitleCase(scanStatus.status)}...`); await sleep(SOOS_CONSTANTS.Status.DelayTime); - return await this.waitForScanToFinish({ scanStatusUrl, scanUrl }); + return await this.waitForScanToFinish({ scanStatusUrl, scanUrl, scanType }); } if (scanStatus.errors.length > 0) { @@ -244,29 +248,43 @@ class AnalysisService { soosLogger.groupEnd(); } - if (scanStatus.isSuccess) { - scanStatus.vulnerabilities > 0 || scanStatus.violations > 0; - } - let statusMessage = `Scan ${scanStatus.isSuccess ? "passed" : "failed"}`; - if (scanStatus.hasIssues) { - const vulnerabilities = StringUtilities.pluralizeTemplate( - scanStatus.vulnerabilities, - "vulnerability", - "vulnerabilities", - ); - - const violations = StringUtilities.pluralizeTemplate(scanStatus.violations, "violation"); - - statusMessage = statusMessage.concat( - `${ - scanStatus.isSuccess ? ", but had" : " because of" - } ${vulnerabilities} and ${violations}`, - ); - } - const resultMessage = `${statusMessage}. View the results at: ${scanUrl}`; - soosLogger.info(resultMessage); + const vulnerabilities = StringUtilities.pluralizeTemplate( + getVulnerabilitiesByScanType(scanStatus.issues, scanType) ?? 0, + "vulnerability", + "vulnerabilities", + ); + + const violations = StringUtilities.pluralizeTemplate( + scanStatus.issues.Violation?.count ?? 0, + "violation", + ); + + const isGeneratedScanType = GeneratedScanTypes.includes(scanType); + + const substitutions = isGeneratedScanType + ? StringUtilities.pluralizeTemplate( + scanStatus.issues.DependencySubstitution?.count ?? 0, + "dependency substitution", + ) + : ""; + + const typos = isGeneratedScanType + ? StringUtilities.pluralizeTemplate( + scanStatus.issues.DependencyTypo?.count ?? 0, + "dependency typo", + ) + : ""; + + statusMessage = statusMessage.concat( + `${scanStatus.isSuccess ? ", with" : " because of"} (${vulnerabilities}) (${violations})${ + substitutions ? ` (${substitutions})` : "" + }${typos ? ` (${typos})` : ""}.`, + ); + + soosLogger.info(statusMessage); + soosLogger.info(`View the results at: ${scanUrl}`); return scanStatus.status; } @@ -336,4 +354,6 @@ class AnalysisService { } } +export { GeneratedScanTypes }; + export default AnalysisService; diff --git a/src/utilities.ts b/src/utilities.ts index 496d5b4..38b0f15 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -1,7 +1,8 @@ import axios, { AxiosError } from "axios"; import { soosLogger } from "./logging/SOOSLogger"; import StringUtilities from "./StringUtilities"; -import { ScanStatus } from "./enums"; +import { ScanStatus, ScanType } from "./enums"; +import { IIssuesModel } from "./api/SOOSAnalysisApiClient"; const isNil = (value: unknown): value is null | undefined => value === null || value === undefined; @@ -136,6 +137,17 @@ const verifyScanStatus = (scanStatus: ScanStatus): boolean => { return fail; }; +const getVulnerabilitiesByScanType = (issues: IIssuesModel, scanType: ScanType) => { + switch (scanType) { + case ScanType.DAST: + return issues.Dast?.count; + case ScanType.SAST: + return issues.Sast?.count; + default: + return issues.Vulnerability?.count; + } +}; + export { isNil, ensureValue, @@ -148,4 +160,5 @@ export { getEnvVariable, formatBytes, verifyScanStatus, + getVulnerabilitiesByScanType, };