Skip to content

Commit

Permalink
feat: Derive Vulnerability Count from LS results [HEAD-942] (#388)
Browse files Browse the repository at this point in the history
* chore: introduce observerable in ProductService

* feat: enable LS based Vulnerability Count Service

* fix: creating subject

* chore: introduce static getOssIssueArgCommand()

* chore: adapt vuln count code action provider

* chore: remove dependency to old OSSService

* fix: linter & cleanup

* chore: adapt test to use LS versions of classes

* chore: delete now obsolote classes

* fix: duplicate diagnostics message

* fix: use strict equal

* chore: convert severities explicitely

* fix: typo
  • Loading branch information
PeterSchafer authored and j-luong committed Nov 23, 2023
1 parent a87c2e1 commit 49c5d4e
Show file tree
Hide file tree
Showing 10 changed files with 179 additions and 72 deletions.
4 changes: 2 additions & 2 deletions src/snyk/base/modules/baseSnykModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { ICodeSettings } from '../../snykCode/codeSettings';
import SnykEditorsWatcher from '../../snykCode/watchers/editorsWatcher';
import { OssServiceLanguageServer } from '../../snykOss/ossServiceLanguageServer';
import { OssService } from '../../snykOss/services/ossService';
import { OssVulnerabilityCountService } from '../../snykOss/services/vulnerabilityCount/ossVulnerabilityCountService';
import { OssVulnerabilityCountServiceLS } from '../../snykOss/services/vulnerabilityCount/ossVulnerabilityCountServiceLS';
import { IAuthenticationService } from '../services/authenticationService';
import { ScanModeService } from '../services/scanModeService';
import SnykStatusBarItem, { IStatusBarItem } from '../statusBarItem/statusBarItem';
Expand All @@ -50,7 +50,7 @@ export default abstract class BaseSnykModule implements IBaseSnykModule {
protected advisorService?: AdvisorProvider;
protected commandController: CommandController;
protected scanModeService: ScanModeService;
protected ossVulnerabilityCountService: OssVulnerabilityCountService;
protected ossVulnerabilityCountServiceLanguageServer: OssVulnerabilityCountServiceLS;
protected advisorScoreDisposable: AdvisorService;
protected languageServer: ILanguageServer;

Expand Down
4 changes: 3 additions & 1 deletion src/snyk/common/services/productService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Subscription } from 'rxjs';
import { Subject, Subscription } from 'rxjs';
import { AnalysisStatusProvider } from '../analysis/statusProvider';
import { IConfiguration } from '../configuration/configuration';
import { IWorkspaceTrust } from '../configuration/trustedFolders';
Expand Down Expand Up @@ -30,6 +30,7 @@ export interface IProductService<T> extends AnalysisStatusProvider, Disposable {

export abstract class ProductService<T> extends AnalysisStatusProvider implements IProductService<T> {
private _result: ProductResult<T>;
readonly newResultAvailable$ = new Subject<void>();

// Track running scan count. Assumption: server sends N success/error messages for N scans in progress.
private runningScanCount = 0;
Expand Down Expand Up @@ -168,6 +169,7 @@ export abstract class ProductService<T> extends AnalysisStatusProvider implement
this.analysisFinished();
this.runningScanCount = 0;

this.newResultAvailable$.next();
this.refreshTreeView();
}
}
Expand Down
16 changes: 8 additions & 8 deletions src/snyk/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ import { OssServiceLanguageServer } from './snykOss/ossServiceLanguageServer';
import { OssDetailPanelProvider } from './snykOss/providers/ossDetailPanelProvider';
import OssIssueTreeProvider from './snykOss/providers/ossVulnerabilityTreeProvider';
import { OssService } from './snykOss/services/ossService';
import { OssVulnerabilityCountService } from './snykOss/services/vulnerabilityCount/ossVulnerabilityCountService';
import { ModuleVulnerabilityCountProvider } from './snykOss/services/vulnerabilityCount/vulnerabilityCountProvider';
import { OssVulnerabilityCountServiceLS } from './snykOss/services/vulnerabilityCount/ossVulnerabilityCountServiceLS';
import { ModuleVulnerabilityCountProviderLS } from './snykOss/services/vulnerabilityCount/vulnerabilityCountProviderLS';
import { OssVulnerabilityTreeProvider } from './snykOss/views/ossVulnerabilityTreeProvider';
import { OssSuggestionWebviewProvider } from './snykOss/views/suggestion/ossSuggestionWebviewProvider';
import { DailyScanJob } from './snykOss/watchers/dailyScanJob';
Expand Down Expand Up @@ -414,24 +414,24 @@ class SnykExtension extends SnykLib implements IExtension {

this.initDependencyDownload();

this.ossVulnerabilityCountService = new OssVulnerabilityCountService(
this.ossVulnerabilityCountServiceLanguageServer = new OssVulnerabilityCountServiceLS(
vsCodeWorkspace,
vsCodeWindow,
vsCodeLanguages,
new ModuleVulnerabilityCountProvider(
this.ossService,
new ModuleVulnerabilityCountProviderLS(
this.ossServiceLanguageServer,
languageClientAdapter,
new UriAdapter(),
new TextDocumentAdapter(),
),
this.ossService,
this.ossServiceLanguageServer,
Logger,
new EditorDecorator(vsCodeWindow, vsCodeLanguages, new ThemeColorAdapter()),
new CodeActionKindAdapter(),
this.analytics,
configuration,
);
this.ossVulnerabilityCountService.activate();
this.ossVulnerabilityCountServiceLanguageServer.activate();

this.advisorScoreDisposable = new AdvisorService(
vsCodeWindow,
Expand Down Expand Up @@ -461,7 +461,7 @@ class SnykExtension extends SnykLib implements IExtension {
}

public async deactivate(): Promise<void> {
this.ossVulnerabilityCountService.dispose();
this.ossVulnerabilityCountServiceLanguageServer.dispose();
await this.languageServer.stop();
await this.analytics.flush();
await ErrorReporter.flush();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import marked from 'marked';
import { IAnalytics } from '../../common/analytics/itly';
import { OpenCommandIssueType, OpenIssueCommandArg } from '../../common/commands/types';
import { SNYK_OPEN_ISSUE_COMMAND } from '../../common/constants/commands';
Expand All @@ -16,16 +17,15 @@ import {
} from '../../common/vscode/types';
import { DIAGNOSTICS_OSS_COLLECTION_NAME } from '../../snykCode/constants/analysis';
import { messages } from '../messages/vulnerabilityCount';
import { isResultCliError } from '../ossResult';
import { OssService } from '../services/ossService';
import { ModuleVulnerabilityCountProvider } from '../services/vulnerabilityCount/vulnerabilityCountProvider';
import { OssVulnerability, isResultCliError } from '../ossResult';
import { ModuleVulnerabilityCountProviderLS } from '../services/vulnerabilityCount/vulnerabilityCountProviderLS';
import { OssIssueCommandArg } from '../views/ossVulnerabilityTreeProvider';

export class VulnerabilityCodeActionProvider implements CodeActionProvider {
export class VulnerabilityCodeActionProviderLS implements CodeActionProvider {
public codeActionKinds: ReadonlyArray<CodeActionKind> = [this.codeActionKindProvider.getQuickFix()];

constructor(
private readonly ossService: OssService,
private readonly vulnerabilityCountProvider: ModuleVulnerabilityCountProvider,
private readonly vulnerabilityCountProvider: ModuleVulnerabilityCountProviderLS,
private readonly codeActionKindProvider: ICodeActionKindAdapter,
private readonly analytics: IAnalytics,
) {}
Expand All @@ -40,7 +40,7 @@ export class VulnerabilityCodeActionProvider implements CodeActionProvider {
return;
}

const ossResult = this.ossService.getResultArray();
const ossResult = this.vulnerabilityCountProvider.getResultArray();
if (!ossResult) {
return;
}
Expand All @@ -65,7 +65,7 @@ export class VulnerabilityCodeActionProvider implements CodeActionProvider {
arguments: [
{
issueType: OpenCommandIssueType.OssVulnerability,
issue: await this.ossService.getOssIssueCommandArg(vulnerability, fileResult.vulnerabilities),
issue: await this.getOssIssueCommandArg(vulnerability, fileResult.vulnerabilities),
} as OpenIssueCommandArg,
],
};
Expand All @@ -78,4 +78,24 @@ export class VulnerabilityCodeActionProvider implements CodeActionProvider {
return [command];
}
}

getOssIssueCommandArg(
vulnerability: OssVulnerability,
allVulnerabilities: OssVulnerability[],
): Promise<OssIssueCommandArg> {
return new Promise((resolve, reject) => {
const matchingIdVulnerabilities = allVulnerabilities.filter(v => v.id === vulnerability.id);
marked.parse(vulnerability.description, (err, overviewHtml) => {
if (err) {
return reject(err);
}

return resolve({
...vulnerability,
matchingIdVulnerabilities: matchingIdVulnerabilities,
overviewHtml,
});
});
});
}
}
14 changes: 14 additions & 0 deletions src/snyk/snykOss/ossResult.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import _ from 'lodash';
import { CliError } from '../cli/services/cliService';
import { IssueSeverity } from '../common/languageServer/types';

export type OssResult = OssFileResult[] | OssFileResult;

Expand Down Expand Up @@ -56,3 +57,16 @@ export function capitalizeOssSeverity(ossSeverity: OssSeverity): Capitalize<OssS
export function isResultCliError(fileResult: OssFileResult): fileResult is CliError {
return (fileResult as CliError).error !== undefined;
}

export function convertSeverity(severity: IssueSeverity): OssSeverity {
switch (severity) {
case IssueSeverity.Low:
return OssSeverity.Low;
case IssueSeverity.Medium:
return OssSeverity.Medium;
case IssueSeverity.High:
return OssSeverity.High;
default:
return OssSeverity.Critical;
}
}
2 changes: 1 addition & 1 deletion src/snyk/snykOss/services/ossService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { IWebViewProvider } from '../../common/views/webviewProvider';
import { ExtensionContext } from '../../common/vscode/extensionContext';
import { IVSCodeWorkspace } from '../../common/vscode/workspace';
import { messages } from '../messages/test';
import { isResultCliError, OssFileResult, OssResult, OssSeverity, OssVulnerability } from '../ossResult';
import { OssFileResult, OssResult, OssSeverity, OssVulnerability, isResultCliError } from '../ossResult';
import { OssIssueCommandArg } from '../views/ossVulnerabilityTreeProvider';
import { DailyScanJob } from '../watchers/dailyScanJob';
import createManifestFileWatcher from '../watchers/manifestFileWatcher';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import { Diagnostic, DiagnosticCollection, Disposable, TextDocument } from '../.
import { IVSCodeWindow } from '../../../common/vscode/window';
import { IVSCodeWorkspace } from '../../../common/vscode/workspace';
import { DIAGNOSTICS_OSS_COLLECTION_NAME } from '../../../snykCode/constants/analysis';
import { VulnerabilityCodeActionProvider } from '../../codeActions/vulnerabilityCodeActionProvider';
import { VulnerabilityCodeActionProviderLS } from '../../codeActions/vulnerabilityCodeActionProviderLS';
import { EditorDecorator } from '../../editor/editorDecorator';
import { VulnerabilityCountHoverProvider } from '../../hoverProvider/vulnerabilityCountHoverProvider';
import { messages } from '../../messages/vulnerabilityCount';
import { OssServiceLanguageServer } from '../../ossServiceLanguageServer';
import { VulnerabilityCountEmitter, VulnerabilityCountEvents } from '../../vulnerabilityCountEmitter';
import { OssService } from '../ossService';
import { ImportedModule, ModuleVulnerabilityCount, ModuleVulnerabilityCountSeverity } from './importedModule';
import { ModuleVulnerabilityCountProvider } from './vulnerabilityCountProvider';
import { ModuleVulnerabilityCountProviderLS } from './vulnerabilityCountProviderLS';

export enum SupportedLanguage {
TypeScript,
Expand All @@ -28,7 +28,7 @@ export enum SupportedLanguage {
PJSON,
}

export class OssVulnerabilityCountService implements Disposable {
export class OssVulnerabilityCountServiceLS implements Disposable {
protected disposables: Disposable[] = [];
protected ossScanFinishedSubscription: Subscription;

Expand All @@ -39,8 +39,8 @@ export class OssVulnerabilityCountService implements Disposable {
private readonly workspace: IVSCodeWorkspace,
private readonly window: IVSCodeWindow,
private readonly languages: IVSCodeLanguages,
private readonly vulnerabilityCountProvider: ModuleVulnerabilityCountProvider,
private readonly ossService: OssService,
private readonly vulnerabilityCountProvider: ModuleVulnerabilityCountProviderLS,
private readonly ossService: OssServiceLanguageServer,
private readonly logger: ILog,
private readonly editorDecorator: EditorDecorator,
private readonly codeActionKindProvider: ICodeActionKindAdapter,
Expand All @@ -67,11 +67,10 @@ export class OssVulnerabilityCountService implements Disposable {
);

// Subscribe to OSS scan finished updates
this.ossScanFinishedSubscription = this.ossService.scanFinished$.subscribe(() => this.processActiveEditor());
this.ossScanFinishedSubscription = this.ossService.newResultAvailable$.subscribe(() => this.processActiveEditor());

[JAVASCRIPT, TYPESCRIPT, PJSON, HTML].forEach(language => {
const provider = new VulnerabilityCodeActionProvider(
this.ossService,
const provider = new VulnerabilityCodeActionProviderLS(
this.vulnerabilityCountProvider,
this.codeActionKindProvider,
this.analytics,
Expand Down Expand Up @@ -188,7 +187,7 @@ export class OssVulnerabilityCountService implements Disposable {

private shouldProcessFile(fileName: string, language: Language): boolean {
if ([Language.TypeScript, Language.JavaScript, Language.PJSON].includes(language)) {
const ossResult = this.ossService.getResultArray();
const ossResult = this.vulnerabilityCountProvider.getResultArray();
if (!ossResult) {
return false;
}
Expand Down Expand Up @@ -254,7 +253,7 @@ export class OssVulnerabilityCountService implements Disposable {
],
module,
);
message += messages.decoratorMessage(module.count);

return message;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { CliError } from '../../../cli/services/cliService';
import { Language } from '../../../common/types';
import { ILanguageClientAdapter } from '../../../common/vscode/languageClient';
import { ITextDocumentAdapter } from '../../../common/vscode/textdocument';
import { InlineValueText, LSPTextDocument } from '../../../common/vscode/types';
import { IUriAdapter } from '../../../common/vscode/uri';
import { OssFileResult, OssResultBody, OssVulnerability, isResultCliError } from '../../ossResult';
import { OssService } from '../ossService';
import { OssFileResult, OssResultBody, OssVulnerability, convertSeverity, isResultCliError } from '../../ossResult';
import { OssServiceLanguageServer } from '../../ossServiceLanguageServer';
import { ImportedModule, ModuleVulnerabilityCount, SeverityCounts } from './importedModule';

export class ModuleVulnerabilityCountProvider {
export class ModuleVulnerabilityCountProviderLS {
constructor(
private readonly ossService: OssService,
private readonly ossService: OssServiceLanguageServer,
private readonly lca: ILanguageClientAdapter,
private readonly uriAdapter: IUriAdapter,
private readonly textDocumentAdapter: ITextDocumentAdapter,
Expand All @@ -29,8 +30,7 @@ export class ModuleVulnerabilityCountProvider {
};

if ([Language.TypeScript, Language.JavaScript, Language.PJSON].includes(language)) {
// TODO use LS when OSS is moved to LS
const ossResult = this.ossService.getResultArray();
const ossResult = this.getResultArray();
if (!ossResult) {
return notCalculated;
}
Expand Down Expand Up @@ -91,7 +91,7 @@ export class ModuleVulnerabilityCountProvider {
continue;
}

const vulnerabilities = this.ossService.getUniqueVulnerabilities((fileResult as OssResultBody).vulnerabilities);
const vulnerabilities = this.getUniqueVulnerabilities((fileResult as OssResultBody).vulnerabilities);

// Sum up all vulnerabilities detected in first-level dependencies by OSS matching the imported module name.
// Ideally we want to use the same mechanism as NPM for determining the version used within users code. For now we stick with direct-vulnerability surfacing only.
Expand Down Expand Up @@ -158,4 +158,73 @@ export class ModuleVulnerabilityCountProvider {
return 0;
})?.[0];
}

public getResultArray = (): ReadonlyArray<OssFileResult> | undefined => {
if (!this.ossService.result) {
return undefined;
}

const tempResultArray: OssFileResult[] = [];
const resultCache = new Map<string, OssResultBody>();

for (const [, value] of this.ossService.result) {
// value is Error
if (value instanceof Error) {
tempResultArray.push(new CliError(value));
}
// value is Issue<T>[]
else {
for (const issue of value) {
// try to access list of vulns for the current file
let res = resultCache.get(issue.filePath);

// add list of vulns to local cache if not there yet
if (res === undefined) {
res = {
path: issue.filePath,
vulnerabilities: [],
projectName: issue.additionalData.projectName,
displayTargetFile: issue.additionalData.displayTargetFile,
packageManager: issue.additionalData.packageManager,
};
resultCache.set(issue.filePath, res);
}

const tempVuln: OssVulnerability = {
id: issue.id,
license: issue.additionalData.license,
identifiers: issue.additionalData.identifiers,
title: issue.title,
description: issue.additionalData.description,
language: issue.additionalData.language,
packageManager: issue.additionalData.packageManager,
packageName: issue.additionalData.packageName,
severity: convertSeverity(issue.severity),
name: issue.additionalData.name,
version: issue.additionalData.version,
exploit: issue.additionalData.exploit,

CVSSv3: issue.additionalData.CVSSv3,
cvssScore: issue.additionalData.cvssScore,

fixedIn: issue.additionalData.fixedIn,
from: issue.additionalData.from,
upgradePath: issue.additionalData.upgradePath,
isPatchable: issue.additionalData.isPatchable,
isUpgradable: issue.additionalData.isUpgradable,
};
res.vulnerabilities.push(tempVuln);
}
}
}

// copy cached results to final result array
resultCache.forEach(value => tempResultArray.push(value));

return tempResultArray;
};

public getUniqueVulnerabilities(vulnerabilities: OssVulnerability[]): OssVulnerability[] {
return vulnerabilities.filter((val, i, arr) => arr.findIndex(el => el.id === val.id) === i);
}
}
Loading

0 comments on commit 49c5d4e

Please sign in to comment.