Skip to content

Commit e86fcb8

Browse files
Merge pull request #41 from GitGuardian/jgriffe/prevent-scanning-gitignored-files
Prevent scanning gitignored files
2 parents 47f2611 + ffd99d0 commit e86fcb8

File tree

6 files changed

+229
-86
lines changed

6 files changed

+229
-86
lines changed

src/extension.ts

Lines changed: 14 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,39 @@
11
import {
2+
cleanUpFileDiagnostics,
3+
createDiagnosticCollection,
24
ggshieldApiKey,
35
ggshieldAuthStatus,
4-
ggshieldScanFile,
56
ignoreLastFound,
67
ignoreSecret,
78
loginGGShield,
89
logoutGGShield,
10+
scanFile,
911
showAPIQuota,
1012
} from "./lib/ggshield-api";
1113
import {
1214
getConfiguration,
13-
GGShieldConfiguration,
1415
setApiKey,
1516
} from "./lib/ggshield-configuration";
16-
import { parseGGShieldResults } from "./lib/ggshield-results-parser";
1717
import {
18-
Diagnostic,
19-
DiagnosticCollection,
2018
ExtensionContext,
2119
Uri,
2220
commands,
2321
languages,
2422
window,
2523
workspace,
26-
StatusBarItem,
27-
StatusBarAlignment,
2824
WebviewView,
2925
} from "vscode";
3026
import { GGShieldResolver } from "./lib/ggshield-resolver";
3127
import { getCurrentFile, isGitInstalled } from "./utils";
3228
import { GitGuardianWebviewProvider } from "./ggshield-webview/gitguardian-webview-view";
33-
import { StatusBarStatus, updateStatusBarItem } from "./gitguardian-interface/gitguardian-status-bar";
29+
import { createStatusBarItem, StatusBarStatus, updateStatusBarItem } from "./gitguardian-interface/gitguardian-status-bar";
3430
import {
3531
generateSecretName,
3632
GitGuardianSecretHoverProvider,
3733
} from "./gitguardian-interface/gitguardian-hover-provider";
3834
import { GitGuardianQuotaWebviewProvider } from "./ggshield-webview/gitguardian-quota-webview";
3935
import { GitGuardianRemediationMessageWebviewProvider } from "./ggshield-webview/gitguardian-remediation-message-view";
4036

41-
/**
42-
* Extension diagnostic collection
43-
*/
44-
let diagnosticCollection: DiagnosticCollection;
45-
let statusBar: StatusBarItem;
46-
47-
/**
48-
* Scan a file using ggshield
49-
*
50-
* - retrieve configuration
51-
* - scan file using ggshield CLI application
52-
* - parse ggshield results
53-
* - set diagnostics collection so the incdients are visible to the user
54-
*
55-
* @param filePath path to file
56-
* @param fileUri file uri
57-
*/
58-
async function scanFile(
59-
this: any,
60-
filePath: string,
61-
fileUri: Uri,
62-
configuration: GGShieldConfiguration
63-
): Promise<void> {
64-
const results = ggshieldScanFile(filePath, configuration);
65-
if (!results) {
66-
updateStatusBarItem(StatusBarStatus.ready, statusBar);
67-
return;
68-
}
69-
let incidentsDiagnostics: Diagnostic[] = parseGGShieldResults(results);
70-
if (incidentsDiagnostics.length !== 0) {
71-
updateStatusBarItem(StatusBarStatus.secretFound, statusBar);
72-
} else {
73-
updateStatusBarItem(StatusBarStatus.noSecretFound, statusBar);
74-
}
75-
diagnosticCollection.set(fileUri, incidentsDiagnostics);
76-
}
77-
78-
/**
79-
* Clean up file diagnostics
80-
*
81-
* @param fileUri file uri
82-
*/
83-
function cleanUpFileDiagnostics(fileUri: Uri): void {
84-
diagnosticCollection.delete(fileUri);
85-
}
8637

