Skip to content

Commit 2091267

Browse files
committed
Begin password migration command
1 parent 0e352dd commit 2091267

File tree

4 files changed

+101
-6
lines changed

4 files changed

+101
-6
lines changed

package.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
"onCommand:intersystems-community.servermanager.addServer",
7272
"onCommand:intersystems-community.servermanager.storePassword",
7373
"onCommand:intersystems-community.servermanager.clearPassword",
74+
"onCommand:intersystems-community.servermanager.migratePasswords",
7475
"onCommand:intersystems-community.servermanager.importServers",
7576
"onCommand:intersystems-community.servermanager-credentials.testLogin",
7677
"onCommand:intersystems-community.servermanager-credentials.testScopedLogin",
@@ -323,6 +324,11 @@
323324
"category": "InterSystems Server Manager",
324325
"title": "Clear Password from Keychain"
325326
},
327+
{
328+
"command": "intersystems-community.servermanager.migratePasswords",
329+
"category": "InterSystems Server Manager",
330+
"title": "Migrate Legacy Passwords"
331+
},
326332
{
327333
"command": "intersystems-community.servermanager.importServers",
328334
"category": "InterSystems Server Manager",
@@ -479,6 +485,18 @@
479485
"command": "intersystems-community.servermanager.importServers",
480486
"when": "isWindows"
481487
},
488+
{
489+
"command": "intersystems-community.servermanager.migratePasswords",
490+
"when": "config.intersystemsServerManager.authentication.provider == intersystems-server-credentials"
491+
},
492+
{
493+
"command": "intersystems-community.servermanager.storePassword",
494+
"when": "false"
495+
},
496+
{
497+
"command": "intersystems-community.servermanager.clearPassword",
498+
"when": "false"
499+
},
482500
{
483501
"command": "intersystems-community.servermanager.addToStarred",
484502
"when": "false"

src/commands/managePasswords.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as vscode from "vscode";
22
import { getServerNames } from "../api/getServerNames";
33
import { credentialCache } from "../api/getServerSpec";
4+
import { ServerManagerAuthenticationProvider } from "../authenticationProvider";
45
import { extensionId } from "../extension";
56
import { Keychain } from "../keychain";
67
import { ServerTreeItem } from "../ui/serverManagerView";
@@ -60,6 +61,57 @@ export async function clearPassword(treeItem?: ServerTreeItem): Promise<string>
6061
return reply;
6162
}
6263

