Skip to content

Commit 92dafb3

Browse files
author
Benjamin Pasero
committed
web - implement credentials provider and add API
1 parent 2b22c0a commit 92dafb3

12 files changed

Lines changed: 179 additions & 51 deletions

File tree

src/vs/workbench/api/browser/mainThreadKeytar.ts

Lines changed: 11 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,49 +5,33 @@
55

66
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
77
import { MainContext, MainThreadKeytarShape, IExtHostContext } from 'vs/workbench/api/common/extHost.protocol';
8-
import { ICredentialsService } from 'vs/platform/credentials/common/credentials';
9-
import { optional } from 'vs/platform/instantiation/common/instantiation';
8+
import { ICredentialsService } from 'vs/workbench/services/credentials/common/credentials';
109

1110
@extHostNamedCustomer(MainContext.MainThreadKeytar)
1211
export class MainThreadKeytar implements MainThreadKeytarShape {
1312

14-
private readonly _credentialsService?: ICredentialsService;
15-
1613
constructor(
1714
_extHostContext: IExtHostContext,
18-
@optional(ICredentialsService) credentialsService: ICredentialsService,
19-
) {
20-
this._credentialsService = credentialsService;
21-
}
22-
23-
dispose(): void {
24-
//
25-
}
15+
@ICredentialsService private readonly _credentialsService: ICredentialsService,
16+
) { }
2617

2718
async $getPassword(service: string, account: string): Promise<string | null> {
28-
if (this._credentialsService) {
29-
return this._credentialsService.getPassword(service, account);
30-
}
31-
return null;
19+
return this._credentialsService.getPassword(service, account);
3220
}
3321

3422
async $setPassword(service: string, account: string, password: string): Promise<void> {
35-
if (this._credentialsService) {
36-
return this._credentialsService.setPassword(service, account, password);
37-
}
23+
return this._credentialsService.setPassword(service, account, password);
3824
}
3925

4026
async $deletePassword(service: string, account: string): Promise<boolean> {
41-
if (this._credentialsService) {
42-
return this._credentialsService.deletePassword(service, account);
43-
}
44-
return false;
27+
return this._credentialsService.deletePassword(service, account);
4528
}
4629

4730
async $findPassword(service: string): Promise<string | null> {
48-
if (this._credentialsService) {
49-
return this._credentialsService.findPassword(service);
50-
}
51-
return null;
31+
return this._credentialsService.findPassword(service);
32+
}
33+
34+
dispose(): void {
35+
//
5236
}
5337
}

