use-github-pat.patch 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. diff --git a/extensions/github-authentication/src/githubServer.ts b/extensions/github-authentication/src/githubServer.ts
  2. index 3d36081..5bac245 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,150 +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' && /^(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. - // TODO@joaomoreno TODO@TylerLeonhardt
  62. - const nocors = await this.isNoCorsEnvironment();
  63. - const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/did-authenticate${nocors ? '?nocors=true' : ''}`));
  64. -
  65. - if (this.isTestEnvironment(callbackUri)) {
  66. - const token = await vscode.window.showInputBox({ prompt: 'GitHub Personal Access Token', ignoreFocusOut: true });
  67. - if (!token) { throw new Error('Sign in failed: No token provided'); }
  68. -
  69. - const tokenScopes = await getScopes(token, this.getServerUri('/'), this._logger); // Example: ['repo', 'user']
  70. - const scopesList = scopes.split(' '); // Example: 'read:user repo user:email'
  71. - if (!scopesList.every(scope => {
  72. - const included = tokenScopes.includes(scope);
  73. - if (included || !scope.includes(':')) {
  74. - return included;
  75. - }
  76. + const token = await vscode.window.showInputBox({ prompt: 'GitHub Personal Access Token', ignoreFocusOut: true });
  77. + if (!token) { throw new Error('Sign in failed: No token provided'); }
  78. - return scope.split(':').some(splitScopes => {
  79. - return tokenScopes.includes(splitScopes);
  80. - });
  81. - })) {
  82. - throw new Error(`The provided token is does not match the requested scopes: ${scopes}`);
  83. + const tokenScopes = await getScopes(token, this.getServerUri('/'), this._logger); // Example: ['repo', 'user']
  84. + const scopesList = scopes.split(' '); // Example: 'read:user repo user:email'
  85. + if (!scopesList.every(scope => {
  86. + const included = tokenScopes.includes(scope);
  87. + if (included || !scope.includes(':')) {
  88. + return included;
  89. }
  90. - return token;
  91. - }
  92. -
  93. - this.updateStatusBarItem(true);
  94. -
  95. - const state = uuid();
  96. - const existingStates = this._pendingStates.get(scopes) || [];
  97. - this._pendingStates.set(scopes, [...existingStates, state]);
  98. -
  99. - 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' : ''}`);
  100. - await vscode.env.openExternal(uri);
  101. -
  102. - // Register a single listener for the URI callback, in case the user starts the login process multiple times
  103. - // before completing it.
  104. - let codeExchangePromise = this._codeExchangePromises.get(scopes);
  105. - if (!codeExchangePromise) {
  106. - codeExchangePromise = promiseFromEvent(this._uriHandler.event, this.exchangeCodeForToken(scopes));
  107. - this._codeExchangePromises.set(scopes, codeExchangePromise);
  108. + return scope.split(':').some(splitScopes => {
  109. + return tokenScopes.includes(splitScopes);
  110. + });
  111. + })) {
  112. + throw new Error(`The provided token is does not match the requested scopes: ${scopes}`);
  113. }
  114. - return Promise.race([
  115. - codeExchangePromise.promise,
  116. - promiseFromEvent<string | undefined, string>(this._onDidManuallyProvideToken.event, (token: string | undefined, resolve, reject): void => {
  117. - if (!token) {
  118. - reject('Cancelled');
  119. - } else {
  120. - resolve(token);
  121. - }
  122. - }).promise,
  123. - new Promise<string>((_, reject) => setTimeout(() => reject('Cancelled'), 60000))
  124. - ]).finally(() => {
  125. - this._pendingStates.delete(scopes);
  126. - codeExchangePromise?.cancel.fire();
  127. - this._codeExchangePromises.delete(scopes);
  128. - this.updateStatusBarItem(false);
  129. - });
  130. + return token;
  131. }
  132. - private exchangeCodeForToken: (scopes: string) => PromiseAdapter<vscode.Uri, string> =
  133. - (scopes) => async (uri, resolve, reject) => {
  134. - const query = parseQuery(uri);
  135. - const code = query.code;
  136. -
  137. - const acceptedStates = this._pendingStates.get(scopes) || [];
  138. - if (!acceptedStates.includes(query.state)) {
  139. - // A common scenario of this happening is if you:
  140. - // 1. Trigger a sign in with one set of scopes
  141. - // 2. Before finishing 1, you trigger a sign in with a different set of scopes
  142. - // In this scenario we should just return and wait for the next UriHandler event
  143. - // to run as we are probably still waiting on the user to hit 'Continue'
  144. - this._logger.info('State not found in accepted state. Skipping this execution...');
  145. - return;
  146. - }
  147. -
  148. - const url = `https://${AUTH_RELAY_SERVER}/token?code=${code}&state=${query.state}`;
  149. - this._logger.info('Exchanging code for token...');
  150. -
  151. - // TODO@joao: remove
  152. - if (query.nocors) {
  153. - try {
  154. - const json: any = await vscode.commands.executeCommand('_workbench.fetchJSON', url, 'POST');
  155. - this._logger.info('Token exchange success!');
  156. - resolve(json.access_token);
  157. - } catch (err) {
  158. - reject(err);
  159. - }
  160. - } else {
  161. - try {
  162. - const result = await fetch(url, {
  163. - method: 'POST',
  164. - headers: {
  165. - Accept: 'application/json'
  166. - }
  167. - });
  168. -
  169. - if (result.ok) {
  170. - const json = await result.json();
  171. - this._logger.info('Token exchange success!');
  172. - resolve(json.access_token);
  173. - } else {
  174. - reject(result.statusText);
  175. - }
  176. - } catch (ex) {
  177. - reject(ex);
  178. - }
  179. - }
  180. - };
  181. -
  182. private getServerUri(path: string = '') {
  183. const apiUri = vscode.Uri.parse('https://api.github.com');
  184. return vscode.Uri.parse(`${apiUri.scheme}://${apiUri.authority}${path}`);
  185. }
  186. - private updateStatusBarItem(isStart?: boolean) {
  187. - if (isStart && !this._statusBarItem) {
  188. - this._statusBarItem = vscode.window.createStatusBarItem('status.git.signIn', vscode.StatusBarAlignment.Left);
  189. - this._statusBarItem.name = localize('status.git.signIn.name', "GitHub Sign-in");
  190. - this._statusBarItem.text = localize('signingIn', "$(mark-github) Signing in to github.com...");
  191. - this._statusBarItem.command = this._statusBarCommandId;
  192. - this._statusBarItem.show();
  193. - }
  194. -
  195. - if (!isStart && this._statusBarItem) {
  196. - this._statusBarItem.dispose();
  197. - this._statusBarItem = undefined;
  198. - }
  199. - }
  200. -
  201. private async manuallyProvideUri() {
  202. const uri = await vscode.window.showInputBox({
  203. prompt: 'Uri',
  204. @@ -290,41 +159,7 @@ export class GitHubServer implements IGitHubServer {
  205. return getUserInfo(token, this.getServerUri('/user'), this._logger);
  206. }
  207. - public async sendAdditionalTelemetryInfo(token: string): Promise<void> {
  208. - const nocors = await this.isNoCorsEnvironment();
  209. -
  210. - if (nocors) {
  211. - return;
  212. - }
  213. -
  214. - try {
  215. - const result = await fetch('https://education.github.com/api/user', {
  216. - headers: {
  217. - Authorization: `token ${token}`,
  218. - 'faculty-check-preview': 'true',
  219. - 'User-Agent': 'Visual-Studio-Code'
  220. - }
  221. - });
  222. -
  223. - if (result.ok) {
  224. - const json: { student: boolean, faculty: boolean } = await result.json();
  225. -
  226. - /* __GDPR__
  227. - "session" : {
  228. - "isEdu": { "classification": "NonIdentifiableDemographicInfo", "purpose": "FeatureInsight" }
  229. - }
  230. - */
  231. - this._telemetryReporter.sendTelemetryEvent('session', {
  232. - isEdu: json.student
  233. - ? 'student'
  234. - : json.faculty
  235. - ? 'faculty'
  236. - : 'none'
  237. - });
  238. - }
  239. - } catch (e) {
  240. - // No-op
  241. - }
  242. + public async sendAdditionalTelemetryInfo(_token: string): Promise<void> {
  243. }
  244. public async checkEnterpriseVersion(token: string): Promise<void> {