mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 13:00:58 +00:00
Improve GHES flow (#172959)
* Improve GHES flow It's clear to me now that a lot of users of the GHES flow have auth behind a VPN. Something that we cannot support in the OAuth flow because we require making a request to vscode.dev who makes a request to the GHES instance. In light of this, we will remove the OAuth flow for GHES. Fear not, though... because both the Device Code Flow and the PAT flow are there and the PAT flow has been improved in 1.75... so in conclusion, I think GHES is handled as best we can with this PR. Additionally, there is some "Hosted GitHub Enterprise" scenarios popping up, and this future proofs that by allowing "Hosted GitHub Enterprise" experiences to go through the OAuth flow because we know for a fact that they will be accessible from vscode.dev. Fixes #169619 Fixes https://github.com/microsoft/vscode-internalbacklog/issues/3398 * Use api. sub domain and update telemetry logic
This commit is contained in:
parent
ab060f0aa7
commit
06eb374137
|
@ -3,6 +3,7 @@
|
||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
import { Uri } from 'vscode';
|
import { Uri } from 'vscode';
|
||||||
|
import { AuthProviderType } from '../github';
|
||||||
|
|
||||||
const VALID_DESKTOP_CALLBACK_SCHEMES = [
|
const VALID_DESKTOP_CALLBACK_SCHEMES = [
|
||||||
'vscode',
|
'vscode',
|
||||||
|
@ -15,7 +16,7 @@ const VALID_DESKTOP_CALLBACK_SCHEMES = [
|
||||||
'vscode-exploration'
|
'vscode-exploration'
|
||||||
];
|
];
|
||||||
|
|
||||||
export function isSupportedEnvironment(uri: Uri): boolean {
|
export function isSupportedClient(uri: Uri): boolean {
|
||||||
return (
|
return (
|
||||||
VALID_DESKTOP_CALLBACK_SCHEMES.includes(uri.scheme) ||
|
VALID_DESKTOP_CALLBACK_SCHEMES.includes(uri.scheme) ||
|
||||||
// vscode.dev & insiders.vscode.dev
|
// vscode.dev & insiders.vscode.dev
|
||||||
|
@ -24,3 +25,10 @@ export function isSupportedEnvironment(uri: Uri): boolean {
|
||||||
/(?:^|\.)github\.dev$/.test(uri.authority)
|
/(?:^|\.)github\.dev$/.test(uri.authority)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isSupportedTarget(type: AuthProviderType, gheUri?: Uri): boolean {
|
||||||
|
return (
|
||||||
|
type === AuthProviderType.github ||
|
||||||
|
/\.ghe\.com$/.test(gheUri!.authority)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -9,13 +9,13 @@ import { PromiseAdapter, promiseFromEvent } from './common/utils';
|
||||||
import { ExperimentationTelemetry } from './common/experimentationService';
|
import { ExperimentationTelemetry } from './common/experimentationService';
|
||||||
import { AuthProviderType, UriEventHandler } from './github';
|
import { AuthProviderType, UriEventHandler } from './github';
|
||||||
import { Log } from './common/logger';
|
import { Log } from './common/logger';
|
||||||
import { isSupportedEnvironment } from './common/env';
|
import { isSupportedClient, isSupportedTarget } from './common/env';
|
||||||
import { LoopbackAuthServer } from './node/authServer';
|
import { LoopbackAuthServer } from './node/authServer';
|
||||||
import { crypto } from './node/crypto';
|
import { crypto } from './node/crypto';
|
||||||
import { fetching } from './node/fetch';
|
import { fetching } from './node/fetch';
|
||||||
|
|
||||||
const CLIENT_ID = '01ab8ac9400c4e429b23';
|
const CLIENT_ID = '01ab8ac9400c4e429b23';
|
||||||
const GITHUB_TOKEN_URL = 'https://vscode.dev/codeExchangeProxyEndpoints/github/login/oauth/access_token';
|
const GITHUB_TOKEN_URL = 'http://localhost:3000/codeExchangeProxyEndpoints/github/login/oauth/access_token';
|
||||||
const NETWORK_ERROR = 'network error';
|
const NETWORK_ERROR = 'network error';
|
||||||
|
|
||||||
const REDIRECT_URL_STABLE = 'https://vscode.dev/redirect';
|
const REDIRECT_URL_STABLE = 'https://vscode.dev/redirect';
|
||||||
|
@ -96,30 +96,15 @@ export class GitHubServer implements IGitHubServer {
|
||||||
if (proxyEndpoints?.github && new URL(proxyEndpoints.github).hostname === 'insiders.vscode.dev') {
|
if (proxyEndpoints?.github && new URL(proxyEndpoints.github).hostname === 'insiders.vscode.dev') {
|
||||||
this._redirectEndpoint = REDIRECT_URL_INSIDERS;
|
this._redirectEndpoint = REDIRECT_URL_INSIDERS;
|
||||||
}
|
}
|
||||||
return this._redirectEndpoint;
|
|
||||||
} else {
|
} else {
|
||||||
// GHES
|
// GHE only supports a single redirect endpoint, so we can't use
|
||||||
const result = await fetching(this.getServerUri('/meta').toString(true));
|
// insiders.vscode.dev/redirect when we're running in Insiders, unfortunately.
|
||||||
if (result.ok) {
|
// Additionally, we make the assumption that this function will only be used
|
||||||
try {
|
// in flows that target supported GHE targets, not on-prem GHES. Because of this
|
||||||
const json: { installed_version: string } = await result.json();
|
// assumption, we can assume that the GHE version used is at least 3.8 which is
|
||||||
const [majorStr, minorStr, _patch] = json.installed_version.split('.');
|
// the version that changed the redirect endpoint to this URI from the old
|
||||||
const major = Number(majorStr);
|
// GitHub maintained server.
|
||||||
const minor = Number(minorStr);
|
this._redirectEndpoint = 'https://vscode.dev/redirect';
|
||||||
if (major >= 4 || major === 3 && minor >= 8
|
|
||||||
) {
|
|
||||||
// GHES 3.8 and above used vscode.dev/redirect as the route.
|
|
||||||
// It only supports a single redirect endpoint, so we can't use
|
|
||||||
// insiders.vscode.dev/redirect when we're running in Insiders, unfortunately.
|
|
||||||
this._redirectEndpoint = 'https://vscode.dev/redirect';
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this._logger.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO in like 1 year change the default vscode.dev/redirect maybe
|
|
||||||
this._redirectEndpoint = 'https://vscode-auth.github.com/';
|
|
||||||
}
|
}
|
||||||
return this._redirectEndpoint;
|
return this._redirectEndpoint;
|
||||||
}
|
}
|
||||||
|
@ -154,8 +139,9 @@ export class GitHubServer implements IGitHubServer {
|
||||||
const nonce: string = crypto.getRandomValues(new Uint32Array(2)).reduce((prev, curr) => prev += curr.toString(16), '');
|
const nonce: string = crypto.getRandomValues(new Uint32Array(2)).reduce((prev, curr) => prev += curr.toString(16), '');
|
||||||
const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/did-authenticate?nonce=${encodeURIComponent(nonce)}`));
|
const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/did-authenticate?nonce=${encodeURIComponent(nonce)}`));
|
||||||
|
|
||||||
const supported = isSupportedEnvironment(callbackUri);
|
const supportedClient = isSupportedClient(callbackUri);
|
||||||
if (supported) {
|
const supportedTarget = isSupportedTarget(this._type, this._ghesUri);
|
||||||
|
if (supportedClient && supportedTarget) {
|
||||||
try {
|
try {
|
||||||
return await this.doLoginWithoutLocalServer(scopes, nonce, callbackUri);
|
return await this.doLoginWithoutLocalServer(scopes, nonce, callbackUri);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -167,9 +153,11 @@ export class GitHubServer implements IGitHubServer {
|
||||||
// Starting a local server is only supported if:
|
// Starting a local server is only supported if:
|
||||||
// 1. We are in a UI extension because we need to open a port on the machine that has the browser
|
// 1. We are in a UI extension because we need to open a port on the machine that has the browser
|
||||||
// 2. We are in a node runtime because we need to open a port on the machine
|
// 2. We are in a node runtime because we need to open a port on the machine
|
||||||
|
// 3. code exchange can only be done with a supported target
|
||||||
if (
|
if (
|
||||||
this._extensionKind === vscode.ExtensionKind.UI &&
|
this._extensionKind === vscode.ExtensionKind.UI &&
|
||||||
typeof navigator === 'undefined'
|
typeof navigator === 'undefined' &&
|
||||||
|
supportedTarget
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
await promptToContinue();
|
await promptToContinue();
|
||||||
|
@ -193,7 +181,7 @@ export class GitHubServer implements IGitHubServer {
|
||||||
|
|
||||||
// In a supported environment, we can't use PAT auth because we use this auth for Settings Sync and it doesn't support PATs.
|
// In a supported environment, we can't use PAT auth because we use this auth for Settings Sync and it doesn't support PATs.
|
||||||
// With that said, GitHub Enterprise isn't used by Settings Sync so we can use PATs for that.
|
// With that said, GitHub Enterprise isn't used by Settings Sync so we can use PATs for that.
|
||||||
if (!supported || this._type === AuthProviderType.githubEnterprise) {
|
if (!supportedClient || this._type === AuthProviderType.githubEnterprise) {
|
||||||
try {
|
try {
|
||||||
await promptToContinue();
|
await promptToContinue();
|
||||||
return await this.doLoginWithPat(scopes);
|
return await this.doLoginWithPat(scopes);
|
||||||
|
@ -502,11 +490,12 @@ export class GitHubServer implements IGitHubServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private getServerUri(path: string = '') {
|
private getServerUri(path: string = '') {
|
||||||
if (this._type === AuthProviderType.github) {
|
|
||||||
return vscode.Uri.parse('https://api.github.com').with({ path });
|
|
||||||
}
|
|
||||||
// GHES
|
|
||||||
const apiUri = this.baseUri;
|
const apiUri = this.baseUri;
|
||||||
|
// github.com and Hosted GitHub Enterprise instances
|
||||||
|
if (isSupportedTarget(this._type, this._ghesUri)) {
|
||||||
|
return vscode.Uri.parse(`${apiUri.scheme}://api.${apiUri.authority}`).with({ path });
|
||||||
|
}
|
||||||
|
// GitHub Enterprise Server (aka on-prem)
|
||||||
return vscode.Uri.parse(`${apiUri.scheme}://${apiUri.authority}/api/v3${path}`);
|
return vscode.Uri.parse(`${apiUri.scheme}://${apiUri.authority}/api/v3${path}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -613,20 +602,25 @@ export class GitHubServer implements IGitHubServer {
|
||||||
|
|
||||||
private async checkEnterpriseVersion(token: string): Promise<void> {
|
private async checkEnterpriseVersion(token: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
|
let version: string;
|
||||||
|
if (!isSupportedTarget(this._type, this._ghesUri)) {
|
||||||
|
const result = await fetching(this.getServerUri('/meta').toString(), {
|
||||||
|
headers: {
|
||||||
|
Authorization: `token ${token}`,
|
||||||
|
'User-Agent': 'Visual-Studio-Code'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const result = await fetching(this.getServerUri('/meta').toString(), {
|
if (!result.ok) {
|
||||||
headers: {
|
return;
|
||||||
Authorization: `token ${token}`,
|
|
||||||
'User-Agent': 'Visual-Studio-Code'
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
if (!result.ok) {
|
const json: { verifiable_password_authentication: boolean; installed_version: string } = await result.json();
|
||||||
return;
|
version = json.installed_version;
|
||||||
|
} else {
|
||||||
|
version = 'hosted';
|
||||||
}
|
}
|
||||||
|
|
||||||
const json: { verifiable_password_authentication: boolean; installed_version: string } = await result.json();
|
|
||||||
|
|
||||||
/* __GDPR__
|
/* __GDPR__
|
||||||
"ghe-session" : {
|
"ghe-session" : {
|
||||||
"owner": "TylerLeonhardt",
|
"owner": "TylerLeonhardt",
|
||||||
|
@ -634,7 +628,7 @@ export class GitHubServer implements IGitHubServer {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
this._telemetryReporter.sendTelemetryEvent('ghe-session', {
|
this._telemetryReporter.sendTelemetryEvent('ghe-session', {
|
||||||
version: json.installed_version
|
version
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
// No-op
|
// No-op
|
||||||
|
|
Loading…
Reference in a new issue