|
1 | 1 | import * as vscode from "vscode"; |
2 | 2 | import { getServerNames } from "../api/getServerNames"; |
3 | 3 | import { credentialCache } from "../api/getServerSpec"; |
| 4 | +import { ServerManagerAuthenticationProvider } from "../authenticationProvider"; |
4 | 5 | import { extensionId } from "../extension"; |
5 | 6 | import { Keychain } from "../keychain"; |
6 | 7 | import { ServerTreeItem } from "../ui/serverManagerView"; |
@@ -60,6 +61,78 @@ export async function clearPassword(treeItem?: ServerTreeItem): Promise<string> |
60 | 61 | return reply; |
61 | 62 | } |
62 | 63 |
|
| 64 | +interface IMigratePasswordItem extends vscode.QuickPickItem { |
| 65 | + serverName: string; |
| 66 | + userName: string; |
| 67 | + password: string; |
| 68 | +}; |
| 69 | + |
| 70 | +export async function migratePasswords(secretStorage: vscode.SecretStorage): Promise<void> { |
| 71 | + const credentials = await Keychain.findCredentials(); |
| 72 | + if (credentials.length === 0) { |
| 73 | + vscode.window.showInformationMessage('No legacy stored passwords found.'); |
| 74 | + } else { |
| 75 | + |
| 76 | + // Collect only those for which server definition exists with a username |
| 77 | + // and no credentials yet stored in our SecretStorage |
| 78 | + const migratableCredentials: IMigratePasswordItem[] = []; |
| 79 | + (await Promise.all( |
| 80 | + credentials.map(async (item): Promise<IMigratePasswordItem | undefined> => { |
| 81 | + const serverName = item.account; |
| 82 | + const username: string | undefined = vscode.workspace.getConfiguration("intersystems.servers." + serverName).get("username"); |
| 83 | + if (!username) { |
| 84 | + return undefined; |
| 85 | + } |
| 86 | + if (username === "" || username === "UnknownUser") { |
| 87 | + return undefined; |
| 88 | + } |
| 89 | + const sessionId = ServerManagerAuthenticationProvider.sessionId(serverName, username); |
| 90 | + const credentialKey = ServerManagerAuthenticationProvider.credentialKey(sessionId); |
| 91 | + return (await secretStorage.get(credentialKey) ? undefined : {label: `${serverName} (${username})`, picked: true, serverName, userName: username, password: item.password}); |
| 92 | + }) |
| 93 | + )) |
| 94 | + .forEach((item) => { |
| 95 | + if (item) { |
| 96 | + migratableCredentials.push(item); |
| 97 | + } |
| 98 | + }); |
| 99 | + |
| 100 | + if (migratableCredentials.length === 0) { |
| 101 | + const message = 'No remaining legacy stored passwords are eligible for migration.'; |
| 102 | + const detail = 'They are either for servers with a password already stored in the new format, or for servers whose definition does not specify a username.' |
| 103 | + await vscode.window.showWarningMessage(message, |
| 104 | + {modal: true, detail} |
| 105 | + ); |
| 106 | + } else { |
| 107 | + const choices = await vscode.window.showQuickPick<IMigratePasswordItem>(migratableCredentials, |
| 108 | + { canPickMany: true, |
| 109 | + title: "Migrate Server Manager legacy stored passwords", |
| 110 | + placeHolder: "Select connections whose passwords you want to migrate" |
| 111 | + } |
| 112 | + ) |
| 113 | + if (!choices) { |
| 114 | + return; |
| 115 | + } else if (choices.length > 0) { |
| 116 | + await Promise.all(choices.map(async (item) => { |
| 117 | + const sessionId = ServerManagerAuthenticationProvider.sessionId(item.serverName, item.userName); |
| 118 | + const credentialKey = ServerManagerAuthenticationProvider.credentialKey(sessionId); |
| 119 | + return secretStorage.store(credentialKey, item.password); |
| 120 | + })); |
| 121 | + vscode.window.showInformationMessage(`Migrated ${choices.length} ${choices.length > 1 ? "passwords" : "password"}.`); |
| 122 | + } |
| 123 | + } |
| 124 | + const detail = "Do this to tidy up your keystore once you have migrated passwords and will not be reverting to an earlier Server Manager."; |
| 125 | + if ((await vscode.window.showInformationMessage(`Delete all legacy stored passwords?`, {modal: true, detail}, {title: "Yes"}, {title: "No", isCloseAffordance: true}))?.title === "Yes") { |
| 126 | + await Promise.all(credentials.map(async (item) => { |
| 127 | + const keychain = new Keychain(item.account); |
| 128 | + return keychain.deletePassword() |
| 129 | + })); |
| 130 | + vscode.window.showInformationMessage(`Deleted ${credentials.length} ${credentials.length > 1 ? "passwords" : "password"}.`); |
| 131 | + } |
| 132 | + } |
| 133 | + return; |
| 134 | +} |
| 135 | + |
63 | 136 | async function commonPickServer(options?: vscode.QuickPickOptions): Promise<string | undefined> { |
64 | 137 | // Deliberately uses its own API to illustrate how other extensions would |
65 | 138 | const serverManagerExtension = vscode.extensions.getExtension(extensionId); |
|
0 commit comments