From 64814ebaa2e3ff47ab3aff3010b117fbe07d8d2f Mon Sep 17 00:00:00 2001 From: Rajpreet Singh <63117988+rajpreet-s@users.noreply.github.com> Date: Mon, 12 Aug 2024 13:52:05 +0530 Subject: [PATCH] 12 hover provider for manifest files (#39) * refactored * Added vulnerabilities data in hover menu * refactored --- package.json | 4 +- src/constants/organization.ts | 6 ++- src/helpers/apiHelper.ts | 4 ++ src/helpers/index.ts | 3 ++ src/helpers/template.ts | 33 ++++++++++++++++ .../manifestDependencyHoverProvider.ts | 38 +++++++++++++------ src/services/dependencyService.ts | 21 +++++++++- src/services/scanService.ts | 20 +++++++++- src/types/dependency.ts | 4 +- src/types/requestParam.ts | 1 + src/types/vulnerability.ts | 20 ++++++++++ 11 files changed, 134 insertions(+), 20 deletions(-) create mode 100644 src/helpers/template.ts create mode 100644 src/types/vulnerability.ts diff --git a/package.json b/package.json index bb32f6d..1d347cb 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,9 @@ "capabilities": { "hoverProvider": "true" }, - "activationEvents": [], + "activationEvents": [ + "*" + ], "main": "./dist/extension.js", "repository": { "type": "git", diff --git a/src/constants/organization.ts b/src/constants/organization.ts index 77ad3b7..8c04dd5 100644 --- a/src/constants/organization.ts +++ b/src/constants/organization.ts @@ -5,7 +5,11 @@ import * as os from "os"; export class Organization { static readonly name = "debricked"; static readonly apiVersion = "1.0"; - static readonly baseUrl = `https://debricked.com/api/${Organization.apiVersion}/`; + static readonly debrickedBaseUrl = "https://debricked.com"; + static readonly baseUrl = `${Organization.debrickedBaseUrl}/api/${Organization.apiVersion}/`; + + static readonly dependencyUrl = "open/dependencies/get-dependencies-hierarchy"; + static readonly vulnerableUrl = "open/vulnerabilities/get-vulnerabilities"; static readonly nameCaps = "Debricked"; // Command and OS-specific constants diff --git a/src/helpers/apiHelper.ts b/src/helpers/apiHelper.ts index 9030c9d..51d1515 100644 --- a/src/helpers/apiHelper.ts +++ b/src/helpers/apiHelper.ts @@ -28,6 +28,10 @@ export class ApiHelper { params.push(`commitId=${requestParam.commitId}`); } + if (requestParam.dependencyId) { + params.push(`dependencyId=${requestParam.dependencyId}`); + } + if (params.length > 0) { url += `?${params.join("&")}`; } diff --git a/src/helpers/index.ts b/src/helpers/index.ts index 002eb81..653dbf0 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -18,6 +18,7 @@ import { ErrorHandler } from "./errorHandler"; import { ApiClient } from "./apiClient"; import { GlobalStore } from "./globalStore"; import { Organization } from "../constants"; +import { Template } from "./template"; class IndexHelper { constructor( @@ -61,6 +62,7 @@ const installHelper = new InstallHelper(Logger, statusBarMessageHelper, commandH const fileHelper = new FileHelper(debrickedDataHelper, Logger, globalStore); const indexHelper = new IndexHelper(debrickedDataHelper, commonHelper, gitHelper); const showQuickPickHelper = new ShowQuickPickHelper(); +const template = new Template(); export { authHelper, @@ -81,4 +83,5 @@ export { apiClient, globalStore, indexHelper, + template, }; diff --git a/src/helpers/template.ts b/src/helpers/template.ts new file mode 100644 index 0000000..9402306 --- /dev/null +++ b/src/helpers/template.ts @@ -0,0 +1,33 @@ +import { Organization } from "../constants"; +import { DependencyVulnerability } from "types/vulnerability"; +import * as vscode from "vscode"; + +export class Template { + constructor() {} + public licenseContent(data: string, contents: vscode.MarkdownString) { + contents.appendMarkdown(`License: **${data}**`); + contents.appendText("\n______________________________\n"); + } + + public vulnerableContent(data: DependencyVulnerability[], contents: vscode.MarkdownString): void { + if (data.length === 0) { + contents.appendMarkdown("No vulnerabilities found"); + return; + } + + contents.appendMarkdown(`Vulnerabilities Found: **${data.length}**\n\n`); + + const vulnerabilitiesToShow = data.slice(0, 2); + vulnerabilitiesToShow.forEach((vulnerability) => { + contents.appendMarkdown( + `[**${vulnerability.cveId}**](${Organization.debrickedBaseUrl + vulnerability.name.link})`, + ); + + if (vulnerability.cvss) { + contents.appendMarkdown(` - CVSS: ${vulnerability.cvss.text} (${vulnerability.cvss.type})`); + } + + contents.appendMarkdown("\n\n"); + }); + } +} diff --git a/src/providers/manifestDependencyHoverProvider.ts b/src/providers/manifestDependencyHoverProvider.ts index c754725..dda5fc7 100644 --- a/src/providers/manifestDependencyHoverProvider.ts +++ b/src/providers/manifestDependencyHoverProvider.ts @@ -1,6 +1,8 @@ import * as vscode from "vscode"; import * as path from "path"; -import { globalStore } from "../helpers"; +import { globalStore, template } from "../helpers"; +import { DependencyService } from "services"; +import { DependencyVulnerability } from "types/vulnerability"; export class ManifestDependencyHoverProvider implements vscode.HoverProvider { private manifestFiles: string[] = []; @@ -30,22 +32,34 @@ export class ManifestDependencyHoverProvider implements vscode.HoverProvider { const lineText = document.lineAt(position.line).text; const dependencyName = this.parseDependencyName(lineText, currentManifestFile); - if (dependencyName) { - const depData = globalStore.getDependencyData().get(dependencyName); - const licenseData = depData?.licenses[0]?.name ?? "License information unavailable"; + if (!dependencyName) { + return null; + } + + const depData = globalStore.getDependencyData().get(dependencyName); + const licenseData = depData?.licenses[0]?.name ?? "License information unavailable"; + const vulnerableData = await this.getVulnerableData(depData?.id); - const contents = new vscode.MarkdownString( - `Debricked`, - ); - contents.supportHtml = true; - contents.isTrusted = true; + const contents = this.createMarkdownString(); + template.licenseContent(licenseData, contents); + template.vulnerableContent(vulnerableData, contents); - const hoverContent = [`**${dependencyName}**`, contents, `License: ${licenseData}`]; + return new vscode.Hover(contents); + } - return new vscode.Hover(hoverContent); + private async getVulnerableData(dependencyId?: number): Promise { + if (dependencyId) { + return await DependencyService.getVulnerableData(dependencyId); } + return []; + } - return null; + private createMarkdownString(): vscode.MarkdownString { + const contents = new vscode.MarkdownString(); + contents.supportHtml = true; + contents.isTrusted = true; + contents.supportThemeIcons = true; + return contents; } private parseDependencyName(lineText: string, fileName: string): string | null { diff --git a/src/services/dependencyService.ts b/src/services/dependencyService.ts index 6286762..39bdf0e 100644 --- a/src/services/dependencyService.ts +++ b/src/services/dependencyService.ts @@ -1,12 +1,14 @@ import { Dependency, DependencyResponse, IndirectDependency } from "types/dependency"; import { apiHelper, globalStore, Logger } from "../helpers"; import { RequestParam } from "../types"; +import { DependencyVulnerabilityWrapper } from "types/vulnerability"; +import { Organization } from "../constants"; export class DependencyService { static async getDependencyData(repoID: number, commitId: number) { Logger.logInfo("Started fetching the Dependency Data"); const requestParam: RequestParam = { - endpoint: "open/dependencies/get-dependencies-hierarchy", + endpoint: Organization.dependencyUrl, repoId: repoID, commitId: commitId, }; @@ -24,6 +26,21 @@ export class DependencyService { }); globalStore.setDependencyData(dependencyMap); - Logger.logObj(response); + } + + static async getVulnerableData(depId: number) { + Logger.logInfo("Started fetching the Vulnerable Data"); + const repoId = await globalStore.getRepoId(); + const commitId = await globalStore.getCommitId(); + + const requestParam: RequestParam = { + endpoint: Organization.vulnerableUrl, + repoId: repoId, + commitId: commitId, + dependencyId: depId, + }; + const response: DependencyVulnerabilityWrapper = await apiHelper.get(requestParam); + const vulnerableData = response.vulnerabilities; + return vulnerableData; } } diff --git a/src/services/scanService.ts b/src/services/scanService.ts index 0b54734..7242a3f 100644 --- a/src/services/scanService.ts +++ b/src/services/scanService.ts @@ -8,10 +8,13 @@ import { commonHelper, commandHelper, authHelper, + fileHelper, } from "../helpers"; import { DebrickedCommands, MessageStatus, Organization } from "../constants/index"; import { DebrickedCommandNode, Flag, RepositoryInfo } from "../types"; import * as vscode from "vscode"; +import * as fs from "fs"; +import { DependencyService } from "./dependencyService"; export class ScanService { static async scanService() { @@ -36,7 +39,6 @@ export class ScanService { await ScanService.handleFlags(command.flags[2], cmdParams, currentRepoData); await ScanService.handleFlags(command.flags[3], cmdParams, currentRepoData); await ScanService.handleFlags(command.flags[4], cmdParams, currentRepoData); - await ScanService.handleFlags(command.flags[4], cmdParams, currentRepoData); await ScanService.handleFlags(command.global_flags[0], cmdParams, currentRepoData); } } else { @@ -61,7 +63,21 @@ export class ScanService { }, async (progress) => { progress.report({ message: "Scanning Manifest Files🚀" }); - await commandHelper.executeAsyncCommand(`${Organization.debrickedCli} ${cmdParams.join(" ")}`); + const output = await commandHelper.executeAsyncCommand( + `${Organization.debrickedCli} ${cmdParams.join(" ")}`, + ); + if (!output.includes("https://debricked.com/app/en/repository/")) { + if (await fs.existsSync(`${Organization.reportsFolderPath}/scan-output.json`)) { + await fileHelper.setRepoID(); + + const repoId = await globalStore.getRepoId(); + const commitId = await globalStore.getCommitId(); + + await DependencyService.getDependencyData(repoId, commitId); + } else { + throw new Error("No reports file exists"); + } + } statusBarMessageHelper.setStatusBarMessage(`Debricked: Scanning Completed $(pass-filled)`, 1000); }, ); diff --git a/src/types/dependency.ts b/src/types/dependency.ts index ade8d8f..3135ffb 100644 --- a/src/types/dependency.ts +++ b/src/types/dependency.ts @@ -12,13 +12,13 @@ export interface DependencyResponse { commitName: string; dependencies: Dependency[]; } -export interface DependencyName { +interface DependencyName { name: string; shortName: string; link: string; } -export interface DependencyLicense { +interface DependencyLicense { name: string; } diff --git a/src/types/requestParam.ts b/src/types/requestParam.ts index fd6e258..955c56e 100644 --- a/src/types/requestParam.ts +++ b/src/types/requestParam.ts @@ -4,4 +4,5 @@ export interface RequestParam { repoId?: number; endpoint: string; commitId?: number; + dependencyId?: number; } diff --git a/src/types/vulnerability.ts b/src/types/vulnerability.ts new file mode 100644 index 0000000..394fa7e --- /dev/null +++ b/src/types/vulnerability.ts @@ -0,0 +1,20 @@ +export interface DependencyVulnerabilityWrapper { + vulnerabilities: DependencyVulnerability[]; +} + +export interface DependencyVulnerability { + cveId: string; + cvss: CVSS; + cpeVersions: string[]; + name: VulnerabilityName +} + +interface CVSS { + text: number; + type: string; +} + +interface VulnerabilityName { + name: string; + link: string; +} \ No newline at end of file