From 33e8e1aa8eda9119d2d0f56aeb59e0b6aaffb336 Mon Sep 17 00:00:00 2001 From: Chanan Merari Date: Tue, 3 Sep 2024 17:03:06 +0300 Subject: [PATCH] [PCSUP-24582] VisualStudioCode - single file exception error. --- .vscodeignore | 2 +- CHANGELOG.md | 9 +- package-lock.json | 45 ++++++---- package.json | 10 +-- src/commands/checkov/install.ts | 2 +- src/extension.ts | 16 ++-- src/services/checkov/executor.ts | 21 +++-- .../checkov/executors/DockerExecutor.ts | 30 +++++-- .../checkov/executors/abstractExecutor.ts | 72 +++++++++------ src/services/filesService.ts | 14 +-- src/services/resultsService.ts | 12 +-- src/utils/fileUtils.ts | 20 +++++ .../checkovResult/messages/focusString.ts | 7 +- .../interface/checkovResult/webviewPanel.ts | 19 ++-- .../dataProviders/iacTreeDataProvider.ts | 11 --- .../dataProviders/licensesTreeDataProvider.ts | 11 --- ...aProvider.ts => resultTreeDataProvider.ts} | 72 +++++++-------- .../dataProviders/secretsTreeDataProvider.ts | 11 --- .../vulnerabilitiesTreeDataProvider.ts | 11 --- .../weaknessesTreeDataProvider.ts | 11 --- src/views/interface/primarySidebar/index.ts | 72 +++------------ .../primarySidebar/services/iconsService.ts | 2 +- .../services/treeDataProvidersContainer.ts | 87 ++++++++++--------- .../primarySidebar/services/treeService.ts | 26 +++--- static/webviews/result/index.html | 6 +- 25 files changed, 279 insertions(+), 320 deletions(-) create mode 100644 src/utils/fileUtils.ts delete mode 100644 src/views/interface/primarySidebar/dataProviders/iacTreeDataProvider.ts delete mode 100644 src/views/interface/primarySidebar/dataProviders/licensesTreeDataProvider.ts rename src/views/interface/primarySidebar/dataProviders/{abstractTreeDataProvider.ts => resultTreeDataProvider.ts} (57%) delete mode 100644 src/views/interface/primarySidebar/dataProviders/secretsTreeDataProvider.ts delete mode 100644 src/views/interface/primarySidebar/dataProviders/vulnerabilitiesTreeDataProvider.ts delete mode 100644 src/views/interface/primarySidebar/dataProviders/weaknessesTreeDataProvider.ts diff --git a/.vscodeignore b/.vscodeignore index 3899967..545bc39 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -7,4 +7,4 @@ vsc-extension-quickstart.md **/tsconfig.json **/.eslintrc.json **/*.map -**/*.ts +**/*.ts \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index fdc6bea..96557b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,17 @@ # Change Log +## [1.0.20] - 2024-09-05 + +### Fixed + +- Fixed an issue where standalone files couldn't be scanned +- Fixed an issue where opening issues from the Prisma Cloud side panel didn't work + ## [1.0.19] - 2024-08-29 ### Added -- Added the following data to Prisma Cloid analytics +- Added the following data to Prisma Cloud analytics - Extension version - VS Code version diff --git a/package-lock.json b/package-lock.json index 0206864..cde1dea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,14 @@ { "name": "prisma-cloud", - "version": "1.0.13", + "version": "1.0.20", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prisma-cloud", - "version": "1.0.13", + "version": "1.0.20", "dependencies": { - "axios": "^1.5.1", + "axios": "^1.7.4", "semver": "^7.5.2", "uuid": "^9.0.1", "winston": "^3.13.0" @@ -573,11 +573,11 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { - "version": "1.5.1", - "resolved": "https://art.code.pan.run:443/artifactory/api/npm/npm-registry/axios/-/axios-1.5.1.tgz", - "integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", "dependencies": { - "follow-redirects": "^1.15.0", + "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } @@ -1231,11 +1231,22 @@ "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://art.code.pan.run:443/artifactory/api/npm/npm-registry/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.8.tgz", + "integrity": "sha512-xgrmBhBToVKay1q2Tao5LI26B83UhrB/vM1avwVSDzt8rx3rO6AizBAaF46EgksTVr+rFTQaqZZ9MVBfUe4nig==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], "engines": { "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } } }, "node_modules/form-data": { @@ -3017,11 +3028,11 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "axios": { - "version": "1.5.1", - "resolved": "https://art.code.pan.run:443/artifactory/api/npm/npm-registry/axios/-/axios-1.5.1.tgz", - "integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", "requires": { - "follow-redirects": "^1.15.0", + "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } @@ -3533,9 +3544,9 @@ "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, "follow-redirects": { - "version": "1.15.3", - "resolved": "https://art.code.pan.run:443/artifactory/api/npm/npm-registry/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==" + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.8.tgz", + "integrity": "sha512-xgrmBhBToVKay1q2Tao5LI26B83UhrB/vM1avwVSDzt8rx3rO6AizBAaF46EgksTVr+rFTQaqZZ9MVBfUe4nig==" }, "form-data": { "version": "4.0.0", diff --git a/package.json b/package.json index 0e73c65..f18645d 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "repository": "https://github.com/bridgecrewio/prisma-cloud-vscode-plugin", "icon": "static/icons/prisma.png", "description": "a static code analysis tool to scan code for Infrastructure-as-Code (IaC) misconfigurations, Software Composition Analysis (SCA) issues and Secrets vulnerabilities.", - "version": "1.0.19", + "version": "1.0.20", "engines": { "vscode": "^1.79.0" }, @@ -14,13 +14,9 @@ "Other" ], "activationEvents": [ - "*" + "onStartupFinished" ], "main": "./out/extension.js", - "files": [ - "out", - "static" - ], "contributes": { "viewsContainers": { "activitybar": [ @@ -272,7 +268,7 @@ "typescript": "^5.1.6" }, "dependencies": { - "axios": "^1.5.1", + "axios": "^1.7.4", "semver": "^7.5.2", "uuid": "^9.0.1", "winston": "^3.13.0" diff --git a/src/commands/checkov/install.ts b/src/commands/checkov/install.ts index b62bede..597e51b 100644 --- a/src/commands/checkov/install.ts +++ b/src/commands/checkov/install.ts @@ -178,4 +178,4 @@ export class CheckovInstall { return `"${join(dirname(envPath), 'checkov')}"`; } } -}; +} diff --git a/src/extension.ts b/src/extension.ts index d542c18..6132f9c 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,21 +1,23 @@ import * as vscode from 'vscode'; import { registerCommands } from './commands'; +import { CONFIG } from './config'; import { COMMAND } from './constants'; import { registerWindowEvents, registerWorkspaceEvents } from './events'; +import logger, { initiateLogger } from './logger'; import { initializeServices } from './services'; -import { registerSidebar } from './views/interface/primarySidebar'; -import { registerCheckovResultView } from './views/interface/checkovResult'; -import { registerCustomHighlight, lineClickDisposable } from './services/customPopupService'; -import { initializeInstallationId } from './utils'; -import { initiateLogger } from './logger'; import { initializeAnalyticsService } from './services/analyticsService'; -import { CustomersModulesService, initializeCustomersModulesService } from './services/customersModulesService'; import { initializeAuthenticationService } from './services/authenticationService'; +import { CustomersModulesService, initializeCustomersModulesService } from './services/customersModulesService'; +import { lineClickDisposable, registerCustomHighlight } from './services/customPopupService'; +import { initializeInstallationId } from './utils'; +import { registerCheckovResultView } from './views/interface/checkovResult'; +import { registerSidebar } from './views/interface/primarySidebar'; export async function activate(context: vscode.ExtensionContext) { initiateLogger(context.logUri.fsPath); - + logger.info(`Initiating Prisma Cloud VS Code extension version ${vscode.extensions.getExtension(CONFIG.extensionId)?.packageJSON.version}`); + logger.info(`Plugin path: ${context.extensionPath}`); initializeInstallationId(context); CustomersModulesService.loadCachedData(context); await initializeAuthenticationService(context); diff --git a/src/services/checkov/executor.ts b/src/services/checkov/executor.ts index eaac5ed..b014545 100644 --- a/src/services/checkov/executor.ts +++ b/src/services/checkov/executor.ts @@ -30,7 +30,7 @@ export class CheckovExecutor { const executor = CheckovExecutor.executors.get(installation.type); executor ? CheckovExecutor.actualCheckovVersion = await CheckovExecutor.executors.get(installation.type)?.getCheckovVersion(installation) : - logger.error(`No executor found for ${installation.type}, can't determine Checkov version`); + logger.error(`No executor found for ${installation?.type}, can't determine Checkov version`); } public static getExecutor() { @@ -53,7 +53,7 @@ export class CheckovExecutor { } if (!executor) { - logger.error(`No executor found for ${installation.type}, aborting scan operation`); + logger.error(`No executor found for ${installation?.type}, aborting scan operation`); return; } @@ -62,6 +62,11 @@ export class CheckovExecutor { return; } + if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 1 && !targetFiles) { + vscode.window.showWarningMessage('Full scan is only supported when working with a single VS Code workspace'); + return; + } + const emptyPrismaSettings = CheckovExecutor.getEmptyPrismaSettings(); if (!emptyPrismaSettings.length) { @@ -76,12 +81,12 @@ export class CheckovExecutor { try { checkovOutput = await executor.execute(installation, targetFiles); } catch (e: any) { - logger.info(`The Checkov execution was failed due to: ${e.message}`); + logger.info(`Checkov execution failed due to: ${e.message}`); AbstractExecutor.isScanInProgress = false; await reRenderViews(); StatusBar.reset(); if (!shouldDisableErrorMessage()) { - vscode.window.showErrorMessage(`Scanning is stopped due to: ${e.message}`); + vscode.window.showErrorMessage(`Scanning stopped due to: ${e.message}`); } return; } @@ -124,18 +129,16 @@ export class CheckovExecutor { private static processOutput(output: CheckovOutput) { if (Array.isArray(output)) { - const failedChecks = output.reduce((acc: CheckovResult[], checkType) => { + return output.reduce((acc: CheckovResult[], checkType) => { if (checkType) { for (const check of checkType.results.failed_checks) { check.check_type = checkType.check_type; check.id = uuidv4(); check.severity = check.severity || SEVERITY.INFO; - }; + } } return acc.concat(checkType?.results.failed_checks ?? []); }, []); - - return failedChecks; } // response from checkov with EmptyCheckovOutput type @@ -169,4 +172,4 @@ export class CheckovExecutor { public static get checkovVersion() { return CheckovExecutor.actualCheckovVersion; } -}; +} diff --git a/src/services/checkov/executors/DockerExecutor.ts b/src/services/checkov/executors/DockerExecutor.ts index c389ebb..cd7a37b 100644 --- a/src/services/checkov/executors/DockerExecutor.ts +++ b/src/services/checkov/executors/DockerExecutor.ts @@ -8,8 +8,10 @@ import { getCertificate, getPrismaApiUrl, getProxyConfigurations } from '../../. import logger from '../../../logger'; import { CheckovInstallation } from '../../../types'; import { asyncExec, isWindows } from '../../../utils'; +import { parseUri } from '../../../utils/fileUtils'; import { reRenderViews } from '../../../views/interface/utils'; import { AbstractExecutor } from './abstractExecutor'; +import * as path from 'path'; export class DockerExecutor extends AbstractExecutor { @@ -28,7 +30,7 @@ export class DockerExecutor extends AbstractExecutor { ...DockerExecutor.getDockerParams(), ...containerName, ...DockerExecutor.getEnvs(), - ...DockerExecutor.getVolumeMounts(), + ...DockerExecutor.getVolumeMounts(files), ...DockerExecutor.getWorkdir(), ...DockerExecutor.getImage(), ...(await DockerExecutor.getCheckovCliParams(installation, DockerExecutor.fixFilePaths(files))), @@ -90,12 +92,19 @@ export class DockerExecutor extends AbstractExecutor { return envs; } - private static getVolumeMounts() { - let volume = `${DockerExecutor.projectPath}:${DockerExecutor.projectPath}`; - const volumeMounts = [ - '--volume', volume - ]; - + private static getVolumeMounts(files?: string[]) { + const volumeMounts = []; + if (files) { + files.forEach(file => { + const dir = path.dirname(file); + volumeMounts.push('--volume', `"${dir}":"${dir}"`); + }); + } else if (vscode.workspace.workspaceFolders) { + const dir = parseUri(vscode.workspace.workspaceFolders[0].uri); + volumeMounts.push('--volume', `${dir}:${dir}`); + } else { + AbstractExecutor.projectPaths.forEach(path => volumeMounts.push('--volume', `${path}:${path}`)); + } const cert = getCertificate(); if (cert) { volumeMounts.push('--volume', `${cert}:${CONFIG.checkov.docker.certificateMountPath}`); @@ -105,7 +114,10 @@ export class DockerExecutor extends AbstractExecutor { } private static getWorkdir() { - return ['--workdir', DockerExecutor.projectPath!]; + if (vscode.workspace.workspaceFolders) { + return ['--workdir', parseUri(vscode.workspace.workspaceFolders[0].uri)]; + } + return []; } private static getImage() { @@ -128,5 +140,5 @@ export class DockerExecutor extends AbstractExecutor { const {stdout} = await asyncExec(`${installation.entrypoint} ${args.join(' ')}`); return stdout.trim(); } -}; +} diff --git a/src/services/checkov/executors/abstractExecutor.ts b/src/services/checkov/executors/abstractExecutor.ts index 196ed07..5f3ca21 100644 --- a/src/services/checkov/executors/abstractExecutor.ts +++ b/src/services/checkov/executors/abstractExecutor.ts @@ -2,35 +2,46 @@ import { ChildProcessWithoutNullStreams } from 'child_process'; import * as vscode from 'vscode'; +import { ShowSettings } from '../../../commands/checkov'; import { CONFIG } from '../../../config'; +import { getAccessKey, getCertificate, getExternalChecksDir, getFrameworks, getNoCertVerify, getSastMaxSizeLimit, getSecretKey, getToken, shouldUseEnforcmentRules } from '../../../config/configUtils'; import { CHECKOV_INSTALLATION_TYPE, REPO_ID } from '../../../constants'; -import { CheckovInstallation, CheckovOutput } from '../../../types'; -import { getDirSize, isPipInstall, isWindows } from '../../../utils'; -import { ShowSettings } from '../../../commands/checkov'; import logger from '../../../logger'; -import { getAccessKey, getCertificate, getExternalChecksDir, getFrameworks, getNoCertVerify, getSastMaxSizeLimit, getSecretKey, getToken, shouldUseEnforcmentRules } from '../../../config/configUtils'; - +import { CheckovInstallation, CheckovOutput } from '../../../types'; +import { getDirSize } from '../../../utils'; +import { getContainingFolderPath, parseUri } from '../../../utils/fileUtils'; export abstract class AbstractExecutor { public static isScanInProgress: boolean = false; - protected static get projectPath() { + /** + * There are 3 possible situations when scanning operation starts: + * 1. The IDE has standalone files opened that are not a part of any workspace + * 2. The IDE has an opened workspace + * 3. The IDE has an opened workspace and some standalone files outside of it that are also opened + * @returns An array of directories containing all possible locations of files that should be scanned. + */ + protected static get projectPaths(): string[] { + const uris: vscode.Uri[] = []; const workspaceFolders = vscode.workspace.workspaceFolders; - - if (!workspaceFolders) { - return null; + if (workspaceFolders) { + uris.push(workspaceFolders[0].uri); } - - if (isWindows()) { - if (isPipInstall()) { - return `"${workspaceFolders[0].uri.fsPath.replace(/\\/g, '/')}"`; - } - else { - return `"/${workspaceFolders[0].uri.path.replace(':', '')}"`; - } - } - - return `"${workspaceFolders[0].uri.path.replace(':', '')}"`; + vscode.window.tabGroups.all.forEach(tabGroup => + tabGroup.tabs.forEach(tab => { + if (tab.input instanceof vscode.TabInputText) { + const document: vscode.TabInputText = tab.input; + if (workspaceFolders) { + if (!workspaceFolders.some(folder => document.uri.fsPath.startsWith(folder.name))) { + uris.push(getContainingFolderPath(document.uri)); + } + } else { + uris.push(getContainingFolderPath(document.uri)); + } + } + }) + ); + return uris.map(uri => parseUri(uri)); } protected static async getCheckovCliParams(installation: CheckovInstallation, files?: string[]) { @@ -64,17 +75,28 @@ export abstract class AbstractExecutor { if (files) { files.forEach((file) => checkovCliParams.push('--file', `"${file}"`)); - } else { - checkovCliParams.push('--directory', AbstractExecutor.projectPath!); - + } else if (vscode.workspace.workspaceFolders) { + const directory = parseUri(vscode.workspace.workspaceFolders![0].uri); + checkovCliParams.push('--directory', directory); + const excludedPaths = AbstractExecutor.projectPaths.filter(path => !path.startsWith(directory)); + if (excludedPaths.length) { + logger.warn(`There are files opened from outside the workspace that won't be scanned in these directories: ${excludedPaths}`); + vscode.window.showWarningMessage('You have opened files from outside your workspace. Those files will not be scanned as part of a full scan'); + } const shouldSkipSast = await AbstractExecutor.shouldSkipSast(); - if (shouldSkipSast) { checkovCliParams.push('--skip-framework', 'sast'); vscode.window.showInformationMessage('SAST didn\'t run due to the size of the repository. Adjust this limit in the settings', 'Prisma Cloud Settings').then(() => { ShowSettings.execute(); }); } + } else { + // If there are no files and no workspace, scan all opened files in the editor + vscode.window.tabGroups.all.forEach(tabGroup => + tabGroup.tabs.forEach(tab => + tab.input instanceof vscode.TabInputText && checkovCliParams.push('--file', parseUri(tab.input.uri)) + ) + ); } const cert = getCertificate(); @@ -141,4 +163,4 @@ export abstract class AbstractExecutor { return true; } } -}; +} diff --git a/src/services/filesService.ts b/src/services/filesService.ts index 168048f..8eb9196 100644 --- a/src/services/filesService.ts +++ b/src/services/filesService.ts @@ -8,22 +8,12 @@ export class FilesService { } public static async openFile(file: string, line: number = 1) { - const workspaceFolders = vscode.workspace.workspaceFolders; - - if (!workspaceFolders) { - return; - } - if (line < 1) { line = 1; } - - const fileUri = vscode.Uri.joinPath(workspaceFolders[0].uri, file); - if (!vscode.window.activeTextEditor) { vscode.commands.executeCommand('workbench.action.previousEditor'); } - - return vscode.window.showTextDocument(fileUri, { selection: new vscode.Range(line - 1, 0, line - 1, 0) }); + return vscode.window.showTextDocument(vscode.Uri.file(file), { selection: new vscode.Range(line - 1, 0, line - 1, 0) }); } -}; +} diff --git a/src/services/resultsService.ts b/src/services/resultsService.ts index f10e26c..ae7e7e8 100644 --- a/src/services/resultsService.ts +++ b/src/services/resultsService.ts @@ -3,10 +3,10 @@ import * as vscode from 'vscode'; import { CONFIG } from '../config'; import { CHECKOV_RESULT_CATEGORY } from '../constants'; import { CheckovResult } from '../types'; +import { isPipInstall, isWindows } from '../utils'; import { TreeDataProvidersContainer } from '../views/interface/primarySidebar/services/treeDataProvidersContainer'; import { CategoriesService } from './categoriesService'; import { CustomPopupService } from './customPopupService'; -import { isPipInstall, isWindows } from '../utils'; type Filter = { filterName: keyof CheckovResult; @@ -70,17 +70,11 @@ export class ResultsService { public static getByFilePath(filePath: string) { const results = ResultsService.get(); - - if (vscode.workspace.workspaceFolders) { - filePath = filePath.replace(vscode.workspace.workspaceFolders[0].uri.path, ''); - } - return results.filter(result => { if (isWindows()) { return result.file_abs_path === `/${filePath}`; } - - return result.repo_file_path === filePath; + return result.file_abs_path === filePath; }); } @@ -155,4 +149,4 @@ export class ResultsService { TreeDataProvidersContainer.refresh(); CustomPopupService.highlightLines(); } -}; +} diff --git a/src/utils/fileUtils.ts b/src/utils/fileUtils.ts new file mode 100644 index 0000000..6202544 --- /dev/null +++ b/src/utils/fileUtils.ts @@ -0,0 +1,20 @@ +import * as path from 'path'; +import * as vscode from 'vscode'; +import { isPipInstall, isWindows } from '.'; + +export const getContainingFolderPath = (uri: vscode.Uri) => { + const filePath = uri.fsPath; + const folderPath = path.dirname(filePath); + return vscode.Uri.file(folderPath); +}; + +export const parseUri = (uri: vscode.Uri) => { + if (isWindows()) { + if (isPipInstall()) { + return `"${uri.fsPath.replace(/\\/g, '/')}"`; + } else { + return `"/${uri.path.replace(':', '')}"`; + } + } + return `"${uri.path.replace(':', '')}"`; +}; \ No newline at end of file diff --git a/src/views/interface/checkovResult/messages/focusString.ts b/src/views/interface/checkovResult/messages/focusString.ts index b29abfc..4fa0797 100644 --- a/src/views/interface/checkovResult/messages/focusString.ts +++ b/src/views/interface/checkovResult/messages/focusString.ts @@ -1,10 +1,9 @@ -import * as vscode from 'vscode'; import { FilesService } from '../../../../services'; export class FocusString { - public static async handle({ repoFilePath, row }: { repoFilePath: string, row: number }) { - if (repoFilePath && row) { - await FilesService.openFile(repoFilePath, row); + public static async handle({ fileAbsPath, row }: { fileAbsPath: string, row: number }) { + if (fileAbsPath && row) { + await FilesService.openFile(fileAbsPath, row); return; } diff --git a/src/views/interface/checkovResult/webviewPanel.ts b/src/views/interface/checkovResult/webviewPanel.ts index 920dc02..99169ed 100644 --- a/src/views/interface/checkovResult/webviewPanel.ts +++ b/src/views/interface/checkovResult/webviewPanel.ts @@ -13,7 +13,6 @@ import { MessageHandlersFactory } from './messages'; export class CheckovResultWebviewPanel { private static context: vscode.ExtensionContext; - private static retryCount: number = 0; public static currentCategory: CHECKOV_RESULT_CATEGORY; public static webviewPanel?: vscode.WebviewPanel; public static checkovResult?: CheckovResult; @@ -24,8 +23,8 @@ export class CheckovResultWebviewPanel { } public static async show(category: CHECKOV_RESULT_CATEGORY, result: CheckovResult, activeEditor: typeof vscode.window.activeTextEditor) { - CheckovResultWebviewPanel.fileEditorMap.set(result.file_abs_path, activeEditor); - CheckovResultWebviewPanel.currentCategory = category; + CheckovResultWebviewPanel.fileEditorMap.set(result.file_abs_path, activeEditor); + CheckovResultWebviewPanel.currentCategory = category; const html = await CheckovResultWebviewPanel.getHtmlTemplate(category); CheckovResultWebviewPanel.checkovResult = result; @@ -81,13 +80,9 @@ export class CheckovResultWebviewPanel { return Boolean(vulnerability_details?.id) || Boolean(check_id); } - if (CategoriesService.isLicensesRisk(check_id) - || CategoriesService.isSecretsRisk(check_id) - || CategoriesService.isWeaknessesRisk(check_type)) { - return false; - } - - return true; + return !(CategoriesService.isLicensesRisk(check_id) + || CategoriesService.isSecretsRisk(check_id) + || CategoriesService.isWeaknessesRisk(check_type)); } private static restrictScaForFile(result: CheckovResult): boolean { @@ -314,7 +309,7 @@ export class CheckovResultWebviewPanel { private static getDataFlowItemString(dataFlow: DataFlow, result: CheckovResult): string { const splitPath = dataFlow.path.split('/'); return `
- ${splitPath[splitPath.length - 1]}: ${dataFlow.start.row}${dataFlow.code_block} + ${splitPath[splitPath.length - 1]}: ${dataFlow.start.row}${dataFlow.code_block}
`; } @@ -327,4 +322,4 @@ export class CheckovResultWebviewPanel { return `${file_line_range[0]} - ${file_line_range[1]}`; } -}; +} diff --git a/src/views/interface/primarySidebar/dataProviders/iacTreeDataProvider.ts b/src/views/interface/primarySidebar/dataProviders/iacTreeDataProvider.ts deleted file mode 100644 index 12c3ec5..0000000 --- a/src/views/interface/primarySidebar/dataProviders/iacTreeDataProvider.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { TreeDataProvider } from './abstractTreeDataProvider'; -import { ResultsService } from '../../../../services'; -import { CHECKOV_RESULT_CATEGORY } from '../../../../constants'; - -export class IaCTreeDataProvider extends TreeDataProvider { - public readonly category = CHECKOV_RESULT_CATEGORY.IAC; - - public getCheckovResults() { - return ResultsService.getByCategory(CHECKOV_RESULT_CATEGORY.IAC); - } -}; diff --git a/src/views/interface/primarySidebar/dataProviders/licensesTreeDataProvider.ts b/src/views/interface/primarySidebar/dataProviders/licensesTreeDataProvider.ts deleted file mode 100644 index edbf892..0000000 --- a/src/views/interface/primarySidebar/dataProviders/licensesTreeDataProvider.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { TreeDataProvider } from './abstractTreeDataProvider'; -import { ResultsService } from '../../../../services'; -import { CHECKOV_RESULT_CATEGORY } from '../../../../constants'; - -export class LicensesTreeDataProvider extends TreeDataProvider { - public readonly category = CHECKOV_RESULT_CATEGORY.LICENSES; - - public getCheckovResults() { - return ResultsService.getByCategory(CHECKOV_RESULT_CATEGORY.LICENSES); - } -}; diff --git a/src/views/interface/primarySidebar/dataProviders/abstractTreeDataProvider.ts b/src/views/interface/primarySidebar/dataProviders/resultTreeDataProvider.ts similarity index 57% rename from src/views/interface/primarySidebar/dataProviders/abstractTreeDataProvider.ts rename to src/views/interface/primarySidebar/dataProviders/resultTreeDataProvider.ts index 32c2b5b..ca9a12c 100644 --- a/src/views/interface/primarySidebar/dataProviders/abstractTreeDataProvider.ts +++ b/src/views/interface/primarySidebar/dataProviders/resultTreeDataProvider.ts @@ -2,40 +2,39 @@ import * as vscode from 'vscode'; import { getPrismaApiUrl } from '../../../../config/configUtils'; import { CHECKOV_RESULT_CATEGORY } from '../../../../constants'; -import { CategoriesService, FilesService } from '../../../../services'; +import logger from '../../../../logger'; +import { CategoriesService, FilesService, ResultsService } from '../../../../services'; import { CheckovResult } from '../../../../types'; import { CheckovResultWebviewPanel } from '../../checkovResult'; import { TreeService } from '../services/treeService'; -export abstract class TreeDataProvider implements vscode.TreeDataProvider { - abstract readonly category: CHECKOV_RESULT_CATEGORY; +export class ResultTreeDataProvider implements vscode.TreeDataProvider { - private readonly _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); - public readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; + readonly category: CHECKOV_RESULT_CATEGORY; - private data: TreeItem[] = []; + private data: ResultTreeItem[] = []; private treeService: TreeService; - constructor() { + constructor(category: CHECKOV_RESULT_CATEGORY) { + this.category = category; this.treeService = new TreeService(); } - abstract getCheckovResults(): CheckovResult[]; + public getCheckovResults() { + return ResultsService.getByCategory(this.category); + }; public refresh() { const checkovResults = this.getCheckovResults(); - this.data = this.treeService.getTreeData(this.category, checkovResults); - this._onDidChangeTreeData.fire(); } - public getTreeItem(element: TreeItem): vscode.TreeItem|Thenable { + public getTreeItem(element: ResultTreeItem): vscode.TreeItem|Thenable { return element; } - public getChildren(element?: TreeItem): vscode.ProviderResult { + public getChildren(element?: ResultTreeItem): vscode.ProviderResult { if (element === undefined) { - const a = vscode.ThemeIcon.Folder; return this.data; } return element.children; @@ -45,40 +44,34 @@ export abstract class TreeDataProvider implements vscode.TreeDataProvider { + public getParent(element: ResultTreeItem): vscode.ProviderResult { return element.parent; } - public async onDidChangeSelection(event: vscode.TreeViewSelectionChangeEvent) { - const result = event.selection[0]?.result; - + public async showResult(result?: CheckovResult | null) { if (!result) { return; } - const isIaC = CategoriesService.isIaCRisk(result.check_id, result.check_type); - if (isIaC) { let fetchedDescription; - if (getPrismaApiUrl()) { + logger.info(`Fetching description for ${result.bc_check_id}`); fetchedDescription = await CheckovResultWebviewPanel.fetchDescription(result.bc_check_id); } - if (!result.description && fetchedDescription) { result.description = fetchedDescription; } } - - const openedTextEditor = await FilesService.openFile(result.repo_file_path, result.file_line_range[0]); + const openedTextEditor = await FilesService.openFile(result.file_abs_path, result.file_line_range[0]); await CheckovResultWebviewPanel.show(this.category, result, openedTextEditor); - } + } public getTreeItemByIds(id: string) { - return this.traverseAndFind(id, { children: this.data } as TreeItem); + return this.traverseAndFind(id, { children: this.data } as ResultTreeItem); } - private traverseAndFind(id: string, treeItem: TreeItem): TreeItem | undefined { + private traverseAndFind(id: string, treeItem: ResultTreeItem): ResultTreeItem | undefined { if (treeItem?.result?.id === id) { return treeItem; } @@ -91,30 +84,39 @@ export abstract class TreeDataProvider implements vscode.TreeDataProvider; - public static secretsTreeView: vscode.TreeView; - public static vulnerabilitiesTreeView: vscode.TreeView; - public static licensesTreeView: vscode.TreeView; - public static weaknessesTreeView: vscode.TreeView; public static initialize(context: vscode.ExtensionContext) { - PrimarySidebar.iacTreeView = vscode.window.createTreeView('iac-misconfiguration', { - treeDataProvider: TreeDataProvidersContainer.iacTreeDataProvider, - }); - PrimarySidebar.secretsTreeView = vscode.window.createTreeView('secrets', { - treeDataProvider: TreeDataProvidersContainer.secretsTreeDataProvicer, - }); - PrimarySidebar.vulnerabilitiesTreeView = vscode.window.createTreeView('vulnerabilities', { - treeDataProvider: TreeDataProvidersContainer.vulnerabilitiesTreeDataProvider, - }); - PrimarySidebar.licensesTreeView = vscode.window.createTreeView('licenses', { - treeDataProvider: TreeDataProvidersContainer.licensesTreeDataProvider, - }); - PrimarySidebar.weaknessesTreeView = vscode.window.createTreeView('weaknesses', { - treeDataProvider: TreeDataProvidersContainer.weaknessesTreeDataProvider, - }); - - PrimarySidebar.iacTreeView.onDidChangeSelection( - TreeDataProvidersContainer.iacTreeDataProvider.onDidChangeSelection.bind(TreeDataProvidersContainer.iacTreeDataProvider), - ); - PrimarySidebar.secretsTreeView.onDidChangeSelection( - TreeDataProvidersContainer.secretsTreeDataProvicer.onDidChangeSelection.bind(TreeDataProvidersContainer.secretsTreeDataProvicer), - ); - PrimarySidebar.vulnerabilitiesTreeView.onDidChangeSelection( - TreeDataProvidersContainer.vulnerabilitiesTreeDataProvider.onDidChangeSelection.bind(TreeDataProvidersContainer.vulnerabilitiesTreeDataProvider), - ); - PrimarySidebar.licensesTreeView.onDidChangeSelection( - TreeDataProvidersContainer.licensesTreeDataProvider.onDidChangeSelection.bind(TreeDataProvidersContainer.licensesTreeDataProvider), - ); - PrimarySidebar.weaknessesTreeView.onDidChangeSelection( - TreeDataProvidersContainer.weaknessesTreeDataProvider.onDidChangeSelection.bind(TreeDataProvidersContainer.weaknessesTreeDataProvider), - ); - } - - public static refreshBadgeCount() { - PrimarySidebar.iacTreeView.badge = { value: ResultsService.getCount(), tooltip: '' }; + TreeDataProvidersContainer.registerTreeProviders(); + context.subscriptions.push(vscode.commands.registerCommand('treeView.click', (result: CheckovResult, category: CHECKOV_RESULT_CATEGORY) => { + TreeDataProvidersContainer.treeViews[category].provider.showResult(result); + })); } public static getTreeView(checkId: string, checkType: string) { @@ -61,25 +25,15 @@ export class PrimarySidebar { logger.error(`Can not get tree data provider by category for checkId: ${checkId}`); } - private static getTreeViewByCategory(category: CHECKOV_RESULT_CATEGORY): vscode.TreeView | undefined { - switch(category) { - case CHECKOV_RESULT_CATEGORY.IAC: - return PrimarySidebar.iacTreeView; - case CHECKOV_RESULT_CATEGORY.LICENSES: - return PrimarySidebar.licensesTreeView; - case CHECKOV_RESULT_CATEGORY.SCA: - return PrimarySidebar.vulnerabilitiesTreeView; - case CHECKOV_RESULT_CATEGORY.SECRETS: - return PrimarySidebar.secretsTreeView; - case CHECKOV_RESULT_CATEGORY.WEAKNESSES: - return PrimarySidebar.weaknessesTreeView; - default: - logger.info(`No such tree view for the category: ${category}`); + private static getTreeViewByCategory(category: CHECKOV_RESULT_CATEGORY): vscode.TreeView | undefined { + if (TreeDataProvidersContainer.treeViews[category]) { + return TreeDataProvidersContainer.treeViews[category].view; } + logger.info(`No such tree view for the category: ${category}`); } } export function registerSidebar(context: vscode.ExtensionContext) { PrimarySidebar.initialize(context); TreeDataProvidersContainer.refresh(); -}; +} diff --git a/src/views/interface/primarySidebar/services/iconsService.ts b/src/views/interface/primarySidebar/services/iconsService.ts index e8f452e..984a47d 100644 --- a/src/views/interface/primarySidebar/services/iconsService.ts +++ b/src/views/interface/primarySidebar/services/iconsService.ts @@ -70,4 +70,4 @@ export class IconsService { light: path.join(__dirname, '..', '..', '..', '..', '..', 'static', 'icons', 'svg', `severities/${severity.toLowerCase()}.svg`), }; } -}; +} diff --git a/src/views/interface/primarySidebar/services/treeDataProvidersContainer.ts b/src/views/interface/primarySidebar/services/treeDataProvidersContainer.ts index d4a273d..6c03c16 100644 --- a/src/views/interface/primarySidebar/services/treeDataProvidersContainer.ts +++ b/src/views/interface/primarySidebar/services/treeDataProvidersContainer.ts @@ -1,53 +1,60 @@ -import { IaCTreeDataProvider } from '../dataProviders/iacTreeDataProvider'; -import { SecretsTreeDataProvider } from '../dataProviders/secretsTreeDataProvider'; -import { WeaknessesTreeDataProvider } from '../dataProviders/weaknessesTreeDataProvider'; -import { VulnerabilitiesTreeDataProvider } from '../dataProviders/vulnerabilitiesTreeDataProvider'; -import { LicensesTreeDataProvider } from '../dataProviders/licensesTreeDataProvider'; -import { PrimarySidebar } from '../../primarySidebar'; -import { CategoriesService } from '../../../../services'; -import { TreeDataProvider } from '../dataProviders/abstractTreeDataProvider'; +import * as vscode from "vscode"; import { CHECKOV_RESULT_CATEGORY } from '../../../../constants'; import logger from '../../../../logger'; +import { CategoriesService, ResultsService } from '../../../../services'; +import { ResultTreeDataProvider, ResultTreeItem } from '../dataProviders/resultTreeDataProvider'; + +interface TreeViewContext { + key: string; + provider: ResultTreeDataProvider; + view?: vscode.TreeView; +} export class TreeDataProvidersContainer { - public static iacTreeDataProvider: IaCTreeDataProvider; - public static secretsTreeDataProvicer: SecretsTreeDataProvider; - public static vulnerabilitiesTreeDataProvider: VulnerabilitiesTreeDataProvider; - public static licensesTreeDataProvider: LicensesTreeDataProvider; - public static weaknessesTreeDataProvider: WeaknessesTreeDataProvider; - static { - TreeDataProvidersContainer.iacTreeDataProvider = new IaCTreeDataProvider(); - TreeDataProvidersContainer.secretsTreeDataProvicer = new SecretsTreeDataProvider(); - TreeDataProvidersContainer.vulnerabilitiesTreeDataProvider = new VulnerabilitiesTreeDataProvider(); - TreeDataProvidersContainer.licensesTreeDataProvider = new LicensesTreeDataProvider(); - TreeDataProvidersContainer.weaknessesTreeDataProvider = new WeaknessesTreeDataProvider(); + public static treeViews: Record = { + [CHECKOV_RESULT_CATEGORY.IAC]: { + key: 'iac-misconfiguration', + provider: new ResultTreeDataProvider(CHECKOV_RESULT_CATEGORY.IAC) + }, + [CHECKOV_RESULT_CATEGORY.SCA]: { + key: 'vulnerabilities', + provider: new ResultTreeDataProvider(CHECKOV_RESULT_CATEGORY.SCA) + }, + [CHECKOV_RESULT_CATEGORY.SECRETS]: { + key: 'secrets', + provider: new ResultTreeDataProvider(CHECKOV_RESULT_CATEGORY.SECRETS) + }, + [CHECKOV_RESULT_CATEGORY.LICENSES]: { + key: 'licenses', + provider: new ResultTreeDataProvider(CHECKOV_RESULT_CATEGORY.LICENSES) + }, + [CHECKOV_RESULT_CATEGORY.WEAKNESSES]: { + key: 'weaknesses', + provider: new ResultTreeDataProvider(CHECKOV_RESULT_CATEGORY.WEAKNESSES) + } + }; + + public static registerTreeProviders() { + Object.values(this.treeViews).forEach(treeViewContext => { + treeViewContext.view = vscode.window.createTreeView(treeViewContext.key, { + treeDataProvider: treeViewContext.provider, + }); + }); } public static refresh() { - TreeDataProvidersContainer.iacTreeDataProvider.refresh(); - TreeDataProvidersContainer.secretsTreeDataProvicer.refresh(); - TreeDataProvidersContainer.vulnerabilitiesTreeDataProvider.refresh(); - TreeDataProvidersContainer.licensesTreeDataProvider.refresh(); - TreeDataProvidersContainer.weaknessesTreeDataProvider.refresh(); - PrimarySidebar.refreshBadgeCount(); + Object.values(this.treeViews).forEach(treeViewContext => { + treeViewContext.provider.refresh(); + treeViewContext.view!.badge = { value: ResultsService.getCount(), tooltip: '' }; + }); } - public static getTreeDataProviderByCategory(category: CHECKOV_RESULT_CATEGORY): TreeDataProvider | undefined { - switch(category) { - case CHECKOV_RESULT_CATEGORY.IAC: - return TreeDataProvidersContainer.iacTreeDataProvider; - case CHECKOV_RESULT_CATEGORY.LICENSES: - return TreeDataProvidersContainer.licensesTreeDataProvider; - case CHECKOV_RESULT_CATEGORY.SCA: - return TreeDataProvidersContainer.vulnerabilitiesTreeDataProvider; - case CHECKOV_RESULT_CATEGORY.SECRETS: - return TreeDataProvidersContainer.secretsTreeDataProvicer; - case CHECKOV_RESULT_CATEGORY.WEAKNESSES: - return TreeDataProvidersContainer.weaknessesTreeDataProvider; - default: - logger.info(`No such tree data provider for the category: ${category}`); + public static getTreeDataProviderByCategory(category: CHECKOV_RESULT_CATEGORY): ResultTreeDataProvider | undefined { + if (this.treeViews[category]) { + return this.treeViews[category].provider; } + logger.info(`No such tree data provider for the category: ${category}`); } public static getTreeItem({ checkId, id, checkType }: { checkId: string, id: string, checkType: string }) { @@ -59,4 +66,4 @@ export class TreeDataProvidersContainer { logger.error(`Can not specify category for the risk. checkId: ${checkId} id: ${id}`); } } -}; +} diff --git a/src/views/interface/primarySidebar/services/treeService.ts b/src/views/interface/primarySidebar/services/treeService.ts index 58864be..ebea345 100644 --- a/src/views/interface/primarySidebar/services/treeService.ts +++ b/src/views/interface/primarySidebar/services/treeService.ts @@ -1,8 +1,8 @@ import * as path from 'path'; -import { TreeItem } from '../dataProviders/abstractTreeDataProvider'; -import { IconsService } from './iconsService'; -import { CheckovResult } from '../../../../types'; import { CHECKOV_RESULT_CATEGORY, PATH_TYPE, SEVERITY, dockerfileName, severityPriorityMap } from '../../../../constants'; +import { CheckovResult } from '../../../../types'; +import { ResultTreeItem } from '../dataProviders/resultTreeDataProvider'; +import { IconsService } from './iconsService'; export type FormattedCheck = { originalFilePath: string; @@ -26,18 +26,18 @@ export class TreeService { this.iconService = new IconsService(); } - public getTreeData(category: CHECKOV_RESULT_CATEGORY, checkovOutput: CheckovResult[]): TreeItem[] { + public getTreeData(category: CHECKOV_RESULT_CATEGORY, checkovOutput: CheckovResult[]): ResultTreeItem[] { const formattedData = this.formatCheckData(category, checkovOutput); - const treeData = this.formTreeData(formattedData); + const treeData = this.formTreeData(category, formattedData); this.setParentLinks(treeData); if (treeData.length === 1) { return treeData; } - this.sortTreeData({children: treeData} as TreeItem); + this.sortTreeData({children: treeData} as ResultTreeItem); return treeData; } - private setParentLinks(formedData: TreeItem[]) { + private setParentLinks(formedData: ResultTreeItem[]) { for (const treeItem of formedData) { if (treeItem.children) { for (const childTreeItem of treeItem.children) { @@ -48,7 +48,7 @@ export class TreeService { } } - private sortTreeData(treeData: TreeItem) { + private sortTreeData(treeData: ResultTreeItem) { if (treeData.children) { if (treeData.children[0].result) { treeData.children.sort((a, b) => { @@ -72,7 +72,7 @@ export class TreeService { } } - private formTreeData(formattedData: Array): TreeItem[] { + private formTreeData(category: CHECKOV_RESULT_CATEGORY, formattedData: Array): ResultTreeItem[] { let formedTreeData: any = []; let level: any = { formedTreeData }; let counter: number = 0; @@ -81,18 +81,18 @@ export class TreeService { formattedCheck.filePath.reduce((r, { path, pathType, severity }, i, a) => { const iconPath = this.iconService.getIconPath(pathType, severity); if (i === a.length - 1) { - r.formedTreeData.push(new TreeItem({ label: path, iconPath, result: formattedCheck.result })); + r.formedTreeData.push(new ResultTreeItem({ label: path, iconPath, result: formattedCheck.result, category })); counter++; } else if(!r[path]) { r[path] = {formedTreeData: []}; - r.formedTreeData.push(new TreeItem({ label: path, iconPath }, r[path].formedTreeData)); + r.formedTreeData.push(new ResultTreeItem({ label: path, iconPath, category }, r[path].formedTreeData)); } return r[path]; }, level); }); - formedTreeData.unshift(new TreeItem({ label: `Found issues: ${counter}`, isCounter: true })); + formedTreeData.unshift(new ResultTreeItem({ label: `Found issues: ${counter}`, isCounter: true, category })); return formedTreeData; } @@ -178,6 +178,6 @@ export class TreeService { } private escapeRedundantChars(filePath: string): string { - return filePath.replace(/\/\//g, '/').replace(/^\//, '');; + return filePath.replace(/\/\//g, '/').replace(/^\//, ''); } } \ No newline at end of file diff --git a/static/webviews/result/index.html b/static/webviews/result/index.html index 56d8998..80f7f07 100644 --- a/static/webviews/result/index.html +++ b/static/webviews/result/index.html @@ -1,5 +1,5 @@ - + @@ -127,8 +127,8 @@ function onDocumentationClick(url) { vscode.postMessage({ type: 'documentationClick', url: url }); } - function onSastStepClick(repoFilePath, row) { - vscode.postMessage({ type: 'sastStepClick', repoFilePath, row }); + function onSastStepClick(fileAbsPath, row) { + vscode.postMessage({ type: 'sastStepClick', fileAbsPath, row }); }