diff --git a/extensions/github-authentication/src/extension.ts b/extensions/github-authentication/src/extension.ts index dee2f40bafa..e8189ff42ac 100644 --- a/extensions/github-authentication/src/extension.ts +++ b/extensions/github-authentication/src/extension.ts @@ -18,11 +18,13 @@ export async function activate(context: vscode.ExtensionContext) { vscode.authentication.registerAuthenticationProvider({ id: 'github', displayName: 'GitHub', + supportsMultipleAccounts: false, onDidChangeSessions: onDidChangeSessions.event, getSessions: () => Promise.resolve(loginService.sessions), - login: async (scopeList: string[]) => { + login: async (scopeList: string[] | undefined) => { try { - const session = await loginService.login(scopeList.join(' ')); + const loginScopes = scopeList ? scopeList.sort().join(' ') : 'user:email'; + const session = await loginService.login(loginScopes); Logger.info('Login success!'); onDidChangeSessions.fire({ added: [session.id], removed: [], changed: [] }); return session; diff --git a/extensions/vscode-account/src/extension.ts b/extensions/vscode-account/src/extension.ts index 83f4135b753..5c7ce631ee2 100644 --- a/extensions/vscode-account/src/extension.ts +++ b/extensions/vscode-account/src/extension.ts @@ -20,17 +20,15 @@ export async function activate(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.authentication.registerAuthenticationProvider({ id: 'microsoft', displayName: 'Microsoft', + supportsMultipleAccounts: true, onDidChangeSessions: onDidChangeSessions.event, getSessions: () => Promise.resolve(loginService.sessions), - login: async (scopes: string[]) => { - try { - await loginService.login(scopes.sort().join(' ')); - const session = loginService.sessions[loginService.sessions.length - 1]; - onDidChangeSessions.fire({ added: [session.id], removed: [], changed: [] }); - return loginService.sessions[0]!; - } catch (e) { - throw e; - } + login: async (scopes: string[] | undefined) => { + const loginScopes = scopes ? scopes.sort().join(' ') : 'https://management.core.windows.net/.default offline_access'; + await loginService.login(loginScopes); + const session = loginService.sessions[loginService.sessions.length - 1]; + onDidChangeSessions.fire({ added: [session.id], removed: [], changed: [] }); + return loginService.sessions[0]!; }, logout: async (id: string) => { await loginService.logout(id); diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 72b565efc75..12ef0a1c953 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -74,6 +74,12 @@ declare module 'vscode' { readonly id: string; readonly displayName: string; + /** + * Whether the authentication provider supports the user being logged into + * multiple different accounts at the same time. + */ + supportsMultipleAccounts: boolean; + /** * An [event](#Event) which fires when the array of sessions has changed, or data * within a session has changed. @@ -88,7 +94,7 @@ declare module 'vscode' { /** * Prompts a user to login. */ - login(scopes: string[]): Thenable; + login(scopes?: string[]): Thenable; logout(sessionId: string): Thenable; } diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index 0d5df383d83..5ba33c07cf6 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -17,22 +17,6 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { INotificationService } from 'vs/platform/notification/common/notification'; -interface AuthDependent { - providerId: string; - label: string; - scopes: string[]; - scopeDescriptions?: string; -} - -const BUILT_IN_AUTH_DEPENDENTS: AuthDependent[] = [ - { - providerId: 'microsoft', - label: 'Settings sync', - scopes: ['https://management.core.windows.net/.default', 'offline_access'], - scopeDescriptions: 'Read user email' - } -]; - interface AllowedExtension { id: string; name: string; @@ -74,12 +58,13 @@ export class MainThreadAuthenticationProvider extends Disposable { private _accounts = new Map(); // Map account name to session ids private _sessions = new Map(); // Map account id to name private _signInMenuItem: IMenuItem | undefined; + private _signInMenuDisposables: IDisposable[] = []; constructor( private readonly _proxy: ExtHostAuthenticationShape, public readonly id: string, public readonly displayName: string, - public readonly dependents: AuthDependent[], + private readonly supportsMultipleAccounts: boolean, private readonly notificationService: INotificationService ) { super(); @@ -135,29 +120,33 @@ export class MainThreadAuthenticationProvider extends Disposable { quickPick.show(); } + private createSignInMenu(hasSessions: boolean): void { + this._signInMenuDisposables.push(CommandsRegistry.registerCommand({ + id: `signIn${this.id}`, + handler: (accessor, args) => { + this.login(); + }, + })); + + this._signInMenuItem = { + group: '2_providers', + command: { + id: `signIn${this.id}`, + title: hasSessions + ? nls.localize('addAnotherAccount', "Sign in to another {0} account", this.displayName) + : nls.localize('addAccount', "Sign in to {0}", this.displayName) + }, + order: 3 + }; + + this._signInMenuDisposables.push(MenuRegistry.appendMenuItem(MenuId.AccountsContext, this._signInMenuItem)); + } + private async registerCommandsAndContextMenuItems(): Promise { const sessions = await this._proxy.$getSessions(this.id); - if (this.dependents.length) { - this._register(CommandsRegistry.registerCommand({ - id: `signIn${this.id}`, - handler: (accessor, args) => { - this.login(this.dependents.reduce((previous: string[], current) => previous.concat(current.scopes), [])); - }, - })); - - this._signInMenuItem = { - group: '2_providers', - command: { - id: `signIn${this.id}`, - title: sessions.length - ? nls.localize('addAnotherAccount', "Sign in to another {0} account", this.displayName) - : nls.localize('addAccount', "Sign in to {0}", this.displayName) - }, - order: 3 - }; - - this._register(MenuRegistry.appendMenuItem(MenuId.AccountsContext, this._signInMenuItem)); + if (!sessions.length || (sessions.length && this.supportsMultipleAccounts)) { + this.createSignInMenu(!!sessions.length); } sessions.forEach(session => this.registerSession(session)); @@ -261,6 +250,8 @@ export class MainThreadAuthenticationProvider extends Disposable { if (this._signInMenuItem) { this._signInMenuItem.command.title = nls.localize('addAccount', "Sign in to {0}", this.displayName); + } else { + this.createSignInMenu(false); } } } @@ -269,11 +260,16 @@ export class MainThreadAuthenticationProvider extends Disposable { addedSessions.forEach(session => this.registerSession(session)); if (addedSessions.length && this._signInMenuItem) { - this._signInMenuItem.command.title = nls.localize('addAnotherAccount', "Sign in to another {0} account", this.displayName); + if (this.supportsMultipleAccounts) { + this._signInMenuItem.command.title = nls.localize('addAnotherAccount', "Sign in to another {0} account", this.displayName); + } else { + this._signInMenuDisposables.forEach(item => item.dispose()); + this._signInMenuItem = undefined; + } } } - login(scopes: string[]): Promise { + login(scopes?: string[]): Promise { return this._proxy.$login(this.id, scopes).then(session => { return { id: session.id, @@ -292,6 +288,7 @@ export class MainThreadAuthenticationProvider extends Disposable { super.dispose(); this._sessionMenuItems.forEach(item => item.forEach(d => d.dispose())); this._sessionMenuItems.clear(); + this._signInMenuDisposables.forEach(item => item.dispose()); } } @@ -310,10 +307,8 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostAuthentication); } - async $registerAuthenticationProvider(id: string, displayName: string): Promise { - const dependentBuiltIns = BUILT_IN_AUTH_DEPENDENTS.filter(dependency => dependency.providerId === id); - - const provider = new MainThreadAuthenticationProvider(this._proxy, id, displayName, dependentBuiltIns, this.notificationService); + async $registerAuthenticationProvider(id: string, displayName: string, supportsMultipleAccounts: boolean): Promise { + const provider = new MainThreadAuthenticationProvider(this._proxy, id, displayName, supportsMultipleAccounts, this.notificationService); this.authenticationService.registerAuthenticationProvider(id, provider); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index f8b8b781752..279cb6cbc13 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -155,7 +155,7 @@ export interface MainThreadCommentsShape extends IDisposable { } export interface MainThreadAuthenticationShape extends IDisposable { - $registerAuthenticationProvider(id: string, displayName: string): void; + $registerAuthenticationProvider(id: string, displayName: string, supportsMultipleAccounts: boolean): void; $unregisterAuthenticationProvider(id: string): void; $onDidChangeSessions(providerId: string, event: modes.AuthenticationSessionsChangeEvent): void; $getSessionsPrompt(providerId: string, accountName: string, providerName: string, extensionId: string, extensionName: string): Promise; @@ -997,7 +997,7 @@ export interface ExtHostLabelServiceShape { export interface ExtHostAuthenticationShape { $getSessions(id: string): Promise>; $getSessionAccessToken(id: string, sessionId: string): Promise; - $login(id: string, scopes: string[]): Promise; + $login(id: string, scopes: string[] | undefined): Promise; $logout(id: string, sessionId: string): Promise; } diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index 65b1b760ecd..f47cd62f39c 100644 --- a/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -114,7 +114,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { this._onDidChangeSessions.fire({ [provider.id]: e }); }); - this._proxy.$registerAuthenticationProvider(provider.id, provider.displayName); + this._proxy.$registerAuthenticationProvider(provider.id, provider.displayName, provider.supportsMultipleAccounts); this._onDidChangeAuthenticationProviders.fire({ added: [provider.id], removed: [] }); return new Disposable(() => { @@ -125,7 +125,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { }); } - $login(providerId: string, scopes: string[]): Promise { + $login(providerId: string, scopes: string[] | undefined): Promise { const authProvider = this._authenticationProviders.get(providerId); if (authProvider) { return Promise.resolve(authProvider.login(scopes)); diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index 81c61d8f718..1409dd9baaa 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -60,7 +60,7 @@ export class AuthenticationService extends Disposable implements IAuthentication this._authenticationProviders.set(id, authenticationProvider); this._onDidRegisterAuthenticationProvider.fire(id); - if (authenticationProvider.dependents.length && this._placeholderMenuItem) { + if (this._placeholderMenuItem) { this._placeholderMenuItem.dispose(); this._placeholderMenuItem = undefined; }