8738
function registerOpenViewsCommands(
8839
context: ExtensionContext,
@@ -162,13 +113,11 @@ export function activate(context: ExtensionContext) {
162113
);
163114
context.subscriptions.push(ggshieldViewProvider, ggshieldRemediationMessageViewProvider, ggshieldQuotaViewProvider);
164115

165-
statusBar = window.createStatusBarItem(StatusBarAlignment.Left, 0);
166-
updateStatusBarItem(StatusBarStatus.initialization, statusBar);
116+
createStatusBarItem(context);
167117

168118
//generic commands to open correct view on status bar click
169119
registerOpenViewsCommands(context, outputChannel);
170120
registerQuotaViewCommands(ggshieldQuotaViewProvider);
171-
context.subscriptions.push(statusBar);
172121

173122
context.subscriptions.push(
174123
languages.registerHoverProvider("*", new GitGuardianSecretHoverProvider())
@@ -180,14 +129,14 @@ export function activate(context: ExtensionContext) {
180129
// Check if ggshield is authenticated
181130
ggshieldAuthStatus(configuration, context);
182131
if (context.globalState.get("isAuthenticated", false)) {
183-
updateStatusBarItem(StatusBarStatus.ready, statusBar);
132+
updateStatusBarItem(StatusBarStatus.ready);
184133
setApiKey(configuration, ggshieldApiKey(configuration));
185134
ggshieldViewProvider.refresh();
186135
ggshieldRemediationMessageViewProvider.refresh();
187136
ggshieldQuotaViewProvider.refresh();
188137

189138
} else {
190-
updateStatusBarItem(StatusBarStatus.unauthenticated, statusBar);
139+
updateStatusBarItem(StatusBarStatus.unauthenticated);
191140
}
192141
})
193142
.then(async () => {
@@ -203,9 +152,7 @@ export function activate(context: ExtensionContext) {
203152
.then(() => {
204153
// Start scanning documents on activation events
205154
// (i.e. when a new document is opened or when the document is saved)
206-
diagnosticCollection = languages.createDiagnosticCollection("ggshield");
207-
208-
context.subscriptions.push(diagnosticCollection);
155+
createDiagnosticCollection(context);
209156
context.subscriptions.push(
210157
workspace.onDidSaveTextDocument((textDocument) => {
211158
// Check if the document is inside the workspace
@@ -249,7 +196,7 @@ export function activate(context: ExtensionContext) {
249196
scanFile(
250197
currentFile,
251198
Uri.file(currentFile),
252-
ggshieldResolver.configuration
199+
ggshieldResolver.configuration,
253200
);
254201
}
255202
),
@@ -262,10 +209,10 @@ export function activate(context: ExtensionContext) {
262209
context
263210
).then(() => {
264211
if (context.globalState.get("isAuthenticated", false)) {
265-
updateStatusBarItem(StatusBarStatus.ready, statusBar);
212+
updateStatusBarItem(StatusBarStatus.ready);
266213
setApiKey(configuration, ggshieldApiKey(configuration));
267214
} else {
268-
updateStatusBarItem(StatusBarStatus.unauthenticated, statusBar);
215+
updateStatusBarItem(StatusBarStatus.unauthenticated);
269216
}
270217
ggshieldViewProvider.refresh();
271218
ggshieldRemediationMessageViewProvider.refresh();
@@ -276,7 +223,7 @@ export function activate(context: ExtensionContext) {
276223
}),
277224
commands.registerCommand("gitguardian.logout", async () => {
278225
logoutGGShield(ggshieldResolver.configuration, context);
279-
updateStatusBarItem(StatusBarStatus.unauthenticated, statusBar);
226+
updateStatusBarItem(StatusBarStatus.unauthenticated);
280227
setApiKey(configuration, undefined);
281228
ggshieldViewProvider.refresh();
282229
ggshieldRemediationMessageViewProvider.refresh();
@@ -286,13 +233,8 @@ export function activate(context: ExtensionContext) {
286233
})
287234
.catch((error) => {
288235
outputChannel.appendLine(`Error: ${error.message}`);
289-
updateStatusBarItem(StatusBarStatus.error, statusBar);
236+
updateStatusBarItem(StatusBarStatus.error);
290237
});
291238
}
292239

293-
export function deactivate() {
294-
if (diagnosticCollection) {
295-
diagnosticCollection.dispose();
296-
statusBar.dispose();
297-
}
298-
}
240+
export function deactivate() {}

src/gitguardian-interface/gitguardian-status-bar.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { StatusBarItem, ThemeColor } from "vscode";
1+
import { ExtensionContext, StatusBarAlignment, StatusBarItem, ThemeColor, window } from "vscode";
2+
3+
4+
let statusBarItem: StatusBarItem;
5+
26

37
export interface StatusBarConfig {
48
text: string;
@@ -14,9 +18,16 @@ export enum StatusBarStatus {
1418
secretFound = "Secret found",
1519
noSecretFound = "No secret found",
1620
error = "Error",
21+
ignoredFile = "Ignored file",
22+
}
23+
24+
export function createStatusBarItem(context: ExtensionContext): void {
25+
statusBarItem = window.createStatusBarItem(StatusBarAlignment.Left, 0);
26+
updateStatusBarItem(StatusBarStatus.initialization);
27+
context.subscriptions.push(statusBarItem);
1728
}
1829

19-
export function getStatusBarConfig(status: StatusBarStatus): StatusBarConfig {
30+
function getStatusBarConfig(status: StatusBarStatus): StatusBarConfig {
2031
switch (status) {
2132
case StatusBarStatus.initialization:
2233
return {
@@ -57,14 +68,18 @@ export function getStatusBarConfig(status: StatusBarStatus): StatusBarConfig {
5768
color: "statusBarItem.errorBackground",
5869
command: "gitguardian.showOutput",
5970
};
71+
case StatusBarStatus.ignoredFile:
72+
return {
73+
text: "GitGuardian - Ignored file",
74+
color: "statusBarItem.warningBackground",
75+
};
6076
default:
6177
return { text: "", color: "statusBar.foreground" };
6278
}
6379
}
6480

6581
export function updateStatusBarItem(
6682
status: StatusBarStatus,
67-
statusBarItem: StatusBarItem
6883
): void {
6984
const config = getStatusBarConfig(status);
7085
statusBarItem.text = config.text;

src/lib/ggshield-api.ts

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,20 @@ import {
33
SpawnOptionsWithoutStdio,
44
spawn,
55
} from "child_process";
6-
import { window, WebviewView, ExtensionContext, commands } from "vscode";
6+
import { window, WebviewView, DiagnosticCollection, commands, ExtensionContext, languages, Uri, Diagnostic } from "vscode";
77
import axios from 'axios';
88
import { GGShieldConfiguration } from "./ggshield-configuration";
99
import { GGShieldScanResults } from "./api-types";
1010
import * as os from "os";
11-
import { apiToDashboard, dasboardToApi } from "../utils";
11+
import { apiToDashboard, dasboardToApi, isFileGitignored } from "../utils";
1212
import { runGGShieldCommand } from "./run-ggshield";
13+
import { StatusBarStatus, updateStatusBarItem } from "../gitguardian-interface/gitguardian-status-bar";
14+
import { parseGGShieldResults } from "./ggshield-results-parser";
1315

16+
/**
17+
* Extension diagnostic collection
18+
*/
19+
export let diagnosticCollection: DiagnosticCollection;
1420

1521
/**
1622
* Display API quota
@@ -115,19 +121,42 @@ export function ignoreSecret(
115121
}
116122
}
117123

124+
export function createDiagnosticCollection(context: ExtensionContext): void {
125+
diagnosticCollection = languages.createDiagnosticCollection("ggshield");
126+
context.subscriptions.push(diagnosticCollection);
127+
}
128+
129+
/**
130+
* Clean up file diagnostics
131+
*
132+
* @param fileUri file uri
133+
*/
134+
export function cleanUpFileDiagnostics(fileUri: Uri): void {
135+
diagnosticCollection.delete(fileUri);
136+
}
137+
138+
118139
/**
119-
* Scan a file using ggshield CLI application
140+
* Scan a file using ggshield
120141
*
121-
* Show error messages on failure
142+
* - retrieve configuration
143+
* - scan file using ggshield CLI application
144+
* - parse ggshield results
145+
* - set diagnostics collection so the incdients are visible to the user
122146
*
123147
* @param filePath path to file
124-
* @param configuration ggshield configuration
125-
* @returns results or undefined if there was an error
148+
* @param fileUri file uri
126149
*/
127-
export function ggshieldScanFile(
150+
export async function scanFile(
151+
this: any,
128152
filePath: string,
153+
fileUri: Uri,
129154
configuration: GGShieldConfiguration
130-
): GGShieldScanResults | undefined {
155+
): Promise<void> {
156+
if (isFileGitignored(filePath)) {
157+
updateStatusBarItem(StatusBarStatus.ignoredFile);
158+
return;
159+
}
131160
const proc = runGGShieldCommand(configuration, [
132161
"secret",
133162
"scan",
@@ -155,9 +184,22 @@ export function ggshieldScanFile(
155184
return undefined;
156185
}
157186

158-
return JSON.parse(proc.stdout);
187+
const results = JSON.parse(proc.stdout);
188+
if (!results) {
189+
updateStatusBarItem(StatusBarStatus.ready);
190+
return;
191+
}
192+
let incidentsDiagnostics: Diagnostic[] = parseGGShieldResults(results);
193+
if (incidentsDiagnostics.length !== 0) {
194+
updateStatusBarItem(StatusBarStatus.secretFound);
195+
} else {
196+
updateStatusBarItem(StatusBarStatus.noSecretFound);
197+
}
198+
199+
diagnosticCollection.set(fileUri, incidentsDiagnostics);
159200
}
160201

202+
161203
export async function loginGGShield(
162204
configuration: GGShieldConfiguration,
163205
outputChannel: any,

src/test/constants.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
export const scanResultsNoIncident =
2+
'{"id": "test.py", "type": "path_scan", "total_incidents": 0, "total_occurrences": 0}';
3+
4+
export const scanResultsWithIncident = `{
5+
"id":"test.py",
6+
"type":"path_scan",
7+
"entities_with_incidents":[
8+
{
9+
"mode":"FILE",
10+
"filename":"test.py",
11+
"incidents":[
12+
{
13+
"policy":"Secrets detection",
14+
"occurrences":[
15+
{
16+
"match":"DDACC73DdB04********************************************057c78317C39",
17+
"type":"apikey",
18+
"line_start":4,
19+
"line_end":4,
20+
"index_start":11,
21+
"index_end":79,
22+
"pre_line_start":4,
23+
"pre_line_end":4
24+
}
25+
],
26+
"type":"Generic High Entropy Secret",
27+
"validity":"no_checker",
28+
"ignore_sha":"38353eb1a2aac5b24f39ed67912234d4b4a2e23976d504a88b28137ed2b9185e",
29+
"total_occurrences":1,
30+
"incident_url":"",
31+
"known_secret":false
32+
}
33+
],
34+
"total_incidents":1,
35+
"total_occurrences":1
36+
}
37+
],
38+
"total_incidents":1,
39+
"total_occurrences":1,
40+
"secrets_engine_version":"2.96.0"
41+
}`;

0 commit comments

Comments
 (0)