Skip to content

Commit d2e34b5

Browse files
authored
Merge pull request #15 from GitGuardian/salomevoltz/add-api-ket-to-settings
Fix authentication flow to include API key
2 parents d741471 + c310eaf commit d2e34b5

File tree

5 files changed

+98
-67
lines changed

5 files changed

+98
-67
lines changed

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@
2929
"type": "string",
3030
"default": "https://api.gitguardian.com",
3131
"markdownDescription": "You can override the value here for On Premise installations"
32+
},
33+
"gitguardian.apiKey": {
34+
"type": "string",
35+
"default": "",
36+
"markdownDescription": "Your API Key"
3237
}
3338
}
3439
},

src/extension.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
ggshieldApiKey,
23
ggshieldAuthStatus,
34
ggshieldScanFile,
45
ignoreLastFound,
@@ -7,8 +8,9 @@ import {
78
showAPIQuota,
89
} from "./lib/ggshield-api";
910
import {
10-
createDefaultConfiguration,
11+
getConfiguration,
1112
GGShieldConfiguration,
13+
setApiKey,
1214
} from "./lib/ggshield-configuration";
1315
import { parseGGShieldResults } from "./lib/ggshield-results-parser";
1416
import {
@@ -121,7 +123,7 @@ function registerQuotaViewCommands(view: GitGuardianQuotaWebviewProvider) {
121123
export function activate(context: ExtensionContext) {
122124
// Check if ggshield if available
123125
const outputChannel = window.createOutputChannel("GitGuardian");
124-
let configuration = createDefaultConfiguration(context);
126+
let configuration = getConfiguration(context);
125127
let authStatus: boolean = false;
126128
const ggshieldResolver = new GGShieldResolver(
127129
outputChannel,
@@ -165,7 +167,10 @@ export function activate(context: ExtensionContext) {
165167
updateStatusBarItem(StatusBarStatus.unauthenticated, statusBar);
166168
} else {
167169
commands.executeCommand('setContext', 'isAuthenticated', true);
168-
}
170+
updateStatusBarItem(StatusBarStatus.ready, statusBar);
171+
const ggshieldApi = ggshieldApiKey(configuration);
172+
setApiKey(configuration, ggshieldApi);
173+
}
169174
})
170175
.then(async () => {
171176
// Check if git is installed
@@ -233,6 +238,8 @@ export function activate(context: ExtensionContext) {
233238
authStatus = true;
234239
updateStatusBarItem(StatusBarStatus.ready, statusBar);
235240
commands.executeCommand('setContext', 'isAuthenticated', true);
241+
const ggshieldApi = ggshieldApiKey(configuration);
242+
setApiKey(configuration, ggshieldApi);
236243
ggshieldViewProvider.refresh();
237244
ggshieldQuotaViewProvider.refresh();
238245
} else {

src/lib/ggshield-api.ts

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable @typescript-eslint/naming-convention */
12
import {
23
SpawnOptionsWithoutStdio,
34
SpawnSyncOptionsWithStringEncoding,
@@ -21,16 +22,26 @@ export function runGGShieldCommand(
2122
configuration: GGShieldConfiguration,
2223
args: string[]
2324
): SpawnSyncReturns<string> {
24-
const { ggshieldPath, apiUrl } = configuration;
25+
const { ggshieldPath, apiUrl, apiKey } = configuration;
26+
let env: {
27+
GITGUARDIAN_API_URL: string;
28+
GG_USER_AGENT: string;
29+
GITGUARDIAN_API_KEY?: string; // Note the ? to indicate this property is optional
30+
} = {
31+
GITGUARDIAN_API_URL: apiUrl,
32+
GG_USER_AGENT: "gitguardian-vscode",
33+
};
34+
35+
if (apiKey) {
36+
env = {
37+
...env,
38+
// eslint-disable-next-line @typescript-eslint/naming-convention
39+
GITGUARDIAN_API_KEY: apiKey,
40+
};
41+
}
2542

2643
let options: SpawnSyncOptionsWithStringEncoding = {
2744
cwd: os.tmpdir(),
28-
env: {
29-
// eslint-disable-next-line @typescript-eslint/naming-convention
30-
GITGUARDIAN_API_URL: apiUrl,
31-
// eslint-disable-next-line @typescript-eslint/naming-convention
32-
GG_USER_AGENT: "gitguardian-vscode",
33-
},
3445
encoding: "utf-8",
3546
windowsHide: true,
3647
};
@@ -178,7 +189,7 @@ export async function loginGGShield(
178189
configuration: GGShieldConfiguration,
179190
outputChannel: any
180191
): Promise<boolean> {
181-
const { ggshieldPath, apiUrl } = configuration;
192+
const { ggshieldPath, apiUrl, apiKey } = configuration;
182193

183194
let options: SpawnOptionsWithoutStdio = {
184195
cwd: os.tmpdir(),
@@ -230,7 +241,35 @@ export function ggshieldAuthStatus(
230241
console.log(proc.stderr);
231242
return false;
232243
} else {
244+
if (proc.stdout.includes("unhealthy")) {
245+
return false;
246+
}
233247
console.log(proc.stdout);
234248
return true;
235249
}
236250
}
251+
252+
export function ggshieldApiKey(
253+
configuration: GGShieldConfiguration,
254+
): string | undefined {
255+
const proc = runGGShieldCommand(configuration, ["config", "list"]);
256+
if (proc.stderr || proc.error) {
257+
console.log(proc.stderr);
258+
return undefined;
259+
} else {
260+
console.log(proc.stdout);
261+
const apiUrl = configuration.apiUrl;
262+
const re = /api/;
263+
264+
const regexInstanceSection = `\\[${apiUrl.replace(re, "dashboard")}\\]([\\s\\S]*?)(?=\\[|$)`;
265+
const instanceSectionMatch = proc.stdout.match(regexInstanceSection);
266+
267+
if (instanceSectionMatch) {
268+
const instanceSection = instanceSectionMatch[0];
269+
const regexToken = /token:\s([a-zA-Z0-9]+)/;
270+
const matchToken = instanceSection.match(regexToken);
271+
272+
return matchToken ? matchToken[1].trim() : undefined;
273+
}
274+
}
275+
}

src/lib/ggshield-configuration.ts

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import { getBinaryAbsolutePath } from "./ggshield-resolver-utils";
2-
import { ExtensionContext, workspace } from "vscode";
2+
import { ConfigurationTarget, ExtensionContext, workspace } from "vscode";
33
import * as os from "os";
44

55
const apiUrlDefault = "https://api.gitguardian.com/";
66

77
export class GGShieldConfiguration {
88
ggshieldPath: string;
99
apiUrl: string;
10+
apiKey: string;
1011

11-
constructor(ggshieldPath: string = "", apiUrl: string = "") {
12+
constructor(ggshieldPath: string = "", apiUrl: string = "", apiKey: string = "") {
1213
this.ggshieldPath = ggshieldPath;
1314
this.apiUrl = apiUrl;
15+
this.apiKey = apiKey;
1416
}
1517
}
1618

@@ -20,26 +22,28 @@ export class GGShieldConfiguration {
2022
* TODO: Check with Mathieu if this behaviour is expected
2123
* @returns {GGShieldConfiguration} from the extension settings
2224
*/
23-
export function getSettingsConfiguration(): GGShieldConfiguration | undefined {
25+
export function getConfiguration(
26+
context: ExtensionContext
27+
): GGShieldConfiguration {
2428
const config = workspace.getConfiguration("gitguardian");
2529

2630
const ggshieldPath: string | undefined = config.get("GGShieldPath");
2731
const apiUrl: string | undefined = config.get("apiUrl");
32+
const apiKey: string | undefined = config.get("apiKey");
2833

29-
if (!ggshieldPath) {
30-
return undefined;
31-
}
3234
return new GGShieldConfiguration(
33-
ggshieldPath,
34-
apiUrl ? apiUrl : apiUrlDefault
35+
ggshieldPath ? ggshieldPath : getBinaryAbsolutePath(os.platform(), os.arch(), context),
36+
apiUrl ? apiUrl : apiUrlDefault,
37+
apiKey ? apiKey : ""
3538
);
3639
}
3740

38-
export function createDefaultConfiguration(
39-
context: ExtensionContext
40-
): GGShieldConfiguration {
41-
return new GGShieldConfiguration(
42-
getBinaryAbsolutePath(os.platform(), os.arch(), context),
43-
"https://api.gitguardian.com/"
44-
);
45-
}
41+
export function setApiKey(configuration: GGShieldConfiguration, apiKey: string | undefined): void {
42+
const config = workspace.getConfiguration("gitguardian");
43+
if (!apiKey) {
44+
throw new Error("Missing API Key");
45+
}
46+
47+
configuration.apiKey = apiKey;
48+
config.update("apiKey", apiKey, ConfigurationTarget.Global);
49+
}

src/lib/ggshield-resolver.ts

Lines changed: 16 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as vscode from "vscode";
22
import {
3-
getSettingsConfiguration,
3+
getConfiguration,
44
GGShieldConfiguration,
55
} from "./ggshield-configuration";
66
import { runGGShieldCommand } from "./ggshield-api";
@@ -32,44 +32,20 @@ export class GGShieldResolver {
3232
* @returns {Promise<void>} A promise that resolves once the `ggshield` path is determined.
3333
*/
3434
async checkGGShieldConfiguration(): Promise<void> {
35-
let settingsConfiguration = getSettingsConfiguration();
36-
if (settingsConfiguration) {
37-
try {
38-
await this.useSettingsConfiguration(settingsConfiguration);
39-
this.channel.appendLine(
40-
`Using ggshield at: ${this.configuration.ggshieldPath}, to change this go to settings.`
41-
);
42-
return;
43-
} catch (error) {
44-
this.channel.appendLine(
45-
`Failed to use ggshield version from settings.
46-
You can remove it from settings, and use the bundled version instead.`
47-
);
48-
window.showErrorMessage(
49-
`Failed to use ggshield version from settings.`
50-
);
51-
throw error;
52-
}
53-
} else {
54-
try {
55-
await this.checkBundledGGShield();
56-
this.channel.appendLine(
57-
`Using bundled ggshield at: ${this.configuration.ggshieldPath}, to change this go to settings.`
58-
);
59-
return;
60-
} catch (error) {
61-
this.channel.appendLine(
62-
`ggshield binary not found: this architecture is not supported ${
63-
(os.arch(), os.platform())
64-
}`
65-
);
66-
window.showErrorMessage(
67-
`ggshield binary not found: this architecture is not supported ${
68-
(os.arch(), os.platform())
69-
}`
70-
);
71-
throw error;
72-
}
35+
try {
36+
await this.testConfiguration(this.configuration);
37+
this.channel.appendLine(
38+
`Using ggshield at: ${this.configuration.ggshieldPath}, to change this go to settings.`
39+
);
40+
return;
41+
} catch (error) {
42+
this.channel.appendLine(
43+
`Failed to use ggshield version ${this.configuration.ggshieldPath}.`
44+
);
45+
window.showErrorMessage(
46+
`Failed to use ggshield.`
47+
);
48+
throw error;
7349
}
7450
}
7551

@@ -78,7 +54,7 @@ export class GGShieldResolver {
7854
*
7955
* @returns {Promise<void>} A promise that resolves if the configuration is valid.
8056
*/
81-
async useSettingsConfiguration(
57+
async testConfiguration(
8258
configuration: GGShieldConfiguration
8359
): Promise<void> {
8460
let proc = runGGShieldCommand(configuration, ["--version"]);

0 commit comments

Comments
 (0)