Merge vscode source through 1.62 release (#19981)

* Build breaks 1

* Build breaks

* Build breaks

* Build breaks

* More build breaks

* Build breaks (#2512)

* Runtime breaks

* Build breaks

* Fix dialog location break

* Update typescript

* Fix ASAR break issue

* Unit test breaks

* Update distro

* Fix breaks in ADO builds (#2513)

* Bump to node 16

* Fix hygiene errors

* Bump distro

* Remove reference to node type

* Delete vscode specific extension

* Bump to node 16 in CI yaml

* Skip integration tests in CI builds (while fixing)

* yarn.lock update

* Bump moment dependency in remote yarn

* Fix drop-down chevron style

* Bump to node 16

* Remove playwrite from ci.yaml

* Skip building build scripts in hygine check
This commit is contained in:
Karl Burtram
2022-07-11 14:09:32 -07:00
committed by GitHub
parent fa0fcef303
commit 26455e9113
1876 changed files with 72050 additions and 37997 deletions

View File

@@ -12,7 +12,11 @@ const withBrowserDefaults = require('../shared.webpack.config').browser;
module.exports = withBrowserDefaults({
context: __dirname,
node: false,
node: {
global: true,
__filename: false,
__dirname: false,
},
entry: {
extension: './src/extension.ts',
},
@@ -23,11 +27,6 @@ module.exports = withBrowserDefaults({
alias: {
'./env/node': path.resolve(__dirname, 'src/env/browser'),
'./authServer': path.resolve(__dirname, 'src/env/browser/authServer'),
'buffer': path.resolve(__dirname, 'node_modules/buffer/index.js'),
'node-fetch': path.resolve(__dirname, 'node_modules/node-fetch/browser.js'),
'randombytes': path.resolve(__dirname, 'node_modules/randombytes/browser.js'),
'stream': path.resolve(__dirname, 'node_modules/stream/index.js'),
'uuid': path.resolve(__dirname, 'node_modules/uuid/dist/esm-browser/index.js')
}
}
});

View File

@@ -54,11 +54,11 @@
"dependencies": {
"buffer": "^5.6.0",
"node-fetch": "^2.6.7",
"randombytes": "github:rmacfarlane/randombytes#b28d4ecee46262801ea09f15fa1f1513a05c5971",
"randombytes": "~2.1.0",
"sha.js": "2.4.11",
"stream": "0.0.2",
"uuid": "^8.2.0",
"vscode-extension-telemetry": "0.2.8",
"vscode-extension-telemetry": "0.4.2",
"vscode-nls": "^5.0.0"
},
"repository": {

View File

@@ -100,7 +100,7 @@ export class AzureActiveDirectoryService {
private _tokens: IToken[] = [];
private _refreshTimeouts: Map<string, NodeJS.Timeout> = new Map<string, NodeJS.Timeout>();
private _uriHandler: UriEventHandler;
private _disposables: vscode.Disposable[] = [];
private _disposable: vscode.Disposable;
// Used to keep track of current requests when not using the local server approach.
private _pendingStates = new Map<string, string[]>();
@@ -112,51 +112,59 @@ export class AzureActiveDirectoryService {
constructor(private _context: vscode.ExtensionContext) {
this._keychain = new Keychain(_context);
this._uriHandler = new UriEventHandler();
this._disposables.push(vscode.window.registerUriHandler(this._uriHandler));
this._disposable = vscode.Disposable.from(
vscode.window.registerUriHandler(this._uriHandler),
this._context.secrets.onDidChange(() => this.checkForUpdates()));
}
public async initialize(): Promise<void> {
const storedData = await this._keychain.getToken() || await this._keychain.tryMigrate();
if (storedData) {
try {
const sessions = this.parseStoredData(storedData);
const refreshes = sessions.map(async session => {
if (!session.refreshToken) {
return Promise.resolve();
}
try {
await this.refreshToken(session.refreshToken, session.scope, session.id);
} catch (e) {
if (e.message === REFRESH_NETWORK_FAILURE) {
const didSucceedOnRetry = await this.handleRefreshNetworkError(session.id, session.refreshToken, session.scope);
if (!didSucceedOnRetry) {
this._tokens.push({
accessToken: undefined,
refreshToken: session.refreshToken,
account: {
label: session.account.label ?? session.account.displayName!,
id: session.account.id
},
scope: session.scope,
sessionId: session.id
});
this.pollForReconnect(session.id, session.refreshToken, session.scope);
}
} else {
await this.removeSession(session.id);
}
}
});
await Promise.all(refreshes);
} catch (e) {
Logger.info('Failed to initialize stored data');
await this.clearSessions();
}
Logger.info('Reading sessions from keychain...');
const storedData = await this._keychain.getToken();
if (!storedData) {
Logger.info('No stored sessions found.');
return;
}
Logger.info('Got stored sessions!');
this._disposables.push(this._context.secrets.onDidChange(() => this.checkForUpdates));
try {
const sessions = this.parseStoredData(storedData);
const refreshes = sessions.map(async session => {
Logger.trace(`Read the following session from the keychain with the following scopes: ${session.scope}`);
if (!session.refreshToken) {
Logger.trace(`Session with the following scopes does not have a refresh token so we will not try to refresh it: ${session.scope}`);
return Promise.resolve();
}
try {
await this.refreshToken(session.refreshToken, session.scope, session.id);
} catch (e) {
// If we aren't connected to the internet, then wait and try to refresh again later.
if (e.message === REFRESH_NETWORK_FAILURE) {
const didSucceedOnRetry = await this.handleRefreshNetworkError(session.id, session.refreshToken, session.scope);
if (!didSucceedOnRetry) {
this._tokens.push({
accessToken: undefined,
refreshToken: session.refreshToken,
account: {
label: session.account.label ?? session.account.displayName!,
id: session.account.id
},
scope: session.scope,
sessionId: session.id
});
this.pollForReconnect(session.id, session.refreshToken, session.scope);
}
} else {
await this.removeSession(session.id);
}
}
});
await Promise.all(refreshes);
} catch (e) {
Logger.error(`Failed to initialize stored data: ${e}`);
await this.clearSessions();
}
}
private parseStoredData(data: string): IStoredSession[] {
@@ -263,8 +271,8 @@ export class AzureActiveDirectoryService {
private async resolveAccessAndIdTokens(token: IToken): Promise<IMicrosoftTokens> {
if (token.accessToken && (!token.expiresAt || token.expiresAt > Date.now())) {
token.expiresAt
? Logger.info(`Token available from cache, expires in ${token.expiresAt - Date.now()} milliseconds`)
: Logger.info('Token available from cache');
? Logger.info(`Token available from cache (for scopes ${token.scope}), expires in ${token.expiresAt - Date.now()} milliseconds`)
: Logger.info('Token available from cache (for scopes ${token.scope})');
return Promise.resolve({
accessToken: token.accessToken,
idToken: token.idToken
@@ -272,7 +280,7 @@ export class AzureActiveDirectoryService {
}
try {
Logger.info('Token expired or unavailable, trying refresh');
Logger.info(`Token expired or unavailable (for scopes ${token.scope}), trying refresh`);
const refreshedToken = await this.refreshToken(token.refreshToken, token.scope, token.sessionId);
if (refreshedToken.accessToken) {
return {
@@ -301,98 +309,97 @@ export class AzureActiveDirectoryService {
}
async getSessions(scopes?: string[]): Promise<vscode.AuthenticationSession[]> {
Logger.info(`Getting sessions for ${scopes?.join(',') ?? 'all scopes'}...`);
if (!scopes) {
return this.sessions;
const sessions = await this.sessions;
Logger.info(`Got ${sessions.length} sessions for all scopes...`);
return sessions;
}
const orderedScopes = scopes.sort().join(' ');
const matchingTokens = this._tokens.filter(token => token.scope === orderedScopes);
Logger.info(`Got ${matchingTokens.length} sessions for ${scopes?.join(',')}...`);
return Promise.all(matchingTokens.map(token => this.convertToSession(token)));
}
public async createSession(scope: string): Promise<vscode.AuthenticationSession> {
Logger.info('Logging in...');
Logger.info(`Logging in for the following scopes: ${scope}`);
if (!scope.includes('offline_access')) {
Logger.info('Warning: The \'offline_access\' scope was not included, so the generated token will not be able to be refreshed.');
}
return new Promise(async (resolve, reject) => {
const runsRemote = vscode.env.remoteName !== undefined;
const runsServerless = vscode.env.remoteName === undefined && vscode.env.uiKind === vscode.UIKind.Web;
const runsRemote = vscode.env.remoteName !== undefined;
const runsServerless = vscode.env.remoteName === undefined && vscode.env.uiKind === vscode.UIKind.Web;
if (runsRemote || runsServerless) {
return this.loginWithoutLocalServer(scope);
}
if (runsRemote || runsServerless) {
resolve(this.loginWithoutLocalServer(scope));
return;
const nonce = randomBytes(16).toString('base64');
const { server, redirectPromise, codePromise } = createServer(nonce);
let token: IToken | undefined;
try {
const port = await startServer(server);
vscode.env.openExternal(vscode.Uri.parse(`http://localhost:${port}/signin?nonce=${encodeURIComponent(nonce)}`));
const redirectReq = await redirectPromise;
if ('err' in redirectReq) {
const { err, res } = redirectReq;
res.writeHead(302, { Location: `/?error=${encodeURIComponent(err && err.message || 'Unknown error')}` });
res.end();
throw err;
}
const nonce = randomBytes(16).toString('base64');
const { server, redirectPromise, codePromise } = createServer(nonce);
const host = redirectReq.req.headers.host || '';
const updatedPortStr = (/^[^:]+:(\d+)$/.exec(Array.isArray(host) ? host[0] : host) || [])[1];
const updatedPort = updatedPortStr ? parseInt(updatedPortStr, 10) : port;
const state = `${updatedPort},${encodeURIComponent(nonce)}`;
const codeVerifier = toBase64UrlEncoding(randomBytes(32).toString('base64'));
const codeChallenge = toBase64UrlEncoding(await sha256(codeVerifier));
const loginUrl = `${loginEndpointUrl}${tenant}/oauth2/v2.0/authorize?response_type=code&response_mode=query&client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUrl)}&state=${state}&scope=${encodeURIComponent(scope)}&prompt=select_account&code_challenge_method=S256&code_challenge=${codeChallenge}`;
redirectReq.res.writeHead(302, { Location: loginUrl });
redirectReq.res.end();
const codeRes = await codePromise;
const res = codeRes.res;
let token: IToken | undefined;
try {
const port = await startServer(server);
vscode.env.openExternal(vscode.Uri.parse(`http://localhost:${port}/signin?nonce=${encodeURIComponent(nonce)}`));
const redirectReq = await redirectPromise;
if ('err' in redirectReq) {
const { err, res } = redirectReq;
res.writeHead(302, { Location: `/?error=${encodeURIComponent(err && err.message || 'Unknown error')}` });
res.end();
throw err;
if ('err' in codeRes) {
throw codeRes.err;
}
const host = redirectReq.req.headers.host || '';
const updatedPortStr = (/^[^:]+:(\d+)$/.exec(Array.isArray(host) ? host[0] : host) || [])[1];
const updatedPort = updatedPortStr ? parseInt(updatedPortStr, 10) : port;
const state = `${updatedPort},${encodeURIComponent(nonce)}`;
const codeVerifier = toBase64UrlEncoding(randomBytes(32).toString('base64'));
const codeChallenge = toBase64UrlEncoding(await sha256(codeVerifier));
const loginUrl = `${loginEndpointUrl}${tenant}/oauth2/v2.0/authorize?response_type=code&response_mode=query&client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUrl)}&state=${state}&scope=${encodeURIComponent(scope)}&prompt=select_account&code_challenge_method=S256&code_challenge=${codeChallenge}`;
await redirectReq.res.writeHead(302, { Location: loginUrl });
redirectReq.res.end();
const codeRes = await codePromise;
const res = codeRes.res;
try {
if ('err' in codeRes) {
throw codeRes.err;
}
token = await this.exchangeCodeForToken(codeRes.code, codeVerifier, scope);
this.setToken(token, scope);
Logger.info('Login successful');
res.writeHead(302, { Location: '/' });
const session = await this.convertToSession(token);
resolve(session);
res.end();
} catch (err) {
res.writeHead(302, { Location: `/?error=${encodeURIComponent(err && err.message || 'Unknown error')}` });
res.end();
reject(err.message);
}
} catch (e) {
Logger.error(e.message);
// If the error was about starting the server, try directly hitting the login endpoint instead
if (e.message === 'Error listening to server' || e.message === 'Closed' || e.message === 'Timeout waiting for port') {
await this.loginWithoutLocalServer(scope);
}
reject(e.message);
token = await this.exchangeCodeForToken(codeRes.code, codeVerifier, scope);
this.setToken(token, scope);
Logger.info(`Login successful for scopes: ${scope}`);
res.writeHead(302, { Location: '/' });
const session = await this.convertToSession(token);
return session;
} catch (err) {
res.writeHead(302, { Location: `/?error=${encodeURIComponent(err && err.message || 'Unknown error')}` });
throw err;
} finally {
setTimeout(() => {
server.close();
}, 5000);
res.end();
}
});
} catch (e) {
Logger.error(`Error creating session for scopes: ${scope} Error: ${e}`);
// If the error was about starting the server, try directly hitting the login endpoint instead
if (e.message === 'Error listening to server' || e.message === 'Closed' || e.message === 'Timeout waiting for port') {
return this.loginWithoutLocalServer(scope);
}
throw e;
} finally {
setTimeout(() => {
server.close();
}, 5000);
}
}
public dispose(): void {
this._disposables.forEach(disposable => disposable.dispose());
this._disposables = [];
this._disposable.dispose();
}
private getCallbackEnvironment(callbackUri: vscode.Uri): string {
@@ -554,7 +561,7 @@ export class AzureActiveDirectoryService {
}
private async exchangeCodeForToken(code: string, codeVerifier: string, scope: string): Promise<IToken> {
Logger.info('Exchanging login code for token');
Logger.info(`Exchanging login code for token for scopes: ${scope}`);
try {
const postData = querystring.stringify({
grant_type: 'authorization_code',
@@ -579,21 +586,21 @@ export class AzureActiveDirectoryService {
});
if (result.ok) {
Logger.info('Exchanging login code for token success');
const json = await result.json();
Logger.info(`Exchanging login code for token (for scopes: ${scope}) succeeded!`);
return this.getTokenFromResponse(json, scope);
} else {
Logger.error('Exchanging login code for token failed');
Logger.error(`Exchanging login code for token (for scopes: ${scope}) failed: ${await result.text()}`);
throw new Error('Unable to login.');
}
} catch (e) {
Logger.error(e.message);
Logger.error(`Error exchanging code for token (for scopes ${scope}): ${e}`);
throw e;
}
}
private async refreshToken(refreshToken: string, scope: string, sessionId: string): Promise<IToken> {
Logger.info('Refreshing token...');
Logger.info(`Refreshing token for scopes: ${scope}`);
const postData = querystring.stringify({
refresh_token: refreshToken,
client_id: clientId,
@@ -615,7 +622,7 @@ export class AzureActiveDirectoryService {
body: postData
});
} catch (e) {
Logger.error('Refreshing token failed');
Logger.error(`Refreshing token failed (for scopes: ${scope}) Error: ${e}`);
throw new Error(REFRESH_NETWORK_FAILURE);
}
@@ -624,14 +631,14 @@ export class AzureActiveDirectoryService {
const json = await result.json();
const token = this.getTokenFromResponse(json, scope, sessionId);
this.setToken(token, scope);
Logger.info('Token refresh success');
Logger.info(`Token refresh success for scopes: ${token.scope}`);
return token;
} else {
throw new Error('Bad request.');
}
} catch (e) {
vscode.window.showErrorMessage(localize('signOut', "You have been signed out because reading stored authentication information failed."));
Logger.error(`Refreshing token failed: ${result.statusText}`);
Logger.error(`Refreshing token failed (for scopes: ${scope}): ${result.statusText}`);
throw new Error('Refreshing token failed');
}
}
@@ -672,7 +679,7 @@ export class AzureActiveDirectoryService {
private handleRefreshNetworkError(sessionId: string, refreshToken: string, scope: string, attempts: number = 1): Promise<boolean> {
return new Promise((resolve, _) => {
if (attempts === 3) {
Logger.error('Token refresh failed after 3 attempts');
Logger.error(`Token refresh (for scopes: ${scope}) failed after 3 attempts`);
return resolve(false);
}

View File

@@ -7,8 +7,6 @@ import * as vscode from 'vscode';
import { AzureActiveDirectoryService, onDidChangeSessions } from './AADHelper';
import TelemetryReporter from 'vscode-extension-telemetry';
export const DEFAULT_SCOPES = 'https://management.core.windows.net/.default offline_access';
export async function activate(context: vscode.ExtensionContext) {
const { name, version, aiKey } = context.extension.packageJSON as { name: string, version: string, aiKey: string };
const telemetryReporter = new TelemetryReporter(name, version, aiKey);

View File

@@ -3,47 +3,17 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// keytar depends on a native module shipped in vscode, so this is
// how we load it
import * as keytarType from 'keytar';
import * as vscode from 'vscode';
import Logger from './logger';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
function getKeytar(): Keytar | undefined {
try {
return require('keytar');
} catch (err) {
console.log(err);
}
return undefined;
}
export type Keytar = {
getPassword: typeof keytarType['getPassword'];
setPassword: typeof keytarType['setPassword'];
deletePassword: typeof keytarType['deletePassword'];
};
const OLD_SERVICE_ID = `${vscode.env.uriScheme}-microsoft.login`;
const SERVICE_ID = `microsoft.login`;
const ACCOUNT_ID = 'account';
export class Keychain {
private keytar: Keytar;
constructor(private context: vscode.ExtensionContext) {
const keytar = getKeytar();
if (!keytar) {
throw new Error('System keychain unavailable');
}
this.keytar = keytar;
}
constructor(private context: vscode.ExtensionContext) { }
async setToken(token: string): Promise<void> {
@@ -87,19 +57,4 @@ export class Keychain {
return Promise.resolve(undefined);
}
}
async tryMigrate(): Promise<string | null> {
try {
const oldValue = await this.keytar.getPassword(OLD_SERVICE_ID, ACCOUNT_ID);
if (oldValue) {
await this.setToken(oldValue);
await this.keytar.deletePassword(OLD_SERVICE_ID, ACCOUNT_ID);
}
return oldValue;
} catch (_) {
// Ignore
return Promise.resolve(null);
}
}
}

View File

@@ -5,7 +5,7 @@
import * as vscode from 'vscode';
type LogLevel = 'Info' | 'Error';
type LogLevel = 'Trace' | 'Info' | 'Error';
class Log {
private output: vscode.OutputChannel;
@@ -24,6 +24,10 @@ class Log {
return data.toString();
}
public trace(message: string, data?: any): void {
this.logLevel('Trace', message, data);
}
public info(message: string, data?: any): void {
this.logLevel('Info', message, data);
}

View File

@@ -112,9 +112,10 @@ node-fetch@^2.6.7:
dependencies:
whatwg-url "^5.0.0"
"randombytes@github:rmacfarlane/randombytes#b28d4ecee46262801ea09f15fa1f1513a05c5971":
randombytes@~2.1.0:
version "2.1.0"
resolved "https://codeload.github.com/rmacfarlane/randombytes/tar.gz/b28d4ecee46262801ea09f15fa1f1513a05c5971"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
dependencies:
safe-buffer "^5.1.0"
@@ -153,10 +154,10 @@ uuid@^8.2.0:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.2.0.tgz#cb10dd6b118e2dada7d0cd9730ba7417c93d920e"
integrity sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q==
vscode-extension-telemetry@0.2.8:
version "0.2.8"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.2.8.tgz#670c625c44791237c040cee2cb9f567ca34784ac"
integrity sha512-Vf52im5qzORRD2K5Ryp8PXo31YXVcJAYRSDDZGegWlt0OATOd83DYabS1U/WIq9nR5g80UQKH3+BsenhpQHUaA==
vscode-extension-telemetry@0.4.2:
version "0.4.2"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.4.2.tgz#6ef847a80c9cfc207eb15e3a254f235acebb65a5"
integrity sha512-y0f51mVoFxHIzULQNCC26TBFIKdEC7uckS3tFoK++OOOl8mU2LlOxgmbd52T/SXoXNg5aI7xqs+4V2ug5ITvKw==
vscode-nls@^5.0.0:
version "5.0.0"