use-github-pat.patch 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. diff --git a/extensions/github-authentication/src/githubServer.ts b/extensions/github-authentication/src/githubServer.ts
  2. index fe0fbfd..656d8bb 100644
  3. --- a/extensions/github-authentication/src/githubServer.ts
  4. +++ b/extensions/github-authentication/src/githubServer.ts
  5. @@ -6,8 +6,6 @@
  6. import * as nls from 'vscode-nls';
  7. import * as vscode from 'vscode';
  8. import fetch, { Response } from 'node-fetch';
  9. -import { v4 as uuid } from 'uuid';
  10. -import { PromiseAdapter, promiseFromEvent } from './common/utils';
  11. import { ExperimentationTelemetry } from './experimentationService';
  12. import { AuthProviderType } from './github';
  13. import { Log } from './common/logger';
  14. @@ -15,8 +13,6 @@ import { Log } from './common/logger';
  15. const localize = nls.loadMessageBundle();
  16. const NETWORK_ERROR = 'network error';
  17. -const AUTH_RELAY_SERVER = 'vscode-auth.github.com';
  18. -// const AUTH_RELAY_STAGING_SERVER = 'client-auth-staging-14a768b.herokuapp.com';
  19. class UriEventHandler extends vscode.EventEmitter<vscode.Uri> implements vscode.UriHandler {
  20. constructor(private readonly Logger: Log) {
  21. @@ -29,14 +25,6 @@ class UriEventHandler extends vscode.EventEmitter<vscode.Uri> implements vscode.
  22. }
  23. }
  24. -function parseQuery(uri: vscode.Uri) {
  25. - return uri.query.split('&').reduce((prev: any, current) => {
  26. - const queryString = current.split('=');
  27. - prev[queryString[0]] = queryString[1];
  28. - return prev;
  29. - }, {});
  30. -}
  31. -
  32. export interface IGitHubServer extends vscode.Disposable {
  33. login(scopes: string): Promise<string>;
  34. getUserInfo(token: string): Promise<{ id: string, accountName: string }>;
  35. @@ -96,11 +84,7 @@ async function getUserInfo(token: string, serverUri: vscode.Uri, logger: Log): P
  36. export class GitHubServer implements IGitHubServer {
  37. friendlyName = 'GitHub';
  38. type = AuthProviderType.github;
  39. - private _statusBarItem: vscode.StatusBarItem | undefined;
  40. - private _onDidManuallyProvideToken = new vscode.EventEmitter<string | undefined>();
  41. - private _pendingStates = new Map<string, string[]>();
  42. - private _codeExchangePromises = new Map<string, { promise: Promise<string>, cancel: vscode.EventEmitter<void> }>();
  43. private _statusBarCommandId = `${this.type}.provide-manually`;
  44. private _disposable: vscode.Disposable;
  45. private _uriHandler = new UriEventHandler(this._logger);
  46. @@ -115,137 +99,35 @@ export class GitHubServer implements IGitHubServer {
  47. this._disposable.dispose();
  48. }
  49. - private isTestEnvironment(url: vscode.Uri): boolean {
  50. - return /\.azurewebsites\.net$/.test(url.authority) || url.authority.startsWith('localhost:');
  51. - }
  52. -
  53. - // TODO@joaomoreno TODO@TylerLeonhardt
  54. - private async isNoCorsEnvironment(): Promise<boolean> {
  55. - const uri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/dummy`));
  56. - return (uri.scheme === 'https' && /^((insiders\.)?vscode|github)\./.test(uri.authority)) || (uri.scheme === 'http' && /^localhost/.test(uri.authority));
  57. - }
  58. -
  59. public async login(scopes: string): Promise<string> {
  60. this._logger.info(`Logging in for the following scopes: ${scopes}`);
  61. - const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/did-authenticate`));
  62. -
  63. - if (this.isTestEnvironment(callbackUri)) {
  64. - const token = await vscode.window.showInputBox({ prompt: 'GitHub Personal Access Token', ignoreFocusOut: true });
  65. - if (!token) { throw new Error('Sign in failed: No token provided'); }
  66. -
  67. - const tokenScopes = await getScopes(token, this.getServerUri('/'), this._logger); // Example: ['repo', 'user']
  68. - const scopesList = scopes.split(' '); // Example: 'read:user repo user:email'
  69. - if (!scopesList.every(scope => {
  70. - const included = tokenScopes.includes(scope);
  71. - if (included || !scope.includes(':')) {
  72. - return included;
  73. - }
  74. + const token = await vscode.window.showInputBox({ prompt: 'GitHub Personal Access Token', ignoreFocusOut: true });
  75. + if (!token) { throw new Error('Sign in failed: No token provided'); }
  76. - return scope.split(':').some(splitScopes => {
  77. - return tokenScopes.includes(splitScopes);
  78. - });
  79. - })) {
  80. - throw new Error(`The provided token does not match the requested scopes: ${scopes}`);
  81. + const tokenScopes = await getScopes(token, this.getServerUri('/'), this._logger); // Example: ['repo', 'user']
  82. + const scopesList = scopes.split(' '); // Example: 'read:user repo user:email'
  83. + if (!scopesList.every(scope => {
  84. + const included = tokenScopes.includes(scope);
  85. + if (included || !scope.includes(':')) {
  86. + return included;
  87. }
  88. - return token;
  89. - }
  90. -
  91. - this.updateStatusBarItem(true);
  92. -
  93. - const state = uuid();
  94. - const existingStates = this._pendingStates.get(scopes) || [];
  95. - this._pendingStates.set(scopes, [...existingStates, state]);
  96. -
  97. - const uri = vscode.Uri.parse(`https://${AUTH_RELAY_SERVER}/authorize/?callbackUri=${encodeURIComponent(callbackUri.toString())}&scope=${scopes}&state=${state}&responseType=code&authServer=https://github.com`);
  98. - await vscode.env.openExternal(uri);
  99. -
  100. - // Register a single listener for the URI callback, in case the user starts the login process multiple times
  101. - // before completing it.
  102. - let codeExchangePromise = this._codeExchangePromises.get(scopes);
  103. - if (!codeExchangePromise) {
  104. - codeExchangePromise = promiseFromEvent(this._uriHandler.event, this.exchangeCodeForToken(scopes));
  105. - this._codeExchangePromises.set(scopes, codeExchangePromise);
  106. + return scope.split(':').some(splitScopes => {
  107. + return tokenScopes.includes(splitScopes);
  108. + });
  109. + })) {
  110. + throw new Error(`The provided token does not match the requested scopes: ${scopes}`);
  111. }
  112. - return Promise.race([
  113. - codeExchangePromise.promise,
  114. - promiseFromEvent<string | undefined, string>(this._onDidManuallyProvideToken.event, (token: string | undefined, resolve, reject): void => {
  115. - if (!token) {
  116. - reject('Cancelled');
  117. - } else {
  118. - resolve(token);
  119. - }
  120. - }).promise,
  121. - new Promise<string>((_, reject) => setTimeout(() => reject('Cancelled'), 60000))
  122. - ]).finally(() => {
  123. - this._pendingStates.delete(scopes);
  124. - codeExchangePromise?.cancel.fire();
  125. - this._codeExchangePromises.delete(scopes);
  126. - this.updateStatusBarItem(false);
  127. - });
  128. + return token;
  129. }
  130. - private exchangeCodeForToken: (scopes: string) => PromiseAdapter<vscode.Uri, string> =
  131. - (scopes) => async (uri, resolve, reject) => {
  132. - const query = parseQuery(uri);
  133. - const code = query.code;
  134. -
  135. - const acceptedStates = this._pendingStates.get(scopes) || [];
  136. - if (!acceptedStates.includes(query.state)) {
  137. - // A common scenario of this happening is if you:
  138. - // 1. Trigger a sign in with one set of scopes
  139. - // 2. Before finishing 1, you trigger a sign in with a different set of scopes
  140. - // In this scenario we should just return and wait for the next UriHandler event
  141. - // to run as we are probably still waiting on the user to hit 'Continue'
  142. - this._logger.info('State not found in accepted state. Skipping this execution...');
  143. - return;
  144. - }
  145. -
  146. - const url = `https://${AUTH_RELAY_SERVER}/token?code=${code}&state=${query.state}`;
  147. - this._logger.info('Exchanging code for token...');
  148. -
  149. - try {
  150. - const result = await fetch(url, {
  151. - method: 'POST',
  152. - headers: {
  153. - Accept: 'application/json'
  154. - }
  155. - });
  156. -
  157. - if (result.ok) {
  158. - const json = await result.json();
  159. - this._logger.info('Token exchange success!');
  160. - resolve(json.access_token);
  161. - } else {
  162. - reject(result.statusText);
  163. - }
  164. - } catch (ex) {
  165. - reject(ex);
  166. - }
  167. - };
  168. -
  169. private getServerUri(path: string = '') {
  170. const apiUri = vscode.Uri.parse('https://api.github.com');
  171. return vscode.Uri.parse(`${apiUri.scheme}://${apiUri.authority}${path}`);
  172. }
  173. - private updateStatusBarItem(isStart?: boolean) {
  174. - if (isStart && !this._statusBarItem) {
  175. - this._statusBarItem = vscode.window.createStatusBarItem('status.git.signIn', vscode.StatusBarAlignment.Left);
  176. - this._statusBarItem.name = localize('status.git.signIn.name', "GitHub Sign-in");
  177. - this._statusBarItem.text = localize('signingIn', "$(mark-github) Signing in to github.com...");
  178. - this._statusBarItem.command = this._statusBarCommandId;
  179. - this._statusBarItem.show();
  180. - }
  181. -
  182. - if (!isStart && this._statusBarItem) {
  183. - this._statusBarItem.dispose();
  184. - this._statusBarItem = undefined;
  185. - }
  186. - }
  187. -
  188. private async manuallyProvideUri() {
  189. const uri = await vscode.window.showInputBox({
  190. prompt: 'Uri',
  191. @@ -277,44 +159,7 @@ export class GitHubServer implements IGitHubServer {
  192. return getUserInfo(token, this.getServerUri('/user'), this._logger);
  193. }
  194. - public async sendAdditionalTelemetryInfo(token: string): Promise<void> {
  195. - if (!vscode.env.isTelemetryEnabled) {
  196. - return;
  197. - }
  198. - const nocors = await this.isNoCorsEnvironment();
  199. -
  200. - if (nocors) {
  201. - return;
  202. - }
  203. -
  204. - try {
  205. - const result = await fetch('https://education.github.com/api/user', {
  206. - headers: {
  207. - Authorization: `token ${token}`,
  208. - 'faculty-check-preview': 'true',
  209. - 'User-Agent': 'Visual-Studio-Code'
  210. - }
  211. - });
  212. -
  213. - if (result.ok) {
  214. - const json: { student: boolean, faculty: boolean } = await result.json();
  215. -
  216. - /* __GDPR__
  217. - "session" : {
  218. - "isEdu": { "classification": "NonIdentifiableDemographicInfo", "purpose": "FeatureInsight" }
  219. - }
  220. - */
  221. - this._telemetryReporter.sendTelemetryEvent('session', {
  222. - isEdu: json.student
  223. - ? 'student'
  224. - : json.faculty
  225. - ? 'faculty'
  226. - : 'none'
  227. - });
  228. - }
  229. - } catch (e) {
  230. - // No-op
  231. - }
  232. + public async sendAdditionalTelemetryInfo(_token: string): Promise<void> {
  233. }
  234. public async checkEnterpriseVersion(token: string): Promise<void> {