Skip to content

Commit

Permalink
CM-35353 - Add code action to open violation card (#84)
Browse files Browse the repository at this point in the history
  • Loading branch information
MarshalX authored May 14, 2024
1 parent e7e208b commit 3743e2f
Show file tree
Hide file tree
Showing 12 changed files with 122 additions and 32 deletions.
8 changes: 0 additions & 8 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,6 @@ export async function activate(context: vscode.ExtensionContext) {
const diagnosticCollection =
vscode.languages.createDiagnosticCollection(extensionName);

// FIXME(MarshalX): works well on vscode open,
// but doesn't work when open another detection without closing the restored panel
// don't forget to register in context.subscriptions when will be fixed
// register "onWebviewPanel:detectionDetails" in activationEvents
// const detectionDetainsPanel = vscode.window.registerWebviewPanelSerializer(
// 'detectionDetails', new DetectionDetailsSerializer()
// );

const isAuthed = extensionContext.getGlobalState(VscodeStates.IsAuthorized);
extensionContext.setContext(VscodeStates.IsAuthorized, !!isAuthed);

Expand Down
39 changes: 36 additions & 3 deletions src/providers/code-actions/commonActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,27 @@ import {DiagnosticCode} from '../../services/common';
import {VscodeCommands} from '../../utils/commands';
import {CommandParameters} from '../../cli-wrapper/constants';
import {IgnoreCommandConfig} from '../../types/commands';
import {scanResultsService} from '../../services/ScanResultsService';

export const createIgnoreRuleAction = (
diagnostics: vscode.Diagnostic[], diagnosticCode: DiagnosticCode, document: vscode.TextDocument
): vscode.CodeAction => {
const detection = scanResultsService.getDetectionById(diagnosticCode.uniqueDetectionId);
const ruleId = detection?.detection_rule_id;

const ignoreRuleAction = new vscode.CodeAction(
`ignore rule ${diagnosticCode.ruleId}`,
`ignore rule ${ruleId}`,
vscode.CodeActionKind.QuickFix
);
ignoreRuleAction.command = {
command: VscodeCommands.IgnoreCommandId,
title: `Ignore rule ID: ${diagnosticCode.ruleId}`,
title: `Ignore rule ID: ${ruleId}`,
tooltip: 'This will always ignore this rule type',
arguments: [
{
scanType: diagnosticCode.scanType,
ignoreBy: CommandParameters.ByRule,
param: diagnosticCode.ruleId,
param: ruleId,
document: document,
} as IgnoreCommandConfig,
],
Expand Down Expand Up @@ -56,3 +60,32 @@ export const createIgnorePathAction = (
return ignorePathAction;
};

export const createOpenViolationCardAction = (
diagnostics: vscode.Diagnostic[], diagnosticCode: DiagnosticCode
): vscode.CodeAction => {
const detection = scanResultsService.getDetectionById(diagnosticCode.uniqueDetectionId);

let message = detection?.message;
if (message && message.length > 50) {
message = message.slice(0, 50) + '...';
}

const openViolationCardAction = new vscode.CodeAction(
`open violation card for ${message}`,
vscode.CodeActionKind.QuickFix
);
openViolationCardAction.command = {
command: VscodeCommands.OpenViolationPanel,
title: `Open Violation Card: ${message}`,
tooltip: 'This will open violation card for this detection',
arguments: [
diagnosticCode.scanType,
detection,
],
};
openViolationCardAction.diagnostics = diagnostics;
openViolationCardAction.isPreferred = true;

return openViolationCardAction;
};

3 changes: 2 additions & 1 deletion src/providers/code-actions/iacCodeActions.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import * as vscode from 'vscode';
import {DiagnosticCode} from '../../services/common';
import {createIgnorePathAction, createIgnoreRuleAction} from './commonActions';
import {createIgnorePathAction, createIgnoreRuleAction, createOpenViolationCardAction} from './commonActions';

export const createCommandCodeActions = (
document: vscode.TextDocument,
diagnostics: vscode.Diagnostic[],
diagnosticCode: DiagnosticCode,
): vscode.CodeAction[] => {
return [
createOpenViolationCardAction(diagnostics, diagnosticCode),
createIgnoreRuleAction(diagnostics, diagnosticCode, document),
createIgnorePathAction(diagnostics, diagnosticCode, document),
];
Expand Down
3 changes: 2 additions & 1 deletion src/providers/code-actions/sastCodeActions.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import * as vscode from 'vscode';
import {DiagnosticCode} from '../../services/common';
import {createIgnorePathAction, createIgnoreRuleAction} from './commonActions';
import {createIgnorePathAction, createIgnoreRuleAction, createOpenViolationCardAction} from './commonActions';

export const createCommandCodeActions = (
document: vscode.TextDocument,
diagnostics: vscode.Diagnostic[],
diagnosticCode: DiagnosticCode,
): vscode.CodeAction[] => {
return [
createOpenViolationCardAction(diagnostics, diagnosticCode),
createIgnoreRuleAction(diagnostics, diagnosticCode, document),
createIgnorePathAction(diagnostics, diagnosticCode, document),
];
Expand Down
3 changes: 2 additions & 1 deletion src/providers/code-actions/scaCodeActions.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import * as vscode from 'vscode';
import {DiagnosticCode} from '../../services/common';
import {createIgnorePathAction, createIgnoreRuleAction} from './commonActions';
import {createIgnorePathAction, createIgnoreRuleAction, createOpenViolationCardAction} from './commonActions';

export const createCommandCodeActions = (
document: vscode.TextDocument,
diagnostics: vscode.Diagnostic[],
diagnosticCode: DiagnosticCode,
): vscode.CodeAction[] => {
return [
createOpenViolationCardAction(diagnostics, diagnosticCode),
createIgnoreRuleAction(diagnostics, diagnosticCode, document),
createIgnorePathAction(diagnostics, diagnosticCode, document),
];
Expand Down
3 changes: 2 additions & 1 deletion src/providers/code-actions/secretsCodeActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {DiagnosticCode} from '../../services/common';
import {VscodeCommands} from '../../utils/commands';
import {CommandParameters} from '../../cli-wrapper/constants';
import {IgnoreCommandConfig} from '../../types/commands';
import {createIgnorePathAction, createIgnoreRuleAction} from './commonActions';
import {createIgnorePathAction, createIgnoreRuleAction, createOpenViolationCardAction} from './commonActions';
import {ScanType} from '../../constants';

const createIgnoreValueAction = (
Expand Down Expand Up @@ -41,6 +41,7 @@ export const createCommandCodeActions = (
diagnosticCode: DiagnosticCode,
): vscode.CodeAction[] => {
return [
createOpenViolationCardAction(diagnostics, diagnosticCode),
createIgnoreValueAction(diagnostics, range, document),
createIgnoreRuleAction(diagnostics, diagnosticCode, document),
createIgnorePathAction(diagnostics, diagnosticCode, document),
Expand Down
52 changes: 52 additions & 0 deletions src/services/ScanResultsService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import * as crypto from 'crypto';
import {getWorkspaceState, updateWorkspaceState} from '../utils/context';
import {AnyDetection} from '../types/detection';

const _STORAGE_KEY_PREFIX = 'CS:';

export const calculateUniqueDetectionId = (detection: AnyDetection): string => {
const hash = crypto.createHash('sha256');

const detectionJson = JSON.stringify(detection);
hash.update(detectionJson);

const hexHash = hash.digest('hex');
const shortHashLength = Math.ceil(hexHash.length / 4); // 2 ** 64 combinations
return hexHash.slice(0, shortHashLength);
};

class ScanResultsService {
public getDetectionById(detectionId: string): AnyDetection | undefined {
const value = getWorkspaceState(detectionId);
if (!value) {
return undefined;
}

return getWorkspaceState(detectionId) as AnyDetection;
}

public getDetections(scanType: string): AnyDetection[] {
const scanTypeKey = `${_STORAGE_KEY_PREFIX}${scanType}`;
return getWorkspaceState(scanTypeKey) as AnyDetection[] || [];
}

public saveDetections(scanType: string, detections: AnyDetection[]): void {
detections.forEach((detection) => {
this.saveDetection(scanType, detection);
});
}

public saveDetection(scanType: string, detection: AnyDetection): void {
const scanTypeKey = `${_STORAGE_KEY_PREFIX}${scanType}`;

const detections = getWorkspaceState(scanTypeKey) as AnyDetection[] || [];
detections.push(detection);

updateWorkspaceState(scanTypeKey, detections);

const uniqueDetectionKey = calculateUniqueDetectionId(detection);
updateWorkspaceState(uniqueDetectionKey, detection);
}
}

export const scanResultsService = new ScanResultsService();
8 changes: 4 additions & 4 deletions src/services/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,15 @@ export const finalizeScanState = (state: VscodeStates, success: boolean, progres

export class DiagnosticCode {
scanType: string;
ruleId: string;
uniqueDetectionId: string;

constructor(scanType: ScanType, ruleId: string) {
constructor(scanType: ScanType, uniqueDetectionId: string) {
this.scanType = scanType;
this.ruleId = ruleId;
this.uniqueDetectionId = uniqueDetectionId;
}

toString(): string {
return `${this.scanType}${DIAGNOSTIC_CODE_SEPARATOR}${this.ruleId}`;
return `${this.scanType}${DIAGNOSTIC_CODE_SEPARATOR}${this.uniqueDetectionId}`;
}

static fromString(diagnosticCode: string): DiagnosticCode {
Expand Down
5 changes: 4 additions & 1 deletion src/services/scanners/IacScanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {refreshTreeViewData} from '../../providers/tree-view/utils';
import {TreeView} from '../../providers/tree-view/types';
import {ScanType} from '../../constants';
import {VscodeStates} from '../../utils/states';
import {calculateUniqueDetectionId, scanResultsService} from '../ScanResultsService';

interface IacScanParams {
pathToScan: string;
Expand Down Expand Up @@ -176,7 +177,7 @@ const detectionsToDiagnostics = async (
);

diagnostic.source = extensionId;
diagnostic.code = new DiagnosticCode(ScanType.Iac, detection.detection_rule_id).toString();
diagnostic.code = new DiagnosticCode(ScanType.Iac, calculateUniqueDetectionId(detection)).toString();

result[documentPath] = result[documentPath] || [];
result[documentPath].push(diagnostic);
Expand Down Expand Up @@ -204,6 +205,8 @@ const handleScanDetections = async (
TrayNotifications.showProblemsDetection(detections.length, ScanType.Iac);
}

scanResultsService.saveDetections(ScanType.Iac, detections);

refreshTreeViewData({
detections,
treeView: treeView,
Expand Down
5 changes: 4 additions & 1 deletion src/services/scanners/SastScanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {refreshTreeViewData} from '../../providers/tree-view/utils';
import {TreeView} from '../../providers/tree-view/types';
import {ScanType} from '../../constants';
import {VscodeStates} from '../../utils/states';
import {calculateUniqueDetectionId, scanResultsService} from '../ScanResultsService';

interface SastScanParams {
pathToScan: string;
Expand Down Expand Up @@ -167,7 +168,7 @@ const detectionsToDiagnostics = async (
);

diagnostic.source = extensionId;
diagnostic.code = new DiagnosticCode(ScanType.Sast, detection.detection_rule_id).toString();
diagnostic.code = new DiagnosticCode(ScanType.Sast, calculateUniqueDetectionId(detection)).toString();

result[documentPath] = result[documentPath] || [];
result[documentPath].push(diagnostic);
Expand Down Expand Up @@ -195,6 +196,8 @@ const handleScanDetections = async (
TrayNotifications.showProblemsDetection(detections.length, ScanType.Sast);
}

scanResultsService.saveDetections(ScanType.Sast, detections);

refreshTreeViewData({
detections,
treeView: treeView,
Expand Down
14 changes: 7 additions & 7 deletions src/services/scanners/ScaScanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@ import * as vscode from 'vscode';
import {extensionOutput} from '../../logging/extension-output';
import {cliWrapper} from '../../cli-wrapper/cli-wrapper';
import statusBar from '../../utils/status-bar';
import {extensionId, StatusBarTexts} from '../../utils/texts';
import {
StatusBarTexts,
extensionId,
} from '../../utils/texts';
import {
finalizeScanState,
DiagnosticCode,
finalizeScanState,
updateDetectionState,
validateCliCommonErrors,
validateCliCommonScanErrors,
updateDetectionState,
} from '../common';
import {getWorkspaceState, updateWorkspaceState} from '../../utils/context';
import {ScaDetection} from '../../types/detection';
Expand All @@ -22,6 +19,7 @@ import {TreeView} from '../../providers/tree-view/types';
import {refreshTreeViewData} from '../../providers/tree-view/utils';
import {getPackageFileForLockFile, isSupportedLockFile, ScanType} from '../../constants';
import {VscodeStates} from '../../utils/states';
import {calculateUniqueDetectionId, scanResultsService} from '../ScanResultsService';

interface ScaScanParams {
pathToScan: string;
Expand Down Expand Up @@ -143,7 +141,7 @@ const detectionsToDiagnostics = async (
);

diagnostic.source = extensionId;
diagnostic.code = new DiagnosticCode(ScanType.Sca, detection.detection_rule_id).toString();
diagnostic.code = new DiagnosticCode(ScanType.Sca, calculateUniqueDetectionId(detection)).toString();

result[file_name] = result[file_name] || [];
result[file_name].push(diagnostic);
Expand Down Expand Up @@ -179,6 +177,8 @@ const handleScanDetections = async (
TrayNotifications.showProblemsDetection(result.detections.length, ScanType.Sca);
}

scanResultsService.saveDetections(ScanType.Sca, detections);

refreshTreeViewData({
detections,
treeView,
Expand Down
11 changes: 7 additions & 4 deletions src/services/scanners/SecretScanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import {cliWrapper} from '../../cli-wrapper/cli-wrapper';
import statusBar from '../../utils/status-bar';
import {extensionId, StatusBarTexts, TrayNotificationTexts} from '../../utils/texts';
import {
finalizeScanState,
DiagnosticCode,
finalizeScanState,
updateDetectionState,
validateCliCommonErrors,
validateCliCommonScanErrors,
updateDetectionState,
} from '../common';
import {getWorkspaceState, updateWorkspaceState} from '../../utils/context';
import {SecretDetection} from '../../types/detection';
Expand All @@ -19,6 +19,7 @@ import {refreshTreeViewData} from '../../providers/tree-view/utils';
import {TreeView} from '../../providers/tree-view/types';
import {ScanType} from '../../constants';
import {VscodeStates} from '../../utils/states';
import {calculateUniqueDetectionId, scanResultsService} from '../ScanResultsService';

interface SecretScanParams {
pathToScan: string;
Expand Down Expand Up @@ -141,7 +142,7 @@ const detectionsToDiagnostics = async (
);
const endPosition = document?.positionAt(
detection.detection_details.start_position +
detection.detection_details.length
detection.detection_details.length
);

if (!startPosition || !endPosition) {
Expand All @@ -163,7 +164,7 @@ const detectionsToDiagnostics = async (
);

diagnostic.source = extensionId;
diagnostic.code = new DiagnosticCode(ScanType.Secrets, detection.detection_rule_id).toString();
diagnostic.code = new DiagnosticCode(ScanType.Secrets, calculateUniqueDetectionId(detection)).toString();

result[documentPath] = result[documentPath] || [];
result[documentPath].push(diagnostic);
Expand Down Expand Up @@ -195,6 +196,8 @@ const handleScanDetections = async (
TrayNotifications.showProblemsDetection(result.detections.length, ScanType.Secrets);
}

scanResultsService.saveDetections(ScanType.Secrets, detections);

refreshTreeViewData({
detections,
treeView: treeView,
Expand Down

0 comments on commit 3743e2f

Please sign in to comment.