diff --git a/src/main/scanLogic/scanManager.ts b/src/main/scanLogic/scanManager.ts index 7017922e..a9e95771 100644 --- a/src/main/scanLogic/scanManager.ts +++ b/src/main/scanLogic/scanManager.ts @@ -8,15 +8,12 @@ import { LogManager } from '../log/logManager'; import { IGraphResponse, XrayScanProgress } from 'jfrog-client-js'; import { RootNode } from '../treeDataProviders/dependenciesTree/dependenciesRoot/rootTree'; -import { AnalyzerUtils } from '../treeDataProviders/utils/analyzerUtils'; import { StepProgress } from '../treeDataProviders/utils/stepProgress'; -import { Configuration } from '../utils/configuration'; import { Resource } from '../utils/resource'; import { ScanUtils } from '../utils/scanUtils'; import { Utils } from '../utils/utils'; import { GraphScanLogic } from './scanGraphLogic'; -import { ApplicabilityRunner, ApplicabilityScanResponse } from './scanRunners/applicabilityScan'; -import { JasScanner } from './scanRunners/binaryRunner'; +import { JasRunner } from './scanRunners/jasRunner'; export interface EntitledScans { dependencies: boolean; @@ -128,7 +125,7 @@ export class ScanManager implements ExtensionComponent { private getResources(supportedScans: EntitledScans): Resource[] { let resources: Resource[] = []; if (supportedScans.applicability || supportedScans.iac || supportedScans.secrets) { - resources.push(JasScanner.getAnalyzerManagerResource(this._logManager)); + resources.push(JasRunner.getAnalyzerManagerResource(this._logManager)); } else { this.logManager.logMessage('You are not entitled to run Advanced Security scans', 'DEBUG'); } @@ -208,29 +205,4 @@ export class ScanManager implements ExtensionComponent { let scanLogic: GraphScanLogic = new GraphScanLogic(this._connectionManager); return await scanLogic.scan(graphRoot, progress, checkCanceled); } - - /** - * Scan CVE in files for applicability issues. - * @param directory - the directory that will be scan - * @param checkCancel - check if should cancel - * @param cveToRun - the CVE list we want to run applicability scan on - * @returns the applicability scan response - */ - public async scanApplicability( - directory: string, - checkCancel: () => void, - cveToRun: Set = new Set() - ): Promise { - let applicableRunner: ApplicabilityRunner = new ApplicabilityRunner(this._connectionManager, this._logManager); - if (!applicableRunner.validateSupported()) { - this._logManager.logMessage('Applicability runner could not find binary to run', 'WARN'); - return {} as ApplicabilityScanResponse; - } - let skipFiles: string[] = AnalyzerUtils.getAnalyzerManagerExcludePattern(Configuration.getScanExcludePattern()); - this._logManager.logMessage( - "Scanning directory '" + directory + "' for CVE issues: " + Array.from(cveToRun.values()) + '. Skipping files: ' + skipFiles, - 'DEBUG' - ); - return await applicableRunner.scan(directory, checkCancel, cveToRun, skipFiles); - } } diff --git a/src/main/scanLogic/scanRunners/applicabilityScan.ts b/src/main/scanLogic/scanRunners/applicabilityScan.ts index 081ccd19..41b50b5d 100644 --- a/src/main/scanLogic/scanRunners/applicabilityScan.ts +++ b/src/main/scanLogic/scanRunners/applicabilityScan.ts @@ -1,10 +1,19 @@ import { ConnectionManager } from '../../connect/connectionManager'; import { LogManager } from '../../log/logManager'; +import { CveTreeNode } from '../../treeDataProviders/issuesTree/descriptorTree/cveTreeNode'; +import { ProjectDependencyTreeNode } from '../../treeDataProviders/issuesTree/descriptorTree/projectDependencyTreeNode'; +import { IssueTreeNode } from '../../treeDataProviders/issuesTree/issueTreeNode'; +import { AnalyzerUtils } from '../../treeDataProviders/utils/analyzerUtils'; +import { StepProgress } from '../../treeDataProviders/utils/stepProgress'; import { Module } from '../../types/jfrogAppsConfig'; +import { PackageType } from '../../types/projectType'; +import { DependencyScanResults } from '../../types/workspaceIssuesDetails'; +import { Configuration } from '../../utils/configuration'; import { Resource } from '../../utils/resource'; -import { ScanUtils } from '../../utils/scanUtils'; -import { AnalyzeIssue, AnalyzeLocation, AnalyzeScanRequest, AnalyzerScanRun, FileIssues, ScanType } from './analyzerModels'; -import { JasScanner } from './binaryRunner'; +import { FileScanBundle, ScanUtils } from '../../utils/scanUtils'; +import { Utils } from '../../utils/utils'; +import { AnalyzeIssue, AnalyzeLocation, AnalyzeScanRequest, AnalyzerScanResponse, AnalyzerScanRun, FileIssues, ScanType } from './analyzerModels'; +import { JasRunner } from './jasRunner'; /** * The request that is sent to the binary to scan applicability @@ -36,10 +45,13 @@ export interface CveApplicableDetails { } /** - * Describes a runner for the Applicability scan executable file. + * Describes a runner for the Applicability scan. */ -export class ApplicabilityRunner extends JasScanner { +export class ApplicabilityRunner extends JasRunner { constructor( + private _bundlesWithIssues: FileScanBundle[], + private _packageType: PackageType, + private _progressManager: StepProgress, connectionManager: ConnectionManager, logManager: LogManager, binary?: Resource, @@ -59,48 +71,183 @@ export class ApplicabilityRunner extends JasScanner { return str.replace('cve_whitelist', 'cve-whitelist'); } + /** @override */ + protected logStartScanning(request: ApplicabilityScanArgs): void { + this._logManager.logMessage( + `Scanning directory ' ${request.roots[0]} + ', for ${this._scanType} issues: ${request.cve_whitelist} Skipping folders: ${request.skipped_folders}`, + 'DEBUG' + ); + } + /** * Scan for applicability issues - * @param directory - the directory the scan will perform on its files - * @param checkCancel - check if cancel - * @param cvesToRun - the CVEs to run the scan on - * @param skipFolders - the subfolders inside the directory to exclude from the scan - * @returns the response generated from the scan */ - public async scan( - directory: string, - checkCancel: () => void, - cveToRun: Set = new Set(), - skipFolders: string[] = [] - ): Promise { - const request: ApplicabilityScanArgs = { - type: ScanType.ContextualAnalysis, - roots: [directory], - cve_whitelist: Array.from(cveToRun), - skipped_folders: skipFolders - } as ApplicabilityScanArgs; - return await this.run(checkCancel, request).then(response => this.convertResponse(response?.runs[0])); + public async scan(): Promise { + let filteredBundles: Map> = this.filterBundlesWithoutIssuesToScan(this._bundlesWithIssues, this._packageType); + let workspaceToBundles: Map>> = this.mapBundlesForApplicableScanning( + this._logManager, + filteredBundles + ); + if (workspaceToBundles.size == 0) { + return; + } + for (let [workspacePath, bundles] of workspaceToBundles) { + let cveToScan: Set = Utils.combineSets(Array.from(bundles.values())); + // Scan workspace for all cve in relevant bundles + let startApplicableTime: number = Date.now(); + let excludePatterns: string[] = AnalyzerUtils.getAnalyzerManagerExcludePatterns(Configuration.getScanExcludePattern()); + + const request: ApplicabilityScanArgs = { + type: ScanType.ContextualAnalysis, + roots: [workspacePath], + cve_whitelist: Array.from(cveToScan), + skipped_folders: excludePatterns + } as ApplicabilityScanArgs; + + this.logStartScanning(request); + let response: AnalyzerScanResponse | undefined = await this.executeRequest(this._progressManager.checkCancel, request); + let applicableIssues: ApplicabilityScanResponse = this.convertResponse(response); + if (applicableIssues?.applicableCve) { + this.transferApplicableResponseToBundles(applicableIssues, bundles, startApplicableTime); + } + } + } + + /** + * Filter bundles without direct cve issues, transform the bundle list to have its relevant cve to scan set. + * @param fileScanBundles - Bundles to process and filter if needed + * @param packageType - Package type of the project + * @returns Map of bundles to their set of direct cves issues, with at least one for each bundle + */ + private filterBundlesWithoutIssuesToScan(fileScanBundles: FileScanBundle[], packageType: PackageType): Map> { + let filtered: Map> = new Map>(); + + for (let fileScanBundle of fileScanBundles) { + if (!(fileScanBundle.dataNode instanceof ProjectDependencyTreeNode)) { + // Filter non dependencies projects + continue; + } + let cvesToScan: Set = new Set(); + fileScanBundle.dataNode.issues.forEach((issue: IssueTreeNode) => { + if (!(issue instanceof CveTreeNode) || !issue.cve?.cve) { + return; + } + // For Python projects, all CVEs should be included because in some cases it is impossible to determine whether a dependency is direct. + // Other project types should include only CVEs on direct dependencies. + if (packageType === PackageType.Python || !issue.parent.indirect) { + cvesToScan.add(issue.cve.cve); + } + }); + if (cvesToScan.size == 0) { + // Nothing to do in bundle + continue; + } + + filtered.set(fileScanBundle, cvesToScan); + } + + return filtered; + } + + /** + * Create a mapping between a workspace and all the given bundles that relevant to it. + * @param logManager - logger to log added map + * @param filteredBundles - bundles to map + * @returns mapped bundles to similar workspace + */ + private mapBundlesForApplicableScanning( + logManager: LogManager, + filteredBundles: Map> + ): Map>> { + let workspaceToScanBundles: Map>> = new Map>>(); + + for (let [fileScanBundle, cvesToScan] of filteredBundles) { + let descriptorIssues: DependencyScanResults = fileScanBundle.data; + // Map information to similar directory space + let workspacePath: string = AnalyzerUtils.getWorkspacePath(fileScanBundle.dataNode, descriptorIssues.fullPath); + if (!workspaceToScanBundles.has(workspacePath)) { + workspaceToScanBundles.set(workspacePath, new Map>()); + } + workspaceToScanBundles.get(workspacePath)?.set(fileScanBundle, cvesToScan); + logManager.logMessage('Adding data from descriptor ' + descriptorIssues.fullPath + ' for cve applicability scan', 'INFO'); + } + + return workspaceToScanBundles; + } + + /** + * Transfer and populate information from a given applicable scan to each bundle + * @param applicableIssues - Full scan response with information relevant to all the bundles + * @param bundles - the bundles that will be populated only with their relevant information + * @param startTime - The start time for the applicable scan + */ + private transferApplicableResponseToBundles( + applicableIssues: ApplicabilityScanResponse, + bundles: Map>, + startTime: number + ) { + for (let [bundle, relevantCve] of bundles) { + let descriptorIssues: DependencyScanResults = bundle.data; + // Filter only relevant information + descriptorIssues.applicableScanTimestamp = Date.now(); + descriptorIssues.applicableIssues = this.filterApplicabilityScanResponse(applicableIssues, relevantCve); + // Populate it in bundle + let issuesCount: number = AnalyzerUtils.populateApplicableIssues( + bundle.rootNode, + bundle.dataNode, + descriptorIssues + ); + super.logNumberOfIssues(issuesCount, descriptorIssues.fullPath, startTime, descriptorIssues.applicableScanTimestamp); + bundle.rootNode.apply(); + } + } + + /** + * For a given full ApplicableScanResponse scan results, filter the results to only contain information relevant to a given cve list + * @param scanResponse - All the applicable information + * @param relevantCve - CVE list to filter information only for them + * @returns ApplicableScanResponse with information relevant only for the given relevant CVEs + */ + private filterApplicabilityScanResponse(scanResponse: ApplicabilityScanResponse, relevantCve: Set): ApplicabilityScanResponse { + let allApplicable: Map = new Map(Object.entries(scanResponse.applicableCve)); + let relevantScannedCve: string[] = []; + let relevantApplicableCve: Map = new Map(); + + for (let scannedCve of scanResponse.scannedCve) { + if (relevantCve.has(scannedCve)) { + relevantScannedCve.push(scannedCve); + let potential: CveApplicableDetails | undefined = allApplicable.get(scannedCve); + if (potential) { + relevantApplicableCve.set(scannedCve, potential); + } + } + } + return { + scannedCve: Array.from(relevantScannedCve), + applicableCve: Object.fromEntries(relevantApplicableCve.entries()) + } as ApplicabilityScanResponse; } /** * Generate response from the run results - * @param run - the run results generated from the binary + * @param response - The run results generated from the binary * @returns the response generated from the scan run */ - public convertResponse(run: AnalyzerScanRun | undefined): ApplicabilityScanResponse { - if (!run) { + public convertResponse(response: AnalyzerScanResponse | undefined): ApplicabilityScanResponse { + if (!response) { return {} as ApplicabilityScanResponse; } // Prepare + let analyzerScanRun: AnalyzerScanRun = response.runs[0]; let applicable: Map = new Map(); let scanned: Set = new Set(); let rulesFullDescription: Map = new Map(); - for (const rule of run.tool.driver.rules) { + for (const rule of analyzerScanRun.tool.driver.rules) { if (rule.fullDescription) { rulesFullDescription.set(rule.id, rule.fullDescription.text); } } - let issues: AnalyzeIssue[] = run.results; + let issues: AnalyzeIssue[] = analyzerScanRun.results; if (issues) { // Generate applicable data for all the issues issues.forEach((analyzeIssue: AnalyzeIssue) => { @@ -127,8 +274,8 @@ export class ApplicabilityRunner extends JasScanner { /** * Get or create if not exists file evidence from the CVE applicable issues - * @param applicableDetails - the CVE applicable issues with the file list - * @param filePath - the file to search or create if not exist + * @param applicableDetails - The CVE applicable issues with the file list + * @param filePath - The file to search or create if not exist * @returns the object that represents the issues in a file for the CVE */ private getOrCreateFileIssues(applicableDetails: CveApplicableDetails, filePath: string): FileIssues { @@ -149,9 +296,9 @@ export class ApplicabilityRunner extends JasScanner { /** * Get or create CVE applicable issue if not exists from the applicable list. - * @param analyzedIssue - the applicable issue to generate information from - * @param applicable - the list of all the applicable CVEs - * @param fullDescription - the full description of the applicable issue + * @param analyzedIssue - Applicable issue to generate information from + * @param applicable - List of all the applicable CVEs + * @param fullDescription - Full description of the applicable issue * @returns the CveApplicableDetails object for the analyzedIssue CVE */ private getOrCreateApplicableDetails( diff --git a/src/main/scanLogic/scanRunners/iacScan.ts b/src/main/scanLogic/scanRunners/iacScan.ts index 625986bd..921bd471 100644 --- a/src/main/scanLogic/scanRunners/iacScan.ts +++ b/src/main/scanLogic/scanRunners/iacScan.ts @@ -8,9 +8,8 @@ import { ScanResults } from '../../types/workspaceIssuesDetails'; import { AppsConfigUtils } from '../../utils/appConfigUtils'; import { Resource } from '../../utils/resource'; import { ScanUtils } from '../../utils/scanUtils'; -import { ScanManager } from '../scanManager'; -import { AnalyzeScanRequest, AnalyzerScanResponse, ScanType } from './analyzerModels'; -import { JasScanner } from './binaryRunner'; +import { AnalyzeScanRequest, AnalyzerScanResponse, AnalyzerScanRun, ScanType } from './analyzerModels'; +import { JasRunner } from './jasRunner'; export interface IacScanResponse { filesWithIssues: FileWithSecurityIssues[]; @@ -19,8 +18,11 @@ export interface IacScanResponse { /** * Describes a runner for the 'Infrastructure As Code' (Iac) scan executable file. */ -export class IacRunner extends JasScanner { +export class IacRunner extends JasRunner { constructor( + private _scanResults: ScanResults, + private _root: IssuesRootTreeNode, + private _progressManager: StepProgress, connectionManager: ConnectionManager, logManager: LogManager, module: Module, @@ -35,63 +37,56 @@ export class IacRunner extends JasScanner { await this.executeBinary(checkCancel, ['iac', yamlConfigPath], executionLogDirectory); } - public async scan(scanResults: ScanResults, root: IssuesRootTreeNode, scanManager: ScanManager, progressManager: StepProgress): Promise { - let startIacTime: number = Date.now(); + /** + * Run IaC scan async task and populate the given bundle with the results. + */ + public async scan(): Promise { + let startTime: number = Date.now(); let request: AnalyzeScanRequest = { type: ScanType.Iac, roots: AppsConfigUtils.GetSourceRoots(this._module, this._module.scanners?.iac), skipped_folders: AppsConfigUtils.GetExcludePatterns(this._module, this._module.scanners?.iac) } as AnalyzeScanRequest; - this._logManager.logMessage( - "Scanning directories '" + request.roots + "', for Iac issues. Skipping folders: " + request.skipped_folders, - 'DEBUG' + super.logStartScanning(request); + let response: IacScanResponse = await this.executeRequest(this._progressManager.checkCancel, request).then(runResult => + this.convertResponse(runResult) ); - let response: IacScanResponse = await this.run(progressManager.checkCancel, request).then(runResult => this.convertResponse(runResult)); if (response) { - scanResults.iacScan = response; - scanResults.iacScanTimestamp = Date.now(); - let issuesCount: number = AnalyzerUtils.populateIacIssues(root, scanResults); - scanManager.logManager.logMessage( - 'Found ' + - issuesCount + - " Iac issues in workspace = '" + - scanResults.path + - "' (elapsed " + - (scanResults.iacScanTimestamp - startIacTime) / 1000 + - ' seconds)', - 'INFO' - ); - root.apply(); + this._scanResults.iacScan = response; + this._scanResults.iacScanTimestamp = Date.now(); + let issuesCount: number = AnalyzerUtils.populateIacIssues(this._root, this._scanResults); + super.logNumberOfIssues(issuesCount, this._scanResults.path, startTime, this._scanResults.iacScanTimestamp); + this._root.apply(); } - progressManager.reportProgress(); + this._progressManager.reportProgress(); } /** * Generate response from the run results - * @param analyzerScanResponse - the run results generated from the binary + * @param response - Run results generated from the binary * @returns the response generated from the scan run */ - public convertResponse(analyzerScanResponse?: AnalyzerScanResponse): IacScanResponse { - if (!analyzerScanResponse) { + public convertResponse(response?: AnalyzerScanResponse): IacScanResponse { + if (!response) { return {} as IacScanResponse; } + let analyzerScanRun: AnalyzerScanRun = response.runs[0]; let iacResponse: IacScanResponse = { filesWithIssues: [] } as IacScanResponse; - for (const run of analyzerScanResponse.runs) { - // Get the full descriptions of all rules - let rulesFullDescription: Map = new Map(); - for (const rule of run.tool.driver.rules) { - if (rule.fullDescription) { - rulesFullDescription.set(rule.id, rule.fullDescription.text); - } + // Get the full descriptions of all rules + let rulesFullDescription: Map = new Map(); + for (const rule of analyzerScanRun.tool.driver.rules) { + if (rule.fullDescription) { + rulesFullDescription.set(rule.id, rule.fullDescription.text); } - // Generate response data - run.results?.forEach(analyzeIssue => - AnalyzerUtils.generateIssueData(iacResponse, analyzeIssue, rulesFullDescription.get(analyzeIssue.ruleId)) - ); } + // Generate response data + analyzerScanRun.results?.forEach(analyzeIssue => + AnalyzerUtils.generateIssueData(iacResponse, analyzeIssue, rulesFullDescription.get(analyzeIssue.ruleId)) + ); + return iacResponse; } } diff --git a/src/main/scanLogic/scanRunners/binaryRunner.ts b/src/main/scanLogic/scanRunners/jasRunner.ts similarity index 74% rename from src/main/scanLogic/scanRunners/binaryRunner.ts rename to src/main/scanLogic/scanRunners/jasRunner.ts index 47f6f391..0fa5d03d 100644 --- a/src/main/scanLogic/scanRunners/binaryRunner.ts +++ b/src/main/scanLogic/scanRunners/jasRunner.ts @@ -22,20 +22,20 @@ import { AnalyzeScanRequest, AnalyzerRequest, AnalyzerScanResponse, ScanType } f */ class RunArgs { // The requests for the run - public requests: RunRequest[] = []; + public request: RunRequest = {} as RunRequest; // The directory that the requests/responses are expected constructor(public readonly directory: string) {} public getRoots(): string[] { let roots: Set = new Set(); - this.requests.forEach(request => request.roots.forEach(root => roots.add(root))); + this.request.roots.forEach(root => roots.add(root)); return Array.from(roots); } } interface RunRequest { type: ScanType; - request: string; + requestContent: string; requestPath: string; roots: string[]; responsePath: string; @@ -44,7 +44,7 @@ interface RunRequest { /** * Base class for a JFrog Advanced Security scanner. */ -export abstract class JasScanner { +export abstract class JasRunner { protected _verbose: boolean = false; protected _runDirectory: string; @@ -70,9 +70,8 @@ export abstract class JasScanner { protected _scanType: ScanType, protected _logManager: LogManager, protected _module: Module, - protected _binary: Resource = JasScanner.getAnalyzerManagerResource(_logManager) + protected _binary: Resource = JasRunner.getAnalyzerManagerResource(_logManager) ) { - this._binary = JasScanner.getAnalyzerManagerResource(_logManager); this._runDirectory = path.dirname(this._binary.fullPath); if (this._abortCheckIntervalMillisecs <= 0) { @@ -82,21 +81,25 @@ export abstract class JasScanner { } public static getDefaultAnalyzerManagerSourceUrl(version: string = '[RELEASE]'): string { - return Utils.addZipSuffix(JasScanner.DOWNLOAD_URL + '/' + version + '/' + Utils.getArchitecture() + '/' + JasScanner.RUNNER_NAME); + return Utils.addZipSuffix(JasRunner.DOWNLOAD_URL + '/' + version + '/' + Utils.getArchitecture() + '/' + JasRunner.RUNNER_NAME); } public static getDefaultAnalyzerManagerTargetPath(baseDirectory?: string): string { - return Utils.addWinSuffixIfNeeded(path.join(baseDirectory ?? ScanUtils.getIssuesPath(), JasScanner.RUNNER_NAME, JasScanner.RUNNER_NAME)); + return Utils.addWinSuffixIfNeeded(path.join(baseDirectory ?? ScanUtils.getIssuesPath(), JasRunner.RUNNER_NAME, JasRunner.RUNNER_NAME)); } public static getAnalyzerManagerResource(logManager: LogManager, targetPath?: string): Resource { return new Resource( - this.getDefaultAnalyzerManagerSourceUrl(JasScanner.RUNNER_VERSION), + this.getDefaultAnalyzerManagerSourceUrl(JasRunner.RUNNER_VERSION), targetPath ?? this.getDefaultAnalyzerManagerTargetPath(), logManager ); } + public get binary(): Resource { + return this._binary; + } + public get verbose(): boolean { return this._verbose; } @@ -105,12 +108,17 @@ export abstract class JasScanner { this._verbose = value; } + /** + * Run full JAS scan for the specific scanner. + */ + public abstract scan(): Promise; + /** * Run the executeBinary method with the provided request path - * @param checkCancel - check if cancel - * @param yamlConfigPath - the path to the request - * @param executionLogDirectory - log file will be written to the dir - * @param responsePath - path to the output file + * @param yamlConfigPath - Path to the request + * @param executionLogDirectory - Log file will be written to the dir + * @param checkCancel - Check if should cancel + * @param responsePath - Path to the output file */ protected abstract runBinary( yamlConfigPath: string, @@ -119,6 +127,9 @@ export abstract class JasScanner { responsePath: string | undefined ): Promise; + /** + * @returns true if should run the JAS scanner + */ public shouldRun(): boolean { if (!this.validateSupported()) { this._logManager.logMessage(this._scanType + ' runner could not find binary to run', 'WARN'); @@ -141,9 +152,9 @@ export abstract class JasScanner { /** * Execute the cmd command to run the binary with given arguments and an option to abort the operation. - * @param checkCancel - check if should cancel - * @param args - the arguments for the command - * @param executionLogDirectory - the directory to save the execution log in + * @param checkCancel - Check if should cancel + * @param args - Arguments for the command + * @param executionLogDirectory - Directory to save the execution log in */ protected async executeBinary(checkCancel: () => void, args: string[], executionLogDirectory?: string): Promise { await RunUtils.runWithTimeout(this._abortCheckIntervalMillisecs, checkCancel, { @@ -177,6 +188,21 @@ export abstract class JasScanner { this._logManager.logMessage(text, isErr ? 'ERR' : 'DEBUG'); } + protected logStartScanning(request: AnalyzeScanRequest): void { + this._logManager.logMessage( + `Scanning directories ' ${request.roots} + ', for ${this._scanType} issues. Skipping folders: ${request.skipped_folders}`, + 'DEBUG' + ); + } + + protected logNumberOfIssues(issuesCount: number, workspace: string, startTime: number, endTime: number): void { + let elapsedTime: number = (endTime - startTime) / 1000; + this._logManager.logMessage( + `Found ${issuesCount} ${this._scanType} issues in workspace '${workspace}' (elapsed ${elapsedTime} seconds)`, + 'INFO' + ); + } + /** * Create the needed environment variables for the runner to run * @param executionLogDirectory - the directory that the log will be written into, if not provided the log will be written in stdout/stderr @@ -189,13 +215,13 @@ export abstract class JasScanner { let binaryVars: NodeJS.ProcessEnv = { JFROG_CLI_LOG_LEVEL: Translators.toAnalyzerLogLevel(Configuration.getLogLevel()) }; // Platform information - binaryVars[JasScanner.ENV_PLATFORM_URL] = this._connectionManager.url; + binaryVars[JasRunner.ENV_PLATFORM_URL] = this._connectionManager.url; // Credentials information if (this._connectionManager.accessToken) { - binaryVars[JasScanner.ENV_TOKEN] = this._connectionManager.accessToken; + binaryVars[JasRunner.ENV_TOKEN] = this._connectionManager.accessToken; } else { - binaryVars[JasScanner.ENV_USER] = this._connectionManager.username; - binaryVars[JasScanner.ENV_PASSWORD] = this._connectionManager.password; + binaryVars[JasRunner.ENV_USER] = this._connectionManager.username; + binaryVars[JasRunner.ENV_PASSWORD] = this._connectionManager.password; } this.populateOptionalInformation(binaryVars, executionLogDirectory); @@ -219,10 +245,10 @@ export abstract class JasScanner { proxyHttpsUrl = 'https://' + proxyUrl; } if (proxyHttpUrl) { - binaryVars[JasScanner.ENV_HTTP_PROXY] = this.addOptionalProxyAuthInformation(proxyHttpUrl); + binaryVars[JasRunner.ENV_HTTP_PROXY] = this.addOptionalProxyAuthInformation(proxyHttpUrl); } if (proxyHttpsUrl) { - binaryVars[JasScanner.ENV_HTTPS_PROXY] = this.addOptionalProxyAuthInformation(proxyHttpsUrl); + binaryVars[JasRunner.ENV_HTTPS_PROXY] = this.addOptionalProxyAuthInformation(proxyHttpsUrl); } // Optional log destination if (executionLogDirectory) { @@ -232,7 +258,7 @@ export abstract class JasScanner { /** * Add optional proxy auth information to the base url if exists - * @param url - the base url to add information on + * @param url - Base url to add information on * @returns the url with the auth information if exists or the given url if not */ private addOptionalProxyAuthInformation(url: string): string { @@ -249,45 +275,60 @@ export abstract class JasScanner { return url; } - public async run(checkCancel: () => void, request: AnalyzeScanRequest): Promise { + /** + * Execute the input scan request. + * @param checkCancel - Check if should cancel + * @param request - Request to perform in YAML format + * @returns + */ + public async executeRequest(checkCancel: () => void, request: AnalyzeScanRequest): Promise { let args: RunArgs = this.createRunArguments(request); + let execErr: Error | undefined; try { - return await this.runTasks(args, checkCancel); + return await this.runRequest(checkCancel, args.request.requestContent, args.request.requestPath, args.request.responsePath); + } catch (err) { + execErr = err; + if (err instanceof ScanCancellationError || err instanceof NotEntitledError || err instanceof NotSupportedError) { + throw err; + } + this._logManager.logError(execErr); } finally { + this.handleExecutionLog(args, execErr); ScanUtils.removeFolder(args.directory); } + return; } /** * Populate the run arguments based on the given requests information - * @param requests - the run requests information + * @param requests - Run requests information * @return run arguments for the given requests */ private createRunArguments(request: AnalyzeScanRequest): RunArgs { let args: RunArgs = new RunArgs(ScanUtils.createTmpDir()); // Prepare request information and insert as an actual request - const requestPath: string = path.join(args.directory, 'request_' + args.requests.length); - const responsePath: string = path.join(args.directory, 'response_' + args.requests.length); + const requestPath: string = path.join(args.directory, 'request'); + const responsePath: string = path.join(args.directory, 'response'); if (request.type !== ScanType.Sast) { request.output = responsePath; } request.type = this._scanType; // Add request to run - args.requests.push({ + args.request = { type: request.type, - request: this.requestsToYaml(request), + requestContent: this.requestsToYaml(request), requestPath: requestPath, roots: request.roots, responsePath: responsePath - } as RunRequest); + } as RunRequest; return args; } /** * Translate the run requests to a single analyze request in yaml format - * @param requests - run requests + * @param requests - Run requests * @returns analyze request in YAML format */ public requestsToYaml(...requests: AnalyzeScanRequest[]): string { @@ -298,42 +339,6 @@ export abstract class JasScanner { .replace('skipped_folders', 'skipped-folders'); } - private async runTasks(args: RunArgs, checkCancel: () => void): Promise { - let runs: Promise[] = []; - let aggResponse: AnalyzerScanResponse = { runs: [] } as AnalyzerScanResponse; - for (let i: number = 0; i < args.requests.length; i++) { - runs.push( - this.runRequest( - checkCancel, - args.requests[i].request, - args.requests[i].requestPath, - args.requests[i].type, - args.requests[i].responsePath - ) - .then(response => { - if (response && response.runs) { - aggResponse.runs.push(...response.runs); - } - }) - .catch(err => { - if (err instanceof ScanCancellationError || err instanceof NotEntitledError || err instanceof NotSupportedError) { - throw err; - } - this._logManager.logError(err); - }) - ); - } - let exeErr: Error | undefined; - await Promise.all(runs) - .catch(err => { - exeErr = err; - throw err; - }) - // Collect log if exist - .finally(() => this.handleExecutionLog(args, exeErr)); - return aggResponse; - } - private handleExecutionLog(args: RunArgs, exeErr: Error | undefined) { let hadError: boolean = exeErr !== undefined; let logPath: string | undefined = this.copyRunLogToFolder(args, hadError); @@ -354,9 +359,9 @@ export abstract class JasScanner { /** * Copy a file that includes 'log' in its name from a given folder to the logs folder - * @param arg - the run arguments that related this log - * @param hadError - if true, will log result as error, otherwise success. - * @param copyToDirectory - optional destination to copy the log + * @param arg - Run arguments that related this log + * @param hadError - If true, will log result as error, otherwise success. + * @param copyToDirectory - Optional destination to copy the log */ private copyRunLogToFolder(args: RunArgs, hadError: boolean, copyToDirectory: string = ScanUtils.getLogsPath()): string | undefined { let logFile: string | undefined = fs.readdirSync(args.directory).find(fileName => fileName.toLowerCase().includes('log')); @@ -381,23 +386,17 @@ export abstract class JasScanner { } /** - * Perform the binary run, with an option to abort on signal in 3 steps : + * Perform the binary run, with an option to abort on signal in 3 steps: * 1. Save the request in a given path * 2. Run the binary * 3. Collect the responses for each run in the request - * @param checkCancel - check if cancel - * @param request - the request to perform in YAML format - * @param requestPath - the path that the request will be - * @param responsePath - the path of the response for request in the run + * @param checkCancel - Check if cancel + * @param request - Request to perform in YAML format + * @param requestPath - Path that the request will be + * @param responsePath - Path of the response for request in the run * @returns the response from all the binary runs */ - public async runRequest( - checkCancel: () => void, - request: string, - requestPath: string, - type: ScanType, - responsePath: string - ): Promise { + public async runRequest(checkCancel: () => void, request: string, requestPath: string, responsePath: string): Promise { // 1. Save requests as yaml file in folder fs.writeFileSync(requestPath, request); this._logManager.debug('Input YAML:\n' + request); @@ -406,13 +405,13 @@ export abstract class JasScanner { await this.runBinary(requestPath, this._verbose ? undefined : path.dirname(requestPath), checkCancel, responsePath).catch(error => { if (error.code) { // Not entitled to run binary - if (error.code === JasScanner.NOT_ENTITLED) { + if (error.code === JasRunner.NOT_ENTITLED) { throw new NotEntitledError(); } - if (error.code === JasScanner.NOT_SUPPORTED) { + if (error.code === JasRunner.NOT_SUPPORTED) { throw new NotSupportedError(Translators.toAnalyzerTypeString(this._scanType)); } - if (error.code === JasScanner.OS_NOT_SUPPORTED) { + if (error.code === JasRunner.OS_NOT_SUPPORTED) { throw new OsNotSupportedError(Translators.toAnalyzerTypeString(this._scanType)); } this._logManager.logMessage( @@ -423,21 +422,12 @@ export abstract class JasScanner { throw error; }); // 3. Collect responses - let analyzerScanResponse: AnalyzerScanResponse = { runs: [] } as AnalyzerScanResponse; if (!fs.existsSync(responsePath)) { throw new Error( "Running '" + Translators.toAnalyzerTypeString(this._scanType) + "' binary didn't produce response.\nRequest: " + request ); } // Load result and parse as response - let result: AnalyzerScanResponse = JSON.parse(fs.readFileSync(responsePath, 'utf8').toString()); - if (result && result.runs) { - analyzerScanResponse.runs.push(...result.runs); - } - return analyzerScanResponse; - } - - public get binary(): Resource { - return this._binary; + return JSON.parse(fs.readFileSync(responsePath, 'utf8').toString()); } } diff --git a/src/main/scanLogic/scanRunners/sastScan.ts b/src/main/scanLogic/scanRunners/sastScan.ts index 74bfff08..6d54b8af 100644 --- a/src/main/scanLogic/scanRunners/sastScan.ts +++ b/src/main/scanLogic/scanRunners/sastScan.ts @@ -10,21 +10,21 @@ import { AppsConfigUtils } from '../../utils/appConfigUtils'; import { Resource } from '../../utils/resource'; import { ScanUtils } from '../../utils/scanUtils'; import { Translators } from '../../utils/translators'; -import { ScanManager } from '../scanManager'; import { AnalyzeIssue, AnalyzeLocation, AnalyzeScanRequest, AnalyzerScanResponse, + AnalyzerScanRun, CodeFlow, FileLocation, FileRegion, ScanType } from './analyzerModels'; -import { JasScanner } from './binaryRunner'; +import { JasRunner } from './jasRunner'; /** - * The request that is sent to the binary to scan Sast + * The request that is sent to the binary to scan SAST */ export interface SastScanRequest extends AnalyzeScanRequest { language: LanguageType; @@ -56,8 +56,11 @@ export interface SastIssueLocation { threadFlows: FileLocation[][]; } -export class SastRunner extends JasScanner { +export class SastRunner extends JasRunner { constructor( + private _scanResults: ScanResults, + private _root: IssuesRootTreeNode, + private _progressManager: StepProgress, connectionManager: ConnectionManager, logManager: LogManager, module: Module, @@ -84,12 +87,9 @@ export class SastRunner extends JasScanner { } /** - * Scan for SAST issues - * @param module - the module that will be scanned - * @param checkCancel - check if cancel - * @returns the response generated from the scan + * Run SAST scan async task and populate the given bundle with the results. */ - public async scan(scanResults: ScanResults, root: IssuesRootTreeNode, scanManager: ScanManager, progressManager: StepProgress): Promise { + public async scan(): Promise { let startTime: number = Date.now(); let sastScanner: SastScanner | undefined = this._module.scanners?.sast; let request: SastScanRequest = { @@ -99,71 +99,58 @@ export class SastRunner extends JasScanner { excluded_rules: sastScanner?.excluded_rules, exclude_patterns: AppsConfigUtils.GetExcludePatterns(this._module, sastScanner) } as SastScanRequest; - this._logManager.logMessage( - "Scanning directories '" + request.roots + "', for SAST issues. Skipping folders: " + request.exclude_patterns, - 'DEBUG' + super.logStartScanning(request); + let response: SastScanResponse = await this.executeRequest(this._progressManager.checkCancel, request).then(runResult => + this.generateScanResponse(runResult) ); - - let response: SastScanResponse = await this.run(progressManager.checkCancel, request).then(runResult => this.generateScanResponse(runResult)); if (response) { - scanResults.sastScan = response; - scanResults.sastScanTimestamp = Date.now(); - let sastIssuesCount: number = AnalyzerUtils.populateSastIssues(root, scanResults); - scanManager.logManager.logMessage( - 'Found ' + - sastIssuesCount + - " SAST issues in workspace = '" + - scanResults.path + - "' (elapsed " + - (scanResults.sastScanTimestamp - startTime) / 1000 + - ' seconds)', - 'INFO' - ); - - root.apply(); - progressManager.reportProgress(); + this._scanResults.sastScan = response; + this._scanResults.sastScanTimestamp = Date.now(); + let issuesCount: number = AnalyzerUtils.populateSastIssues(this._root, this._scanResults); + super.logNumberOfIssues(issuesCount, this._scanResults.path, startTime, this._scanResults.sastScanTimestamp); + this._root.apply(); } + this._progressManager.reportProgress(); } /** * Generate response from the run results - * @param run - the run results generated from the binary + * @param response - Run results generated from the binary * @returns the response generated from the scan run */ public generateScanResponse(response?: AnalyzerScanResponse): SastScanResponse { if (!response) { return {} as SastScanResponse; } + let analyzerScanRun: AnalyzerScanRun = response.runs[0]; let sastResponse: SastScanResponse = { filesWithIssues: [] } as SastScanResponse; - for (const run of response.runs) { - // Prepare - let rulesFullDescription: Map = new Map(); - for (const rule of run.tool.driver.rules) { - if (rule.fullDescription) { - rulesFullDescription.set(rule.id, rule.fullDescription.text); - } + // Prepare + let rulesFullDescription: Map = new Map(); + for (const rule of analyzerScanRun.tool.driver.rules) { + if (rule.fullDescription) { + rulesFullDescription.set(rule.id, rule.fullDescription.text); } - // Generate response data - run.results?.forEach((analyzeIssue: AnalyzeIssue) => { - if (analyzeIssue.suppressions && analyzeIssue.suppressions.length > 0) { - // Suppress issue - return; - } - this.generateIssueData(sastResponse, analyzeIssue, rulesFullDescription.get(analyzeIssue.ruleId)); - }); } + // Generate response data + analyzerScanRun.results?.forEach((analyzeIssue: AnalyzeIssue) => { + if (analyzeIssue.suppressions && analyzeIssue.suppressions.length > 0) { + // Suppress issue + return; + } + this.generateIssueData(sastResponse, analyzeIssue, rulesFullDescription.get(analyzeIssue.ruleId)); + }); return sastResponse; } /** * Generate the data for a specific analyze issue (the file object, the issue in the file object and all the location objects of this issue). * If the issue also contains codeFlow generate the needed information for it as well - * @param sastResponse - the response of the scan that holds all the file objects - * @param analyzeIssue - the issue to handle and generate information base on it - * @param fullDescription - the description of the analyzeIssue + * @param sastResponse - Response of the scan that holds all the file objects + * @param analyzeIssue - Issue to handle and generate information base on it + * @param fullDescription - The description of the analyzeIssue */ public generateIssueData(sastResponse: SastScanResponse, analyzeIssue: AnalyzeIssue, fullDescription?: string) { analyzeIssue.locations.forEach(location => { @@ -179,9 +166,9 @@ export class SastRunner extends JasScanner { /** * Generate the code flow data. * Search the code flows for the given location (in a given file), the code flow belong to a location if the last location in the flow matches the given location. - * @param filePath - the path to the file the issue location belongs to - * @param issueLocation - the issue in a location to search code flows that belongs to it - * @param codeFlows - all the code flows for this issue + * @param filePath - Path to the file the issue location belongs to + * @param issueLocation - Issue in a location to search code flows that belongs to it + * @param codeFlows - All the code flows for this issue */ private generateCodeFlowData(filePath: string, issueLocation: SastIssueLocation, codeFlows: CodeFlow[]) { // Check if exists flows for the current location in this issue diff --git a/src/main/scanLogic/scanRunners/secretsScan.ts b/src/main/scanLogic/scanRunners/secretsScan.ts index de9a05cf..0e64c14a 100644 --- a/src/main/scanLogic/scanRunners/secretsScan.ts +++ b/src/main/scanLogic/scanRunners/secretsScan.ts @@ -8,9 +8,8 @@ import { ScanResults } from '../../types/workspaceIssuesDetails'; import { AppsConfigUtils } from '../../utils/appConfigUtils'; import { Resource } from '../../utils/resource'; import { ScanUtils } from '../../utils/scanUtils'; -import { ScanManager } from '../scanManager'; -import { AnalyzeScanRequest, AnalyzerScanResponse, ScanType } from './analyzerModels'; -import { JasScanner } from './binaryRunner'; +import { AnalyzeScanRequest, AnalyzerScanResponse, AnalyzerScanRun, ScanType } from './analyzerModels'; +import { JasRunner } from './jasRunner'; export interface SecretsScanResponse { filesWithIssues: FileWithSecurityIssues[]; @@ -19,8 +18,11 @@ export interface SecretsScanResponse { /** * Describes a runner for the Secrets scan executable file. */ -export class SecretsRunner extends JasScanner { +export class SecretsRunner extends JasRunner { constructor( + private _scanResults: ScanResults, + private _root: IssuesRootTreeNode, + private _progressManager: StepProgress, connectionManager: ConnectionManager, logManager: LogManager, module: Module, @@ -37,73 +39,57 @@ export class SecretsRunner extends JasScanner { /** * Run Secrets scan async task and populate the given bundle with the results. - * @param scanResults - the data object that will be populated with the results - * @param root - the view object that will be populated with the results - * @param scanManager - the ScanManager that preforms the actual scans - * @param module - the module that will be scanned - * @param progressManager - the progress for the given scan */ - public async scan(scanResults: ScanResults, root: IssuesRootTreeNode, scanManager: ScanManager, progressManager: StepProgress): Promise { - let startSecretsTime: number = Date.now(); + public async scan(): Promise { + let startTime: number = Date.now(); let request: AnalyzeScanRequest = { type: ScanType.Secrets, roots: AppsConfigUtils.GetSourceRoots(this._module, this._module.scanners?.secrets), skipped_folders: AppsConfigUtils.GetExcludePatterns(this._module, this._module.scanners?.secrets) } as AnalyzeScanRequest; - this._logManager.logMessage( - "Scanning directories '" + request.roots + "', for secrets. Skipping folders: " + request.skipped_folders, - 'DEBUG' + super.logStartScanning(request); + let response: SecretsScanResponse = await this.executeRequest(this._progressManager.checkCancel, request).then(runResult => + this.convertResponse(runResult) ); - let response: SecretsScanResponse = await this.run(progressManager.checkCancel, request).then(runResult => this.convertResponse(runResult)); if (response) { - scanResults.secretsScan = response; - scanResults.secretsScanTimestamp = Date.now(); - let issuesCount: number = AnalyzerUtils.populateSecretsIssues(root, scanResults); - scanManager.logManager.logMessage( - 'Found ' + - issuesCount + - " Secret issues in workspace = '" + - scanResults.path + - "' (elapsed " + - (scanResults.secretsScanTimestamp - startSecretsTime) / 1000 + - ' seconds)', - 'INFO' - ); - root.apply(); + this._scanResults.secretsScan = response; + this._scanResults.secretsScanTimestamp = Date.now(); + let issuesCount: number = AnalyzerUtils.populateSecretsIssues(this._root, this._scanResults); + super.logNumberOfIssues(issuesCount, this._scanResults.path, startTime, this._scanResults.secretsScanTimestamp); + this._root.apply(); } - progressManager.reportProgress(); + this._progressManager.reportProgress(); } /** * Generate response from the run results - * @param run - the run results generated from the binary + * @param response - Run results generated from the binary * @returns the response generated from the scan run */ public convertResponse(response?: AnalyzerScanResponse): SecretsScanResponse { if (!response) { return {} as SecretsScanResponse; } + let analyzerScanRun: AnalyzerScanRun = response.runs[0]; let secretsResponse: SecretsScanResponse = { filesWithIssues: [] } as SecretsScanResponse; - for (const run of response.runs) { - // Get the full descriptions of all rules - let rulesFullDescription: Map = new Map(); - for (const rule of run.tool.driver.rules) { - if (rule.fullDescription) { - rulesFullDescription.set(rule.id, rule.fullDescription.text); - } + // Get the full descriptions of all rules + let rulesFullDescription: Map = new Map(); + for (const rule of analyzerScanRun.tool.driver.rules) { + if (rule.fullDescription) { + rulesFullDescription.set(rule.id, rule.fullDescription.text); } - // Generate response data - run.results?.forEach(analyzeIssue => { - if (analyzeIssue.suppressions && analyzeIssue.suppressions.length > 0) { - // Suppress issue - return; - } - AnalyzerUtils.generateIssueData(secretsResponse, analyzeIssue, rulesFullDescription.get(analyzeIssue.ruleId)); - }); } + // Generate response data + analyzerScanRun.results?.forEach(analyzeIssue => { + if (analyzeIssue.suppressions && analyzeIssue.suppressions.length > 0) { + // Suppress issue + return; + } + AnalyzerUtils.generateIssueData(secretsResponse, analyzeIssue, rulesFullDescription.get(analyzeIssue.ruleId)); + }); return secretsResponse; } } diff --git a/src/main/treeDataProviders/issuesTree/issuesTreeDataProvider.ts b/src/main/treeDataProviders/issuesTree/issuesTreeDataProvider.ts index 6a4c292c..c27d292b 100644 --- a/src/main/treeDataProviders/issuesTree/issuesTreeDataProvider.ts +++ b/src/main/treeDataProviders/issuesTree/issuesTreeDataProvider.ts @@ -1,11 +1,13 @@ import * as vscode from 'vscode'; import { CacheManager } from '../../cache/cacheManager'; +import { ConnectionManager } from '../../connect/connectionManager'; import { LogManager } from '../../log/logManager'; import { EntitledScans, ScanManager } from '../../scanLogic/scanManager'; import { IacRunner } from '../../scanLogic/scanRunners/iacScan'; +import { JasRunner } from '../../scanLogic/scanRunners/jasRunner'; import { SastRunner } from '../../scanLogic/scanRunners/sastScan'; import { SecretsRunner } from '../../scanLogic/scanRunners/secretsScan'; -import { JFrogAppsConfig } from '../../types/jfrogAppsConfig'; +import { JFrogAppsConfig, Module } from '../../types/jfrogAppsConfig'; import { PackageType } from '../../types/projectType'; import { Severity, SeverityUtils } from '../../types/severity'; import { DependencyScanResults, EntryIssuesData, ScanResults } from '../../types/workspaceIssuesDetails'; @@ -230,32 +232,17 @@ export class IssuesTreeDataProvider implements vscode.TreeDataProvider ScanUtils.onScanError(err, this._logManager, true)) - ); - } - let secretsScanner: SecretsRunner = new SecretsRunner(this._treesManager.connectionManager, this._logManager, module); - if (this._scanManager.entitledScans.secrets && secretsScanner.shouldRun()) { - // Scan the workspace for Secrets issues - scansPromises.push( - secretsScanner - .scan(scanResults, root, this._scanManager, progressManager) - .catch(err => ScanUtils.onScanError(err, this._logManager, true)) - ); - } - let sastScanner: SastRunner = new SastRunner(this._treesManager.connectionManager, this._logManager, module); - if (this._scanManager.entitledScans.sast && sastScanner.shouldRun()) { - // Scan the workspace for Sast issues - scansPromises.push( - secretsScanner - .scan(scanResults, root, this._scanManager, progressManager) - .catch(err => ScanUtils.onScanError(err, this._logManager, true)) - ); + for (let runner of this.createJasRunners( + scanResults, + root, + progressManager, + this._treesManager.connectionManager, + this._logManager, + module + )) { + if (runner.shouldRun()) { + scansPromises.push(runner.scan().catch(err => ScanUtils.onScanError(err, this._logManager, true))); + } } } @@ -263,6 +250,27 @@ export class IssuesTreeDataProvider implements vscode.TreeDataProvider { - let filteredBundles: Map> = this.filterBundlesWithoutIssuesToScan(fileScanBundles, type); - let spaceToBundles: Map>> = this.mapBundlesForApplicableScanning( - scanManager.logManager, - filteredBundles - ); - if (spaceToBundles.size == 0) { - return; - } - for (let [spacePath, bundles] of spaceToBundles) { - let cveToScan: Set = Utils.combineSets(Array.from(bundles.values())); - // Scan workspace for all cve in relevant bundles - let startApplicableTime: number = Date.now(); - let skipFiles: string[] = AnalyzerUtils.getAnalyzerManagerExcludePattern(Configuration.getScanExcludePattern()); - - let applicableIssues: ApplicabilityScanResponse = await applicableRunner.scan( - spacePath, - progressManager.checkCancel, - cveToScan, - skipFiles - ); - if (applicableIssues && applicableIssues.applicableCve) { - let applicableScanTimestamp: number = Date.now(); - AnalyzerUtils.transferApplicableResponseToBundles( - applicableIssues, - bundles, - scanManager.logManager, - applicableScanTimestamp, - applicableScanTimestamp - startApplicableTime - ); - } - } - } - - /** - * Filter bundles without direct cve issues, transform the bundle list to have its relevant cve to scan set. - * @param fileScanBundles - bundles to process and filter if needed - * @returns Map of bundles to their set of direct cves issues, with at least one for each bundle - */ - private static filterBundlesWithoutIssuesToScan(fileScanBundles: FileScanBundle[], type: PackageType): Map> { - let filtered: Map> = new Map>(); - - for (let fileScanBundle of fileScanBundles) { - if (!(fileScanBundle.dataNode instanceof ProjectDependencyTreeNode)) { - // Filter non dependencies projects - continue; - } - let cvesToScan: Set = new Set(); - fileScanBundle.dataNode.issues.forEach((issue: IssueTreeNode) => { - if (!(issue instanceof CveTreeNode) || !issue.cve?.cve) { - return; - } - // For Python projects, all CVEs should be included because in some cases it is impossible to determine whether a dependency is direct. - // Other project types should include only CVEs on direct dependencies. - if (type === PackageType.Python || !issue.parent.indirect) { - cvesToScan.add(issue.cve.cve); - } - }); - if (cvesToScan.size == 0) { - // Nothing to do in bundle - continue; - } - - filtered.set(fileScanBundle, cvesToScan); - } - - return filtered; - } - - /** - * Create a mapping between a workspace and all the given bundles that relevant to it. - * @param logManager - logger to log added map - * @param fileScanBundles - bundles to map - * @returns mapped bundles to similar workspace - */ - private static mapBundlesForApplicableScanning( - logManager: LogManager, - filteredBundles: Map> - ): Map>> { - let workspaceToScanBundles: Map>> = new Map>>(); - - for (let [fileScanBundle, cvesToScan] of filteredBundles) { - let descriptorIssues: DependencyScanResults = fileScanBundle.data; - // Map information to similar directory space - let workspacePath: string = AnalyzerUtils.getWorkspacePath(fileScanBundle.dataNode, descriptorIssues.fullPath); - if (!workspaceToScanBundles.has(workspacePath)) { - workspaceToScanBundles.set(workspacePath, new Map>()); - } - workspaceToScanBundles.get(workspacePath)?.set(fileScanBundle, cvesToScan); - logManager.logMessage('Adding data from descriptor ' + descriptorIssues.fullPath + ' for cve applicability scan', 'INFO'); - } - - return workspaceToScanBundles; - } - /** * Retrieve the workspace path, whether it's a file or an environment. * @param fileScanBundle - The data node for file tree, usually DescriptorTreeNode or EnvironmentTreeNode * @param fullWorkspacePath - Full path to the scanning directory or file * @returns the path to the workspace directory */ - private static getWorkspacePath(fileScanBundle: FileTreeNode | undefined, fullWorkspacePath: string): string { + public static getWorkspacePath(fileScanBundle: FileTreeNode | undefined, fullWorkspacePath: string): string { if (fileScanBundle instanceof EnvironmentTreeNode) { return fullWorkspacePath; } return path.dirname(fullWorkspacePath); } - /** - * Transfer and populate information from a given applicable scan to each bundle - * @param applicableIssues - full scan response with information relevant to all the bundles - * @param bundles - the bundles that will be populated only with their relevant information - * @param logManager - logger to log information to the user - * @param applicableTimeStamp - ended time stamp for the applicable scan - * @param elapsedTimeInMillsSec - elapsed time that took the scan to run - */ - private static transferApplicableResponseToBundles( - applicableIssues: ApplicabilityScanResponse, - bundles: Map>, - logManager: LogManager, - applicableTimeStamp: number, - elapsedTimeInMillsSec: number - ) { - for (let [bundle, relevantCve] of bundles) { - let descriptorIssues: DependencyScanResults = bundle.data; - // Filter only relevant information - descriptorIssues.applicableScanTimestamp = applicableTimeStamp; - descriptorIssues.applicableIssues = AnalyzerUtils.filterApplicabilityScanResponse(applicableIssues, relevantCve); - // Populate it in bundle - let applicableIssuesCount: number = AnalyzerUtils.populateApplicableIssues( - bundle.rootNode, - bundle.dataNode, - descriptorIssues - ); - logManager.logMessage( - 'Found ' + - applicableIssuesCount + - " applicable CVE issues in descriptor = '" + - descriptorIssues.fullPath + - "' (elapsed " + - elapsedTimeInMillsSec / 1000 + - ' seconds)', - 'INFO' - ); - bundle.rootNode.apply(); - } - } - - /** - * For a given full ApplicableScanResponse scan results, filter the results to only contain information relevant to a given cve list - * @param responseToFilter - all the applicable information - * @param relevantCve - cve list to filter information only for them - * @returns ApplicableScanResponse with information relevant only for the given relevantCve - */ - private static filterApplicabilityScanResponse(responseToFilter: ApplicabilityScanResponse, relevantCve: Set): ApplicabilityScanResponse { - let allApplicable: Map = new Map(Object.entries(responseToFilter.applicableCve)); - let relevantScannedCve: string[] = []; - let relevantApplicableCve: Map = new Map(); - - for (let scannedCve of responseToFilter.scannedCve) { - if (relevantCve.has(scannedCve)) { - relevantScannedCve.push(scannedCve); - let potential: CveApplicableDetails | undefined = allApplicable.get(scannedCve); - if (potential) { - relevantApplicableCve.set(scannedCve, potential); - } - } - } - return { - scannedCve: Array.from(relevantScannedCve), - applicableCve: Object.fromEntries(relevantApplicableCve.entries()) - } as ApplicabilityScanResponse; - } - /** * Populate the applicable data to the view (create file issue nodes) * @param root - the root to populate the data inside diff --git a/src/main/treeDataProviders/utils/dependencyUtils.ts b/src/main/treeDataProviders/utils/dependencyUtils.ts index 8c64eabd..e9d98f62 100644 --- a/src/main/treeDataProviders/utils/dependencyUtils.ts +++ b/src/main/treeDataProviders/utils/dependencyUtils.ts @@ -1,35 +1,34 @@ -import * as vscode from 'vscode'; import { IComponent, IGraphResponse, IViolation, IVulnerability } from 'jfrog-client-js'; -import { RootNode } from '../dependenciesTree/dependenciesRoot/rootTree'; -import { DependenciesTreeNode } from '../dependenciesTree/dependenciesTreeNode'; -import { Severity, SeverityUtils } from '../../types/severity'; -import { DependencyIssuesTreeNode } from '../issuesTree/descriptorTree/dependencyIssuesTreeNode'; -import { CveTreeNode } from '../issuesTree/descriptorTree/cveTreeNode'; +import { IImpactGraph, ILicense } from 'jfrog-ide-webview'; +import * as vscode from 'vscode'; +import { FocusType } from '../../constants/contextKeys'; +import { LogManager } from '../../log/logManager'; +import { ScanManager } from '../../scanLogic/scanManager'; +import { ApplicabilityRunner } from '../../scanLogic/scanRunners/applicabilityScan'; +import { GeneralInfo } from '../../types/generalInfo'; import { PackageType } from '../../types/projectType'; -import { LicenseIssueTreeNode } from '../issuesTree/descriptorTree/licenseIssueTreeNode'; +import { Severity, SeverityUtils } from '../../types/severity'; +import { DependencyScanResults, ScanResults } from '../../types/workspaceIssuesDetails'; import { GoUtils } from '../../utils/goUtils'; import { MavenUtils } from '../../utils/mavenUtils'; import { NpmUtils } from '../../utils/npmUtils'; +import { NugetUtils } from '../../utils/nugetUtils'; import { PypiUtils } from '../../utils/pypiUtils'; +import { FileScanBundle, FileScanError, ScanUtils } from '../../utils/scanUtils'; import { YarnUtils } from '../../utils/yarnUtils'; -import { IImpactGraph, ILicense } from 'jfrog-ide-webview'; -import { IssueTreeNode } from '../issuesTree/issueTreeNode'; -import { FocusType } from '../../constants/contextKeys'; -import { DependencyScanResults, ScanResults } from '../../types/workspaceIssuesDetails'; +import { RootNode } from '../dependenciesTree/dependenciesRoot/rootTree'; +import { VirtualEnvPypiTree } from '../dependenciesTree/dependenciesRoot/virtualEnvPypiTree'; +import { DependenciesTreeNode } from '../dependenciesTree/dependenciesTreeNode'; +import { CveTreeNode } from '../issuesTree/descriptorTree/cveTreeNode'; +import { DependencyIssuesTreeNode } from '../issuesTree/descriptorTree/dependencyIssuesTreeNode'; +import { DescriptorTreeNode } from '../issuesTree/descriptorTree/descriptorTreeNode'; import { EnvironmentTreeNode } from '../issuesTree/descriptorTree/environmentTreeNode'; +import { LicenseIssueTreeNode } from '../issuesTree/descriptorTree/licenseIssueTreeNode'; import { ProjectDependencyTreeNode } from '../issuesTree/descriptorTree/projectDependencyTreeNode'; -import { NugetUtils } from '../../utils/nugetUtils'; +import { FileTreeNode } from '../issuesTree/fileTreeNode'; +import { IssueTreeNode } from '../issuesTree/issueTreeNode'; import { IssuesRootTreeNode } from '../issuesTree/issuesRootTreeNode'; import { GraphScanProgress, StepProgress } from './stepProgress'; -import { AnalyzerUtils } from './analyzerUtils'; -import { DescriptorTreeNode } from '../issuesTree/descriptorTree/descriptorTreeNode'; -import { VirtualEnvPypiTree } from '../dependenciesTree/dependenciesRoot/virtualEnvPypiTree'; -import { ScanManager } from '../../scanLogic/scanManager'; -import { FileScanBundle, FileScanError, ScanUtils } from '../../utils/scanUtils'; -import { LogManager } from '../../log/logManager'; -import { GeneralInfo } from '../../types/generalInfo'; -import { FileTreeNode } from '../issuesTree/fileTreeNode'; -import { ApplicabilityRunner } from '../../scanLogic/scanRunners/applicabilityScan'; export class DependencyUtils { public static readonly FAIL_TO_SCAN: string = '[Fail to scan]'; @@ -107,11 +106,15 @@ export class DependencyUtils { this.reportNotFoundDescriptors(descriptorsPaths, descriptorsParsed, scanManager.logManager); await Promise.all(scansPromises); - let applicabilityRunner: ApplicabilityRunner = new ApplicabilityRunner(scanManager.connectionManager, scanManager.logManager); + let applicabilityRunner: ApplicabilityRunner = new ApplicabilityRunner( + bundlesWithIssues, + type, + progressManager, + scanManager.connectionManager, + scanManager.logManager + ); if (contextualScan && bundlesWithIssues.length > 0 && applicabilityRunner.shouldRun()) { - await AnalyzerUtils.cveApplicableScanning(scanManager, bundlesWithIssues, progressManager, type, applicabilityRunner).catch(err => - ScanUtils.onScanError(err, scanManager.logManager, true) - ); + await applicabilityRunner.scan().catch(err => ScanUtils.onScanError(err, scanManager.logManager, true)); } } diff --git a/src/main/utils/appConfigUtils.ts b/src/main/utils/appConfigUtils.ts index ea0318c3..97cbbb52 100644 --- a/src/main/utils/appConfigUtils.ts +++ b/src/main/utils/appConfigUtils.ts @@ -10,13 +10,16 @@ export class AppsConfigUtils { public static LoadConfig(workspace: string): JFrogAppsConfig { let appConfigPath: string = path.join(workspace, '.jfrog', 'jfrog-apps-config.yml'); - if (!fs.existsSync(appConfigPath)) { - return { - version: AppsConfigUtils.JFROG_APP_CONFIG_VERSION, - modules: [{ source_root: workspace } as Module] - } as JFrogAppsConfig; + if (fs.existsSync(appConfigPath)) { + let jfrogAppsConfig: JFrogAppsConfig = yaml.load(fs.readFileSync(appConfigPath, 'utf8')) as JFrogAppsConfig; + if (jfrogAppsConfig?.modules?.length > 0) { + return jfrogAppsConfig; + } } - return yaml.load(fs.readFileSync(appConfigPath, 'utf8')) as JFrogAppsConfig; + return { + version: AppsConfigUtils.JFROG_APP_CONFIG_VERSION, + modules: [{ source_root: workspace } as Module] + } as JFrogAppsConfig; } public static ShouldSkipScanner(module: Module, scanType: ScanType): boolean { @@ -46,7 +49,7 @@ export class AppsConfigUtils { excludePatterns = excludePatterns.concat(scanner.exclude_patterns); } if (excludePatterns.length === 0) { - return AnalyzerUtils.getAnalyzerManagerExcludePattern(Configuration.getScanExcludePattern()); + return AnalyzerUtils.getAnalyzerManagerExcludePatterns(Configuration.getScanExcludePattern()); } return excludePatterns; } diff --git a/src/test/tests/analyzerUtils.test.ts b/src/test/tests/analyzerUtils.test.ts index 43e3110b..ff02d38b 100644 --- a/src/test/tests/analyzerUtils.test.ts +++ b/src/test/tests/analyzerUtils.test.ts @@ -29,7 +29,7 @@ describe('Analyzer Utils Tests', async () => { } ].forEach(testCase => { it('Get analyzer manager exclude pattern test - ' + testCase.pattern, () => { - let results: string[] = AnalyzerUtils.getAnalyzerManagerExcludePattern(testCase.pattern); + let results: string[] = AnalyzerUtils.getAnalyzerManagerExcludePatterns(testCase.pattern); assert.sameMembers(testCase.results, results); }); }); diff --git a/src/test/tests/applicabilityScan.test.ts b/src/test/tests/applicabilityScan.test.ts index d6483acd..e20a88af 100644 --- a/src/test/tests/applicabilityScan.test.ts +++ b/src/test/tests/applicabilityScan.test.ts @@ -1,38 +1,35 @@ -import * as path from 'path'; -import * as fs from 'fs'; import { assert } from 'chai'; +import * as fs from 'fs'; +import * as path from 'path'; +import { IEvidence } from 'jfrog-ide-webview'; import { ConnectionManager } from '../../main/connect/connectionManager'; import { LogManager } from '../../main/log/logManager'; +import { AnalyzerScanResponse, FileIssues, FileRegion } from '../../main/scanLogic/scanRunners/analyzerModels'; import { ApplicabilityRunner, - CveApplicableDetails, ApplicabilityScanArgs, - ApplicabilityScanResponse + ApplicabilityScanResponse, + CveApplicableDetails } from '../../main/scanLogic/scanRunners/applicabilityScan'; -import { FileScanBundle, ScanUtils } from '../../main/utils/scanUtils'; -import { FileIssues, FileRegion } from '../../main/scanLogic/scanRunners/analyzerModels'; -import { IssuesRootTreeNode } from '../../main/treeDataProviders/issuesTree/issuesRootTreeNode'; -import { createDummyCveIssue, createDummyDependencyIssues, createRootTestNode, getTestCodeFileNode } from './utils/treeNodeUtils.test'; -import { DescriptorTreeNode } from '../../main/treeDataProviders/issuesTree/descriptorTree/descriptorTreeNode'; -import { PackageType } from '../../main/types/projectType'; -import { DependencyScanResults, ScanResults } from '../../main/types/workspaceIssuesDetails'; -import { AnalyzerUtils } from '../../main/treeDataProviders/utils/analyzerUtils'; -import { DependencyIssuesTreeNode } from '../../main/treeDataProviders/issuesTree/descriptorTree/dependencyIssuesTreeNode'; -import { Severity } from '../../main/types/severity'; -import { CveTreeNode } from '../../main/treeDataProviders/issuesTree/descriptorTree/cveTreeNode'; +import { ApplicableTreeNode } from '../../main/treeDataProviders/issuesTree/codeFileTree/applicableTreeNode'; import { CodeFileTreeNode } from '../../main/treeDataProviders/issuesTree/codeFileTree/codeFileTreeNode'; -import { IEvidence } from 'jfrog-ide-webview'; import { CodeIssueTreeNode } from '../../main/treeDataProviders/issuesTree/codeFileTree/codeIssueTreeNode'; -import { ApplicableTreeNode } from '../../main/treeDataProviders/issuesTree/codeFileTree/applicableTreeNode'; -import { createTestConnectionManager, getAnalyzerScanResponse, removeWindowsWhiteSpace } from './utils/utils.test'; -import { ScanManager } from '../../main/scanLogic/scanManager'; -import { StepProgress } from '../../main/treeDataProviders/utils/stepProgress'; -import { ProjectDependencyTreeNode } from '../../main/treeDataProviders/issuesTree/descriptorTree/projectDependencyTreeNode'; +import { CveTreeNode } from '../../main/treeDataProviders/issuesTree/descriptorTree/cveTreeNode'; +import { DependencyIssuesTreeNode } from '../../main/treeDataProviders/issuesTree/descriptorTree/dependencyIssuesTreeNode'; +import { DescriptorTreeNode } from '../../main/treeDataProviders/issuesTree/descriptorTree/descriptorTreeNode'; import { EnvironmentTreeNode } from '../../main/treeDataProviders/issuesTree/descriptorTree/environmentTreeNode'; +import { ProjectDependencyTreeNode } from '../../main/treeDataProviders/issuesTree/descriptorTree/projectDependencyTreeNode'; +import { IssuesRootTreeNode } from '../../main/treeDataProviders/issuesTree/issuesRootTreeNode'; +import { AnalyzerUtils } from '../../main/treeDataProviders/utils/analyzerUtils'; +import { PackageType } from '../../main/types/projectType'; +import { Severity } from '../../main/types/severity'; +import { DependencyScanResults, ScanResults } from '../../main/types/workspaceIssuesDetails'; +import { FileScanBundle, ScanUtils } from '../../main/utils/scanUtils'; +import { createDummyCveIssue, createDummyDependencyIssues, createRootTestNode, getTestCodeFileNode } from './utils/treeNodeUtils.test'; +import { createTestStepProgress, getAnalyzerScanResponse, removeWindowsWhiteSpace } from './utils/utils.test'; let logManager: LogManager = new LogManager().activate(); - describe('Applicability Scan Tests', () => { const scanApplicable: string = path.join(__dirname, '..', 'resources', 'applicableScan'); let tempFolder: string = ScanUtils.createTmpDir(); @@ -56,7 +53,7 @@ describe('Applicability Scan Tests', () => { it('Check generated Yaml request for - ' + test.name, () => { let request: ApplicabilityScanArgs = getApplicabilityScanRequest(test.roots, test.cves, test.skip); let actualYaml: string = path.join(tempFolder, test.name); - fs.writeFileSync(actualYaml, getDummyRunner().requestsToYaml(request)); + fs.writeFileSync(actualYaml, getDummyRunner([], PackageType.Unknown).requestsToYaml(request)); assert.deepEqual( fs.readFileSync(actualYaml, 'utf-8').toString(), removeWindowsWhiteSpace(fs.readFileSync(test.expectedYaml, 'utf-8').toString()) @@ -68,7 +65,7 @@ describe('Applicability Scan Tests', () => { let response: ApplicabilityScanResponse; before(() => { - response = getDummyRunner().convertResponse(undefined); + response = getDummyRunner([], PackageType.Unknown).convertResponse(undefined); }); it('Check response defined', () => { @@ -82,8 +79,8 @@ describe('Applicability Scan Tests', () => { }); describe('Run applicability scan', () => { - let npmScanManager: DummyScanManager; - let pythonScanManager: DummyScanManager; + let npmApplicabilityRunner: DummyApplicabilityRunner; + let pythonApplicabilityRunner: DummyApplicabilityRunner; const testRoot: IssuesRootTreeNode = createRootTestNode(path.join('root')); const testDescriptor: ProjectDependencyTreeNode = new EnvironmentTreeNode('path', PackageType.Unknown, testRoot); @@ -91,8 +88,6 @@ describe('Applicability Scan Tests', () => { let expectedPythonScannedCve: string[] = ['CVE-2021-3807', 'CVE-2021-3918', 'CVE-2022-25878']; before(async () => { - npmScanManager = getDummyScanManager().activate(); - pythonScanManager = getDummyScanManager().activate(); // Create dummy cve let testDependency: DependencyIssuesTreeNode = createDummyDependencyIssues('dummy', '9.9.9', testDescriptor); for (let cve of new Set(expectedNpmScannedCve)) { @@ -111,24 +106,24 @@ describe('Applicability Scan Tests', () => { } as FileScanBundle; // Run scan - let connectionManager: ConnectionManager = await createTestConnectionManager(logManager); - let applicabilityRunner: ApplicabilityRunner = new ApplicabilityRunner(connectionManager, logManager); - await AnalyzerUtils.cveApplicableScanning(npmScanManager, [scanBundle], {} as StepProgress, PackageType.Npm, applicabilityRunner); - await AnalyzerUtils.cveApplicableScanning(pythonScanManager, [scanBundle], {} as StepProgress, PackageType.Python, applicabilityRunner); + npmApplicabilityRunner = getDummyRunner([scanBundle], PackageType.Npm); + await npmApplicabilityRunner.scan().catch(err => assert.fail(err)); + pythonApplicabilityRunner = getDummyRunner([scanBundle], PackageType.Python); + await pythonApplicabilityRunner.scan().catch(err => assert.fail(err)); }); it('Check Virtual Environment is scanned', () => { - assert.isTrue(pythonScanManager.scanned); + assert.isTrue(pythonApplicabilityRunner.scanned); }); it('Only scan direct cve', () => { - npmScanManager.cvesScanned.keys(); - assert.sameMembers(expectedNpmScannedCve, [...npmScanManager.cvesScanned]); + npmApplicabilityRunner.cvesScanned.keys(); + assert.sameMembers(expectedNpmScannedCve, [...npmApplicabilityRunner.cvesScanned]); }); // For Python projects, we scan all CVEs. it('All cve with python project', () => { - pythonScanManager.cvesScanned.keys(); - assert.sameMembers(expectedPythonScannedCve, [...pythonScanManager.cvesScanned]); + pythonApplicabilityRunner.cvesScanned.keys(); + assert.sameMembers(expectedPythonScannedCve, [...pythonApplicabilityRunner.cvesScanned]); }); }); @@ -142,8 +137,8 @@ describe('Applicability Scan Tests', () => { before(() => { // Read test data and populate scanResult and dummy Cve nodes in test dependency - let response: ApplicabilityScanResponse = getDummyRunner().convertResponse( - getAnalyzerScanResponse(path.join(scanApplicable, 'analyzerResponse.json'))?.runs[0] + let response: ApplicabilityScanResponse = getDummyRunner([], PackageType.Unknown).convertResponse( + getAnalyzerScanResponse(path.join(scanApplicable, 'analyzerResponse.json')) ); for (let cve of response.scannedCve) { testCves.push(createDummyCveIssue(Severity.Medium, testDependency, cve, cve)); @@ -359,34 +354,27 @@ describe('Applicability Scan Tests', () => { } as ApplicabilityScanArgs; } - function getDummyRunner(): ApplicabilityRunner { - return new ApplicabilityRunner({} as ConnectionManager, logManager); - } - - function getDummyScanManager(): DummyScanManager { - return new DummyScanManager({} as ConnectionManager, logManager); + function getDummyRunner(scanBundles: FileScanBundle[], packageType: PackageType): DummyApplicabilityRunner { + return new DummyApplicabilityRunner(scanBundles, packageType); } }); -class DummyScanManager extends ScanManager { - scanned: boolean = false; +class DummyApplicabilityRunner extends ApplicabilityRunner { cvesScanned: Set = new Set(); + scanned: boolean = false; - constructor(connectionManager: ConnectionManager, logManager: LogManager) { - super(connectionManager, logManager); + constructor(scanBundles: FileScanBundle[], packageType: PackageType) { + super(scanBundles, packageType, createTestStepProgress(), {} as ConnectionManager, logManager); } /** @override */ - public async scanApplicability( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - directory: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - checkCancel: () => void, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - cveToRun: Set - ): Promise { - this.cvesScanned = new Set(cveToRun); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public executeRequest(checkCancel: () => void, request: ApplicabilityScanArgs): Promise { this.scanned = true; - return {} as ApplicabilityScanResponse; + this.cvesScanned = new Set(request.cve_whitelist); + + return new Promise(resolve => { + resolve(undefined); + }); } } diff --git a/src/test/tests/iacScan.test.ts b/src/test/tests/iacScan.test.ts index 513aaf74..2c63f6b5 100644 --- a/src/test/tests/iacScan.test.ts +++ b/src/test/tests/iacScan.test.ts @@ -3,29 +3,29 @@ import * as path from 'path'; import { assert } from 'chai'; import { ConnectionManager } from '../../main/connect/connectionManager'; import { LogManager } from '../../main/log/logManager'; +import { FileRegion } from '../../main/scanLogic/scanRunners/analyzerModels'; import { IacRunner, IacScanResponse } from '../../main/scanLogic/scanRunners/iacScan'; +import { CodeFileTreeNode } from '../../main/treeDataProviders/issuesTree/codeFileTree/codeFileTreeNode'; +import { CodeIssueTreeNode } from '../../main/treeDataProviders/issuesTree/codeFileTree/codeIssueTreeNode'; +import { IacTreeNode } from '../../main/treeDataProviders/issuesTree/codeFileTree/iacTreeNode'; import { IssuesRootTreeNode } from '../../main/treeDataProviders/issuesTree/issuesRootTreeNode'; -import { createRootTestNode } from './utils/treeNodeUtils.test'; -import { ScanResults } from '../../main/types/workspaceIssuesDetails'; import { AnalyzerUtils, FileWithSecurityIssues } from '../../main/treeDataProviders/utils/analyzerUtils'; -import { getAnalyzerScanResponse, getEmptyAnalyzerScanResponse } from './utils/utils.test'; -import { FileRegion } from '../../main/scanLogic/scanRunners/analyzerModels'; -import { IacTreeNode } from '../../main/treeDataProviders/issuesTree/codeFileTree/iacTreeNode'; -import { CodeIssueTreeNode } from '../../main/treeDataProviders/issuesTree/codeFileTree/codeIssueTreeNode'; -import { CodeFileTreeNode } from '../../main/treeDataProviders/issuesTree/codeFileTree/codeFileTreeNode'; +import { Module } from '../../main/types/jfrogAppsConfig'; +import { ScanResults } from '../../main/types/workspaceIssuesDetails'; import { assertFileNodesCreated, assertIssueNodesCreated, assertIssuesFullDescription, + assertIssuesSnippet, assertNodeLabelRuleName, assertNodesSeverity, - assertIssuesSnippet, assertSameNumberOfFileNodes, assertSameNumberOfIssueNodes, findLocationNode, groupFiles } from './utils/testAnalyzer.test'; -import { Module } from '../../main/types/jfrogAppsConfig'; +import { createRootTestNode } from './utils/treeNodeUtils.test'; +import { createTestStepProgress, getAnalyzerScanResponse, getEmptyAnalyzerScanResponse } from './utils/utils.test'; describe('Iac Scan Tests', () => { const scanIac: string = path.join(__dirname, '..', 'resources', 'iacScan'); @@ -121,6 +121,6 @@ describe('Iac Scan Tests', () => { }); function getDummyRunner(): IacRunner { - return new IacRunner({} as ConnectionManager, logManager, {} as Module); + return new IacRunner({} as ScanResults, createRootTestNode(''), createTestStepProgress(), {} as ConnectionManager, logManager, {} as Module); } }); diff --git a/src/test/tests/integration/applicability.test.ts b/src/test/tests/integration/applicability.test.ts index 6a8288df..196b78da 100644 --- a/src/test/tests/integration/applicability.test.ts +++ b/src/test/tests/integration/applicability.test.ts @@ -2,12 +2,19 @@ import { assert } from 'chai'; import * as fs from 'fs'; import * as path from 'path'; -import { ApplicabilityRunner, ApplicabilityScanResponse, CveApplicableDetails } from '../../../main/scanLogic/scanRunners/applicabilityScan'; +import { + ApplicabilityRunner, + ApplicabilityScanArgs, + ApplicabilityScanResponse, + CveApplicableDetails +} from '../../../main/scanLogic/scanRunners/applicabilityScan'; import { FileIssues, FileRegion } from '../../../main/scanLogic/scanRunners/analyzerModels'; -import { JasScanner } from '../../../main/scanLogic/scanRunners/binaryRunner'; +import { JasRunner } from '../../../main/scanLogic/scanRunners/jasRunner'; import { AnalyzerUtils } from '../../../main/treeDataProviders/utils/analyzerUtils'; import { AnalyzerManagerIntegrationEnv } from '../utils/testIntegration.test'; +import { PackageType } from '../../../main/types/projectType'; +import { createTestStepProgress } from '../utils/utils.test'; describe('Applicability Integration Tests', async () => { let integrationManager: AnalyzerManagerIntegrationEnv = new AnalyzerManagerIntegrationEnv(); @@ -18,7 +25,14 @@ describe('Applicability Integration Tests', async () => { before(async () => { await integrationManager.initialize(); // Must be created after integration initialization - runner = new ApplicabilityRunner(integrationManager.connectionManager, integrationManager.logManager, integrationManager.resource); + runner = new ApplicabilityRunner( + [], + PackageType.Unknown, + createTestStepProgress(), + integrationManager.connectionManager, + integrationManager.logManager, + integrationManager.resource + ); runner.verbose = true; assert.isTrue(runner.validateSupported(), "Can't find runner binary file in path: " + runner.binary.fullPath); }); @@ -38,7 +52,10 @@ describe('Applicability Integration Tests', async () => { expectedContent = JSON.parse(fs.readFileSync(expectedResponseContentPath, 'utf8').toString()); assert.isDefined(expectedContent, 'Failed to read expected ApplicabilityScanResponse content from ' + expectedResponseContentPath); // Run scan - response = await runner.scan(directoryToScan, () => undefined, new Set(expectedContent.scannedCve)); + response = await runner + .executeRequest(() => undefined, { roots: [directoryToScan], cve_whitelist: expectedContent.scannedCve } as ApplicabilityScanArgs) + .then(runResult => runner.convertResponse(runResult)) + .catch(err => assert.fail(err)); }); it('Check response defined', () => { @@ -122,7 +139,7 @@ describe('Applicability Integration Tests', async () => { } it('Check all expected locations exists', async function() { - if (JasScanner.RUNNER_VERSION === '1.3.2.2005632') { + if (JasRunner.RUNNER_VERSION === '1.3.2.2005632') { // Duplicate results are found in this AM version, which may be fixed in the next release. this.skip(); } @@ -136,7 +153,7 @@ describe('Applicability Integration Tests', async () => { }); it('Check snippet data', async function() { - if (JasScanner.RUNNER_VERSION === '1.3.2.2005632') { + if (JasRunner.RUNNER_VERSION === '1.3.2.2005632') { // Duplicate results are found in this AM version, which may be fixed in the next release. this.skip(); } diff --git a/src/test/tests/integration/iac.test.ts b/src/test/tests/integration/iac.test.ts index 21ff2e1f..4ba455e1 100644 --- a/src/test/tests/integration/iac.test.ts +++ b/src/test/tests/integration/iac.test.ts @@ -5,6 +5,7 @@ import * as path from 'path'; import { AnalyzeScanRequest } from '../../../main/scanLogic/scanRunners/analyzerModels'; import { IacRunner, IacScanResponse } from '../../../main/scanLogic/scanRunners/iacScan'; import { Module } from '../../../main/types/jfrogAppsConfig'; +import { ScanResults } from '../../../main/types/workspaceIssuesDetails'; import { AnalyzerManagerIntegrationEnv, assertFileIssuesExist, @@ -15,6 +16,8 @@ import { assertIssuesRuleNameExist, assertIssuesSeverityExist } from '../utils/testIntegration.test'; +import { createRootTestNode } from '../utils/treeNodeUtils.test'; +import { createTestStepProgress } from '../utils/utils.test'; describe('Iac Integration Tests', async () => { const integrationManager: AnalyzerManagerIntegrationEnv = new AnalyzerManagerIntegrationEnv(); @@ -27,7 +30,15 @@ describe('Iac Integration Tests', async () => { before(async function() { // Integration initialization await integrationManager.initialize(); - runner = new IacRunner(integrationManager.connectionManager, integrationManager.logManager, {} as Module, integrationManager.resource); + runner = new IacRunner( + {} as ScanResults, + createRootTestNode(''), + createTestStepProgress(), + integrationManager.connectionManager, + integrationManager.logManager, + {} as Module, + integrationManager.resource + ); runner.verbose = true; assert.isTrue(runner.validateSupported(), "Can't find runner binary file in path: " + runner.binary.fullPath); // Get expected partial result that the scan should contain @@ -37,7 +48,7 @@ describe('Iac Integration Tests', async () => { // Run scan // Try/Catch (with skip) should be removed after Iac is released response = await runner - .run(() => undefined, { roots: [testDataRoot] } as AnalyzeScanRequest) + .executeRequest(() => undefined, { roots: [testDataRoot] } as AnalyzeScanRequest) .then(runResult => runner.convertResponse(runResult)); }); diff --git a/src/test/tests/integration/secrets.test.ts b/src/test/tests/integration/secrets.test.ts index b2279712..a1ae89c8 100644 --- a/src/test/tests/integration/secrets.test.ts +++ b/src/test/tests/integration/secrets.test.ts @@ -4,6 +4,8 @@ import * as path from 'path'; import { AnalyzeScanRequest } from '../../../main/scanLogic/scanRunners/analyzerModels'; import { SecretsRunner, SecretsScanResponse } from '../../../main/scanLogic/scanRunners/secretsScan'; +import { Module } from '../../../main/types/jfrogAppsConfig'; +import { ScanResults } from '../../../main/types/workspaceIssuesDetails'; import { AnalyzerManagerIntegrationEnv, assertFileIssuesExist, @@ -14,7 +16,8 @@ import { assertIssuesRuleNameExist, assertIssuesSeverityExist } from '../utils/testIntegration.test'; -import { Module } from '../../../main/types/jfrogAppsConfig'; +import { createRootTestNode } from '../utils/treeNodeUtils.test'; +import { createTestStepProgress } from '../utils/utils.test'; describe('Secrets Scan Integration Tests', async () => { const integrationManager: AnalyzerManagerIntegrationEnv = new AnalyzerManagerIntegrationEnv(); @@ -27,7 +30,15 @@ describe('Secrets Scan Integration Tests', async () => { before(async function() { // Integration initialization await integrationManager.initialize(); - runner = new SecretsRunner(integrationManager.connectionManager, integrationManager.logManager, {} as Module, integrationManager.resource); + runner = new SecretsRunner( + {} as ScanResults, + createRootTestNode(''), + createTestStepProgress(), + integrationManager.connectionManager, + integrationManager.logManager, + {} as Module, + integrationManager.resource + ); runner.verbose = true; assert.isTrue(runner.validateSupported(), "Can't find runner binary file in path: " + runner.binary.fullPath); // Get expected partial result that the scan should contain @@ -37,7 +48,7 @@ describe('Secrets Scan Integration Tests', async () => { // Run scan // Try/Catch (with skip) should be removed after Secrets scan is released response = await runner - .run(() => undefined, { roots: [testDataRoot] } as AnalyzeScanRequest) + .executeRequest(() => undefined, { roots: [testDataRoot] } as AnalyzeScanRequest) .then(runResult => runner.convertResponse(runResult)); }); diff --git a/src/test/tests/maven.test.ts b/src/test/tests/maven.test.ts index 879f7352..cbd2b8cd 100644 --- a/src/test/tests/maven.test.ts +++ b/src/test/tests/maven.test.ts @@ -320,31 +320,40 @@ describe('Maven Tests', async () => { function expectedBuildPrototypePomTree(): PomTree[][] { return [ - [new PomTree('org.jfrog.test:multi2:3.7-SNAPSHOT', path.join(__dirname, '..', 'resources', 'maven', 'treeTestsProjects', 'dependency', 'pom.xml'))], [ - new PomTree('org.jfrog.test:multi:3.7-SNAPSHOT', path.join(__dirname, '..', 'resources', 'maven', 'treeTestsProjects', 'multiPomDependency', 'pom.xml'), [ - new PomTree( - 'org.jfrog.test:multi1:3.7-SNAPSHOT', - path.join(__dirname, '..', 'resources', 'maven', 'treeTestsProjects', 'multiPomDependency', 'multi1', 'pom.xml'), - [], - undefined, - 'org.jfrog.test:multi:3.7-SNAPSHOT' - ), - new PomTree( - 'org.jfrog.test:multi2:3.7-SNAPSHOT', - path.join(__dirname, '..', 'resources', 'maven', 'treeTestsProjects', 'multiPomDependency', 'multi2', 'pom.xml'), - [], - undefined, - 'org.jfrog.test:multi:3.7-SNAPSHOT' - ), - new PomTree( - 'org.jfrog.test:multi3:3.7-SNAPSHOT', - path.join(__dirname, '..', 'resources', 'maven', 'treeTestsProjects', 'multiPomDependency', 'multi3', 'pom.xml'), - [], - undefined, - 'org.jfrog.test:multi:3.7-SNAPSHOT' - ) - ]) + new PomTree( + 'org.jfrog.test:multi2:3.7-SNAPSHOT', + path.join(__dirname, '..', 'resources', 'maven', 'treeTestsProjects', 'dependency', 'pom.xml') + ) + ], + [ + new PomTree( + 'org.jfrog.test:multi:3.7-SNAPSHOT', + path.join(__dirname, '..', 'resources', 'maven', 'treeTestsProjects', 'multiPomDependency', 'pom.xml'), + [ + new PomTree( + 'org.jfrog.test:multi1:3.7-SNAPSHOT', + path.join(__dirname, '..', 'resources', 'maven', 'treeTestsProjects', 'multiPomDependency', 'multi1', 'pom.xml'), + [], + undefined, + 'org.jfrog.test:multi:3.7-SNAPSHOT' + ), + new PomTree( + 'org.jfrog.test:multi2:3.7-SNAPSHOT', + path.join(__dirname, '..', 'resources', 'maven', 'treeTestsProjects', 'multiPomDependency', 'multi2', 'pom.xml'), + [], + undefined, + 'org.jfrog.test:multi:3.7-SNAPSHOT' + ), + new PomTree( + 'org.jfrog.test:multi3:3.7-SNAPSHOT', + path.join(__dirname, '..', 'resources', 'maven', 'treeTestsProjects', 'multiPomDependency', 'multi3', 'pom.xml'), + [], + undefined, + 'org.jfrog.test:multi:3.7-SNAPSHOT' + ) + ] + ) ] ]; } diff --git a/src/test/tests/mavenUpdate.test.ts b/src/test/tests/mavenUpdate.test.ts index ed344edb..05e49ff8 100644 --- a/src/test/tests/mavenUpdate.test.ts +++ b/src/test/tests/mavenUpdate.test.ts @@ -22,76 +22,80 @@ import { ScanManager } from '../../main/scanLogic/scanManager'; import { TreesManager } from '../../main/treeDataProviders/treesManager'; import { createScanCacheManager } from './utils/utils.test'; +describe('Maven - Update to fixed version', async () => { + let logManager: LogManager = new LogManager().activate(); + let dummyScanCacheManager: ScanCacheManager = createScanCacheManager(); + let treesManager: TreesManager = new TreesManager( + [], + new ConnectionManager(logManager), + dummyScanCacheManager, + {} as ScanManager, + {} as CacheManager, + logManager + ); + const expectedVersion: string = '3.0.16'; + + before(function() { + // Install maven dependencies + // exec.execSync('mvn clean install', { cwd: path.join(__dirname, '..', 'resources', 'maven','updateToFixVersionProjects', 'updateParentPomProperty') }); + // MavenUtils.installMavenGavReader(); + }); + it('Without version property', async () => { + const [mavenDependencyUpdate, issueToUpdate, pomXmlPath] = await setupTestEnvironment('updatePom'); + + // Operate the test + mavenDependencyUpdate.update(issueToUpdate, expectedVersion); + + // Check results + const fileContent: string = fs.readFileSync(pomXmlPath, 'utf-8'); + assert.include(fileContent, `${expectedVersion}`); + assert.isFalse( + fs.existsSync(path.join(__dirname, '..', 'resources', 'maven', 'updateToFixVersionProjects', 'updatePom', 'pom.xml.versionsBackup')) + ); + }); + + it('With property', async () => { + const [mavenDependencyUpdate, issueToUpdate, pomXmlPath] = await setupTestEnvironment('updatePomProperty'); + + // Operate the test + mavenDependencyUpdate.update(issueToUpdate, expectedVersion); + + // Check results + const fileContent: string = fs.readFileSync(pomXmlPath, 'utf-8'); + assert.include(fileContent, `${expectedVersion}`); + assert.isFalse( + fs.existsSync( + path.join(__dirname, '..', 'resources', 'maven', 'updateToFixVersionProjects', 'updatePomProperty', 'pom.xml.versionsBackup') + ) + ); + }); - describe('Maven - Update to fixed version', async () => { - - let logManager: LogManager = new LogManager().activate(); - let dummyScanCacheManager: ScanCacheManager = createScanCacheManager(); - let treesManager: TreesManager = new TreesManager( - [], - new ConnectionManager(logManager), - dummyScanCacheManager, - {} as ScanManager, - {} as CacheManager, - logManager + it('Multi module with property', async () => { + const [mavenDependencyUpdate, issueToUpdate, pomXmlPath] = await setupMultiModuleTestEnvironment( + path.join('updateParentPomProperty', 'multi1'), + 'updateParentPomProperty' ); - const expectedVersion: string = '3.0.16'; - - before(function() { - // Install maven dependencies - // exec.execSync('mvn clean install', { cwd: path.join(__dirname, '..', 'resources', 'maven','updateToFixVersionProjects', 'updateParentPomProperty') }); - // MavenUtils.installMavenGavReader(); - }); - it('Without version property', async () => { - const [mavenDependencyUpdate, issueToUpdate, pomXmlPath] = await setupTestEnvironment('updatePom'); - - // Operate the test - mavenDependencyUpdate.update(issueToUpdate, expectedVersion); - - // Check results - const fileContent: string = fs.readFileSync(pomXmlPath, 'utf-8'); - assert.include(fileContent, `${expectedVersion}`); - assert.isFalse(fs.existsSync(path.join(__dirname, '..', 'resources', 'maven','updateToFixVersionProjects', 'updatePom', 'pom.xml.versionsBackup'))); - }); - - it('With property', async () => { - const [mavenDependencyUpdate, issueToUpdate, pomXmlPath] = await setupTestEnvironment('updatePomProperty'); - - // Operate the test - mavenDependencyUpdate.update(issueToUpdate, expectedVersion); - - // Check results - const fileContent: string = fs.readFileSync(pomXmlPath, 'utf-8'); - assert.include(fileContent, `${expectedVersion}`); - assert.isFalse( - fs.existsSync(path.join(__dirname, '..', 'resources', 'maven','updateToFixVersionProjects', 'updatePomProperty', 'pom.xml.versionsBackup')) - ); - }); - - it('Multi module with property', async () => { - const [mavenDependencyUpdate, issueToUpdate, pomXmlPath] = await setupMultiModuleTestEnvironment( - path.join('updateParentPomProperty', 'multi1'), - 'updateParentPomProperty' - ); - - // Operate the test - mavenDependencyUpdate.update(issueToUpdate, expectedVersion); - - // Check results - const fileContent: string = fs.readFileSync(pomXmlPath, 'utf-8'); - assert.include(fileContent, `${expectedVersion}`); - assert.isFalse( - fs.existsSync(path.join(__dirname, '..', 'resources', 'maven','updateToFixVersionProjects', 'updateParentPomProperty', 'pom.xml.versionsBackup')) - ); - }); + + // Operate the test + mavenDependencyUpdate.update(issueToUpdate, expectedVersion); + + // Check results + const fileContent: string = fs.readFileSync(pomXmlPath, 'utf-8'); + assert.include(fileContent, `${expectedVersion}`); + assert.isFalse( + fs.existsSync( + path.join(__dirname, '..', 'resources', 'maven', 'updateToFixVersionProjects', 'updateParentPomProperty', 'pom.xml.versionsBackup') + ) + ); + }); async function setupTestEnvironment(projectDir: string): Promise<[MavenDependencyUpdate, DependencyIssuesTreeNode, string]> { const mavenDependencyUpdate: MavenDependencyUpdate = new MavenDependencyUpdate(); - const pomXmlPath: string = path.join(__dirname, '..', 'resources', 'maven','updateToFixVersionProjects', projectDir, 'pom.xml'); + const pomXmlPath: string = path.join(__dirname, '..', 'resources', 'maven', 'updateToFixVersionProjects', projectDir, 'pom.xml'); const localWorkspaceFolders: vscode.WorkspaceFolder[] = [ { - uri: vscode.Uri.file(path.join(__dirname, '..', 'resources', 'maven','updateToFixVersionProjects', projectDir)), + uri: vscode.Uri.file(path.join(__dirname, '..', 'resources', 'maven', 'updateToFixVersionProjects', projectDir)), name: '', index: 0 } as vscode.WorkspaceFolder @@ -117,12 +121,12 @@ import { createScanCacheManager } from './utils/utils.test'; parentPomDir: string ): Promise<[MavenDependencyUpdate, DependencyIssuesTreeNode, string]> { const mavenDependencyUpdate: MavenDependencyUpdate = new MavenDependencyUpdate(); - const pomXmlPath: string = path.join(__dirname, '..', 'resources', 'maven','updateToFixVersionProjects', pomDir, 'pom.xml'); - const parentPomXmlPath: string = path.join(__dirname, '..', 'resources', 'maven','updateToFixVersionProjects', parentPomDir, 'pom.xml'); + const pomXmlPath: string = path.join(__dirname, '..', 'resources', 'maven', 'updateToFixVersionProjects', pomDir, 'pom.xml'); + const parentPomXmlPath: string = path.join(__dirname, '..', 'resources', 'maven', 'updateToFixVersionProjects', parentPomDir, 'pom.xml'); const localWorkspaceFolders: vscode.WorkspaceFolder[] = [ { - uri: vscode.Uri.file(path.join(__dirname, '..', 'resources', 'maven','updateToFixVersionProjects', parentPomDir)), + uri: vscode.Uri.file(path.join(__dirname, '..', 'resources', 'maven', 'updateToFixVersionProjects', parentPomDir)), name: '', index: 0 } as vscode.WorkspaceFolder @@ -149,7 +153,6 @@ import { createScanCacheManager } from './utils/utils.test'; VulnerablePom ); return [mavenDependencyUpdate, issueToUpdate, parentPomXmlPath]; - } async function locatePomXmls(workspaceFolders: vscode.WorkspaceFolder[]): Promise { let packageDescriptors: Map = await ScanUtils.locatePackageDescriptors(workspaceFolders, treesManager.logManager); diff --git a/src/test/tests/sastScan.test.ts b/src/test/tests/sastScan.test.ts index fce51689..1a16771a 100644 --- a/src/test/tests/sastScan.test.ts +++ b/src/test/tests/sastScan.test.ts @@ -4,12 +4,13 @@ import { assert } from 'chai'; import { ConnectionManager } from '../../main/connect/connectionManager'; import { LogManager } from '../../main/log/logManager'; import { FileRegion } from '../../main/scanLogic/scanRunners/analyzerModels'; -import { SastScanResponse, SastRunner } from '../../main/scanLogic/scanRunners/sastScan'; +import { SastRunner, SastScanResponse } from '../../main/scanLogic/scanRunners/sastScan'; import { CodeFileTreeNode } from '../../main/treeDataProviders/issuesTree/codeFileTree/codeFileTreeNode'; import { CodeIssueTreeNode } from '../../main/treeDataProviders/issuesTree/codeFileTree/codeIssueTreeNode'; import { SastTreeNode } from '../../main/treeDataProviders/issuesTree/codeFileTree/sastTreeNode'; import { IssuesRootTreeNode } from '../../main/treeDataProviders/issuesTree/issuesRootTreeNode'; import { AnalyzerUtils, FileWithSecurityIssues } from '../../main/treeDataProviders/utils/analyzerUtils'; +import { Module } from '../../main/types/jfrogAppsConfig'; import { ScanResults } from '../../main/types/workspaceIssuesDetails'; import { assertFileNodesCreated, @@ -24,8 +25,7 @@ import { groupFiles } from './utils/testAnalyzer.test'; import { createRootTestNode } from './utils/treeNodeUtils.test'; -import { getAnalyzerScanResponse, getEmptyAnalyzerScanResponse } from './utils/utils.test'; -import { Module } from '../../main/types/jfrogAppsConfig'; +import { createTestStepProgress, getAnalyzerScanResponse, getEmptyAnalyzerScanResponse } from './utils/utils.test'; describe('Sast Tests', () => { const scanSast: string = path.join(__dirname, '..', 'resources', 'sastScan'); @@ -121,6 +121,6 @@ describe('Sast Tests', () => { }); function getDummyRunner(): SastRunner { - return new SastRunner({} as ConnectionManager, logManager, {} as Module); + return new SastRunner({} as ScanResults, createRootTestNode(''), createTestStepProgress(), {} as ConnectionManager, logManager, {} as Module); } }); diff --git a/src/test/tests/scanAnlayzerRunner.test.ts b/src/test/tests/scanAnlayzerRunner.test.ts index e526b6ba..06746b01 100644 --- a/src/test/tests/scanAnlayzerRunner.test.ts +++ b/src/test/tests/scanAnlayzerRunner.test.ts @@ -1,16 +1,16 @@ -import * as path from 'path'; -import * as fs from 'fs'; import { assert } from 'chai'; +import * as fs from 'fs'; import { describe } from 'mocha'; +import * as path from 'path'; import { ConnectionManager } from '../../main/connect/connectionManager'; import { LogManager } from '../../main/log/logManager'; -import { AnalyzerScanResponse, ScanType, AnalyzeScanRequest } from '../../main/scanLogic/scanRunners/analyzerModels'; -import { JasScanner } from '../../main/scanLogic/scanRunners/binaryRunner'; -import { NotEntitledError, ScanCancellationError, ScanTimeoutError, ScanUtils } from '../../main/utils/scanUtils'; +import { AnalyzeScanRequest, AnalyzerScanRun, ScanType } from '../../main/scanLogic/scanRunners/analyzerModels'; +import { JasRunner } from '../../main/scanLogic/scanRunners/jasRunner'; +import { Module } from '../../main/types/jfrogAppsConfig'; import { RunUtils } from '../../main/utils/runUtils'; +import { NotEntitledError, ScanCancellationError, ScanTimeoutError, ScanUtils } from '../../main/utils/scanUtils'; import { Translators } from '../../main/utils/translators'; -import { Module } from '../../main/types/jfrogAppsConfig'; // binary runner describe('Analyzer BinaryRunner tests', async () => { @@ -42,8 +42,12 @@ describe('Analyzer BinaryRunner tests', async () => { connection: ConnectionManager = connectionManager, timeout: number = ScanUtils.ANALYZER_TIMEOUT_MILLISECS, dummyAction: () => Promise = () => Promise.resolve() - ): JasScanner { - return new (class extends JasScanner { + ): JasRunner { + return new (class extends JasRunner { + public scan(): Promise { + throw new Error('Method not implemented.'); + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars async runBinary( // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -111,7 +115,7 @@ describe('Analyzer BinaryRunner tests', async () => { ].forEach(test => { it('Create environment variables for execution - ' + test.name, () => { // - let runner: JasScanner = createDummyBinaryRunner(createBinaryRunnerConnectionManager(test.url, test.user, test.pass, test.token)); + let runner: JasRunner = createDummyBinaryRunner(createBinaryRunnerConnectionManager(test.url, test.user, test.pass, test.token)); process.env['HTTP_PROXY'] = test.proxy; process.env['HTTPS_PROXY'] = test.proxy; @@ -121,17 +125,17 @@ describe('Analyzer BinaryRunner tests', async () => { } else { assert.isDefined(envVars); // Validate platform vars - assert.equal(envVars?.[JasScanner.ENV_PLATFORM_URL] ?? '', test.url); - assert.equal(envVars?.[JasScanner.ENV_USER] ?? '', test.user); - assert.equal(envVars?.[JasScanner.ENV_PASSWORD] ?? '', test.pass); - assert.equal(envVars?.[JasScanner.ENV_TOKEN] ?? '', test.token); + assert.equal(envVars?.[JasRunner.ENV_PLATFORM_URL] ?? '', test.url); + assert.equal(envVars?.[JasRunner.ENV_USER] ?? '', test.user); + assert.equal(envVars?.[JasRunner.ENV_PASSWORD] ?? '', test.pass); + assert.equal(envVars?.[JasRunner.ENV_TOKEN] ?? '', test.token); // Validate proxy vars if (test.proxy) { - assert.equal(envVars?.[JasScanner.ENV_HTTP_PROXY], test.proxy); - assert.equal(envVars?.[JasScanner.ENV_HTTPS_PROXY], test.proxy); + assert.equal(envVars?.[JasRunner.ENV_HTTP_PROXY], test.proxy); + assert.equal(envVars?.[JasRunner.ENV_HTTPS_PROXY], test.proxy); } // Validate log vars - assert.equal(envVars?.[JasScanner.ENV_LOG_DIR], test.logPath); + assert.equal(envVars?.[JasRunner.ENV_LOG_DIR], test.logPath); } }); }); @@ -210,7 +214,7 @@ describe('Analyzer BinaryRunner tests', async () => { let requestPath: string = path.join(tempFolder, 'request'); let responsePath: string = path.join(tempFolder, 'response'); - let runner: JasScanner = createDummyBinaryRunner(connectionManager, test.timeout, async () => { + let runner: JasRunner = createDummyBinaryRunner(connectionManager, test.timeout, async () => { if (test.shouldAbort) { throw new ScanCancellationError(); } else if (test.name === 'Not entitled') { @@ -219,13 +223,13 @@ describe('Analyzer BinaryRunner tests', async () => { await RunUtils.delay(ScanUtils.ANALYZER_TIMEOUT_MILLISECS); } if (test.createDummyResponse) { - fs.writeFileSync(responsePath, JSON.stringify({ runs: [] } as AnalyzerScanResponse)); + fs.writeFileSync(responsePath, JSON.stringify({} as AnalyzerScanRun)); } }); if (test.expectedErr) { try { - await runner.runRequest(() => undefined, 'request data', requestPath, ScanType.ContextualAnalysis, responsePath); + await runner.runRequest(() => undefined, 'request data', requestPath, responsePath); assert.fail('Expected run to throw error'); } catch (err) { if (err instanceof Error) { @@ -235,14 +239,12 @@ describe('Analyzer BinaryRunner tests', async () => { } } } else { - assert.doesNotThrow( - async () => await runner.runRequest(() => undefined, 'request data', requestPath, ScanType.ContextualAnalysis, responsePath) - ); + assert.doesNotThrow(async () => await runner.runRequest(() => undefined, 'request data', requestPath, responsePath)); } }); }); }); class DummyRunnerError extends Error { - public code: number = JasScanner.NOT_ENTITLED; + public code: number = JasRunner.NOT_ENTITLED; } diff --git a/src/test/tests/secretsScan.test.ts b/src/test/tests/secretsScan.test.ts index b678cf88..8073dea2 100644 --- a/src/test/tests/secretsScan.test.ts +++ b/src/test/tests/secretsScan.test.ts @@ -3,29 +3,29 @@ import * as path from 'path'; import { assert } from 'chai'; import { ConnectionManager } from '../../main/connect/connectionManager'; import { LogManager } from '../../main/log/logManager'; -import { IssuesRootTreeNode } from '../../main/treeDataProviders/issuesTree/issuesRootTreeNode'; -import { createRootTestNode } from './utils/treeNodeUtils.test'; -import { ScanResults } from '../../main/types/workspaceIssuesDetails'; -import { AnalyzerUtils, FileWithSecurityIssues } from '../../main/treeDataProviders/utils/analyzerUtils'; -import { getAnalyzerScanResponse, getEmptyAnalyzerScanResponse } from './utils/utils.test'; import { FileRegion } from '../../main/scanLogic/scanRunners/analyzerModels'; -import { CodeIssueTreeNode } from '../../main/treeDataProviders/issuesTree/codeFileTree/codeIssueTreeNode'; -import { CodeFileTreeNode } from '../../main/treeDataProviders/issuesTree/codeFileTree/codeFileTreeNode'; import { SecretsRunner, SecretsScanResponse } from '../../main/scanLogic/scanRunners/secretsScan'; +import { CodeFileTreeNode } from '../../main/treeDataProviders/issuesTree/codeFileTree/codeFileTreeNode'; +import { CodeIssueTreeNode } from '../../main/treeDataProviders/issuesTree/codeFileTree/codeIssueTreeNode'; import { SecretTreeNode } from '../../main/treeDataProviders/issuesTree/codeFileTree/secretsTreeNode'; +import { IssuesRootTreeNode } from '../../main/treeDataProviders/issuesTree/issuesRootTreeNode'; +import { AnalyzerUtils, FileWithSecurityIssues } from '../../main/treeDataProviders/utils/analyzerUtils'; +import { Module } from '../../main/types/jfrogAppsConfig'; +import { ScanResults } from '../../main/types/workspaceIssuesDetails'; import { assertFileNodesCreated, assertIssueNodesCreated, assertIssuesFullDescription, + assertIssuesSnippet, assertNodeLabelRuleName, assertNodesSeverity, - assertIssuesSnippet, assertSameNumberOfFileNodes, assertSameNumberOfIssueNodes, findLocationNode, groupFiles } from './utils/testAnalyzer.test'; -import { Module } from '../../main/types/jfrogAppsConfig'; +import { createRootTestNode } from './utils/treeNodeUtils.test'; +import { createTestStepProgress, getAnalyzerScanResponse, getEmptyAnalyzerScanResponse } from './utils/utils.test'; describe('Secrets Scan Tests', () => { const scanSecrets: string = path.join(__dirname, '..', 'resources', 'secretsScan'); @@ -121,6 +121,13 @@ describe('Secrets Scan Tests', () => { }); function getDummyRunner(): SecretsRunner { - return new SecretsRunner({} as ConnectionManager, logManager, {} as Module); + return new SecretsRunner( + {} as ScanResults, + createRootTestNode(''), + createTestStepProgress(), + {} as ConnectionManager, + logManager, + {} as Module + ); } }); diff --git a/src/test/tests/usageUtils.test.ts b/src/test/tests/usageUtils.test.ts index a02a9793..f4c6ba43 100644 --- a/src/test/tests/usageUtils.test.ts +++ b/src/test/tests/usageUtils.test.ts @@ -31,6 +31,7 @@ describe('Usage Utils Tests', async () => { descriptors: getDummyDescriptors(PackageType.Go, PackageType.Npm), expectedFeatures: [ { featureId: 'go-deps' }, + { featureId: 'go-contextual' }, { featureId: 'npm-deps' }, { featureId: 'npm-contextual' }, { featureId: 'iac' }, diff --git a/src/test/tests/utils/testIntegration.test.ts b/src/test/tests/utils/testIntegration.test.ts index 359a6556..11f37fc7 100644 --- a/src/test/tests/utils/testIntegration.test.ts +++ b/src/test/tests/utils/testIntegration.test.ts @@ -6,7 +6,7 @@ import { LogManager } from '../../../main/log/logManager'; import { ScanUtils } from '../../../main/utils/scanUtils'; import { createTestConnectionManager } from './utils.test'; import { Resource } from '../../../main/utils/resource'; -import { JasScanner } from '../../../main/scanLogic/scanRunners/binaryRunner'; +import { JasRunner } from '../../../main/scanLogic/scanRunners/jasRunner'; import { AnalyzerUtils, FileWithSecurityIssues, SecurityIssue } from '../../../main/treeDataProviders/utils/analyzerUtils'; import { FileRegion } from '../../../main/scanLogic/scanRunners/analyzerModels'; @@ -81,14 +81,14 @@ export class AnalyzerManagerIntegrationEnv extends BaseIntegrationEnv { // Download from a different place in releases this._resource = new Resource( process.env[AnalyzerManagerIntegrationEnv.ENV_BINARY_DOWNLOAD_URL], - JasScanner.getDefaultAnalyzerManagerTargetPath(BaseIntegrationEnv.directory), + JasRunner.getDefaultAnalyzerManagerTargetPath(BaseIntegrationEnv.directory), this.logManager ); } else { // Run on latest from Releases - this._resource = JasScanner.getAnalyzerManagerResource( + this._resource = JasRunner.getAnalyzerManagerResource( this.logManager, - JasScanner.getDefaultAnalyzerManagerTargetPath(this._localPath ?? BaseIntegrationEnv.directory) + JasRunner.getDefaultAnalyzerManagerTargetPath(this._localPath ?? BaseIntegrationEnv.directory) ); } } diff --git a/src/test/tests/utils/utils.test.ts b/src/test/tests/utils/utils.test.ts index a8b9a709..7eeb2027 100644 --- a/src/test/tests/utils/utils.test.ts +++ b/src/test/tests/utils/utils.test.ts @@ -1,16 +1,17 @@ +import * as fs from 'fs'; +import { JfrogClient } from 'jfrog-client-js'; import * as os from 'os'; import * as tmp from 'tmp'; import * as vscode from 'vscode'; -import * as fs from 'fs'; -import { ConnectionManager } from '../../../main/connect/connectionManager'; import { ScanCacheManager } from '../../../main/cache/scanCacheManager'; -import { DependenciesTreeNode } from '../../../main/treeDataProviders/dependenciesTree/dependenciesTreeNode'; -import { TestMemento } from './testMemento.test'; -import { LogManager } from '../../../main/log/logManager'; -import { ContextKeys, SessionStatus } from '../../../main/constants/contextKeys'; +import { ConnectionManager } from '../../../main/connect/connectionManager'; import { ConnectionUtils } from '../../../main/connect/connectionUtils'; -import { JfrogClient } from 'jfrog-client-js'; +import { ContextKeys, SessionStatus } from '../../../main/constants/contextKeys'; +import { LogManager } from '../../../main/log/logManager'; import { AnalyzerDriver, AnalyzerScanResponse, AnalyzerScanRun } from '../../../main/scanLogic/scanRunners/analyzerModels'; +import { DependenciesTreeNode } from '../../../main/treeDataProviders/dependenciesTree/dependenciesTreeNode'; +import { StepProgress } from '../../../main/treeDataProviders/utils/stepProgress'; +import { TestMemento } from './testMemento.test'; export function isWindows(): boolean { return os.platform() === 'win32'; @@ -72,6 +73,11 @@ export async function createTestConnectionManager(logManager: LogManager, timeou } as vscode.ExtensionContext); } +export function createTestStepProgress(): StepProgress { + // eslint-disable-next-line @typescript-eslint/no-empty-function + return new StepProgress({} as vscode.Progress<{ message?: string; increment?: number }>, () => {}); +} + export class ConnectionManagerWrapper extends ConnectionManager { constructor(logManager: LogManager, private _timeout?: number, private _retry?: number) { super(logManager);