64+
export async function migratePasswords(secretStorage: vscode.SecretStorage): Promise<void> {
65+
const credentials = await Keychain.findCredentials();
66+
console.log(credentials);
67+
if (credentials.length === 0) {
68+
vscode.window.showInformationMessage('No legacy passwords found');
69+
} else {
70+
71+
// Collect only those for which server definition exists with a username
72+
// and no credentials yet stored in our SecretStorage
73+
const migratableCredentials = (await Promise.all(
74+
credentials.map(async (item) => {
75+
const serverName = item.account;
76+
const username: string | undefined = vscode.workspace.getConfiguration("intersystems.servers." + serverName).get("username");
77+
if (!username) {
78+
return undefined;
79+
}
80+
if (username === "" || username === "UnknownUser") {
81+
return undefined;
82+
}
83+
const sessionId = ServerManagerAuthenticationProvider.sessionId(serverName, username);
84+
const credentialKey = ServerManagerAuthenticationProvider.credentialKey(sessionId);
85+
return (await secretStorage.get(credentialKey) ? {...item, username} : undefined);
86+
})
87+
))
88+
.filter((item) => item);
89+
if (migratableCredentials.length === 0) {
90+
vscode.window.showInformationMessage('No legacy passwords found for servers whose definitions specify a username');
91+
} else {
92+
const disqualified = credentials.length - migratableCredentials.length;
93+
const detail = disqualified > 0 ? `${disqualified} other ${disqualified > 1 ? "passwords" : "password"} ignored because associated server is no longer defined, or has no username set, or already has a password in the new keystore.` : "";
94+
const message = `Migrate ${migratableCredentials.length} legacy stored ${migratableCredentials.length > 1 ? "passwords" : "password"}?`;
95+
switch (await vscode.window.showInformationMessage(message, {modal: true, detail}, "Yes", "No")) {
96+
case undefined:
97+
return;
98+
99+
case "Yes":
100+
vscode.window.showInformationMessage('TODO migration');
101+
break;
102+
103+
default:
104+
break;
105+
}
106+
}
107+
const detail = "Do this to tidy up your keystore once you have migrated passwords and will not be reverting to an earlier Server Manager.";
108+
if (await vscode.window.showInformationMessage(`Delete all legacy stored passwords?`, {modal: true, detail}, "Yes", "No") === "Yes") {
109+
vscode.window.showInformationMessage('TODO deletion');
110+
}
111+
}
112+
return;
113+
}
114+
63115
async function commonPickServer(options?: vscode.QuickPickOptions): Promise<string | undefined> {
64116
// Deliberately uses its own API to illustrate how other extensions would
65117
const serverManagerExtension = vscode.extensions.getExtension(extensionId);

src/extension.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { getServerSummary } from "./api/getServerSummary";
99
import { pickServer } from "./api/pickServer";
1010
import { AUTHENTICATION_PROVIDER, ServerManagerAuthenticationProvider } from "./authenticationProvider";
1111
import { importFromRegistry } from "./commands/importFromRegistry";
12-
import { clearPassword, storePassword } from "./commands/managePasswords";
12+
import { clearPassword, migratePasswords, storePassword } from "./commands/managePasswords";
1313
import { cookieJar } from "./makeRESTRequest";
1414
import { NamespaceTreeItem, ProjectTreeItem, ServerManagerView, ServerTreeItem, SMTreeItem } from "./ui/serverManagerView";
1515

@@ -269,6 +269,11 @@ export function activate(context: vscode.ExtensionContext) {
269269
});
270270
}),
271271
);
272+
context.subscriptions.push(
273+
vscode.commands.registerCommand(`${extensionId}.migratePasswords`, async () => {
274+
await migratePasswords(context.secrets);
275+
}),
276+
);
272277
context.subscriptions.push(
273278
vscode.commands.registerCommand(`${extensionId}.setIconRed`, (server?: ServerTreeItem) => {
274279
if (server?.name) {

src/keychain.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,16 @@ export interface IKeytar {
1818
getPassword: typeof keytarType["getPassword"];
1919
setPassword: typeof keytarType["setPassword"];
2020
deletePassword: typeof keytarType["deletePassword"];
21+
findCredentials: typeof keytarType["findCredentials"];
22+
}
23+
24+
function serviceId(): string {
25+
return `${vscode.env.uriScheme}-${extensionId}:password`;
2126
}
2227

2328
export class Keychain {
29+
2430
private keytar: IKeytar;
25-
private serviceId: string;
2631
private accountId: string;
2732

2833
constructor(connectionName: string) {
@@ -32,13 +37,12 @@ export class Keychain {
3237
}
3338

3439
this.keytar = keytar;
35-
this.serviceId = `${vscode.env.uriScheme}-${extensionId}:password`;
3640
this.accountId = connectionName;
3741
}
3842

3943
public async setPassword(password: string): Promise<void> {
4044
try {
41-
return await this.keytar.setPassword(this.serviceId, this.accountId, password);
45+
return await this.keytar.setPassword(serviceId(), this.accountId, password);
4246
} catch (e) {
4347
// Ignore
4448
await vscode.window.showErrorMessage(
@@ -50,7 +54,7 @@ export class Keychain {
5054

5155
public async getPassword(): Promise<string | null | undefined> {
5256
try {
53-
return await this.keytar.getPassword(this.serviceId, this.accountId);
57+
return await this.keytar.getPassword(serviceId(), this.accountId);
5458
} catch (e) {
5559
// Ignore
5660
logger.error(`Getting password failed: ${e}`);
@@ -60,11 +64,27 @@ export class Keychain {
6064

6165
public async deletePassword(): Promise<boolean | undefined> {
6266
try {
63-
return await this.keytar.deletePassword(this.serviceId, this.accountId);
67+
return await this.keytar.deletePassword(serviceId(), this.accountId);
6468
} catch (e) {
6569
// Ignore
6670
logger.error(`Deleting password failed: ${e}`);
6771
return Promise.resolve(undefined);
6872
}
6973
}
74+
75+
public static async findCredentials(): Promise<{account: string; password: string}[]> {
76+
console.log(serviceId());
77+
const keytar = getKeytar();
78+
try {
79+
if (!keytar) {
80+
throw new Error("System keychain unavailable");
81+
}
82+
return await keytar.findCredentials(serviceId());
83+
} catch (e) {
84+
// Ignore
85+
logger.error(`Finding credentials failed: ${e}`);
86+
return Promise.resolve([]);
87+
}
88+
89+
}
7090
}

0 commit comments

Comments
 (0)