Skip to content

Commit 18cf806

Browse files
Add SecretStorage.keys() as proposed API (microsoft#252804)
* Add `SecretStorage.keys()` as proposed API * Make keys() optional; throw if used when unimplemented * Handle another case where keys() may be unimplemented
1 parent a62ed27 commit 18cf806

File tree

10 files changed

+82
-0
lines changed

10 files changed

+82
-0
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,11 @@ export class LocalStorageSecretStorageProvider implements ISecretStorageProvider
276276
this.save();
277277
}
278278

279+
async keys(): Promise<string[]> {
280+
const secrets = await this.secretsPromise;
281+
return Object.keys(secrets) || [];
282+
}
283+
279284
private async save(): Promise<void> {
280285
try {
281286
const encrypted = await this.crypto.seal(JSON.stringify(await this.secretsPromise));

src/vs/platform/extensions/common/extensionsApiProposals.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,9 @@ const _allApiProposals = {
335335
scmValidation: {
336336
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmValidation.d.ts',
337337
},
338+
secretStorageKeys: {
339+
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.secretStorageKeys.d.ts',
340+
},
338341
shareProvider: {
339342
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.shareProvider.d.ts',
340343
},

src/vs/platform/secrets/common/secrets.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export interface ISecretStorageProvider {
1919
get(key: string): Promise<string | undefined>;
2020
set(key: string, value: string): Promise<void>;
2121
delete(key: string): Promise<void>;
22+
keys?(): Promise<string[]>;
2223
}
2324

2425
export interface ISecretStorageService extends ISecretStorageProvider {
@@ -123,6 +124,16 @@ export class BaseSecretStorageService extends Disposable implements ISecretStora
123124
});
124125
}
125126

127+
keys(): Promise<string[]> {
128+
return this._sequencer.queue('__keys__', async () => {
129+
const storageService = await this.resolvedStorageService;
130+
this._logService.trace('[secrets] fetching keys of all secrets');
131+
const allKeys = storageService.keys(StorageScope.APPLICATION, StorageTarget.MACHINE);
132+
this._logService.trace('[secrets] fetched keys of all secrets');
133+
return allKeys.filter(key => key.startsWith(this._storagePrefix)).map(key => key.slice(this._storagePrefix.length));
134+
});
135+
}
136+
126137
private async initialize(): Promise<IStorageService> {
127138
let storageService;
128139
if (!this._useInMemoryStorage && await this._encryptionService.isEncryptionAvailable()) {

src/vs/platform/secrets/test/common/testSecretStorageService.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ export class TestSecretStorageService implements ISecretStorageService {
2929
this._onDidChangeSecretEmitter.fire(key);
3030
}
3131

32+
async keys(): Promise<string[]> {
33+
return Array.from(this._storage.keys());
34+
}
35+
3236
// Helper method for tests to clear all secrets
3337
clear(): void {
3438
this._storage.clear();

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,24 @@ export class MainThreadSecretState extends Disposable implements MainThreadSecre
7373
this.logService.trace('[mainThreadSecretState] Password deleted for: ', extensionId, key);
7474
}
7575

76+
$getKeys(extensionId: string): Promise<string[]> {
77+
this.logService.trace(`[mainThreadSecretState] Getting keys for ${extensionId} extension: `);
78+
return this._sequencer.queue(extensionId, () => this.doGetKeys(extensionId));
79+
}
80+
81+
private async doGetKeys(extensionId: string): Promise<string[]> {
82+
if (!this.secretStorageService.keys) {
83+
throw new Error('Secret storage service does not support keys() method');
84+
}
85+
const allKeys = await this.secretStorageService.keys();
86+
const keys = allKeys
87+
.map(key => this.parseKey(key))
88+
.filter(({ extensionId: id }) => id === extensionId)
89+
.map(({ key }) => key); // Return only my keys
90+
this.logService.trace(`[mainThreadSecretState] Got ${keys.length}key(s) for: `, extensionId);
91+
return keys;
92+
}
93+
7694
private getKey(extensionId: string, key: string): string {
7795
return JSON.stringify({ extensionId, key });
7896
}

src/vs/workbench/api/common/extHost.protocol.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ export interface MainThreadSecretStateShape extends IDisposable {
203203
$getPassword(extensionId: string, key: string): Promise<string | undefined>;
204204
$setPassword(extensionId: string, key: string, value: string): Promise<void>;
205205
$deletePassword(extensionId: string, key: string): Promise<void>;
206+
$getKeys(extensionId: string): Promise<string[]>;
206207
}
207208

208209
export interface MainThreadConfigurationShape extends IDisposable {

src/vs/workbench/api/common/extHostSecretState.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ export class ExtHostSecretState implements ExtHostSecretStateShape {
3232
delete(extensionId: string, key: string): Promise<void> {
3333
return this._proxy.$deletePassword(extensionId, key);
3434
}
35+
36+
keys(extensionId: string): Promise<string[]> {
37+
return this._proxy.$getKeys(extensionId);
38+
}
3539
}
3640

3741
export interface IExtHostSecretState extends ExtHostSecretState { }

src/vs/workbench/api/common/extHostSecrets.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,19 @@ import { ExtHostSecretState } from './extHostSecretState.js';
1111
import { ExtensionIdentifier, IExtensionDescription } from '../../../platform/extensions/common/extensions.js';
1212
import { Event } from '../../../base/common/event.js';
1313
import { DisposableStore } from '../../../base/common/lifecycle.js';
14+
import { checkProposedApiEnabled } from '../../services/extensions/common/extensions.js';
1415

1516
export class ExtensionSecrets implements vscode.SecretStorage {
1617

18+
private readonly _extensionDescription: IExtensionDescription;
1719
protected readonly _id: string;
1820
readonly #secretState: ExtHostSecretState;
1921

2022
readonly onDidChange: Event<vscode.SecretStorageChangeEvent>;
2123
readonly disposables = new DisposableStore();
2224

2325
constructor(extensionDescription: IExtensionDescription, secretState: ExtHostSecretState) {
26+
this._extensionDescription = extensionDescription;
2427
this._id = ExtensionIdentifier.toKey(extensionDescription.identifier);
2528
this.#secretState = secretState;
2629

@@ -46,4 +49,9 @@ export class ExtensionSecrets implements vscode.SecretStorage {
4649
delete(key: string): Promise<void> {
4750
return this.#secretState.delete(this._id, key);
4851
}
52+
53+
keys(): Promise<string[]> {
54+
checkProposedApiEnabled(this._extensionDescription, 'secretStorageKeys');
55+
return this.#secretState.keys(this._id) || [];
56+
}
4957
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,18 @@ export class BrowserSecretStorageService extends BaseSecretStorageService {
6969

7070
return super.type;
7171
}
72+
73+
override keys(): Promise<string[]> {
74+
if (this._secretStorageProvider) {
75+
if (!this._secretStorageProvider.keys) {
76+
throw new Error('Secret storage provider does not support keys() method');
77+
}
78+
return this._secretStorageProvider!.keys();
79+
}
80+
81+
return super.keys();
82+
83+
}
7284
}
7385

7486
registerSingleton(ISecretStorageService, BrowserSecretStorageService, InstantiationType.Delayed);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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+
declare module 'vscode' {
7+
8+
// https://github.com/microsoft/vscode/issues/196616
9+
10+
export interface SecretStorage {
11+
/**
12+
* Retrieve the keys of all the secrets stored by this extension.
13+
*/
14+
keys(): Thenable<string[] | undefined>;
15+
}
16+
}

0 commit comments

Comments
 (0)