src/vs/workbench/browser/web.main.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,7 @@ class CodeRendererMain extends Disposable {
117117
const payload = await this.resolveWorkspaceInitializationPayload();
118118

119119
// Environment
120-
const environmentService = new BrowserWorkbenchEnvironmentService({
121-
workspaceId: payload.id,
122-
remoteAuthority: this.configuration.remoteAuthority,
123-
webviewEndpoint: this.configuration.webviewEndpoint,
124-
connectionToken: this.configuration.connectionToken
125-
});
120+
const environmentService = new BrowserWorkbenchEnvironmentService(payload.id, this.configuration);
126121
serviceCollection.set(IWorkbenchEnvironmentService, environmentService);
127122

128123
// Product
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
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 { ICredentialsService } from 'vs/workbench/services/credentials/common/credentials';
7+
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
8+
import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
9+
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
10+
11+
export interface ICredentialsProvider {
12+
getPassword(service: string, account: string): Promise<string | null>;
13+
setPassword(service: string, account: string, password: string): Promise<void>;
14+
deletePassword(service: string, account: string): Promise<boolean>;
15+
findPassword(service: string): Promise<string | null>;
16+
}
17+
18+
export class BrowserCredentialsService implements ICredentialsService {
19+
20+
_serviceBrand!: ServiceIdentifier<any>;
21+
22+
private credentialsProvider: ICredentialsProvider;
23+
24+
constructor(@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService) {
25+
if (environmentService.options && environmentService.options.credentialsProvider) {
26+
this.credentialsProvider = environmentService.options.credentialsProvider;
27+
} else {
28+
this.credentialsProvider = new LocalStorageCredentialsProvider();
29+
}
30+
}
31+
32+
async getPassword(service: string, account: string): Promise<string | null> {
33+
return this.credentialsProvider.getPassword(service, account);
34+
}
35+
36+
async setPassword(service: string, account: string, password: string): Promise<void> {
37+
return this.credentialsProvider.setPassword(service, account, password);
38+
}
39+
40+
async deletePassword(service: string, account: string): Promise<boolean> {
41+
return this.credentialsProvider.deletePassword(service, account);
42+
}
43+
44+
async findPassword(service: string): Promise<string | null> {
45+
return this.credentialsProvider.findPassword(service);
46+
}
47+
}
48+
49+
interface ICredential {
50+
service: string;
51+
account: string;
52+
password: string;
53+
}
54+
55+
class LocalStorageCredentialsProvider implements ICredentialsProvider {
56+
57+
static readonly CREDENTIALS_OPENED_KEY = 'credentials.provider';
58+
59+
private _credentials: ICredential[];
60+
private get credentials(): ICredential[] {
61+
if (!this._credentials) {
62+
try {
63+
const serializedCredentials = window.localStorage.getItem(LocalStorageCredentialsProvider.CREDENTIALS_OPENED_KEY);
64+
if (serializedCredentials) {
65+
this._credentials = JSON.parse(serializedCredentials);
66+
}
67+
} catch (error) {
68+
// ignore
69+
}
70+
71+
if (!Array.isArray(this._credentials)) {
72+
this._credentials = [];
73+
}
74+
}
75+
76+
return this._credentials;
77+
}
78+
79+
private save(): void {
80+
window.localStorage.setItem(LocalStorageCredentialsProvider.CREDENTIALS_OPENED_KEY, JSON.stringify(this.credentials));
81+
}
82+
83+
async getPassword(service: string, account: string): Promise<string | null> {
84+
return this.doGetPassword(service, account);
85+
}
86+
87+
private async doGetPassword(service: string, account?: string): Promise<string | null> {
88+
for (const credential of this.credentials) {
89+
if (credential.service === service) {
90+
if (typeof account !== 'string' || account === credential.account) {
91+
return credential.password;
92+
}
93+
}
94+
}
95+
96+
return null;
97+
}
98+
99+
async setPassword(service: string, account: string, password: string): Promise<void> {
100+
this.deletePassword(service, account);
101+
102+
this.credentials.push({ service, account, password });
103+
104+
this.save();
105+
}
106+
107+
async deletePassword(service: string, account: string): Promise<boolean> {
108+
let found = false;
109+
110+
this._credentials = this.credentials.filter(credential => {
111+
if (credential.service === service && credential.account === account) {
112+
found = true;
113+
114+
return false;
115+
}
116+
117+
return true;
118+
});
119+
120+
if (found) {
121+
this.save();
122+
}
123+
124+
return found;
125+
}
126+
127+
async findPassword(service: string): Promise<string | null> {
128+
return this.doGetPassword(service);
129+
}
130+
}
131+
132+
registerSingleton(ICredentialsService, BrowserCredentialsService, true);

src/vs/platform/credentials/common/credentials.ts renamed to src/vs/workbench/services/credentials/common/credentials.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
6+
import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
77

88
export const ICredentialsService = createDecorator<ICredentialsService>('ICredentialsService');
99

1010
export interface ICredentialsService {
11-
_serviceBrand: any;
11+
12+
_serviceBrand: ServiceIdentifier<any>;
13+
1214
getPassword(service: string, account: string): Promise<string | null>;
1315
setPassword(service: string, account: string, password: string): Promise<void>;
1416
deletePassword(service: string, account: string): Promise<boolean>;

src/vs/platform/credentials/node/credentialsService.ts renamed to src/vs/workbench/services/credentials/node/credentialsService.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { ICredentialsService } from 'vs/platform/credentials/common/credentials';
6+
import { ICredentialsService } from 'vs/workbench/services/credentials/common/credentials';
77
import { IdleValue } from 'vs/base/common/async';
8+
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
9+
import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
810

911
type KeytarModule = {
1012
getPassword(service: string, account: string): Promise<string | null>;
@@ -15,7 +17,7 @@ type KeytarModule = {
1517

1618
export class KeytarCredentialsService implements ICredentialsService {
1719

18-
_serviceBrand: any;
20+
_serviceBrand!: ServiceIdentifier<any>;
1921

2022
private readonly _keytar = new IdleValue<Promise<KeytarModule>>(() => import('keytar'));
2123

@@ -39,3 +41,5 @@ export class KeytarCredentialsService implements ICredentialsService {
3941
return keytar.findPassword(service);
4042
}
4143
}
44+
45+
registerSingleton(ICredentialsService, KeytarCredentialsService, true);

src/vs/workbench/services/environment/browser/environmentService.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { IWindowConfiguration, IPath, IPathsToWaitFor } from 'vs/platform/windows/common/windows';
7-
import { IEnvironmentService, IExtensionHostDebugParams, IDebugParams, BACKUPS } from 'vs/platform/environment/common/environment';
7+
import { IExtensionHostDebugParams, IDebugParams, BACKUPS } from 'vs/platform/environment/common/environment';
88
import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
99
import { URI } from 'vs/base/common/uri';
1010
import { IProcessEnvironment } from 'vs/base/common/platform';
@@ -13,6 +13,8 @@ import { ExportData } from 'vs/base/common/performance';
1313
import { LogLevel } from 'vs/platform/log/common/log';
1414
import { joinPath } from 'vs/base/common/resources';
1515
import { Schemas } from 'vs/base/common/network';
16+
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
17+
import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api';
1618

1719
export class BrowserWindowConfiguration implements IWindowConfiguration {
1820

@@ -66,26 +68,26 @@ export interface IBrowserWindowConfiguration {
6668
connectionToken?: string;
6769
}
6870

69-
export class BrowserWorkbenchEnvironmentService implements IEnvironmentService {
71+
export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironmentService {
7072

71-
_serviceBrand!: ServiceIdentifier<IEnvironmentService>;
73+
_serviceBrand!: ServiceIdentifier<IWorkbenchEnvironmentService>;
7274

7375
readonly configuration: IWindowConfiguration = new BrowserWindowConfiguration();
7476

75-
constructor(configuration: IBrowserWindowConfiguration) {
77+
constructor(workspaceId: string, public readonly options: IWorkbenchConstructionOptions) {
7678
this.args = { _: [] };
7779
this.appRoot = '/web/';
7880
this.appNameLong = 'Visual Studio Code - Web';
7981

80-
this.configuration.remoteAuthority = configuration.remoteAuthority;
82+
this.configuration.remoteAuthority = options.remoteAuthority;
8183
this.userRoamingDataHome = URI.file('/User').with({ scheme: Schemas.userData });
8284
this.settingsResource = joinPath(this.userRoamingDataHome, 'settings.json');
8385
this.keybindingsResource = joinPath(this.userRoamingDataHome, 'keybindings.json');
8486
this.keyboardLayoutResource = joinPath(this.userRoamingDataHome, 'keyboardLayout.json');
8587
this.localeResource = joinPath(this.userRoamingDataHome, 'locale.json');
8688
this.backupHome = joinPath(this.userRoamingDataHome, BACKUPS);
87-
this.configuration.backupWorkspaceResource = joinPath(this.backupHome, configuration.workspaceId);
88-
this.configuration.connectionToken = configuration.connectionToken || this.getConnectionTokenFromLocation();
89+
this.configuration.backupWorkspaceResource = joinPath(this.backupHome, workspaceId);
90+
this.configuration.connectionToken = options.connectionToken || this.getConnectionTokenFromLocation();
8991

9092
this.logsPath = '/web/logs';
9193

@@ -94,7 +96,7 @@ export class BrowserWorkbenchEnvironmentService implements IEnvironmentService {
9496
break: false
9597
};
9698

97-
this.webviewEndpoint = configuration.webviewEndpoint;
99+
this.webviewEndpoint = options.webviewEndpoint;
98100
this.untitledWorkspacesHome = URI.from({ scheme: Schemas.untitled, path: 'Workspaces' });
99101

100102
if (document && document.location && document.location.search) {

src/vs/workbench/services/environment/common/environmentService.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
77
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
88
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
9+
import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api';
910

1011
export const IWorkbenchEnvironmentService = createDecorator<IWorkbenchEnvironmentService>('environmentService');
1112

@@ -14,4 +15,6 @@ export interface IWorkbenchEnvironmentService extends IEnvironmentService {
1415
_serviceBrand: ServiceIdentifier<IEnvironmentService>;
1516

1617
readonly configuration: IWindowConfiguration;
18+
19+
readonly options?: IWorkbenchConstructionOptions;
1720
}

src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ suite('FileUserDataProvider', () => {
4747
userDataResource = URI.file(userDataPath).with({ scheme: Schemas.userData });
4848
await Promise.all([pfs.mkdirp(userDataPath), pfs.mkdirp(backupsPath)]);
4949

50-
const environmentService = new BrowserWorkbenchEnvironmentService({ workspaceId: 'workspaceId' });
50+
const environmentService = new BrowserWorkbenchEnvironmentService('workspaceId', { remoteAuthority: 'remote' });
5151
environmentService.userRoamingDataHome = userDataResource;
5252

5353
const userDataFileSystemProvider = new FileUserDataProvider(URI.file(userDataPath), URI.file(backupsPath), diskFileSystemProvider, environmentService);
@@ -321,7 +321,7 @@ suite('FileUserDataProvider - Watching', () => {
321321
localUserDataResource = URI.file(userDataPath);
322322
userDataResource = localUserDataResource.with({ scheme: Schemas.userData });
323323

324-
const environmentService = new BrowserWorkbenchEnvironmentService({ workspaceId: 'workspaceId' });
324+
const environmentService = new BrowserWorkbenchEnvironmentService('workspaceId', { remoteAuthority: 'remote' });
325325
environmentService.userRoamingDataHome = userDataResource;
326326

327327
const userDataFileSystemProvider = new FileUserDataProvider(localUserDataResource, localBackupsResource, new TestFileSystemProvider(fileEventEmitter.event), environmentService);
@@ -475,4 +475,4 @@ suite('FileUserDataProvider - Watching', () => {
475475
type: FileChangeType.DELETED
476476
}]);
477477
});
478-
});
478+
});

src/vs/workbench/workbench.desktop.main.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import 'vs/workbench/services/accessibility/node/accessibilityService';
4949
import 'vs/workbench/services/remote/node/tunnelService';
5050
import 'vs/workbench/services/backup/node/backupFileService';
5151
import 'vs/workbench/services/opener/electron-browser/openerService';
52+
import 'vs/workbench/services/credentials/node/credentialsService';
5253

5354
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
5455
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
@@ -74,8 +75,6 @@ import { IMenubarService } from 'vs/platform/menubar/common/menubar';
7475
import { MenubarService } from 'vs/platform/menubar/electron-browser/menubarService';
7576
import { IURLService } from 'vs/platform/url/common/url';
7677
import { RelayURLService } from 'vs/platform/url/electron-browser/urlService';
77-
import { ICredentialsService } from 'vs/platform/credentials/common/credentials';
78-
import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService';
7978

8079
registerSingleton(IClipboardService, ClipboardService, true);
8180
registerSingleton(IRequestService, RequestService, true);
@@ -89,7 +88,6 @@ registerSingleton(IIssueService, IssueService);
8988
registerSingleton(IWorkspacesService, WorkspacesService);
9089
registerSingleton(IMenubarService, MenubarService);
9190
registerSingleton(IURLService, RelayURLService);
92-
registerSingleton(ICredentialsService, KeytarCredentialsService, true);
9391

9492
//#endregion
9593

src/vs/workbench/workbench.web.api.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { main } from 'vs/workbench/browser/web.main';
88
import { UriComponents } from 'vs/base/common/uri';
99
import { IFileSystemProvider } from 'vs/platform/files/common/files';
1010
import { IWebSocketFactory } from 'vs/platform/remote/browser/browserSocketFactory';
11+
import { ICredentialsProvider } from 'vs/workbench/services/credentials/browser/credentialsService';
1112

1213
export interface IWorkbenchConstructionOptions {
1314

@@ -53,6 +54,11 @@ export interface IWorkbenchConstructionOptions {
5354
* Experimental: Whether to enable the smoke test driver.
5455
*/
5556
driver?: boolean;
57+
58+
/**
59+
* Experimental: The credentials provider to store and retrieve secrets.
60+
*/
61+
credentialsProvider?: ICredentialsProvider;
5662
}
5763

5864
/**

0 commit comments

Comments
 (0)