chore(vscode): update to 1.53.2

These conflicts will be resolved in the following commits. We do it this way so
that PR review is possible.
This commit is contained in:
Joe Previte
2021-02-25 11:27:27 -07:00
1900 changed files with 83066 additions and 64589 deletions

View File

@@ -3,18 +3,28 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { Disposable } from 'vs/base/common/lifecycle';
import { Event } from 'vs/base/common/event';
import { FileAccess } from 'vs/base/common/network';
import { BrowserWindow, BrowserWindowConstructorOptions, app, AuthInfo, WebContents, Event as ElectronEvent } from 'electron';
import { hash } from 'vs/base/common/hash';
import { app, AuthInfo, WebContents, Event as ElectronEvent, AuthenticationResponseDetails } from 'electron';
import { ILogService } from 'vs/platform/log/common/log';
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
import { INativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService';
import { IEncryptionMainService } from 'vs/platform/encryption/electron-main/encryptionMainService';
import { generateUuid } from 'vs/base/common/uuid';
import product from 'vs/platform/product/common/product';
import { CancellationToken } from 'vs/base/common/cancellation';
interface ElectronAuthenticationResponseDetails extends AuthenticationResponseDetails {
firstAuthAttempt?: boolean; // https://github.com/electron/electron/blob/84a42a050e7d45225e69df5bd2d2bf9f1037ea41/shell/browser/login_handler.cc#L70
}
type LoginEvent = {
event: ElectronEvent;
webContents: WebContents;
req: Request;
authInfo: AuthInfo;
cb: (username: string, password: string) => void;
req: ElectronAuthenticationResponseDetails;
callback: (username?: string, password?: string) => void;
};
type Credentials = {
@@ -22,81 +32,211 @@ type Credentials = {
password: string;
};
enum ProxyAuthState {
/**
* Initial state: we will try to use stored credentials
* first to reply to the auth challenge.
*/
Initial = 1,
/**
* We used stored credentials and are still challenged,
* so we will show a login dialog next.
*/
StoredCredentialsUsed,
/**
* Finally, if we showed a login dialog already, we will
* not show any more login dialogs until restart to reduce
* the UI noise.
*/
LoginDialogShown
}
export class ProxyAuthHandler extends Disposable {
declare readonly _serviceBrand: undefined;
private static PROXY_CREDENTIALS_SERVICE_KEY = `${product.urlProtocol}.proxy-credentials`;
private retryCount = 0;
private pendingProxyResolve: Promise<Credentials | undefined> | undefined = undefined;
constructor() {
private state = ProxyAuthState.Initial;
private sessionCredentials: Credentials | undefined = undefined;
constructor(
@ILogService private readonly logService: ILogService,
@IWindowsMainService private readonly windowsMainService: IWindowsMainService,
@INativeHostMainService private readonly nativeHostMainService: INativeHostMainService,
@IEncryptionMainService private readonly encryptionMainService: IEncryptionMainService
) {
super();
this.registerListeners();
}
private registerListeners(): void {
const onLogin = Event.fromNodeEventEmitter<LoginEvent>(app, 'login', (event, webContents, req, authInfo, cb) => ({ event, webContents, req, authInfo, cb }));
const onLogin = Event.fromNodeEventEmitter<LoginEvent>(app, 'login', (event: ElectronEvent, webContents: WebContents, req: ElectronAuthenticationResponseDetails, authInfo: AuthInfo, callback) => ({ event, webContents, req, authInfo, callback }));
this._register(onLogin(this.onLogin, this));
}
private onLogin({ event, authInfo, cb }: LoginEvent): void {
private async onLogin({ event, authInfo, req, callback }: LoginEvent): Promise<void> {
if (!authInfo.isProxy) {
return;
return; // only for proxy
}
if (this.retryCount++ > 1) {
return;
if (!this.pendingProxyResolve && this.state === ProxyAuthState.LoginDialogShown && req.firstAuthAttempt) {
this.logService.trace('auth#onLogin (proxy) - exit - proxy dialog already shown');
return; // only one dialog per session at max (except when firstAuthAttempt: false which indicates a login problem)
}
// Signal we handle this event on our own, otherwise
// Electron will ignore our provided credentials.
event.preventDefault();
const opts: BrowserWindowConstructorOptions = {
alwaysOnTop: true,
skipTaskbar: true,
resizable: false,
width: 450,
height: 225,
show: true,
title: 'VS Code',
webPreferences: {
preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js', require).fsPath,
sandbox: true,
contextIsolation: true,
enableWebSQL: false,
enableRemoteModule: false,
spellcheck: false,
devTools: false
}
};
let credentials: Credentials | undefined = undefined;
if (!this.pendingProxyResolve) {
this.logService.trace('auth#onLogin (proxy) - no pending proxy handling found, starting new');
const focusedWindow = BrowserWindow.getFocusedWindow();
if (focusedWindow) {
opts.parent = focusedWindow;
opts.modal = true;
this.pendingProxyResolve = this.resolveProxyCredentials(authInfo);
try {
credentials = await this.pendingProxyResolve;
} finally {
this.pendingProxyResolve = undefined;
}
} else {
this.logService.trace('auth#onLogin (proxy) - pending proxy handling found');
credentials = await this.pendingProxyResolve;
}
const win = new BrowserWindow(opts);
const windowUrl = FileAccess.asBrowserUri('vs/code/electron-sandbox/proxy/auth.html', require);
const proxyUrl = `${authInfo.host}:${authInfo.port}`;
const title = localize('authRequire', "Proxy Authentication Required");
const message = localize('proxyauth', "The proxy {0} requires authentication.", proxyUrl);
// According to Electron docs, it is fine to call back without
// username or password to signal that the authentication was handled
// by us, even though without having credentials received:
//
// > If `callback` is called without a username or password, the authentication
// > request will be cancelled and the authentication error will be returned to the
// > page.
callback(credentials?.username, credentials?.password);
}
const onWindowClose = () => cb('', '');
win.on('close', onWindowClose);
private async resolveProxyCredentials(authInfo: AuthInfo): Promise<Credentials | undefined> {
this.logService.trace('auth#resolveProxyCredentials (proxy) - enter');
win.setMenu(null);
win.webContents.on('did-finish-load', () => {
const data = { title, message };
win.webContents.send('vscode:openProxyAuthDialog', data);
});
win.webContents.on('ipc-message', (event, channel, credentials: Credentials) => {
if (channel === 'vscode:proxyAuthResponse') {
const { username, password } = credentials;
cb(username, password);
win.removeListener('close', onWindowClose);
win.close();
try {
const credentials = await this.doResolveProxyCredentials(authInfo);
if (credentials) {
this.logService.trace('auth#resolveProxyCredentials (proxy) - got credentials');
return credentials;
} else {
this.logService.trace('auth#resolveProxyCredentials (proxy) - did not get credentials');
}
} finally {
this.logService.trace('auth#resolveProxyCredentials (proxy) - exit');
}
return undefined;
}
private async doResolveProxyCredentials(authInfo: AuthInfo): Promise<Credentials | undefined> {
this.logService.trace('auth#doResolveProxyCredentials - enter', authInfo);
// Compute a hash over the authentication info to be used
// with the credentials store to return the right credentials
// given the properties of the auth request
// (see https://github.com/microsoft/vscode/issues/109497)
const authInfoHash = String(hash({ scheme: authInfo.scheme, host: authInfo.host, port: authInfo.port }));
// Find any previously stored credentials
let storedUsername: string | undefined = undefined;
let storedPassword: string | undefined = undefined;
try {
const encryptedSerializedProxyCredentials = await this.nativeHostMainService.getPassword(undefined, ProxyAuthHandler.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash);
if (encryptedSerializedProxyCredentials) {
const credentials: Credentials = JSON.parse(await this.encryptionMainService.decrypt(encryptedSerializedProxyCredentials));
storedUsername = credentials.username;
storedPassword = credentials.password;
}
} catch (error) {
this.logService.error(error); // handle errors by asking user for login via dialog
}
// Reply with stored credentials unless we used them already.
// In that case we need to show a login dialog again because
// they seem invalid.
if (this.state !== ProxyAuthState.StoredCredentialsUsed && typeof storedUsername === 'string' && typeof storedPassword === 'string') {
this.logService.trace('auth#doResolveProxyCredentials (proxy) - exit - found stored credentials to use');
this.state = ProxyAuthState.StoredCredentialsUsed;
return { username: storedUsername, password: storedPassword };
}
// Find suitable window to show dialog: prefer to show it in the
// active window because any other network request will wait on
// the credentials and we want the user to present the dialog.
const window = this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow();
if (!window) {
this.logService.trace('auth#doResolveProxyCredentials (proxy) - exit - no opened window found to show dialog in');
return undefined; // unexpected
}
this.logService.trace(`auth#doResolveProxyCredentials (proxy) - asking window ${window.id} to handle proxy login`);
// Open proxy dialog
const payload = {
authInfo,
username: this.sessionCredentials?.username ?? storedUsername, // prefer to show already used username (if any) over stored
password: this.sessionCredentials?.password ?? storedPassword, // prefer to show already used password (if any) over stored
replyChannel: `vscode:proxyAuthResponse:${generateUuid()}`
};
window.sendWhenReady('vscode:openProxyAuthenticationDialog', CancellationToken.None, payload);
this.state = ProxyAuthState.LoginDialogShown;
// Handle reply
const loginDialogCredentials = await new Promise<Credentials | undefined>(resolve => {
const proxyAuthResponseHandler = async (event: ElectronEvent, channel: string, reply: Credentials & { remember: boolean } | undefined /* canceled */) => {
if (channel === payload.replyChannel) {
this.logService.trace(`auth#doResolveProxyCredentials - exit - received credentials from window ${window.id}`);
window.win.webContents.off('ipc-message', proxyAuthResponseHandler);
// We got credentials from the window
if (reply) {
const credentials: Credentials = { username: reply.username, password: reply.password };
// Update stored credentials based on `remember` flag
try {
if (reply.remember) {
const encryptedSerializedCredentials = await this.encryptionMainService.encrypt(JSON.stringify(credentials));
await this.nativeHostMainService.setPassword(undefined, ProxyAuthHandler.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash, encryptedSerializedCredentials);
} else {
await this.nativeHostMainService.deletePassword(undefined, ProxyAuthHandler.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash);
}
} catch (error) {
this.logService.error(error); // handle gracefully
}
resolve({ username: credentials.username, password: credentials.password });
}
// We did not get any credentials from the window (e.g. cancelled)
else {
resolve(undefined);
}
}
};
window.win.webContents.on('ipc-message', proxyAuthResponseHandler);
});
win.loadURL(windowUrl.toString(true));
// Remember credentials for the session in case
// the credentials are wrong and we show the dialog
// again
this.sessionCredentials = loginDialogCredentials;
return loginDialogCredentials;
}
}