Skip to content

Commit 6f07f6b

Browse files
committed
Add commands for managing passwords in keychain
1 parent d759887 commit 6f07f6b

File tree

6 files changed

+100
-26
lines changed

6 files changed

+100
-26
lines changed

package.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@
6060
"activationEvents": [
6161
"onCommand:intersystems-community.servermanager.testPickServer",
6262
"onCommand:intersystems-community.servermanager.testPickServerFlushingCachedCredentials",
63-
"onCommand:intersystems-community.servermanager.testPickServerDetailed"
63+
"onCommand:intersystems-community.servermanager.testPickServerDetailed",
64+
"onCommand:intersystems-community.servermanager.storePassword",
65+
"onCommand:intersystems-community.servermanager.clearPassword"
6466
],
6567
"contributes": {
6668
"configuration": {
@@ -180,6 +182,16 @@
180182
}
181183
},
182184
"commands": [
185+
{
186+
"command": "intersystems-community.servermanager.storePassword",
187+
"category": "InterSystems Server Manager",
188+
"title": "Store Password in Keychain"
189+
},
190+
{
191+
"command": "intersystems-community.servermanager.clearPassword",
192+
"category": "InterSystems Server Manager",
193+
"title": "Clear Password from Keychain"
194+
},
183195
{
184196
"command": "intersystems-community.servermanager.testPickServer",
185197
"category": "InterSystems Server Manager",

src/api/getServerSpec.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ interface CredentialSet {
77
password: string
88
}
99

10-
let credentialCache = new Map<string, CredentialSet>();
10+
export let credentialCache = new Map<string, CredentialSet>();
1111

1212
export async function getServerSpec(name: string, scope?: vscode.ConfigurationScope, flushCredentialCache: boolean = false): Promise<ServerSpec | undefined> {
1313
if (flushCredentialCache) {
@@ -21,7 +21,6 @@ export async function getServerSpec(name: string, scope?: vscode.ConfigurationSc
2121
}
2222

2323
server.name = name;
24-
server.storePassword = server.storePassword ?? true;
2524
server.description = server.description || '';
2625
server.webServer.scheme = server.webServer.scheme || 'http';
2726
server.webServer.pathPrefix = server.webServer.pathPrefix || '';
@@ -51,7 +50,7 @@ export async function getServerSpec(name: string, scope?: vscode.ConfigurationSc
5150
if (server.username && !server.password) {
5251
if (credentialCache[name] && credentialCache[name].username === server.username) {
5352
server.password = credentialCache[name];
54-
} else if (server.storePassword) {
53+
} else {
5554
const keychain = new Keychain(name);
5655
const password = await keychain.getPassword().then(result => {
5756
if (typeof result === 'string') {

src/api/pickServer.ts

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import * as vscode from 'vscode';
2-
import { ServerSpec } from '../extension';
32
import { getServerNames } from './getServerNames';
4-
import { getServerSpec } from './getServerSpec';
53

6-
export async function pickServer(scope?: vscode.ConfigurationScope, options: vscode.QuickPickOptions = {}, flushCredentialCache: boolean = false): Promise<ServerSpec | undefined> {
4+
export async function pickServer(scope?: vscode.ConfigurationScope, options: vscode.QuickPickOptions = {}): Promise<string | undefined> {
75
const names = getServerNames(scope);
86

97
let qpItems: vscode.QuickPickItem[] = [];
@@ -16,14 +14,6 @@ export async function pickServer(scope?: vscode.ConfigurationScope, options: vsc
1614
qpItems.push({label: element.name, description: element.description, detail: options?.matchOnDetail ? element.detail : undefined});
1715
});
1816
return await vscode.window.showQuickPick(qpItems, options).then(item => {
19-
if (item) {
20-
const name = item.label;
21-
return getServerSpec(name, scope, flushCredentialCache).then(connSpec => {
22-
if (connSpec) {
23-
connSpec.name = name;
24-
}
25-
return connSpec;
26-
});
27-
}
28-
})
17+
return item ? item.label : undefined;
18+
});
2919
}

src/commands/managePasswords.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import * as vscode from 'vscode';
2+
import { extensionId } from '../extension';
3+
import { Keychain } from '../keychain';
4+
import { credentialCache } from '../api/getServerSpec';
5+
6+
export async function storePassword() {
7+
const name = await commonPickServer({matchOnDetail: true});
8+
if (name) {
9+
await vscode.window
10+
.showInputBox({
11+
password: true,
12+
placeHolder: 'Password to store in keychain',
13+
prompt: `For connection to InterSystems server '${name}'`,
14+
validateInput: (value => {
15+
return value.length > 0 ? '' : 'Mandatory field';
16+
}),
17+
ignoreFocusOut: true,
18+
})
19+
.then((password) => {
20+
if (password) {
21+
credentialCache[name] = undefined;
22+
new Keychain(name).setPassword(password).then(() => {
23+
vscode.window.showInformationMessage(`Password for '${name}' stored in keychain.`);
24+
});
25+
}
26+
})
27+
28+
}
29+
}
30+
31+
export async function clearPassword() {
32+
const name = await commonPickServer({matchOnDetail: true});
33+
if (name) {
34+
credentialCache[name] = undefined;
35+
const keychain = new Keychain(name);
36+
if (!await keychain.getPassword()) {
37+
vscode.window.showWarningMessage(`No password for '${name}' found in keychain.`);
38+
} else if (await keychain.deletePassword()) {
39+
vscode.window.showInformationMessage(`Password for '${name}' removed from keychain.`);
40+
} else {
41+
vscode.window.showWarningMessage(`Failed to remove password for '${name}' from keychain.`);
42+
}
43+
}
44+
}
45+
46+
async function commonPickServer(options?: vscode.QuickPickOptions): Promise<string | undefined> {
47+
// Deliberately uses its own API to illustrate how other extensions would
48+
const serverManagerExtension = vscode.extensions.getExtension(extensionId);
49+
if (!serverManagerExtension) {
50+
vscode.window.showErrorMessage(`Extension '${extensionId}' is not installed, or has been disabled.`)
51+
return;
52+
}
53+
if (!serverManagerExtension.isActive) {
54+
serverManagerExtension.activate();
55+
}
56+
const myApi = serverManagerExtension.exports;
57+
58+
return await myApi.pickServer(undefined, options);
59+
}

src/commands/testPickServer.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as vscode from 'vscode';
2-
import { extensionId } from '../extension';
2+
import { extensionId, ServerSpec } from '../extension';
33

44
export async function testPickServer() {
55
await commonTestPickServer();
@@ -25,8 +25,11 @@ async function commonTestPickServer(options?: vscode.QuickPickOptions, flushCred
2525
}
2626
const myApi = serverManagerExtension.exports;
2727

28-
const connSpec = await myApi.pickServer(undefined, options, flushCredentialCache);
29-
if (connSpec) {
30-
vscode.window.showInformationMessage(`Picked server '${connSpec.name}' at ${connSpec.webServer.scheme}://${connSpec.webServer.host}:${connSpec.webServer.port}/${connSpec.webServer.pathPrefix} ${!connSpec.username ? 'with unauthenticated access' : 'as user ' + connSpec.username }.`, 'OK');
28+
const name: string = await myApi.pickServer(undefined, options);
29+
if (name) {
30+
const connSpec: ServerSpec = await myApi.getServerSpec(name, undefined, flushCredentialCache);
31+
if (connSpec) {
32+
vscode.window.showInformationMessage(`Picked server '${connSpec.name}' at ${connSpec.webServer.scheme}://${connSpec.webServer.host}:${connSpec.webServer.port}/${connSpec.webServer.pathPrefix} ${!connSpec.username ? 'with unauthenticated access' : 'as user ' + connSpec.username }.`, 'OK');
33+
}
3134
}
3235
}

src/extension.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { testPickServer, testPickServerWithoutCachedCredentials as testPickServe
66
import { pickServer } from './api/pickServer';
77
import { getServerNames } from './api/getServerNames';
88
import { getServerSpec } from './api/getServerSpec';
9+
import { storePassword, clearPassword } from './commands/managePasswords';
910

1011
export interface ServerName {
1112
name: string,
@@ -25,14 +26,24 @@ export interface ServerSpec {
2526
webServer: WebServerSpec,
2627
username: string,
2728
password: string,
28-
storePassword: boolean,
2929
description: string
3030
}
3131

3232
export function activate(context: vscode.ExtensionContext) {
3333

3434

3535
// Register the commands
36+
context.subscriptions.push(
37+
vscode.commands.registerCommand(`${extensionId}.storePassword`, () => {
38+
storePassword();
39+
})
40+
);
41+
context.subscriptions.push(
42+
vscode.commands.registerCommand(`${extensionId}.clearPassword`, () => {
43+
clearPassword();
44+
})
45+
);
46+
3647
context.subscriptions.push(
3748
vscode.commands.registerCommand(`${extensionId}.testPickServer`, () => {
3849
testPickServer();
@@ -50,16 +61,16 @@ export function activate(context: vscode.ExtensionContext) {
5061
);
5162

5263
let api = {
53-
async pickServer(scope?: vscode.ConfigurationScope, options: vscode.QuickPickOptions = {}, flushCredentialCache: boolean = false): Promise<ServerSpec | undefined> {
54-
return await pickServer(scope, options, flushCredentialCache);
64+
async pickServer(scope?: vscode.ConfigurationScope, options: vscode.QuickPickOptions = {}): Promise<string | undefined> {
65+
return await pickServer(scope, options);
5566

5667
},
5768
getServerNames(scope?: vscode.ConfigurationScope): ServerName[] {
5869
return getServerNames(scope);
5970
},
6071

61-
async getServerSpec(name: string, scope?: vscode.ConfigurationScope): Promise<ServerSpec | undefined> {
62-
return await getServerSpec(name, scope);
72+
async getServerSpec(name: string, scope?: vscode.ConfigurationScope, flushCredentialCache: boolean = false): Promise<ServerSpec | undefined> {
73+
return await getServerSpec(name, scope, flushCredentialCache);
6374
}
6475

6576
};

0 commit comments

Comments
 (0)