Skip to content

Commit 337794a

Browse files
authored
Merge pull request #38 from GitGuardian/salomevoltz/load_env_for_configuration
Refacto authentication and add tests suite
2 parents 26dc552 + 51d041c commit 337794a

12 files changed

+524
-400
lines changed

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,8 @@ tsconfig.base.tsbuildinfo%
3434
out/
3535

3636
# build files
37-
**/*.vsix
37+
**/*.vsix
38+
39+
# cache dir
40+
.cache_ggshield
41+
.vscode-test/

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,13 +135,15 @@
135135
"@types/glob": "^8.1.0",
136136
"@types/mocha": "^10.0.1",
137137
"@types/node": "20.2.5",
138+
"@types/simple-mock": "^0.8.6",
138139
"@types/vscode": "^1.81.0",
139140
"@typescript-eslint/eslint-plugin": "^5.59.8",
140141
"@typescript-eslint/parser": "^5.59.8",
141142
"@vscode/test-electron": "^2.3.2",
142143
"eslint": "^8.41.0",
143144
"glob": "^8.1.0",
144145
"mocha": "^10.2.0",
146+
"simple-mock": "^0.8.0",
145147
"typescript": "^5.1.3"
146148
},
147149
"dependencies": {

src/extension.ts

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -125,26 +125,31 @@ function registerQuotaViewCommands(view: GitGuardianQuotaWebviewProvider) {
125125

126126
export function activate(context: ExtensionContext) {
127127
// Check if ggshield if available
128+
commands.executeCommand('setContext', 'isAuthenticated', false);
129+
128130
const outputChannel = window.createOutputChannel("GitGuardian");
129131
let configuration = getConfiguration(context);
130-
let authStatus: boolean = false;
132+
131133
const ggshieldResolver = new GGShieldResolver(
132134
outputChannel,
133135
context,
134136
configuration
135137
);
136138
const ggshieldViewProvider = new GitGuardianWebviewProvider(
137139
configuration,
138-
context.extensionUri
140+
context.extensionUri,
141+
context
139142
);
140143

141144
const ggshieldRemediationMessageViewProvider = new GitGuardianRemediationMessageWebviewProvider(
142145
configuration,
143-
context.extensionUri
146+
context.extensionUri,
147+
context
144148
);
145149
const ggshieldQuotaViewProvider = new GitGuardianQuotaWebviewProvider(
146150
configuration,
147-
context.extensionUri
151+
context.extensionUri,
152+
context
148153
);
149154
window.registerWebviewViewProvider("gitguardianView", ggshieldViewProvider);
150155
window.registerWebviewViewProvider(
@@ -173,15 +178,16 @@ export function activate(context: ExtensionContext) {
173178
.checkGGShieldConfiguration()
174179
.then(() => {
175180
// Check if ggshield is authenticated
176-
authStatus = ggshieldAuthStatus(configuration);
177-
const ggshieldApi = ggshieldApiKey(configuration);
178-
if (authStatus && ggshieldApi) {
179-
commands.executeCommand('setContext', 'isAuthenticated', true);
181+
ggshieldAuthStatus(configuration, context);
182+
if (context.globalState.get("isAuthenticated", false)) {
180183
updateStatusBarItem(StatusBarStatus.ready, statusBar);
181-
setApiKey(configuration, ggshieldApi);
184+
setApiKey(configuration, ggshieldApiKey(configuration));
185+
ggshieldViewProvider.refresh();
186+
ggshieldRemediationMessageViewProvider.refresh();
187+
ggshieldQuotaViewProvider.refresh();
188+
182189
} else {
183190
updateStatusBarItem(StatusBarStatus.unauthenticated, statusBar);
184-
authStatus = false;
185191
}
186192
})
187193
.then(async () => {
@@ -204,7 +210,9 @@ export function activate(context: ExtensionContext) {
204210
workspace.onDidSaveTextDocument((textDocument) => {
205211
// Check if the document is inside the workspace
206212
const workspaceFolder = workspace.getWorkspaceFolder(textDocument.uri);
207-
if (authStatus && workspaceFolder) {
213+
console.log(context.globalState.get("isAuthenticated", false), workspaceFolder);
214+
console.log('»»»»»»»»»»»»»»', context.globalState.get("isAuthenticated", false) && workspaceFolder);
215+
if (context.globalState.get("isAuthenticated", false) && workspaceFolder) {
208216
scanFile(
209217
textDocument.fileName,
210218
textDocument.uri,
@@ -247,28 +255,28 @@ export function activate(context: ExtensionContext) {
247255
),
248256
commands.registerCommand("gitguardian.authenticate", async () => {
249257
commands.executeCommand("gitguardian.openSidebar");
250-
const isAuthenticated = await loginGGShield(
258+
await loginGGShield(
251259
ggshieldResolver.configuration,
252260
outputChannel,
253-
ggshieldViewProvider.getView() as WebviewView
254-
);
255-
if (isAuthenticated) {
256-
authStatus = true;
257-
updateStatusBarItem(StatusBarStatus.ready, statusBar);
258-
commands.executeCommand('setContext', 'isAuthenticated', true);
259-
const ggshieldApi = ggshieldApiKey(configuration);
260-
setApiKey(configuration, ggshieldApi);
261+
ggshieldViewProvider.getView() as WebviewView,
262+
context
263+
).then(() => {
264+
if (context.globalState.get("isAuthenticated", false)) {
265+
updateStatusBarItem(StatusBarStatus.ready, statusBar);
266+
setApiKey(configuration, ggshieldApiKey(configuration));
267+
} else {
268+
updateStatusBarItem(StatusBarStatus.unauthenticated, statusBar);
269+
}
261270
ggshieldViewProvider.refresh();
262271
ggshieldRemediationMessageViewProvider.refresh();
263272
ggshieldQuotaViewProvider.refresh();
264-
} else {
265-
updateStatusBarItem(StatusBarStatus.unauthenticated, statusBar);
266-
}
273+
}).catch((err) => {
274+
outputChannel.appendLine(`Authentication failed: ${err.message}`);
275+
});
267276
}),
268277
commands.registerCommand("gitguardian.logout", async () => {
269-
logoutGGShield(ggshieldResolver.configuration);
278+
logoutGGShield(ggshieldResolver.configuration, context);
270279
updateStatusBarItem(StatusBarStatus.unauthenticated, statusBar);
271-
commands.executeCommand('setContext', 'isAuthenticated', false);
272280
setApiKey(configuration, undefined);
273281
ggshieldViewProvider.refresh();
274282
ggshieldRemediationMessageViewProvider.refresh();

src/ggshield-webview/gitguardian-quota-webview.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getAPIquota, ggshieldAuthStatus } from "../lib/ggshield-api";
1+
import { getAPIquota } from "../lib/ggshield-api";
22
import { GGShieldConfiguration } from "../lib/ggshield-configuration";
33
import * as vscode from "vscode";
44

@@ -13,7 +13,8 @@ export class GitGuardianQuotaWebviewProvider
1313

1414
constructor(
1515
private ggshieldConfiguration: GGShieldConfiguration,
16-
private readonly _extensionUri: vscode.Uri
16+
private readonly _extensionUri: vscode.Uri,
17+
private context: vscode.ExtensionContext
1718
) {
1819
this.checkAuthenticationStatus();
1920
this.updateQuota();
@@ -36,7 +37,7 @@ export class GitGuardianQuotaWebviewProvider
3637
}
3738

3839
private async checkAuthenticationStatus() {
39-
this.isAuthenticated = ggshieldAuthStatus(this.ggshieldConfiguration);
40+
this.isAuthenticated = this.context.globalState.get("isAuthenticated", false);
4041
}
4142

4243
private async updateQuota() {

src/ggshield-webview/gitguardian-remediation-message-view.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getRemediationMessage, ggshieldAuthStatus } from "../lib/ggshield-api";
1+
import { getRemediationMessage } from "../lib/ggshield-api";
22
import { GGShieldConfiguration } from "../lib/ggshield-configuration";
33
import * as vscode from "vscode";
44

@@ -13,7 +13,8 @@ export class GitGuardianRemediationMessageWebviewProvider
1313

1414
constructor(
1515
private ggshieldConfiguration: GGShieldConfiguration,
16-
private readonly _extensionUri: vscode.Uri
16+
private readonly _extensionUri: vscode.Uri,
17+
private context: vscode.ExtensionContext
1718
) {
1819
this.checkAuthenticationStatus();
1920
this.updateRemediationMessage();
@@ -36,7 +37,7 @@ export class GitGuardianRemediationMessageWebviewProvider
3637
}
3738

3839
private async checkAuthenticationStatus() {
39-
this.isAuthenticated = ggshieldAuthStatus(this.ggshieldConfiguration);
40+
this.isAuthenticated = this.context.globalState.get("isAuthenticated", false);
4041
}
4142

4243
private async updateRemediationMessage() {
@@ -51,7 +52,7 @@ export class GitGuardianRemediationMessageWebviewProvider
5152
}
5253
}
5354

54-
private escapeHtml(unsafe: string):string {
55+
private escapeHtml(unsafe: string): string {
5556
return unsafe
5657
.replace(/&/g, "&")
5758
.replace(/</g, "&lt;")
@@ -97,7 +98,7 @@ ${this.escapeHtml(this.remediationMessage)}
9798
this.updateWebViewContent(this._view);
9899

99100
await this.checkAuthenticationStatus();
100-
console.log("Well authentificated");
101+
console.log("Well authenticated");
101102
await this.updateRemediationMessage();
102103

103104
this.isLoading = false;

src/ggshield-webview/gitguardian-webview-view.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import * as vscode from "vscode";
22
import { GGShieldConfiguration } from "../lib/ggshield-configuration";
3-
import { ggshieldAuthStatus } from "../lib/ggshield-api";
43

54
const projectDiscussionUri = vscode.Uri.parse(
65
"https://github.com/GitGuardian/gitguardian-vscode/discussions"
@@ -19,7 +18,8 @@ export class GitGuardianWebviewProvider implements vscode.WebviewViewProvider {
1918

2019
constructor(
2120
private ggshieldConfiguration: GGShieldConfiguration,
22-
private readonly _extensionUri: vscode.Uri
21+
private readonly _extensionUri: vscode.Uri,
22+
private context: vscode.ExtensionContext
2323
) {
2424
this.checkAuthenticationStatus();
2525
}
@@ -54,7 +54,7 @@ export class GitGuardianWebviewProvider implements vscode.WebviewViewProvider {
5454

5555

5656
private async checkAuthenticationStatus() {
57-
this.isAuthenticated = ggshieldAuthStatus(this.ggshieldConfiguration);
57+
this.isAuthenticated = this.context.globalState.get("isAuthenticated", false);
5858
}
5959

6060
private updateWebViewContent(webviewView?: vscode.WebviewView) {

src/lib/ggshield-api.ts

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
SpawnOptionsWithoutStdio,
44
spawn,
55
} from "child_process";
6-
import { window, WebviewView } from "vscode";
6+
import { window, WebviewView, ExtensionContext, commands } from "vscode";
77
import axios from 'axios';
88
import { GGShieldConfiguration } from "./ggshield-configuration";
99
import { GGShieldScanResults } from "./api-types";
@@ -162,7 +162,8 @@ export async function loginGGShield(
162162
configuration: GGShieldConfiguration,
163163
outputChannel: any,
164164
webviewView: WebviewView,
165-
): Promise<boolean> {
165+
context: ExtensionContext
166+
): Promise<void> {
166167
const { ggshieldPath, apiUrl, apiKey } = configuration;
167168

168169
let options: SpawnOptionsWithoutStdio = {
@@ -176,9 +177,9 @@ export async function loginGGShield(
176177
windowsHide: true,
177178
};
178179

179-
let args = ["auth", "login", "--method=web"];
180+
let args = ["auth", "login", "--method=web", "--debug"];
180181

181-
return new Promise((resolve) => {
182+
return new Promise<void>((resolve, reject) => {
182183
const proc = spawn(ggshieldPath, args, options);
183184

184185
proc.stdout.on("data", (data) => {
@@ -194,52 +195,59 @@ export async function loginGGShield(
194195
});
195196
}
196197
});
197-
;
198198

199199
proc.stderr.on("data", (data) => {
200200
outputChannel.appendLine(`ggshield stderr: ${data.toString()}`);
201201
});
202202

203-
proc.on("close", (code) => {
203+
proc.on("close", async (code) => {
204204
if (code !== 0) {
205205
outputChannel.appendLine(`ggshield process exited with code ${code}`);
206-
resolve(false);
206+
reject(new Error(`ggshield process exited with code ${code}`));
207207
} else {
208208
outputChannel.appendLine("ggshield login completed successfully");
209-
resolve(true);
209+
commands.executeCommand("setContext", "isAuthenticated", true);
210+
await context.globalState.update('isAuthenticated', true);
211+
resolve();
210212
}
211213
});
212214

213215
proc.on("error", (err) => {
214216
outputChannel.appendLine(`ggshield process error: ${err.message}`);
215-
resolve(false);
217+
reject(err);
216218
});
217219
});
218220
}
219221

220-
export function logoutGGShield(
221-
configuration: GGShieldConfiguration
222-
): void {
222+
223+
export async function logoutGGShield(
224+
configuration: GGShieldConfiguration,
225+
context: ExtensionContext
226+
): Promise<void> {
223227
runGGShieldCommand(configuration, ["auth", "logout"]);
228+
commands.executeCommand('setContext', 'isAuthenticated', false);
229+
await context.globalState.update('isAuthenticated', false);
230+
224231
}
225232

226-
export function ggshieldAuthStatus(
227-
configuration: GGShieldConfiguration
228-
): boolean {
229-
const proc = runGGShieldCommand(configuration, ["api-status"]);
230-
if (proc.stderr || proc.error) {
231-
if (proc.stderr.includes("Config key")){
232-
window.showErrorMessage(`Gitguardian: ${proc.stderr}`);
233-
}
234-
console.log(proc.stderr);
235-
return false;
236-
} else {
237-
if (proc.stdout.includes("unhealthy")) {
238-
return false;
233+
export async function ggshieldAuthStatus(
234+
configuration: GGShieldConfiguration,
235+
context: ExtensionContext
236+
): Promise<void> {
237+
let isAuthenticated: boolean;
238+
const proc = runGGShieldCommand(configuration, ["api-status", "--json"]);
239+
if (proc.status === 0 && JSON.parse(proc.stdout).status_code === 200) {
240+
isAuthenticated = true;
239241
}
240-
console.log(proc.stdout);
241-
return true;
242-
}
242+
else{
243+
if (proc.stderr && proc.stderr.includes("Config key")){
244+
window.showErrorMessage(`Gitguardian: ${proc.stderr}`);
245+
}
246+
console.log(proc.stderr);
247+
isAuthenticated = false;
248+
}
249+
commands.executeCommand('setContext', 'isAuthenticated', isAuthenticated);
250+
await context.globalState.update('isAuthenticated', isAuthenticated);
243251
}
244252

245253
/**

src/lib/ggshield-resolver.ts

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ export class GGShieldResolver {
2424
* Ensures the availability of ggshield by determining the executable path.
2525
*
2626
* The function performs the following checks in order:
27-
* 2. Checks if a custom path is configured in the settings and uses it.
28-
* 3. Else, falls back to using the standalone version bundled with the extension.
27+
* 1. Checks if a custom path is configured in the settings and uses it.
28+
* 2. Else, falls back to using the standalone version bundled with the extension.
2929
*
3030
* @returns {Promise<void>} A promise that resolves once the `ggshield` path is determined.
3131
*/
@@ -56,24 +56,12 @@ export class GGShieldResolver {
5656
async testConfiguration(
5757
configuration: GGShieldConfiguration
5858
): Promise<void> {
59-
let proc = runGGShieldCommand(configuration, ["quota"]);
60-
if (proc.error || proc.stderr.length > 0) {
61-
if (proc.error) {
62-
if (proc.error.message.includes("ENOENT")) {
63-
throw new Error(
64-
`GGShield path provided in settings is invalid: ${configuration.ggshieldPath}.`
65-
);
66-
} else {
67-
throw new Error(proc.error.message);
68-
}
69-
} else if (proc.stderr.includes("Invalid API key")) {
70-
throw new Error(
71-
`API key provided in settings is invalid.`
72-
);
73-
}
74-
} else {
75-
this.configuration = configuration;
76-
return;
59+
// Check if the ggshield path is valid
60+
let proc = runGGShieldCommand(configuration, ["--version"]);
61+
if (proc.status !== 0) {
62+
window.showErrorMessage(`GitGuardian: Invalid ggshield path. ${proc.stderr}`);
63+
throw new Error(proc.stderr);
7764
}
7865
}
7966
}
67+

0 commit comments

Comments
 (0)