use-github-pat.patch 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. diff --git a/extensions/github-authentication/src/githubServer.ts b/extensions/github-authentication/src/githubServer.ts
  2. index 49a523b..d68588e 100644
  3. --- a/extensions/github-authentication/src/githubServer.ts
  4. +++ b/extensions/github-authentication/src/githubServer.ts
  5. @@ -6,23 +6,14 @@
  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. -import { isSupportedEnvironment } from './common/env';
  15. const localize = nls.loadMessageBundle();
  16. const CLIENT_ID = '01ab8ac9400c4e429b23';
  17. -const GITHUB_AUTHORIZE_URL = 'https://github.com/login/oauth/authorize';
  18. -// TODO: change to stable when that happens
  19. -const GITHUB_TOKEN_URL = 'https://vscode.dev/codeExchangeProxyEndpoints/github/login/oauth/access_token';
  20. const NETWORK_ERROR = 'network error';
  21. -const REDIRECT_URL_STABLE = 'https://vscode.dev/redirect';
  22. -const REDIRECT_URL_INSIDERS = 'https://insiders.vscode.dev/redirect';
  23. -
  24. class UriEventHandler extends vscode.EventEmitter<vscode.Uri> implements vscode.UriHandler {
  25. constructor(private readonly Logger: Log) {
  26. super();
  27. @@ -110,10 +101,7 @@ async function getUserInfo(token: string, serverUri: vscode.Uri, logger: Log): P
  28. export class GitHubServer implements IGitHubServer {
  29. friendlyName = 'GitHub';
  30. type = AuthProviderType.github;
  31. - private _onDidManuallyProvideToken = new vscode.EventEmitter<string | undefined>();
  32. - private _pendingNonces = new Map<string, string[]>();
  33. - private _codeExchangePromises = new Map<string, { promise: Promise<string>; cancel: vscode.EventEmitter<void> }>();
  34. private _disposable: vscode.Disposable;
  35. private _uriHandler = new UriEventHandler(this._logger);
  36. @@ -125,87 +113,31 @@ export class GitHubServer implements IGitHubServer {
  37. this._disposable.dispose();
  38. }
  39. - // TODO@joaomoreno TODO@TylerLeonhardt
  40. - private async isNoCorsEnvironment(): Promise<boolean> {
  41. - const uri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/dummy`));
  42. - return (uri.scheme === 'https' && /^((insiders\.)?vscode|github)\./.test(uri.authority)) || (uri.scheme === 'http' && /^localhost/.test(uri.authority));
  43. - }
  44. -
  45. public async login(scopes: string): Promise<string> {
  46. this._logger.info(`Logging in for the following scopes: ${scopes}`);
  47. - const nonce = uuid();
  48. - const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/did-authenticate?nonce=${encodeURIComponent(nonce)}`));
  49. -
  50. - if (!isSupportedEnvironment(callbackUri)) {
  51. - const token = this._supportDeviceCodeFlow
  52. - ? await this.doDeviceCodeFlow(scopes)
  53. - : await vscode.window.showInputBox({ prompt: 'GitHub Personal Access Token', ignoreFocusOut: true });
  54. + const token = this._supportDeviceCodeFlow
  55. + ? await this.doDeviceCodeFlow(scopes)
  56. + : await vscode.window.showInputBox({ prompt: 'GitHub Personal Access Token', ignoreFocusOut: true });
  57. - if (!token) { throw new Error('No token provided'); }
  58. + if (!token) { throw new Error('No token provided'); }
  59. - const tokenScopes = await getScopes(token, this.getServerUri('/'), this._logger); // Example: ['repo', 'user']
  60. - const scopesList = scopes.split(' '); // Example: 'read:user repo user:email'
  61. - if (!scopesList.every(scope => {
  62. - const included = tokenScopes.includes(scope);
  63. - if (included || !scope.includes(':')) {
  64. - return included;
  65. - }
  66. -
  67. - return scope.split(':').some(splitScopes => {
  68. - return tokenScopes.includes(splitScopes);
  69. - });
  70. - })) {
  71. - throw new Error(`The provided token does not match the requested scopes: ${scopes}`);
  72. + const tokenScopes = await getScopes(token, this.getServerUri('/'), this._logger); // Example: ['repo', 'user']
  73. + const scopesList = scopes.split(' '); // Example: 'read:user repo user:email'
  74. + if (!scopesList.every(scope => {
  75. + const included = tokenScopes.includes(scope);
  76. + if (included || !scope.includes(':')) {
  77. + return included;
  78. }
  79. - return token;
  80. + return scope.split(':').some(splitScopes => {
  81. + return tokenScopes.includes(splitScopes);
  82. + });
  83. + })) {
  84. + throw new Error(`The provided token does not match the requested scopes: ${scopes}`);
  85. }
  86. - const existingNonces = this._pendingNonces.get(scopes) || [];
  87. - this._pendingNonces.set(scopes, [...existingNonces, nonce]);
  88. -
  89. - const proxyEndpoints: { [providerId: string]: string } | undefined = await vscode.commands.executeCommand('workbench.getCodeExchangeProxyEndpoints');
  90. - // If we are running in insiders vscode.dev, then ensure we use the redirect route on that.
  91. - const redirectUri = proxyEndpoints?.github?.includes('https://insiders.vscode.dev') ? REDIRECT_URL_INSIDERS : REDIRECT_URL_STABLE;
  92. - const searchParams = new URLSearchParams([
  93. - ['client_id', CLIENT_ID],
  94. - ['redirect_uri', redirectUri],
  95. - ['scope', scopes],
  96. - ['state', encodeURIComponent(callbackUri.toString(true))]
  97. - ]);
  98. - const uri = vscode.Uri.parse(`${GITHUB_AUTHORIZE_URL}?${searchParams.toString()}`);
  99. -
  100. - return vscode.window.withProgress({
  101. - location: vscode.ProgressLocation.Window,
  102. - title: localize('signingIn', " $(mark-github) Signing in to github.com..."),
  103. - }, async () => {
  104. - await vscode.env.openExternal(uri);
  105. -
  106. - // Register a single listener for the URI callback, in case the user starts the login process multiple times
  107. - // before completing it.
  108. - let codeExchangePromise = this._codeExchangePromises.get(scopes);
  109. - if (!codeExchangePromise) {
  110. - codeExchangePromise = promiseFromEvent(this._uriHandler.event, this.exchangeCodeForToken(scopes));
  111. - this._codeExchangePromises.set(scopes, codeExchangePromise);
  112. - }
  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._pendingNonces.delete(scopes);
  126. - codeExchangePromise?.cancel.fire();
  127. - this._codeExchangePromises.delete(scopes);
  128. - });
  129. - });
  130. + return token;
  131. }
  132. private async doDeviceCodeFlow(scopes: string): Promise<string> {
  133. @@ -299,57 +231,6 @@ export class GitHubServer implements IGitHubServer {
  134. throw new Error('Cancelled');
  135. }
  136. - private exchangeCodeForToken: (scopes: string) => PromiseAdapter<vscode.Uri, string> =
  137. - (scopes) => async (uri, resolve, reject) => {
  138. - const query = new URLSearchParams(uri.query);
  139. - const code = query.get('code');
  140. -
  141. - const acceptedNonces = this._pendingNonces.get(scopes) || [];
  142. - const nonce = query.get('nonce');
  143. - if (!nonce) {
  144. - this._logger.error('No nonce in response.');
  145. - return;
  146. - }
  147. - if (!acceptedNonces.includes(nonce)) {
  148. - // A common scenario of this happening is if you:
  149. - // 1. Trigger a sign in with one set of scopes
  150. - // 2. Before finishing 1, you trigger a sign in with a different set of scopes
  151. - // In this scenario we should just return and wait for the next UriHandler event
  152. - // to run as we are probably still waiting on the user to hit 'Continue'
  153. - this._logger.info('Nonce not found in accepted nonces. Skipping this execution...');
  154. - return;
  155. - }
  156. -
  157. - this._logger.info('Exchanging code for token...');
  158. -
  159. - const proxyEndpoints: { [providerId: string]: string } | undefined = await vscode.commands.executeCommand('workbench.getCodeExchangeProxyEndpoints');
  160. - const endpointUrl = proxyEndpoints?.github ? `${proxyEndpoints.github}login/oauth/access_token` : GITHUB_TOKEN_URL;
  161. -
  162. - try {
  163. - const body = `code=${code}`;
  164. - const result = await fetch(endpointUrl, {
  165. - method: 'POST',
  166. - headers: {
  167. - Accept: 'application/json',
  168. - 'Content-Type': 'application/x-www-form-urlencoded',
  169. - 'Content-Length': body.toString()
  170. -
  171. - },
  172. - body
  173. - });
  174. -
  175. - if (result.ok) {
  176. - const json = await result.json();
  177. - this._logger.info('Token exchange success!');
  178. - resolve(json.access_token);
  179. - } else {
  180. - reject(result.statusText);
  181. - }
  182. - } catch (ex) {
  183. - reject(ex);
  184. - }
  185. - };
  186. -
  187. private getServerUri(path: string = '') {
  188. const apiUri = vscode.Uri.parse('https://api.github.com');
  189. return vscode.Uri.parse(`${apiUri.scheme}://${apiUri.authority}${path}`);
  190. @@ -359,44 +240,7 @@ export class GitHubServer implements IGitHubServer {
  191. return getUserInfo(token, this.getServerUri('/user'), this._logger);
  192. }
  193. - public async sendAdditionalTelemetryInfo(token: string): Promise<void> {
  194. - if (!vscode.env.isTelemetryEnabled) {
  195. - return;
  196. - }
  197. - const nocors = await this.isNoCorsEnvironment();
  198. -
  199. - if (nocors) {
  200. - return;
  201. - }
  202. -
  203. - try {
  204. - const result = await fetch('https://education.github.com/api/user', {
  205. - headers: {
  206. - Authorization: `token ${token}`,
  207. - 'faculty-check-preview': 'true',
  208. - 'User-Agent': 'Visual-Studio-Code'
  209. - }
  210. - });
  211. -
  212. - if (result.ok) {
  213. - const json: { student: boolean; faculty: boolean } = await result.json();
  214. -
  215. - /* __GDPR__
  216. - "session" : {
  217. - "isEdu": { "classification": "NonIdentifiableDemographicInfo", "purpose": "FeatureInsight" }
  218. - }
  219. - */
  220. - this._telemetryReporter.sendTelemetryEvent('session', {
  221. - isEdu: json.student
  222. - ? 'student'
  223. - : json.faculty
  224. - ? 'faculty'
  225. - : 'none'
  226. - });
  227. - }
  228. - } catch (e) {
  229. - // No-op
  230. - }
  231. + public async sendAdditionalTelemetryInfo(_: string): Promise<void> {
  232. }
  233. public async checkEnterpriseVersion(token: string): Promise<void> {
  234. diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts
  235. index 36647e6..55e722b 100644
  236. --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts
  237. +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts
  238. @@ -271,7 +271,7 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
  239. }
  240. });
  241. - if (providers.length && !menus.length) {
  242. + if (!menus.length) {
  243. const noAccountsAvailableAction = disposables.add(new Action('noAccountsAvailable', localize('noAccounts', "You are not signed in to any accounts"), undefined, false));
  244. menus.push(noAccountsAvailableAction);
  245. }
  246. diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts
  247. index 5f7431d..278cd3d 100644
  248. --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts
  249. +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts
  250. @@ -13,7 +13,6 @@ import { isString } from 'vs/base/common/types';
  251. import * as nls from 'vs/nls';
  252. import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
  253. import { CommandsRegistry } from 'vs/platform/commands/common/commands';
  254. -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
  255. import { ICredentialsService } from 'vs/platform/credentials/common/credentials';
  256. import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
  257. import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
  258. @@ -197,13 +196,6 @@ export class AuthenticationService extends Disposable implements IAuthentication
  259. @IQuickInputService private readonly quickInputService: IQuickInputService
  260. ) {
  261. super();
  262. - this._placeholderMenuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, {
  263. - command: {
  264. - id: 'noAuthenticationProviders',
  265. - title: nls.localize('loading', "Loading..."),
  266. - precondition: ContextKeyExpr.false()
  267. - },
  268. - });
  269. authenticationExtPoint.setHandler((extensions, { added, removed }) => {
  270. added.forEach(point => {
  271. @@ -272,16 +264,6 @@ export class AuthenticationService extends Disposable implements IAuthentication
  272. this.removeAccessRequest(id, extensionId);
  273. });
  274. }
  275. -
  276. - if (!this._authenticationProviders.size) {
  277. - this._placeholderMenuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, {
  278. - command: {
  279. - id: 'noAuthenticationProviders',
  280. - title: nls.localize('loading', "Loading..."),
  281. - precondition: ContextKeyExpr.false()
  282. - },
  283. - });
  284. - }
  285. }
  286. async sessionsUpdate(id: string, event: AuthenticationSessionsChangeEvent): Promise<void> {