Skip to content

Commit 322bc2d

Browse files
Have getCurrentAuthenticationSessionInfo take in a secretStorageService (microsoft#187092)
To start the migration process, we have `getCurrentAuthenticationSessionInfo` take in a `secretStorageService` and also replaces the `LocalStorageCredentialProvider` with a `LocalStorageSecretStorageProvider`. After this goes in we can then update all embedders (vscode.dev, github.dev, Codespaces, vscode-web-test?) and replace ICredentialProvider usages with ISecretStorageProviders and then once we do that, we can get rid of a bunch of code!
1 parent c7d219b commit 322bc2d

File tree

8 files changed

+71
-145
lines changed

8 files changed

+71
-145
lines changed

src/vs/code/browser/workbench/workbench.ts

Lines changed: 36 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,32 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { isStandalone } from 'vs/base/browser/browser';
7-
import { CancellationToken } from 'vs/base/common/cancellation';
87
import { parse } from 'vs/base/common/marshalling';
98
import { Emitter } from 'vs/base/common/event';
109
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
1110
import { Schemas } from 'vs/base/common/network';
1211
import { isEqual } from 'vs/base/common/resources';
1312
import { URI, UriComponents } from 'vs/base/common/uri';
14-
import { request } from 'vs/base/parts/request/browser/request';
1513
import product from 'vs/platform/product/common/product';
1614
import { isFolderToOpen, isWorkspaceToOpen } from 'vs/platform/window/common/window';
1715
import { create } from 'vs/workbench/workbench.web.main';
1816
import { posix } from 'vs/base/common/path';
1917
import { ltrim } from 'vs/base/common/strings';
20-
import type { ICredentialsProvider } from 'vs/platform/credentials/common/credentials';
2118
import type { IURLCallbackProvider } from 'vs/workbench/services/url/browser/urlService';
2219
import type { IWorkbenchConstructionOptions } from 'vs/workbench/browser/web.api';
2320
import type { IWorkspace, IWorkspaceProvider } from 'vs/workbench/services/host/browser/browserHostService';
21+
import { ISecretStorageProvider } from 'vs/platform/secrets/common/secrets';
22+
import { AuthenticationSessionInfo } from 'vs/workbench/services/authentication/browser/authenticationService';
2423

25-
interface ICredential {
26-
service: string;
27-
account: string;
28-
password: string;
29-
}
30-
31-
class LocalStorageCredentialsProvider implements ICredentialsProvider {
24+
class LocalStorageSecretStorageProvider implements ISecretStorageProvider {
25+
private static readonly STORAGE_KEY = 'secrets.provider';
3226

33-
private static readonly CREDENTIALS_STORAGE_KEY = 'credentials.provider';
27+
private _secrets: Record<string, string> | undefined;
3428

35-
private readonly authService: string | undefined;
29+
type: 'in-memory' | 'persisted' | 'unknown' = 'persisted';
3630

3731
constructor() {
38-
let authSessionInfo: { readonly id: string; readonly accessToken: string; readonly providerId: string; readonly canSignOut?: boolean; readonly scopes: string[][] } | undefined;
32+
let authSessionInfo: (AuthenticationSessionInfo & { scopes: string[][] }) | undefined;
3933
const authSessionElement = document.getElementById('vscode-workbench-auth-session');
4034
const authSessionElementAttribute = authSessionElement ? authSessionElement.getAttribute('data-settings') : undefined;
4135
if (authSessionElementAttribute) {
@@ -46,133 +40,60 @@ class LocalStorageCredentialsProvider implements ICredentialsProvider {
4640

4741
if (authSessionInfo) {
4842
// Settings Sync Entry
49-
this.setPassword(`${product.urlProtocol}.login`, 'account', JSON.stringify(authSessionInfo));
43+
this.set(`${product.urlProtocol}.loginAccount`, JSON.stringify(authSessionInfo));
5044

5145
// Auth extension Entry
52-
this.authService = `${product.urlProtocol}-${authSessionInfo.providerId}.login`;
53-
this.setPassword(this.authService, 'account', JSON.stringify(authSessionInfo.scopes.map(scopes => ({
46+
if (authSessionInfo.providerId !== 'github') {
47+
console.error(`Unexpected auth provider: ${authSessionInfo.providerId}. Expected 'github'.`);
48+
return;
49+
}
50+
const authAccount = JSON.stringify({ extensionId: 'vscode.github-authentication', key: 'github.auth' });
51+
this.set(authAccount, JSON.stringify(authSessionInfo.scopes.map(scopes => ({
5452
id: authSessionInfo!.id,
5553
scopes,
5654
accessToken: authSessionInfo!.accessToken
5755
}))));
5856
}
5957
}
6058

61-
private _credentials: ICredential[] | undefined;
62-
private get credentials(): ICredential[] {
63-
if (!this._credentials) {
64-
try {
65-
const serializedCredentials = window.localStorage.getItem(LocalStorageCredentialsProvider.CREDENTIALS_STORAGE_KEY);
66-
if (serializedCredentials) {
67-
this._credentials = JSON.parse(serializedCredentials);
68-
}
69-
} catch (error) {
70-
// ignore
71-
}
72-
73-
if (!Array.isArray(this._credentials)) {
74-
this._credentials = [];
75-
}
76-
}
77-
78-
return this._credentials;
79-
}
80-
81-
private save(): void {
82-
window.localStorage.setItem(LocalStorageCredentialsProvider.CREDENTIALS_STORAGE_KEY, JSON.stringify(this.credentials));
83-
}
84-
85-
async getPassword(service: string, account: string): Promise<string | null> {
86-
return this.doGetPassword(service, account);
59+
get(key: string): Promise<string | undefined> {
60+
return Promise.resolve(this.secrets[key]);
8761
}
62+
set(key: string, value: string): Promise<void> {
63+
this.secrets[key] = value;
64+
this.save();
8865

89-
private async doGetPassword(service: string, account?: string): Promise<string | null> {
90-
for (const credential of this.credentials) {
91-
if (credential.service === service) {
92-
if (typeof account !== 'string' || account === credential.account) {
93-
return credential.password;
94-
}
95-
}
96-
}
97-
98-
return null;
66+
return Promise.resolve();
9967
}
100-
101-
async setPassword(service: string, account: string, password: string): Promise<void> {
102-
this.doDeletePassword(service, account);
103-
104-
this.credentials.push({ service, account, password });
68+
async delete(key: string): Promise<void> {
69+
delete this.secrets[key];
10570

10671
this.save();
10772

108-
try {
109-
if (password && service === this.authService) {
110-
const value = JSON.parse(password);
111-
if (Array.isArray(value) && value.length === 0) {
112-
await this.logout(service);
113-
}
114-
}
115-
} catch (error) {
116-
console.log(error);
117-
}
73+
return Promise.resolve();
11874
}
11975

120-
async deletePassword(service: string, account: string): Promise<boolean> {
121-
const result = await this.doDeletePassword(service, account);
122-
123-
if (result && service === this.authService) {
76+
private get secrets(): Record<string, string> {
77+
if (!this._secrets) {
12478
try {
125-
await this.logout(service);
79+
const serializedCredentials = window.localStorage.getItem(LocalStorageSecretStorageProvider.STORAGE_KEY);
80+
if (serializedCredentials) {
81+
this._secrets = JSON.parse(serializedCredentials);
82+
}
12683
} catch (error) {
127-
console.log(error);
84+
// ignore
12885
}
129-
}
130-
131-
return result;
132-
}
13386

134-
private async doDeletePassword(service: string, account: string): Promise<boolean> {
135-
let found = false;
136-
137-
this._credentials = this.credentials.filter(credential => {
138-
if (credential.service === service && credential.account === account) {
139-
found = true;
140-
141-
return false;
87+
if (!(this._secrets instanceof Object)) {
88+
this._secrets = {};
14289
}
143-
144-
return true;
145-
});
146-
147-
if (found) {
148-
this.save();
14990
}
15091

151-
return found;
152-
}
153-
154-
async findPassword(service: string): Promise<string | null> {
155-
return this.doGetPassword(service);
92+
return this._secrets;
15693
}
15794

158-
async findCredentials(service: string): Promise<Array<{ account: string; password: string }>> {
159-
return this.credentials
160-
.filter(credential => credential.service === service)
161-
.map(({ account, password }) => ({ account, password }));
162-
}
163-
164-
private async logout(service: string): Promise<void> {
165-
const queryValues: Map<string, string> = new Map();
166-
queryValues.set('logout', String(true));
167-
queryValues.set('service', service);
168-
169-
await request({
170-
url: doCreateUri('/auth/logout', queryValues).toString(true)
171-
}, CancellationToken.None);
172-
}
173-
174-
async clear(): Promise<void> {
175-
window.localStorage.removeItem(LocalStorageCredentialsProvider.CREDENTIALS_STORAGE_KEY);
95+
private save(): void {
96+
window.localStorage.setItem(LocalStorageSecretStorageProvider.STORAGE_KEY, JSON.stringify(this.secrets));
17697
}
17798
}
17899

@@ -469,24 +390,6 @@ class WorkspaceProvider implements IWorkspaceProvider {
469390
}
470391
}
471392

472-
function doCreateUri(path: string, queryValues: Map<string, string>): URI {
473-
let query: string | undefined = undefined;
474-
475-
if (queryValues) {
476-
let index = 0;
477-
queryValues.forEach((value, key) => {
478-
if (!query) {
479-
query = '';
480-
}
481-
482-
const prefix = (index++ === 0) ? '' : '&';
483-
query += `${prefix}${key}=${encodeURIComponent(value)}`;
484-
});
485-
}
486-
487-
return URI.parse(window.location.href).with({ path, query });
488-
}
489-
490393
(function () {
491394

492395
// Find config by checking for DOM
@@ -504,6 +407,6 @@ function doCreateUri(path: string, queryValues: Map<string, string>): URI {
504407
settingsSyncOptions: config.settingsSyncOptions ? { enabled: config.settingsSyncOptions.enabled, } : undefined,
505408
workspaceProvider: WorkspaceProvider.create(config),
506409
urlCallbackProvider: new LocalStorageURLCallbackProvider(config.callbackRoute),
507-
credentialsProvider: config.remoteAuthority ? undefined /* with a remote, we don't use a local credentials provider */ : new LocalStorageCredentialsProvider()
410+
secretStorageProvider: config.remoteAuthority ? undefined /* with a remote, we don't use a local secret storage provider */ : new LocalStorageSecretStorageProvider()
508411
});
509412
})();

src/vs/workbench/browser/parts/activitybar/activitybarActions.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import { ICredentialsService } from 'vs/platform/credentials/common/credentials'
4141
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
4242
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
4343
import { ILogService } from 'vs/platform/log/common/log';
44+
import { ISecretStorageService } from 'vs/platform/secrets/common/secrets';
4445

4546
export class ViewContainerActivityAction extends ActivityAction {
4647

@@ -235,7 +236,7 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
235236
private readonly problematicProviders: Set<string> = new Set();
236237

237238
private initialized = false;
238-
private sessionFromEmbedder = getCurrentAuthenticationSessionInfo(this.credentialsService, this.productService);
239+
private sessionFromEmbedder = getCurrentAuthenticationSessionInfo(this.credentialsService, this.secretStorageService, this.productService);
239240

240241
constructor(
241242
action: ActivityAction,
@@ -253,6 +254,7 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
253254
@IConfigurationService configurationService: IConfigurationService,
254255
@IStorageService private readonly storageService: IStorageService,
255256
@IKeybindingService keybindingService: IKeybindingService,
257+
@ISecretStorageService private readonly secretStorageService: ISecretStorageService,
256258
@ICredentialsService private readonly credentialsService: ICredentialsService,
257259
@ILogService private readonly logService: ILogService
258260
) {

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ import { BrowserRemoteResourceLoader } from 'vs/workbench/services/remote/browse
9292
import { BufferLogger } from 'vs/platform/log/common/bufferLog';
9393
import { FileLoggerService } from 'vs/platform/log/common/fileLog';
9494
import { IEmbedderTerminalService } from 'vs/workbench/services/terminal/common/embedderTerminalService';
95+
import { BrowserSecretStorageService } from 'vs/workbench/services/secrets/browser/secretStorageService';
96+
import { EncryptionService } from 'vs/workbench/services/encryption/browser/encryptionService';
97+
import { IEncryptionService } from 'vs/platform/encryption/common/encryptionService';
98+
import { ISecretStorageService } from 'vs/platform/secrets/common/secrets';
9599

96100
export class BrowserMain extends Disposable {
97101

@@ -384,9 +388,14 @@ export class BrowserMain extends Disposable {
384388
const credentialsService = new BrowserCredentialsService(environmentService, remoteAgentService, productService);
385389
serviceCollection.set(ICredentialsService, credentialsService);
386390

391+
const encryptionService = new EncryptionService();
392+
serviceCollection.set(IEncryptionService, encryptionService);
393+
const secretStorageService = new BrowserSecretStorageService(storageService, encryptionService, environmentService, logService);
394+
serviceCollection.set(ISecretStorageService, secretStorageService);
395+
387396
// Userdata Initialize Service
388397
const userDataInitializers: IUserDataInitializer[] = [];
389-
userDataInitializers.push(new UserDataSyncInitializer(environmentService, credentialsService, userDataSyncStoreManagementService, fileService, userDataProfilesService, storageService, productService, requestService, logService, uriIdentityService));
398+
userDataInitializers.push(new UserDataSyncInitializer(environmentService, secretStorageService, credentialsService, userDataSyncStoreManagementService, fileService, userDataProfilesService, storageService, productService, requestService, logService, uriIdentityService));
390399
if (environmentService.options.profile) {
391400
userDataInitializers.push(new UserDataProfileInitializer(environmentService, fileService, userDataProfileService, storageService, logService, uriIdentityService, requestService));
392401
}

src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { IUserDataSyncMachinesService, UserDataSyncMachinesService } from 'vs/pl
2525
import { Emitter } from 'vs/base/common/event';
2626
import { CancellationError } from 'vs/base/common/errors';
2727
import { EditSessionsStoreClient } from 'vs/workbench/contrib/editSessions/common/editSessionsStorageClient';
28+
import { ISecretStorageService } from 'vs/platform/secrets/common/secrets';
2829

2930
type ExistingSession = IQuickPickItem & { session: AuthenticationSession & { providerId: string } };
3031
type AuthenticationProviderOption = IQuickPickItem & { provider: IAuthenticationProvider };
@@ -81,6 +82,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes
8182
@IProductService private readonly productService: IProductService,
8283
@IContextKeyService private readonly contextKeyService: IContextKeyService,
8384
@IDialogService private readonly dialogService: IDialogService,
85+
@ISecretStorageService private readonly secretStorageService: ISecretStorageService,
8486
@ICredentialsService private readonly credentialsService: ICredentialsService
8587
) {
8688
super();
@@ -278,7 +280,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes
278280
// If settings sync is already enabled, avoid asking again to authenticate
279281
if (this.shouldAttemptEditSessionInit()) {
280282
this.logService.info(`Reusing user data sync enablement`);
281-
const authenticationSessionInfo = await getCurrentAuthenticationSessionInfo(this.credentialsService, this.productService);
283+
const authenticationSessionInfo = await getCurrentAuthenticationSessionInfo(this.credentialsService, this.secretStorageService, this.productService);
282284
if (authenticationSessionInfo !== undefined) {
283285
this.logService.info(`Using current authentication session with ID ${authenticationSessionInfo.id}`);
284286
this.existingSessionId = authenticationSessionInfo.id;

src/vs/workbench/services/authentication/browser/authenticationService.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/
1919
import { Severity } from 'vs/platform/notification/common/notification';
2020
import { IProductService } from 'vs/platform/product/common/productService';
2121
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
22+
import { ISecretStorageService } from 'vs/platform/secrets/common/secrets';
2223
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
2324
import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity';
2425
import { IAuthenticationCreateSessionOptions, AuthenticationProviderInformation, AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationProvider, IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication';
@@ -76,9 +77,17 @@ export function addAccountUsage(storageService: IStorageService, providerId: str
7677
storageService.store(accountKey, JSON.stringify(usages), StorageScope.APPLICATION, StorageTarget.MACHINE);
7778
}
7879

80+
// TODO: pull this out into its own service
7981
export type AuthenticationSessionInfo = { readonly id: string; readonly accessToken: string; readonly providerId: string; readonly canSignOut?: boolean };
80-
export async function getCurrentAuthenticationSessionInfo(credentialsService: ICredentialsService, productService: IProductService): Promise<AuthenticationSessionInfo | undefined> {
81-
const authenticationSessionValue = await credentialsService.getPassword(`${productService.urlProtocol}.login`, 'account');
82+
export async function getCurrentAuthenticationSessionInfo(
83+
// TODO: Remove when all known embedders implement SecretStorageProviders instead of CredentialsProviders
84+
credentialsService: ICredentialsService,
85+
secretStorageService: ISecretStorageService,
86+
productService: IProductService
87+
): Promise<AuthenticationSessionInfo | undefined> {
88+
const authenticationSessionValue =
89+
await secretStorageService.get(`${productService.urlProtocol}.loginAccount`)
90+
?? await credentialsService.getPassword(`${productService.urlProtocol}.login`, 'account');
8291
if (authenticationSessionValue) {
8392
try {
8493
const authenticationSessionInfo: AuthenticationSessionInfo = JSON.parse(authenticationSessionValue);
@@ -90,7 +99,8 @@ export async function getCurrentAuthenticationSessionInfo(credentialsService: IC
9099
return authenticationSessionInfo;
91100
}
92101
} catch (e) {
93-
// ignore as this is a best effort operation.
102+
// This is a best effort operation.
103+
console.error(`Failed parsing current auth session value: ${e}`);
94104
}
95105
}
96106
return undefined;

src/vs/workbench/services/secrets/browser/secretStorageService.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@
55

66
import { IEncryptionService } from 'vs/platform/encryption/common/encryptionService';
77
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
8-
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
98
import { ILogService } from 'vs/platform/log/common/log';
10-
import { INotificationService } from 'vs/platform/notification/common/notification';
119
import { ISecretStorageProvider, ISecretStorageService, BaseSecretStorageService } from 'vs/platform/secrets/common/secrets';
1210
import { IStorageService } from 'vs/platform/storage/common/storage';
1311
import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService';
@@ -20,8 +18,6 @@ export class BrowserSecretStorageService extends BaseSecretStorageService {
2018
@IStorageService storageService: IStorageService,
2119
@IEncryptionService encryptionService: IEncryptionService,
2220
@IBrowserWorkbenchEnvironmentService environmentService: IBrowserWorkbenchEnvironmentService,
23-
@IInstantiationService instantiationService: IInstantiationService,
24-
@INotificationService notificationService: INotificationService,
2521
@ILogService logService: ILogService
2622
) {
2723
super(storageService, encryptionService, logService);

src/vs/workbench/services/userDataSync/browser/userDataSyncInit.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { TasksInitializer } from 'vs/platform/userDataSync/common/tasksSync';
3535
import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
3636
import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService';
3737
import { IUserDataInitializer } from 'vs/workbench/services/userData/browser/userDataInit';
38+
import { ISecretStorageService } from 'vs/platform/secrets/common/secrets';
3839

3940
export class UserDataSyncInitializer implements IUserDataInitializer {
4041

@@ -46,6 +47,7 @@ export class UserDataSyncInitializer implements IUserDataInitializer {
4647

4748
constructor(
4849
@IBrowserWorkbenchEnvironmentService private readonly environmentService: IBrowserWorkbenchEnvironmentService,
50+
@ISecretStorageService private readonly secretStorageService: ISecretStorageService,
4951
@ICredentialsService private readonly credentialsService: ICredentialsService,
5052
@IUserDataSyncStoreManagementService private readonly userDataSyncStoreManagementService: IUserDataSyncStoreManagementService,
5153
@IFileService private readonly fileService: IFileService,
@@ -90,7 +92,7 @@ export class UserDataSyncInitializer implements IUserDataInitializer {
9092

9193
let authenticationSession;
9294
try {
93-
authenticationSession = await getCurrentAuthenticationSessionInfo(this.credentialsService, this.productService);
95+
authenticationSession = await getCurrentAuthenticationSessionInfo(this.credentialsService, this.secretStorageService, this.productService);
9496
} catch (error) {
9597
this.logService.error(error);
9698
}

0 commit comments

Comments
 (0)