diff --git a/extensions/github-authentication/src/github.ts b/extensions/github-authentication/src/github.ts index aa4bff53236b94748d0bbf7937b795b07c2281fa..943332f1db7d566b7970382490e88c3f6a8889b3 100644 --- a/extensions/github-authentication/src/github.ts +++ b/extensions/github-authentication/src/github.ts @@ -12,12 +12,27 @@ import Logger from './common/logger'; export const onDidChangeSessions = new vscode.EventEmitter(); interface SessionData { + id: string; + account?: { + displayName: string; + id: string; + } + scopes: string[]; + accessToken: string; +} + +// TODO remove +interface OldSessionData { id: string; accountName: string; scopes: string[]; accessToken: string; } +function isOldSessionData(x: any): x is OldSessionData { + return !!x.accountName; +} + export class GitHubAuthenticationProvider { private _sessions: vscode.AuthenticationSession[] = []; private _githubServer = new GitHubServer(); @@ -68,15 +83,34 @@ export class GitHubAuthenticationProvider { const storedSessions = await keychain.getToken(); if (storedSessions) { try { - const sessionData: SessionData[] = JSON.parse(storedSessions); - return sessionData.map(session => { - return { - id: session.id, - accountName: session.accountName, - scopes: session.scopes, - getAccessToken: () => Promise.resolve(session.accessToken) - }; + const sessionData: (SessionData | OldSessionData)[] = JSON.parse(storedSessions); + const sessionPromises = sessionData.map(async (session: SessionData | OldSessionData): Promise => { + try { + const needsUserInfo = isOldSessionData(session) || !session.account; + let userInfo: { id: string, accountName: string }; + if (needsUserInfo) { + userInfo = await this._githubServer.getUserInfo(session.accessToken); + } + + return { + id: session.id, + account: { + displayName: isOldSessionData(session) + ? session.accountName + : session.account?.displayName ?? userInfo!.accountName, + id: isOldSessionData(session) + ? userInfo!.id + : session.account?.id ?? userInfo!.id + }, + scopes: session.scopes, + getAccessToken: () => Promise.resolve(session.accessToken) + }; + } catch (e) { + return undefined; + } }); + + return (await Promise.all(sessionPromises)).filter((x: vscode.AuthenticationSession | undefined): x is vscode.AuthenticationSession => !!x); } catch (e) { Logger.error(`Error reading sessions: ${e}`); } @@ -90,7 +124,7 @@ export class GitHubAuthenticationProvider { const resolvedAccessToken = await session.getAccessToken(); return { id: session.id, - accountName: session.accountName, + account: session.account, scopes: session.scopes, accessToken: resolvedAccessToken }; @@ -125,7 +159,10 @@ export class GitHubAuthenticationProvider { return { id: uuid(), getAccessToken: () => Promise.resolve(token), - accountName: userInfo.accountName, + account: { + displayName: userInfo.accountName, + id: userInfo.id + }, scopes: scopes }; } diff --git a/extensions/github-authentication/src/githubServer.ts b/extensions/github-authentication/src/githubServer.ts index b9d0150a67088f0e95bf715d10d1466227c3c32c..1a5f9f09ba06cbda9d9655c8a6d9a139c3ac59ef 100644 --- a/extensions/github-authentication/src/githubServer.ts +++ b/extensions/github-authentication/src/githubServer.ts @@ -145,6 +145,7 @@ export class GitHubServer { Logger.info('Got account info!'); resolve({ id: json.id, accountName: json.login }); } else { + Logger.error('Getting account info failed'); reject(new Error(result.statusMessage)); } }); diff --git a/extensions/vscode-account/src/AADHelper.ts b/extensions/vscode-account/src/AADHelper.ts index 2a90c33e8d8197446385b3a73b1783738712872a..c5a4a9dca328766e472a1e2a38f8c66b6c67c35e 100644 --- a/extensions/vscode-account/src/AADHelper.ts +++ b/extensions/vscode-account/src/AADHelper.ts @@ -25,7 +25,10 @@ interface IToken { expiresAt?: number; // UNIX epoch time at which token will expire refreshToken: string; - accountName: string; + account: { + displayName: string; + id: string; + }; scope: string; sessionId: string; // The account id + the scope } @@ -44,7 +47,10 @@ interface IStoredSession { id: string; refreshToken: string; scope: string; // Scopes are alphabetized and joined with a space - accountName: string; + account: { + displayName: string, + id: string + } } function parseQuery(uri: vscode.Uri) { @@ -93,7 +99,10 @@ export class AzureActiveDirectoryService { this._tokens.push({ accessToken: undefined, refreshToken: session.refreshToken, - accountName: session.accountName, + account: { + displayName: session.account.displayName, + id: session.account.id + }, scope: session.scope, sessionId: session.id }); @@ -125,7 +134,7 @@ export class AzureActiveDirectoryService { id: token.sessionId, refreshToken: token.refreshToken, scope: token.scope, - accountName: token.accountName + account: token.account }; }); @@ -199,7 +208,7 @@ export class AzureActiveDirectoryService { return { id: token.sessionId, getAccessToken: () => this.resolveAccessToken(token), - accountName: token.accountName, + account: token.account, scopes: token.scope.split(' ') }; } @@ -223,8 +232,6 @@ export class AzureActiveDirectoryService { } catch (e) { throw new Error('Unavailable due to network problems'); } - - throw new Error('Unavailable due to network problems'); } private getTokenClaims(accessToken: string): ITokenClaims { @@ -419,7 +426,10 @@ export class AzureActiveDirectoryService { refreshToken: json.refresh_token, scope, sessionId: existingId || `${claims.tid}/${(claims.oid || (claims.altsecid || '' + claims.ipd || ''))}/${uuid()}`, - accountName: claims.email || claims.unique_name || 'user@example.com' + account: { + displayName: claims.email || claims.unique_name || 'user@example.com', + id: `${claims.tid}/${(claims.oid || (claims.altsecid || '' + claims.ipd || ''))}` + } }; } diff --git a/extensions/vscode-account/src/extension.ts b/extensions/vscode-account/src/extension.ts index 7162367222c93abcdf0d685f7458f294e82c1e5c..e6dadbcbac720a8393a619596ed94290ed318865 100644 --- a/extensions/vscode-account/src/extension.ts +++ b/extensions/vscode-account/src/extension.ts @@ -69,7 +69,7 @@ export async function activate(context: vscode.ExtensionContext) { const selectedSession = await vscode.window.showQuickPick(sessions.map(session => { return { id: session.id, - label: session.accountName + label: session.account.displayName }; })); diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 5fbc897f07335f4f8c1e64b08f53f1e703856bc2..949f656342cf5339fcab4cbf82527761f65e0a48 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -1412,7 +1412,10 @@ export interface RenameProvider { export interface AuthenticationSession { id: string; getAccessToken(): Thenable; - accountName: string; + account: { + displayName: string; + id: string; + } } /** diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index e7d96752c61d66e3ac2df360591b0722c093ef8c..3674f3e1bee4acbf89353ea4f302e7f36d047ffa 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -21,7 +21,10 @@ declare module 'vscode' { export interface AuthenticationSession { id: string; getAccessToken(): Thenable; - accountName: string; + account: { + displayName: string; + id: string; + }; scopes: string[] } diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index 0b54b52f530f74b417905bfdb35fc559c73400b3..f1ad17bd9f2ecffaace840fdbc3c4a0185a3c901 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -129,21 +129,21 @@ export class MainThreadAuthenticationProvider extends Disposable { } private registerSession(session: modes.AuthenticationSession) { - this._sessions.set(session.id, session.accountName); + this._sessions.set(session.id, session.account.displayName); - const existingSessionsForAccount = this._accounts.get(session.accountName); + const existingSessionsForAccount = this._accounts.get(session.account.displayName); if (existingSessionsForAccount) { - this._accounts.set(session.accountName, existingSessionsForAccount.concat(session.id)); + this._accounts.set(session.account.displayName, existingSessionsForAccount.concat(session.id)); return; } else { - this._accounts.set(session.accountName, [session.id]); + this._accounts.set(session.account.displayName, [session.id]); } const menuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, { group: '1_accounts', command: { id: `configureSessions${session.id}`, - title: `${session.accountName} (${this.displayName})` + title: `${session.account.displayName} (${this.displayName})` }, order: 3 }); @@ -170,11 +170,11 @@ export class MainThreadAuthenticationProvider extends Disposable { } if (selected.label === manage) { - this.manageTrustedExtensions(quickInputService, storageService, session.accountName); + this.manageTrustedExtensions(quickInputService, storageService, session.account.displayName); } if (selected.label === showUsage) { - this.showUsage(quickInputService, session.accountName); + this.showUsage(quickInputService, session.account.displayName); } quickPick.dispose(); @@ -188,28 +188,28 @@ export class MainThreadAuthenticationProvider extends Disposable { }, }); - this._sessionMenuItems.set(session.accountName, [menuItem, manageCommand]); + this._sessionMenuItems.set(session.account.displayName, [menuItem, manageCommand]); } async signOut(dialogService: IDialogService, session: modes.AuthenticationSession): Promise { const providerUsage = accountUsages.get(this.id); - const accountUsage = (providerUsage || {})[session.accountName] || []; - const sessionsForAccount = this._accounts.get(session.accountName); + const accountUsage = (providerUsage || {})[session.account.displayName] || []; + const sessionsForAccount = this._accounts.get(session.account.displayName); // Skip dialog if nothing is using the account if (!accountUsage.length) { - accountUsages.set(this.id, { [session.accountName]: [] }); + accountUsages.set(this.id, { [session.account.displayName]: [] }); sessionsForAccount?.forEach(sessionId => this.logout(sessionId)); return; } const result = await dialogService.confirm({ - title: nls.localize('signOutConfirm', "Sign out of {0}", session.accountName), - message: nls.localize('signOutMessage', "The account {0} is currently used by: \n\n{1}\n\n Sign out of these features?", session.accountName, accountUsage.join('\n')) + title: nls.localize('signOutConfirm', "Sign out of {0}", session.account.displayName), + message: nls.localize('signOutMessage', "The account {0} is currently used by: \n\n{1}\n\n Sign out of these features?", session.account.displayName, accountUsage.join('\n')) }); if (result.confirmed) { - accountUsages.set(this.id, { [session.accountName]: [] }); + accountUsages.set(this.id, { [session.account.displayName]: [] }); sessionsForAccount?.forEach(sessionId => this.logout(sessionId)); } } @@ -218,9 +218,9 @@ export class MainThreadAuthenticationProvider extends Disposable { return (await this._proxy.$getSessions(this.id)).map(session => { return { id: session.id, - accountName: session.accountName, + account: session.account, getAccessToken: () => { - addAccountUsage(this.id, session.accountName, nls.localize('sync', "Preferences Sync")); + addAccountUsage(this.id, session.account.displayName, nls.localize('sync', "Preferences Sync")); return this._proxy.$getSessionAccessToken(this.id, session.id); } }; @@ -258,7 +258,7 @@ export class MainThreadAuthenticationProvider extends Disposable { return this._proxy.$login(this.id, scopes).then(session => { return { id: session.id, - accountName: session.accountName, + account: session.account, getAccessToken: () => this._proxy.$getSessionAccessToken(this.id, session.id) }; }); diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index 65b1b760ecd7c6b8ff96d4d7f4e06be257b31893..210a5dec82ee62e25c48eeef10c4ab07d731bbba 100644 --- a/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -47,12 +47,12 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { .map(session => { return { id: session.id, - accountName: session.accountName, + account: session.account, scopes: session.scopes, getAccessToken: async () => { const isAllowed = await this._proxy.$getSessionsPrompt( provider.id, - session.accountName, + session.account.displayName, provider.displayName, extensionId, requestingExtension.displayName || requestingExtension.name); @@ -80,15 +80,15 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { } const session = await provider.login(scopes); - await this._proxy.$setTrustedExtension(provider.id, session.accountName, ExtensionIdentifier.toKey(requestingExtension.identifier), extensionName); + await this._proxy.$setTrustedExtension(provider.id, session.account.displayName, ExtensionIdentifier.toKey(requestingExtension.identifier), extensionName); return { id: session.id, - accountName: session.accountName, + account: session.account, scopes: session.scopes, getAccessToken: async () => { const isAllowed = await this._proxy.$getSessionsPrompt( provider.id, - session.accountName, + session.account.displayName, provider.displayName, ExtensionIdentifier.toKey(requestingExtension.identifier), requestingExtension.displayName || requestingExtension.name); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncAccount.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncAccount.ts index 4b7e1650e8df92f849299a44d4a64a70e672b88b..f9c16b8b2fb24d6f4242787ee620107b95265705 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncAccount.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncAccount.ts @@ -157,7 +157,7 @@ export class UserDataSyncAccounts extends Disposable { const sessions = await this.authenticationService.getSessions(authenticationProviderId) || []; for (const session of sessions) { - const account: IUserDataSyncAccount = { authenticationProviderId, sessionId: session.id, accountName: session.accountName }; + const account: IUserDataSyncAccount = { authenticationProviderId, sessionId: session.id, accountName: session.account.displayName }; accounts.set(account.accountName, account); if (this.isCurrentAccount(account)) { currentAccount = account; @@ -196,7 +196,7 @@ export class UserDataSyncAccounts extends Disposable { if (isAuthenticationProvider(result)) { const session = await this.authenticationService.login(result.id, result.scopes); sessionId = session.id; - accountName = session.accountName; + accountName = session.account.displayName; } else { sessionId = result.sessionId; accountName = result.accountName;