UNPKG

sussudio

Version:

An unofficial VS Code Internal API

205 lines (204 loc) 9.32 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; import { InMemoryCredentialsProvider } from "./credentials.mjs"; import { Emitter } from "../../../base/common/event.mjs"; import { Disposable } from "../../../base/common/lifecycle.mjs"; import { ILogService } from "../../log/common/log.mjs"; import { isWindows } from "../../../base/common/platform.mjs"; import { retry } from "../../../base/common/async.mjs"; let BaseCredentialsMainService = class BaseCredentialsMainService extends Disposable { logService; static MAX_PASSWORD_LENGTH = 2500; static PASSWORD_CHUNK_SIZE = BaseCredentialsMainService.MAX_PASSWORD_LENGTH - 100; _onDidChangePassword = this._register(new Emitter()); onDidChangePassword = this._onDidChangePassword.event; _keytarCache; constructor(logService) { super(); this.logService = logService; } //#endregion async getPassword(service, account) { this.logService.trace('Getting password from keytar:', service, account); let keytar; try { keytar = await this.withKeytar(); } catch (e) { // for get operations, we don't want to surface errors to the user return null; } const password = await retry(() => keytar.getPassword(service, account), 50, 3); if (!password) { this.logService.trace('Did not get a password from keytar for account:', account); return password; } let content; let hasNextChunk; try { const parsed = JSON.parse(password); content = parsed.content; hasNextChunk = parsed.hasNextChunk; } catch { // Ignore this similar to how we ignore parse errors in the delete // because on non-windows this will not be a JSON string. } if (!content || !hasNextChunk) { this.logService.trace('Got password from keytar for account:', account); return password; } try { let index = 1; while (hasNextChunk) { const nextChunk = await retry(() => keytar.getPassword(service, `${account}-${index}`), 50, 3); const result = JSON.parse(nextChunk); content += result.content; hasNextChunk = result.hasNextChunk; index++; } this.logService.trace(`Got ${index}-chunked password from keytar for account:`, account); return content; } catch (e) { this.logService.error(e); return password; } } async setPassword(service, account, password) { this.logService.trace('Setting password using keytar:', service, account); let keytar; try { keytar = await this.withKeytar(); } catch (e) { this.surfaceKeytarLoadError?.(e); throw e; } if (isWindows && password.length > BaseCredentialsMainService.MAX_PASSWORD_LENGTH) { let index = 0; let chunk = 0; let hasNextChunk = true; while (hasNextChunk) { const passwordChunk = password.substring(index, index + BaseCredentialsMainService.PASSWORD_CHUNK_SIZE); index += BaseCredentialsMainService.PASSWORD_CHUNK_SIZE; hasNextChunk = password.length - index > 0; const content = { content: passwordChunk, hasNextChunk: hasNextChunk }; await retry(() => keytar.setPassword(service, chunk ? `${account}-${chunk}` : account, JSON.stringify(content)), 50, 3); chunk++; } this.logService.trace(`Got${chunk ? ` ${chunk}-chunked` : ''} password from keytar for account:`, account); } else { await retry(() => keytar.setPassword(service, account, password), 50, 3); this.logService.trace('Got password from keytar for account:', account); } this._onDidChangePassword.fire({ service, account }); } async deletePassword(service, account) { this.logService.trace('Deleting password using keytar:', service, account); let keytar; try { keytar = await this.withKeytar(); } catch (e) { this.surfaceKeytarLoadError?.(e); throw e; } const password = await keytar.getPassword(service, account); if (!password) { this.logService.trace('Did not get a password to delete from keytar for account:', account); return false; } let content; let hasNextChunk; try { const possibleChunk = JSON.parse(password); content = possibleChunk.content; hasNextChunk = possibleChunk.hasNextChunk; } catch { // When the password is saved the entire JSON payload is encrypted then stored, thus the result from getPassword might not be valid JSON // https://github.com/microsoft/vscode/blob/c22cb87311b5eb1a3bf5600d18733f7485355dc0/src/vs/workbench/api/browser/mainThreadSecretState.ts#L83 // However in the chunked case we JSONify each chunk after encryption so for the chunked case we do expect valid JSON here // https://github.com/microsoft/vscode/blob/708cb0c507d656b760f9d08115b8ebaf8964fd73/src/vs/platform/credentials/common/credentialsMainService.ts#L128 // Empty catch here just as in getPassword because we expect to handle both JSON cases and non JSON cases here it's not an error case to fail to parse // https://github.com/microsoft/vscode/blob/708cb0c507d656b760f9d08115b8ebaf8964fd73/src/vs/platform/credentials/common/credentialsMainService.ts#L76 } let index = 0; if (content && hasNextChunk) { try { // need to delete additional chunks index++; while (hasNextChunk) { const accountWithIndex = `${account}-${index}`; const nextChunk = await keytar.getPassword(service, accountWithIndex); await keytar.deletePassword(service, accountWithIndex); const result = JSON.parse(nextChunk); hasNextChunk = result.hasNextChunk; index++; } } catch (e) { this.logService.error(e); } } // Delete the first account to determine deletion success if (await keytar.deletePassword(service, account)) { this._onDidChangePassword.fire({ service, account }); this.logService.trace(`Deleted${index ? ` ${index}-chunked` : ''} password from keytar for account:`, account); return true; } this.logService.trace(`Keytar failed to delete${index ? ` ${index}-chunked` : ''} password for account:`, account); return false; } async findPassword(service) { let keytar; try { keytar = await this.withKeytar(); } catch (e) { // for get operations, we don't want to surface errors to the user return null; } return await keytar.findPassword(service); } async findCredentials(service) { let keytar; try { keytar = await this.withKeytar(); } catch (e) { // for get operations, we don't want to surface errors to the user return []; } return await keytar.findCredentials(service); } clear() { 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(); } }; BaseCredentialsMainService = __decorate([ __param(0, ILogService) ], BaseCredentialsMainService); export { BaseCredentialsMainService };