瀏覽代碼

use github personal access token (#672)

Baptiste Augrain 3 年之前
父節點
當前提交
0d433893b7
共有 2 個文件被更改,包括 268 次插入0 次删除
  1. 11 0
      DOCS.md
  2. 257 0
      patches/use-github-pat.patch

+ 11 - 0
DOCS.md

@@ -9,6 +9,7 @@
   - [Proprietary Debugging Tools](#proprietary-debugging-tools)
   - [Proprietary Extensions](#proprietary-extensions)
 - [Migrating from Visual Studio Code to VSCodium](#migrating)
+- [Sign in with GitHub](#signin-github)
 - [How do I run VSCodium in portable mode?](#portable)
 - [How do I press and hold a key and have it repeat in VSCodium?](#press-and-hold)
 - [How do I open VSCodium from the terminal?](#terminal-support)
@@ -122,6 +123,16 @@ To copy your settings manually:
 - Click the three dots `...` and choose 'Open settings.json'
 - Copy the contents of settings.json into the same place in VSCodium
 
+## <a id="signin-github"></a>Sign in with GitHub
+
+In VSCodium, `Sign in with GitHub` is using a Personal Access Token.<br />
+Follow the documentation https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token to create your token.<br />
+Select the scopes dependending of the extension which need access to GitHub. (GitLens requires the `repo` scope.)
+
+### Linux
+
+If you are getting the error `Writing login information to the keychain failed with error 'The name org.freedesktop.secrets was not provided by any .service files'.`, you need to install the package `gnome-keyring`.
+
 ## <a id="portable"></a>How do I run VSCodium in portable mode?
 You can follow the [Portable Mode instructions](https://code.visualstudio.com/docs/editor/portable) from the Visual Studio Code website. 
 - **Windows** / **Linux** : the instructions can be followed as written.

+ 257 - 0
patches/use-github-pat.patch

@@ -0,0 +1,257 @@
+diff --git a/extensions/github-authentication/src/githubServer.ts b/extensions/github-authentication/src/githubServer.ts
+index 3d36081..5bac245 100644
+--- a/extensions/github-authentication/src/githubServer.ts
++++ b/extensions/github-authentication/src/githubServer.ts
+@@ -6,8 +6,6 @@
+ import * as nls from 'vscode-nls';
+ import * as vscode from 'vscode';
+ import fetch, { Response } from 'node-fetch';
+-import { v4 as uuid } from 'uuid';
+-import { PromiseAdapter, promiseFromEvent } from './common/utils';
+ import { ExperimentationTelemetry } from './experimentationService';
+ import { AuthProviderType } from './github';
+ import { Log } from './common/logger';
+@@ -15,8 +13,6 @@ import { Log } from './common/logger';
+ const localize = nls.loadMessageBundle();
+ 
+ const NETWORK_ERROR = 'network error';
+-const AUTH_RELAY_SERVER = 'vscode-auth.github.com';
+-// const AUTH_RELAY_STAGING_SERVER = 'client-auth-staging-14a768b.herokuapp.com';
+ 
+ class UriEventHandler extends vscode.EventEmitter<vscode.Uri> implements vscode.UriHandler {
+ 	constructor(private readonly Logger: Log) {
+@@ -29,14 +25,6 @@ class UriEventHandler extends vscode.EventEmitter<vscode.Uri> implements vscode.
+ 	}
+ }
+ 
+-function parseQuery(uri: vscode.Uri) {
+-	return uri.query.split('&').reduce((prev: any, current) => {
+-		const queryString = current.split('=');
+-		prev[queryString[0]] = queryString[1];
+-		return prev;
+-	}, {});
+-}
+-
+ export interface IGitHubServer extends vscode.Disposable {
+ 	login(scopes: string): Promise<string>;
+ 	getUserInfo(token: string): Promise<{ id: string, accountName: string }>;
+@@ -96,11 +84,7 @@ async function getUserInfo(token: string, serverUri: vscode.Uri, logger: Log): P
+ export class GitHubServer implements IGitHubServer {
+ 	friendlyName = 'GitHub';
+ 	type = AuthProviderType.github;
+-	private _statusBarItem: vscode.StatusBarItem | undefined;
+-	private _onDidManuallyProvideToken = new vscode.EventEmitter<string | undefined>();
+ 
+-	private _pendingStates = new Map<string, string[]>();
+-	private _codeExchangePromises = new Map<string, { promise: Promise<string>, cancel: vscode.EventEmitter<void> }>();
+ 	private _statusBarCommandId = `${this.type}.provide-manually`;
+ 	private _disposable: vscode.Disposable;
+ 	private _uriHandler = new UriEventHandler(this._logger);
+@@ -115,150 +99,35 @@ export class GitHubServer implements IGitHubServer {
+ 		this._disposable.dispose();
+ 	}
+ 
+-	private isTestEnvironment(url: vscode.Uri): boolean {
+-		return /\.azurewebsites\.net$/.test(url.authority) || url.authority.startsWith('localhost:');
+-	}
+-
+-	// TODO@joaomoreno TODO@TylerLeonhardt
+-	private async isNoCorsEnvironment(): Promise<boolean> {
+-		const uri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/dummy`));
+-		return (uri.scheme === 'https' && /^(vscode|github)\./.test(uri.authority)) || (uri.scheme === 'http' && /^localhost/.test(uri.authority));
+-	}
+-
+ 	public async login(scopes: string): Promise<string> {
+ 		this._logger.info(`Logging in for the following scopes: ${scopes}`);
+ 
+-		// TODO@joaomoreno TODO@TylerLeonhardt
+-		const nocors = await this.isNoCorsEnvironment();
+-		const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/did-authenticate${nocors ? '?nocors=true' : ''}`));
+-
+-		if (this.isTestEnvironment(callbackUri)) {
+-			const token = await vscode.window.showInputBox({ prompt: 'GitHub Personal Access Token', ignoreFocusOut: true });
+-			if (!token) { throw new Error('Sign in failed: No token provided'); }
+-
+-			const tokenScopes = await getScopes(token, this.getServerUri('/'), this._logger); // Example: ['repo', 'user']
+-			const scopesList = scopes.split(' '); // Example: 'read:user repo user:email'
+-			if (!scopesList.every(scope => {
+-				const included = tokenScopes.includes(scope);
+-				if (included || !scope.includes(':')) {
+-					return included;
+-				}
++		const token = await vscode.window.showInputBox({ prompt: 'GitHub Personal Access Token', ignoreFocusOut: true });
++		if (!token) { throw new Error('Sign in failed: No token provided'); }
+ 
+-				return scope.split(':').some(splitScopes => {
+-					return tokenScopes.includes(splitScopes);
+-				});
+-			})) {
+-				throw new Error(`The provided token is does not match the requested scopes: ${scopes}`);
++		const tokenScopes = await getScopes(token, this.getServerUri('/'), this._logger); // Example: ['repo', 'user']
++		const scopesList = scopes.split(' '); // Example: 'read:user repo user:email'
++		if (!scopesList.every(scope => {
++			const included = tokenScopes.includes(scope);
++			if (included || !scope.includes(':')) {
++				return included;
+ 			}
+ 
+-			return token;
+-		}
+-
+-		this.updateStatusBarItem(true);
+-
+-		const state = uuid();
+-		const existingStates = this._pendingStates.get(scopes) || [];
+-		this._pendingStates.set(scopes, [...existingStates, state]);
+-
+-		const uri = vscode.Uri.parse(`https://${AUTH_RELAY_SERVER}/authorize/?callbackUri=${encodeURIComponent(callbackUri.toString())}&scope=${scopes}&state=${state}&responseType=code&authServer=https://github.com${nocors ? '&nocors=true' : ''}`);
+-		await vscode.env.openExternal(uri);
+-
+-		// Register a single listener for the URI callback, in case the user starts the login process multiple times
+-		// before completing it.
+-		let codeExchangePromise = this._codeExchangePromises.get(scopes);
+-		if (!codeExchangePromise) {
+-			codeExchangePromise = promiseFromEvent(this._uriHandler.event, this.exchangeCodeForToken(scopes));
+-			this._codeExchangePromises.set(scopes, codeExchangePromise);
++			return scope.split(':').some(splitScopes => {
++				return tokenScopes.includes(splitScopes);
++			});
++		})) {
++			throw new Error(`The provided token is does not match the requested scopes: ${scopes}`);
+ 		}
+ 
+-		return Promise.race([
+-			codeExchangePromise.promise,
+-			promiseFromEvent<string | undefined, string>(this._onDidManuallyProvideToken.event, (token: string | undefined, resolve, reject): void => {
+-				if (!token) {
+-					reject('Cancelled');
+-				} else {
+-					resolve(token);
+-				}
+-			}).promise,
+-			new Promise<string>((_, reject) => setTimeout(() => reject('Cancelled'), 60000))
+-		]).finally(() => {
+-			this._pendingStates.delete(scopes);
+-			codeExchangePromise?.cancel.fire();
+-			this._codeExchangePromises.delete(scopes);
+-			this.updateStatusBarItem(false);
+-		});
++		return token;
+ 	}
+ 
+-	private exchangeCodeForToken: (scopes: string) => PromiseAdapter<vscode.Uri, string> =
+-		(scopes) => async (uri, resolve, reject) => {
+-			const query = parseQuery(uri);
+-			const code = query.code;
+-
+-			const acceptedStates = this._pendingStates.get(scopes) || [];
+-			if (!acceptedStates.includes(query.state)) {
+-				// A common scenario of this happening is if you:
+-				// 1. Trigger a sign in with one set of scopes
+-				// 2. Before finishing 1, you trigger a sign in with a different set of scopes
+-				// In this scenario we should just return and wait for the next UriHandler event
+-				// to run as we are probably still waiting on the user to hit 'Continue'
+-				this._logger.info('State not found in accepted state. Skipping this execution...');
+-				return;
+-			}
+-
+-			const url = `https://${AUTH_RELAY_SERVER}/token?code=${code}&state=${query.state}`;
+-			this._logger.info('Exchanging code for token...');
+-
+-			// TODO@joao: remove
+-			if (query.nocors) {
+-				try {
+-					const json: any = await vscode.commands.executeCommand('_workbench.fetchJSON', url, 'POST');
+-					this._logger.info('Token exchange success!');
+-					resolve(json.access_token);
+-				} catch (err) {
+-					reject(err);
+-				}
+-			} else {
+-				try {
+-					const result = await fetch(url, {
+-						method: 'POST',
+-						headers: {
+-							Accept: 'application/json'
+-						}
+-					});
+-
+-					if (result.ok) {
+-						const json = await result.json();
+-						this._logger.info('Token exchange success!');
+-						resolve(json.access_token);
+-					} else {
+-						reject(result.statusText);
+-					}
+-				} catch (ex) {
+-					reject(ex);
+-				}
+-			}
+-		};
+-
+ 	private getServerUri(path: string = '') {
+ 		const apiUri = vscode.Uri.parse('https://api.github.com');
+ 		return vscode.Uri.parse(`${apiUri.scheme}://${apiUri.authority}${path}`);
+ 	}
+ 
+-	private updateStatusBarItem(isStart?: boolean) {
+-		if (isStart && !this._statusBarItem) {
+-			this._statusBarItem = vscode.window.createStatusBarItem('status.git.signIn', vscode.StatusBarAlignment.Left);
+-			this._statusBarItem.name = localize('status.git.signIn.name', "GitHub Sign-in");
+-			this._statusBarItem.text = localize('signingIn', "$(mark-github) Signing in to github.com...");
+-			this._statusBarItem.command = this._statusBarCommandId;
+-			this._statusBarItem.show();
+-		}
+-
+-		if (!isStart && this._statusBarItem) {
+-			this._statusBarItem.dispose();
+-			this._statusBarItem = undefined;
+-		}
+-	}
+-
+ 	private async manuallyProvideUri() {
+ 		const uri = await vscode.window.showInputBox({
+ 			prompt: 'Uri',
+@@ -290,41 +159,7 @@ export class GitHubServer implements IGitHubServer {
+ 		return getUserInfo(token, this.getServerUri('/user'), this._logger);
+ 	}
+ 
+-	public async sendAdditionalTelemetryInfo(token: string): Promise<void> {
+-		const nocors = await this.isNoCorsEnvironment();
+-
+-		if (nocors) {
+-			return;
+-		}
+-
+-		try {
+-			const result = await fetch('https://education.github.com/api/user', {
+-				headers: {
+-					Authorization: `token ${token}`,
+-					'faculty-check-preview': 'true',
+-					'User-Agent': 'Visual-Studio-Code'
+-				}
+-			});
+-
+-			if (result.ok) {
+-				const json: { student: boolean, faculty: boolean } = await result.json();
+-
+-				/* __GDPR__
+-					"session" : {
+-						"isEdu": { "classification": "NonIdentifiableDemographicInfo", "purpose": "FeatureInsight" }
+-					}
+-				*/
+-				this._telemetryReporter.sendTelemetryEvent('session', {
+-					isEdu: json.student
+-						? 'student'
+-						: json.faculty
+-							? 'faculty'
+-							: 'none'
+-				});
+-			}
+-		} catch (e) {
+-			// No-op
+-		}
++	public async sendAdditionalTelemetryInfo(_token: string): Promise<void> {
+ 	}
+ 
+ 	public async checkEnterpriseVersion(token: string): Promise<void> {