diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 08153cffacb..ec72e2adb43 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -50,13 +50,7 @@ declare module 'vscode' { export const onDidRegisterAuthenticationProvider: Event; export const onDidUnregisterAuthenticationProvider: Event; - /** - * Fires with the provider id that changed sessions. - */ - export const onDidChangeSessions: Event; - export function login(providerId: string): Promise; - export function logout(providerId: string, accountId: string): Promise; - export function getSessions(providerId: string): Promise>; + export const providers: ReadonlyArray; } //#endregion diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index 593871bc297..0d09f570e5c 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -5,9 +5,13 @@ import { Disposable } from 'vs/base/common/lifecycle'; import * as modes from 'vs/editor/common/modes'; +import * as nls from 'vs/nls'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; import { ExtHostAuthenticationShape, ExtHostContext, IExtHostContext, MainContext, MainThreadAuthenticationShape } from '../common/extHost.protocol'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import Severity from 'vs/base/common/severity'; export class MainThreadAuthenticationProvider { constructor( @@ -35,6 +39,8 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu constructor( extHostContext: IExtHostContext, @IAuthenticationService private readonly authenticationService: IAuthenticationService, + @IDialogService private readonly dialogService: IDialogService, + @IStorageService private readonly storageService: IStorageService ) { super(); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostAuthentication); @@ -49,7 +55,55 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu this.authenticationService.unregisterAuthenticationProvider(id); } - $onDidChangeSessions(id: string) { + $onDidChangeSessions(id: string): void { this.authenticationService.sessionsUpdate(id); } + + async $getSessionsPrompt(providerId: string, providerName: string, extensionId: string, extensionName: string): Promise { + const alwaysAllow = this.storageService.get(`${extensionId}-${providerId}`, StorageScope.GLOBAL); + if (alwaysAllow) { + return true; + } + + const { choice } = await this.dialogService.show( + Severity.Info, + nls.localize('confirmAuthenticationAccess', "The extension '{0}' is trying to access authentication information from {1}.", extensionName, providerName), + [nls.localize('cancel', "Cancel"), nls.localize('allow', "Allow"), nls.localize('alwaysAllow', "Always Allow"),], + { cancelId: 0 } + ); + + switch (choice) { + case 1/** Allow */: + return true; + case 2 /** Always Allow */: + this.storageService.store(`${extensionId}-${providerId}`, 'true', StorageScope.GLOBAL); + return true; + default: + return false; + } + } + + async $loginPrompt(providerId: string, providerName: string, extensionId: string, extensionName: string): Promise { + const alwaysAllow = this.storageService.get(`${extensionId}-${providerId}`, StorageScope.GLOBAL); + if (alwaysAllow) { + return true; + } + + const { choice } = await this.dialogService.show( + Severity.Info, + nls.localize('confirmLogin', "The extension '{0}' wants to sign in using {1}.", extensionName, providerName), + [nls.localize('cancel', "Cancel"), nls.localize('continue', "Continue"), nls.localize('neverAgain', "Don't Show Again")], + { cancelId: 0 } + ); + + switch (choice) { + case 1/** Allow */: + return true; + case 2 /** Always Allow */: + this.storageService.store(`${extensionId}-${providerId}`, 'true', StorageScope.GLOBAL); + return true; + default: + return false; + } + } } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 97180bb9611..948ec4d65ad 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -183,17 +183,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerAuthenticationProvider(provider: vscode.AuthenticationProvider): vscode.Disposable { return extHostAuthentication.registerAuthenticationProvider(provider); }, - login(providerId: string): Promise { - return extHostAuthentication.$login(providerId); - }, - logout(providerId: string, accountId: string): Promise { - return extHostAuthentication.$logout(providerId, accountId); - }, - getSessions(providerId: string): Promise> { - return extHostAuthentication.$getSessions(providerId); - }, - get onDidChangeSessions() { - return extHostAuthentication.onDidChangeSessions; + get providers() { + return extHostAuthentication.providers(extension); }, get onDidRegisterAuthenticationProvider() { return extHostAuthentication.onDidRegisterAuthenticationProvider; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 326a47d74fc..6feb8349842 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -151,6 +151,8 @@ export interface MainThreadAuthenticationShape extends IDisposable { $registerAuthenticationProvider(id: string): void; $unregisterAuthenticationProvider(id: string): void; $onDidChangeSessions(id: string): void; + $getSessionsPrompt(providerId: string, providerName: string, extensionId: string, extensionName: string): Promise; + $loginPrompt(providerId: string, providerName: string, extensionId: string, extensionName: string): Promise; } export interface MainThreadConfigurationShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index afde02d3918..935260096dd 100644 --- a/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -7,69 +7,90 @@ import * as vscode from 'vscode'; import * as modes from 'vs/editor/common/modes'; import { Emitter, Event } from 'vs/base/common/event'; import { IMainContext, MainContext, MainThreadAuthenticationShape, ExtHostAuthenticationShape } from 'vs/workbench/api/common/extHost.protocol'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/workbench/api/common/extHostTypes'; +import { IExtensionDescription, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -const _onDidUnregisterAuthenticationProvider = new Emitter(); -const _onDidChangeSessions = new Emitter(); +export class AuthenticationProviderWrapper implements vscode.AuthenticationProvider { + onDidChangeSessions: Event; -export class ExtHostAuthenticationProvider implements IDisposable { - constructor(private _provider: vscode.AuthenticationProvider, - private _id: string, + constructor(private _requestingExtension: IExtensionDescription, + private _provider: vscode.AuthenticationProvider, private _proxy: MainThreadAuthenticationShape) { - this._provider.onDidChangeSessions(x => { - this._proxy.$onDidChangeSessions(this._id); - _onDidChangeSessions.fire(this._id); - }); + + this.onDidChangeSessions = this._provider.onDidChangeSessions; } - getSessions(): Promise> { + get id(): string { + return this._provider.id; + } + + get displayName(): string { + return this._provider.displayName; + } + + async getSessions(): Promise> { + const isAllowed = await this._proxy.$getSessionsPrompt(this._provider.id, this.displayName, ExtensionIdentifier.toKey(this._requestingExtension.identifier), this._requestingExtension.displayName || this._requestingExtension.name); + if (!isAllowed) { + throw new Error('User did not consent to session access.'); + } + return this._provider.getSessions(); } - login(): Promise { + async login(): Promise { + const isAllowed = await this._proxy.$loginPrompt(this._provider.id, this.displayName, ExtensionIdentifier.toKey(this._requestingExtension.identifier), this._requestingExtension.displayName || this._requestingExtension.name); + if (!isAllowed) { + throw new Error('User did not consent to login.'); + } + return this._provider.login(); } logout(sessionId: string): Promise { return this._provider.logout(sessionId); } - - dispose(): void { - this._proxy.$unregisterAuthenticationProvider(this._id); - _onDidUnregisterAuthenticationProvider.fire(this._id); - } } export class ExtHostAuthentication implements ExtHostAuthenticationShape { private _proxy: MainThreadAuthenticationShape; - private _authenticationProviders: Map = new Map(); + private _authenticationProviders: Map = new Map(); private _onDidRegisterAuthenticationProvider = new Emitter(); readonly onDidRegisterAuthenticationProvider: Event = this._onDidRegisterAuthenticationProvider.event; - readonly onDidUnregisterAuthenticationProvider: Event = _onDidUnregisterAuthenticationProvider.event; - - readonly onDidChangeSessions: Event = _onDidChangeSessions.event; + private _onDidUnregisterAuthenticationProvider = new Emitter(); + readonly onDidUnregisterAuthenticationProvider: Event = this._onDidUnregisterAuthenticationProvider.event; constructor(mainContext: IMainContext) { this._proxy = mainContext.getProxy(MainContext.MainThreadAuthentication); - - this.onDidUnregisterAuthenticationProvider(providerId => { - this._authenticationProviders.delete(providerId); - }); } - registerAuthenticationProvider(provider: vscode.AuthenticationProvider) { + providers(requestingExtension: IExtensionDescription): vscode.AuthenticationProvider[] { + let providers: vscode.AuthenticationProvider[] = []; + this._authenticationProviders.forEach(provider => providers.push(new AuthenticationProviderWrapper(requestingExtension, provider, this._proxy))); + return providers; + } + + registerAuthenticationProvider(provider: vscode.AuthenticationProvider): vscode.Disposable { if (this._authenticationProviders.get(provider.id)) { throw new Error(`An authentication provider with id '${provider.id}' is already registered.`); } - const authenticationProvider = new ExtHostAuthenticationProvider(provider, provider.id, this._proxy); - this._authenticationProviders.set(provider.id, authenticationProvider); + this._authenticationProviders.set(provider.id, provider); + + const listener = provider.onDidChangeSessions(_ => { + this._proxy.$onDidChangeSessions(provider.id); + }); this._proxy.$registerAuthenticationProvider(provider.id); this._onDidRegisterAuthenticationProvider.fire(provider.id); - return authenticationProvider; + + return new Disposable(() => { + listener.dispose(); + this._authenticationProviders.delete(provider.id); + this._proxy.$unregisterAuthenticationProvider(provider.id); + this._onDidUnregisterAuthenticationProvider.fire(provider.id); + }); } $login(providerId: string): Promise {