Skip to content

Commit dafce59

Browse files
Rachel Macfarlanesbatten
Rachel Macfarlane
andauthored
Add getPassword, setPassword, and deletePassword APIs, #95475
Co-authored-by: SteVen Batten <[email protected]>
1 parent 1a9e0af commit dafce59

File tree

19 files changed

+279
-40
lines changed

19 files changed

+279
-40
lines changed

extensions/github-authentication/src/common/keychain.ts

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,24 +28,12 @@ export type Keytar = {
2828
deletePassword: typeof keytarType['deletePassword'];
2929
};
3030

31-
const SERVICE_ID = `${vscode.env.uriScheme}-github.login`;
32-
const ACCOUNT_ID = 'account';
31+
const SERVICE_ID = `github.auth`;
3332

3433
export class Keychain {
35-
private keytar: Keytar;
36-
37-
constructor() {
38-
const keytar = getKeytar();
39-
if (!keytar) {
40-
throw new Error('System keychain unavailable');
41-
}
42-
43-
this.keytar = keytar;
44-
}
45-
4634
async setToken(token: string): Promise<void> {
4735
try {
48-
return await this.keytar.setPassword(SERVICE_ID, ACCOUNT_ID, token);
36+
return await vscode.authentication.setPassword(SERVICE_ID, token);
4937
} catch (e) {
5038
// Ignore
5139
Logger.error(`Setting token failed: ${e}`);
@@ -59,23 +47,43 @@ export class Keychain {
5947

6048
async getToken(): Promise<string | null | undefined> {
6149
try {
62-
return await this.keytar.getPassword(SERVICE_ID, ACCOUNT_ID);
50+
return await vscode.authentication.getPassword(SERVICE_ID);
6351
} catch (e) {
6452
// Ignore
6553
Logger.error(`Getting token failed: ${e}`);
6654
return Promise.resolve(undefined);
6755
}
6856
}
6957

70-
async deleteToken(): Promise<boolean | undefined> {
58+
async deleteToken(): Promise<void> {
7159
try {
72-
return await this.keytar.deletePassword(SERVICE_ID, ACCOUNT_ID);
60+
return await vscode.authentication.deletePassword(SERVICE_ID);
7361
} catch (e) {
7462
// Ignore
7563
Logger.error(`Deleting token failed: ${e}`);
7664
return Promise.resolve(undefined);
7765
}
7866
}
67+
68+
async tryMigrate(): Promise<string | null | undefined> {
69+
try {
70+
const keytar = getKeytar();
71+
if (!keytar) {
72+
throw new Error('keytar unavailable');
73+
}
74+
75+
const oldValue = await keytar.getPassword(`${vscode.env.uriScheme}-github.login`, 'account');
76+
if (oldValue) {
77+
await this.setToken(oldValue);
78+
await keytar.deletePassword(`${vscode.env.uriScheme}-github.login`, 'account');
79+
}
80+
81+
return oldValue;
82+
} catch (_) {
83+
// Ignore
84+
return Promise.resolve(undefined);
85+
}
86+
}
7987
}
8088

8189
export const keychain = new Keychain();

extensions/github-authentication/src/github.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export class GitHubAuthenticationProvider {
8282
}
8383

8484
private async readSessions(): Promise<vscode.AuthenticationSession[]> {
85-
const storedSessions = await keychain.getToken();
85+
const storedSessions = await keychain.getToken() || await keychain.tryMigrate();
8686
if (storedSessions) {
8787
try {
8888
const sessionData: SessionData[] = JSON.parse(storedSessions);

extensions/github-authentication/yarn.lock

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,10 @@ base64-js@^1.0.2:
7474
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
7575
integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
7676

77-
bl@^4.0.1:
78-
version "4.0.2"
79-
resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.2.tgz#52b71e9088515d0606d9dd9cc7aa48dc1f98e73a"
80-
integrity sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==
77+
bl@^4.0.3:
78+
version "4.0.3"
79+
resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.3.tgz#12d6287adc29080e22a705e5764b2a9522cdc489"
80+
integrity sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==
8181
dependencies:
8282
buffer "^5.5.0"
8383
inherits "^2.0.4"
@@ -287,9 +287,9 @@ napi-build-utils@^1.0.1:
287287
integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==
288288

289289
node-abi@^2.7.0:
290-
version "2.17.0"
291-
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.17.0.tgz#f167c92780497ff01eeaf473fcf8138e0fcc87fa"
292-
integrity sha512-dFRAA0ACk/aBo0TIXQMEWMLUTyWYYT8OBYIzLmEUrQTElGRjxDCvyBZIsDL0QA7QCaj9PrawhOmTEdsuLY4uOQ==
290+
version "2.19.1"
291+
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.19.1.tgz#6aa32561d0a5e2fdb6810d8c25641b657a8cea85"
292+
integrity sha512-HbtmIuByq44yhAzK7b9j/FelKlHYISKQn0mtvcBrU5QBkhoCMp5bu8Hv5AI34DcKfOAcJBcOEMwLlwO62FFu9A==
293293
dependencies:
294294
semver "^5.4.1"
295295

@@ -427,9 +427,9 @@ signal-exit@^3.0.0:
427427
integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==
428428

429429
simple-concat@^1.0.0:
430-
version "1.0.0"
431-
resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.0.tgz#7344cbb8b6e26fb27d66b2fc86f9f6d5997521c6"
432-
integrity sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=
430+
version "1.0.1"
431+
resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f"
432+
integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
433433

434434
simple-get@^3.0.3:
435435
version "3.1.0"
@@ -501,11 +501,11 @@ tar-fs@^2.0.0:
501501
tar-stream "^2.0.0"
502502

503503
tar-stream@^2.0.0:
504-
version "2.1.2"
505-
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.2.tgz#6d5ef1a7e5783a95ff70b69b97455a5968dc1325"
506-
integrity sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q==
504+
version "2.1.4"
505+
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.4.tgz#c4fb1a11eb0da29b893a5b25476397ba2d053bfa"
506+
integrity sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw==
507507
dependencies:
508-
bl "^4.0.1"
508+
bl "^4.0.3"
509509
end-of-stream "^1.4.1"
510510
fs-constants "^1.0.0"
511511
inherits "^2.0.3"

extensions/microsoft-authentication/src/AADHelper.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ export class AzureActiveDirectoryService {
100100
}
101101

102102
public async initialize(): Promise<void> {
103-
const storedData = await keychain.getToken();
103+
const storedData = await keychain.getToken() || await keychain.tryMigrate();
104104
if (storedData) {
105105
try {
106106
const sessions = this.parseStoredData(storedData);

extensions/microsoft-authentication/src/keychain.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ export type Keytar = {
2828
deletePassword: typeof keytarType['deletePassword'];
2929
};
3030

31-
const SERVICE_ID = `${vscode.env.uriScheme}-microsoft.login`;
31+
const OLD_SERVICE_ID = `${vscode.env.uriScheme}-microsoft.login`;
32+
const SERVICE_ID = `microsoft.login`;
3233
const ACCOUNT_ID = 'account';
3334

3435
export class Keychain {
@@ -46,7 +47,7 @@ export class Keychain {
4647

4748
async setToken(token: string): Promise<void> {
4849
try {
49-
return await this.keytar.setPassword(SERVICE_ID, ACCOUNT_ID, token);
50+
return await vscode.authentication.setPassword(SERVICE_ID, token);
5051
} catch (e) {
5152
Logger.error(`Setting token failed: ${e}`);
5253

@@ -68,23 +69,38 @@ export class Keychain {
6869

6970
async getToken(): Promise<string | null | undefined> {
7071
try {
71-
return await this.keytar.getPassword(SERVICE_ID, ACCOUNT_ID);
72+
return await vscode.authentication.getPassword(SERVICE_ID);
7273
} catch (e) {
7374
// Ignore
7475
Logger.error(`Getting token failed: ${e}`);
7576
return Promise.resolve(undefined);
7677
}
7778
}
7879

79-
async deleteToken(): Promise<boolean | undefined> {
80+
async deleteToken(): Promise<void> {
8081
try {
81-
return await this.keytar.deletePassword(SERVICE_ID, ACCOUNT_ID);
82+
return await vscode.authentication.deletePassword(SERVICE_ID);
8283
} catch (e) {
8384
// Ignore
8485
Logger.error(`Deleting token failed: ${e}`);
8586
return Promise.resolve(undefined);
8687
}
8788
}
89+
90+
async tryMigrate(): Promise<string | null> {
91+
try {
92+
const oldValue = await this.keytar.getPassword(OLD_SERVICE_ID, ACCOUNT_ID);
93+
if (oldValue) {
94+
await this.setToken(oldValue);
95+
await this.keytar.deletePassword(OLD_SERVICE_ID, ACCOUNT_ID);
96+
}
97+
98+
return oldValue;
99+
} catch (_) {
100+
// Ignore
101+
return Promise.resolve(null);
102+
}
103+
}
88104
}
89105

90106
export const keychain = new Keychain();

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "code-oss-dev",
33
"version": "1.51.0",
4-
"distro": "e42a5273a865424266dd22838961d286c5c45ddc",
4+
"distro": "ee20dc175593a951f2c01e62d044796a60004cb7",
55
"author": {
66
"name": "Microsoft Corporation"
77
},

src/vs/code/electron-main/app.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ import { IFileService } from 'vs/platform/files/common/files';
8282
import { stripComments } from 'vs/base/common/json';
8383
import { generateUuid } from 'vs/base/common/uuid';
8484
import { VSBuffer } from 'vs/base/common/buffer';
85+
import { EncryptionMainService, IEncryptionMainService } from 'vs/platform/encryption/electron-main/encryptionMainService';
8586

8687
export class CodeApplication extends Disposable {
8788
private windowsMainService: IWindowsMainService | undefined;
@@ -443,6 +444,7 @@ export class CodeApplication extends Disposable {
443444
services.set(IDiagnosticsService, createChannelSender(getDelayedChannel(sharedProcessReady.then(client => client.getChannel('diagnostics')))));
444445

445446
services.set(IIssueMainService, new SyncDescriptor(IssueMainService, [machineId, this.userEnv]));
447+
services.set(IEncryptionMainService, new SyncDescriptor(EncryptionMainService, [machineId]));
446448
services.set(INativeHostMainService, new SyncDescriptor(NativeHostMainService));
447449
services.set(IWebviewManagerService, new SyncDescriptor(WebviewMainService));
448450
services.set(IWorkspacesService, new SyncDescriptor(WorkspacesService));
@@ -531,6 +533,10 @@ export class CodeApplication extends Disposable {
531533
const issueChannel = createChannelReceiver(issueMainService);
532534
electronIpcServer.registerChannel('issue', issueChannel);
533535

536+
const encryptionMainService = accessor.get(IEncryptionMainService);
537+
const encryptionChannel = createChannelReceiver(encryptionMainService);
538+
electronIpcServer.registerChannel('encryption', encryptionChannel);
539+
534540
const nativeHostMainService = accessor.get(INativeHostMainService);
535541
const nativeHostChannel = createChannelReceiver(nativeHostMainService);
536542
electronIpcServer.registerChannel('nativeHost', nativeHostChannel);
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
export interface ICommonEncryptionService {
7+
8+
readonly _serviceBrand: undefined;
9+
10+
encrypt(value: string): Promise<string>;
11+
12+
decrypt(value: string): Promise<string>;
13+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
7+
import { ICommonEncryptionService } from 'vs/platform/encryption/electron-main/common/encryptionService';
8+
9+
export const IEncryptionMainService = createDecorator<IEncryptionMainService>('encryptionMainService');
10+
11+
export interface IEncryptionMainService extends ICommonEncryptionService { }
12+
13+
export interface Encryption {
14+
encrypt(salt: string, value: string): Promise<string>;
15+
decrypt(salt: string, value: string): Promise<string>;
16+
}
17+
export class EncryptionMainService implements ICommonEncryptionService {
18+
declare readonly _serviceBrand: undefined;
19+
constructor(
20+
private machineId: string) {
21+
22+
}
23+
24+
private encryption(): Promise<Encryption> {
25+
return new Promise((resolve, reject) => require(['vscode-encrypt'], resolve, reject));
26+
}
27+
28+
async encrypt(value: string): Promise<string> {
29+
try {
30+
const encryption = await this.encryption();
31+
return encryption.encrypt(this.machineId, value);
32+
} catch (e) {
33+
return value;
34+
}
35+
}
36+
37+
async decrypt(value: string): Promise<string> {
38+
try {
39+
const encryption = await this.encryption();
40+
return encryption.decrypt(this.machineId, value);
41+
} catch (e) {
42+
return value;
43+
}
44+
}
45+
}

src/vs/vscode.proposed.d.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,26 @@ declare module 'vscode' {
143143
* provider
144144
*/
145145
export function logout(providerId: string, sessionId: string): Thenable<void>;
146+
147+
/**
148+
* Retrieve a password that was stored with key. Returns undefined if there
149+
* is no password matching that key.
150+
* @param key The key the password was stored under.
151+
*/
152+
export function getPassword(key: string): Thenable<string | undefined>;
153+
154+
/**
155+
* Store a password under a given key.
156+
* @param key The key to store the password under
157+
* @param value The password
158+
*/
159+
export function setPassword(key: string, value: string): Thenable<void>;
160+
161+
/**
162+
* Remove a password from storage.
163+
* @param key The key the password was stored under.
164+
*/
165+
export function deletePassword(key: string): Thenable<void>;
146166
}
147167

148168
//#endregion

0 commit comments

Comments
 (0)