mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-08 17:19:48 +01:00
When keytar fails to be used, use an in-memory credential store (#141120)
* have inmemory fallback * move InMemoryCredentialsProvider to common for shared scenarios
This commit is contained in:
committed by
GitHub
parent
5993199079
commit
319ee9a6af
@@ -38,3 +38,47 @@ export interface ICredentialsService extends ICredentialsProvider {
|
||||
export const ICredentialsMainService = createDecorator<ICredentialsMainService>('credentialsMainService');
|
||||
|
||||
export interface ICredentialsMainService extends ICredentialsService { }
|
||||
|
||||
interface ISecretVault {
|
||||
[service: string]: { [account: string]: string } | undefined;
|
||||
}
|
||||
|
||||
export class InMemoryCredentialsProvider implements ICredentialsProvider {
|
||||
private secretVault: ISecretVault = {};
|
||||
|
||||
async getPassword(service: string, account: string): Promise<string | null> {
|
||||
return this.secretVault[service]?.[account] ?? null;
|
||||
}
|
||||
|
||||
async setPassword(service: string, account: string, password: string): Promise<void> {
|
||||
this.secretVault[service] = this.secretVault[service] ?? {};
|
||||
this.secretVault[service]![account] = password;
|
||||
}
|
||||
|
||||
async deletePassword(service: string, account: string): Promise<boolean> {
|
||||
if (!this.secretVault[service]?.[account]) {
|
||||
return false;
|
||||
}
|
||||
delete this.secretVault[service]![account];
|
||||
if (Object.keys(this.secretVault[service]!).length === 0) {
|
||||
delete this.secretVault[service];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async findPassword(service: string): Promise<string | null> {
|
||||
return JSON.stringify(this.secretVault[service]) ?? null;
|
||||
}
|
||||
|
||||
async findCredentials(service: string): Promise<Array<{ account: string, password: string }>> {
|
||||
const credentials: { account: string, password: string }[] = [];
|
||||
for (const account of Object.keys(this.secretVault[service] || {})) {
|
||||
credentials.push({ account, password: this.secretVault[service]![account] });
|
||||
}
|
||||
return credentials;
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
this.secretVault = {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ICredentialsChangeEvent, ICredentialsMainService } from 'vs/platform/credentials/common/credentials';
|
||||
import { ICredentialsChangeEvent, ICredentialsMainService, InMemoryCredentialsProvider } from 'vs/platform/credentials/common/credentials';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
@@ -16,6 +16,8 @@ interface ChunkedPassword {
|
||||
hasNextChunk: boolean;
|
||||
}
|
||||
|
||||
type KeytarModule = typeof import('keytar');
|
||||
|
||||
export class CredentialsMainService extends Disposable implements ICredentialsMainService {
|
||||
|
||||
private static readonly MAX_PASSWORD_LENGTH = 2500;
|
||||
@@ -25,6 +27,8 @@ export class CredentialsMainService extends Disposable implements ICredentialsMa
|
||||
private _onDidChangePassword: Emitter<ICredentialsChangeEvent> = this._register(new Emitter());
|
||||
readonly onDidChangePassword = this._onDidChangePassword.event;
|
||||
|
||||
private _keytarCache: KeytarModule | undefined;
|
||||
|
||||
// If the credentials service is running on the server, we add a suffix -server to differentiate from the location that the
|
||||
// client would store the credentials.
|
||||
public async getSecretStoragePrefix() { return `${this.productService.urlProtocol}${this.isRunningOnServer ? '-server' : ''}`; }
|
||||
@@ -139,18 +143,36 @@ export class CredentialsMainService extends Disposable implements ICredentialsMa
|
||||
return keytar.findCredentials(service);
|
||||
}
|
||||
|
||||
private async withKeytar(): Promise<typeof import('keytar')> {
|
||||
if (this.environmentMainService.disableKeytar) {
|
||||
throw new Error('keytar has been disabled via --disable-keytar option');
|
||||
private async withKeytar(): Promise<KeytarModule> {
|
||||
if (this._keytarCache) {
|
||||
return this._keytarCache;
|
||||
}
|
||||
|
||||
return await import('keytar');
|
||||
if (this.environmentMainService.disableKeytar) {
|
||||
this.logService.info('Keytar is disabled. Using in-memory credential store instead.');
|
||||
this._keytarCache = new InMemoryCredentialsProvider();
|
||||
return this._keytarCache;
|
||||
}
|
||||
|
||||
try {
|
||||
this._keytarCache = await import('keytar');
|
||||
// Try using keytar to see if it throws or not.
|
||||
await this._keytarCache.findCredentials('test-keytar-loads');
|
||||
} catch (e) {
|
||||
this.logService.warn(`Switching to using in-memory credential store instead because Keytar failed to load: ${e.message}`);
|
||||
this._keytarCache = new InMemoryCredentialsProvider();
|
||||
}
|
||||
return this._keytarCache;
|
||||
}
|
||||
|
||||
// This class doesn't implement the clear() function because we don't know
|
||||
// what services have stored credentials. For reference, a "service" is an extension.
|
||||
// TODO: should we clear credentials for the built-in auth extensions?
|
||||
public clear(): Promise<void> {
|
||||
if (this._keytarCache instanceof InMemoryCredentialsProvider) {
|
||||
return this._keytarCache.clear();
|
||||
}
|
||||
|
||||
// We don't know how to properly clear Keytar because we don't know
|
||||
// what services have stored credentials. For reference, a "service" is an extension.
|
||||
// TODO: should we clear credentials for the built-in auth extensions?
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ICredentialsService, ICredentialsProvider, ICredentialsChangeEvent } from 'vs/platform/credentials/common/credentials';
|
||||
import { ICredentialsService, ICredentialsProvider, ICredentialsChangeEvent, InMemoryCredentialsProvider } from 'vs/platform/credentials/common/credentials';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
@@ -77,55 +77,3 @@ export class BrowserCredentialsService extends Disposable implements ICredential
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ICredential {
|
||||
service: string;
|
||||
account: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
class InMemoryCredentialsProvider implements ICredentialsProvider {
|
||||
|
||||
private credentials: ICredential[] = [];
|
||||
|
||||
async getPassword(service: string, account: string): Promise<string | null> {
|
||||
const credential = this.doFindPassword(service, account);
|
||||
|
||||
return credential ? credential.password : null;
|
||||
}
|
||||
|
||||
async setPassword(service: string, account: string, password: string): Promise<void> {
|
||||
this.deletePassword(service, account);
|
||||
this.credentials.push({ service, account, password });
|
||||
}
|
||||
|
||||
async deletePassword(service: string, account: string): Promise<boolean> {
|
||||
const credential = this.doFindPassword(service, account);
|
||||
if (credential) {
|
||||
this.credentials.splice(this.credentials.indexOf(credential), 1);
|
||||
}
|
||||
|
||||
return !!credential;
|
||||
}
|
||||
|
||||
async findPassword(service: string): Promise<string | null> {
|
||||
const credential = this.doFindPassword(service);
|
||||
|
||||
return credential ? credential.password : null;
|
||||
}
|
||||
|
||||
private doFindPassword(service: string, account?: string): ICredential | undefined {
|
||||
return this.credentials.find(credential =>
|
||||
credential.service === service && (typeof account !== 'string' || credential.account === account));
|
||||
}
|
||||
|
||||
async findCredentials(service: string): Promise<Array<{ account: string, password: string; }>> {
|
||||
return this.credentials
|
||||
.filter(credential => credential.service === service)
|
||||
.map(({ account, password }) => ({ account, password }));
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
this.credentials = [];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user