diff --git a/packages/core/src/browser/authentication-service.ts b/packages/core/src/browser/authentication-service.ts index c47af042a4ae0..2572a4d0a1e61 100644 --- a/packages/core/src/browser/authentication-service.ts +++ b/packages/core/src/browser/authentication-service.ts @@ -47,9 +47,9 @@ export interface AuthenticationProviderInformation { /** Should match the definition from the theia/vscode types */ export interface AuthenticationProviderAuthenticationSessionsChangeEvent { - readonly added: ReadonlyArray; - readonly removed: ReadonlyArray; - readonly changed: ReadonlyArray; + readonly added: readonly AuthenticationSession[] | undefined; + readonly removed: readonly AuthenticationSession[] | undefined; + readonly changed: readonly AuthenticationSession[] | undefined; } export interface SessionRequest { @@ -61,24 +61,35 @@ export interface SessionRequestInfo { [scopes: string]: SessionRequest; } -/** Should match the definition from the theia/vscode types */ +/** + * Our authentication provider should at least contain the following information: + * - The signuature of authentication providers from vscode + * - Registration information about the provider (id, label) + * - Provider options (supportsMultipleAccounts) + * + * Additionally, we provide the possibility to sign out of a specific account name. + */ export interface AuthenticationProvider { id: string; - supportsMultipleAccounts: boolean; - label: string; + supportsMultipleAccounts: boolean; + hasSessions(): boolean; signOut(accountName: string): Promise; - getSessions(scopes?: string[]): Promise>; - updateSessionItems(event: AuthenticationProviderAuthenticationSessionsChangeEvent): Promise; + /** + * @deprecated use `createSession` instead. + */ login(scopes: string[]): Promise; + /** + * @deprecated use `removeSession` instead. + */ logout(sessionId: string): Promise; /** @@ -169,7 +180,7 @@ export class AuthenticationServiceImpl implements AuthenticationService { } protected async handleSessionChange(changeEvent: SessionChangeEvent): Promise { - if (changeEvent.event.added.length > 0) { + if (changeEvent.event.added && changeEvent.event.added.length > 0) { const sessions = await this.getSessions(changeEvent.providerId); sessions.forEach(session => { if (!this.sessionMap.get(session.id)) { @@ -177,7 +188,7 @@ export class AuthenticationServiceImpl implements AuthenticationService { } }); } - for (const removed of changeEvent.event.removed) { + for (const removed of changeEvent.event.removed || []) { const sessionId = typeof removed === 'string' ? removed : removed?.id; if (sessionId) { this.sessionMap.get(sessionId)?.dispose(); @@ -400,7 +411,7 @@ export class AuthenticationServiceImpl implements AuthenticationService { async login(id: string, scopes: string[]): Promise { const authProvider = this.authenticationProviders.get(id); if (authProvider) { - return authProvider.login(scopes); + return authProvider.createSession(scopes); } else { throw new Error(`No authentication provider '${id}' is currently registered.`); } @@ -409,7 +420,7 @@ export class AuthenticationServiceImpl implements AuthenticationService { async logout(id: string, sessionId: string): Promise { const authProvider = this.authenticationProviders.get(id); if (authProvider) { - return authProvider.logout(sessionId); + return authProvider.removeSession(sessionId); } else { throw new Error(`No authentication provider '${id}' is currently registered.`); } diff --git a/packages/plugin-ext/src/common/plugin-api-rpc.ts b/packages/plugin-ext/src/common/plugin-api-rpc.ts index f46c2c0a31f3f..83c59b66091d7 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -1943,20 +1943,15 @@ export interface AuthenticationExt { $getSessions(id: string, scopes?: string[]): Promise>; $createSession(id: string, scopes: string[]): Promise; $removeSession(id: string, sessionId: string): Promise; - $onDidChangeAuthenticationSessions(id: string, label: string): Promise; - $onDidChangeAuthenticationProviders(added: theia.AuthenticationProviderInformation[], removed: theia.AuthenticationProviderInformation[]): Promise; - $setProviders(providers: theia.AuthenticationProviderInformation[]): Promise; + $onDidChangeAuthenticationSessions(provider: theia.AuthenticationProviderInformation): Promise; } export interface AuthenticationMain { $registerAuthenticationProvider(id: string, label: string, supportsMultipleAccounts: boolean): void; $unregisterAuthenticationProvider(id: string): void; - $getProviderIds(): Promise; - $ensureProvider(id: string): Promise; - $sendDidChangeSessions(providerId: string, event: AuthenticationProviderAuthenticationSessionsChangeEvent): void; + $onDidChangeSessions(providerId: string, event: AuthenticationProviderAuthenticationSessionsChangeEvent): void; $getSession(providerId: string, scopes: readonly string[], extensionId: string, extensionName: string, options: theia.AuthenticationGetSessionOptions): Promise; - $removeSession(providerId: string, sessionId: string): Promise; } export interface RawColorInfo { diff --git a/packages/plugin-ext/src/main/browser/authentication-main.ts b/packages/plugin-ext/src/main/browser/authentication-main.ts index 171d7238cc535..1dee9c1f062ef 100644 --- a/packages/plugin-ext/src/main/browser/authentication-main.ts +++ b/packages/plugin-ext/src/main/browser/authentication-main.ts @@ -21,7 +21,7 @@ // code copied and modified from https://github.com/microsoft/vscode/blob/1.47.3/src/vs/workbench/api/browser/mainThreadAuthentication.ts import { interfaces } from '@theia/core/shared/inversify'; -import { AuthenticationExt, AuthenticationMain, PluginManagerExt, MAIN_RPC_CONTEXT } from '../../common/plugin-api-rpc'; +import { AuthenticationExt, AuthenticationMain, MAIN_RPC_CONTEXT } from '../../common/plugin-api-rpc'; import { RPCProtocol } from '../../common/rpc-protocol'; import { MessageService } from '@theia/core/lib/common/message-service'; import { Dialog, StorageService } from '@theia/core/lib/browser'; @@ -43,28 +43,17 @@ export class AuthenticationMainImpl implements AuthenticationMain { private readonly storageService: StorageService; private readonly authenticationService: AuthenticationService; private readonly quickPickService: QuickPickService; - private readonly extensionService: PluginManagerExt; constructor(rpc: RPCProtocol, container: interfaces.Container) { this.proxy = rpc.getProxy(MAIN_RPC_CONTEXT.AUTHENTICATION_EXT); this.messageService = container.get(MessageService); this.storageService = container.get(StorageService); this.authenticationService = container.get(AuthenticationService); this.quickPickService = container.get(QuickPickService); - this.extensionService = rpc.getProxy(MAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT); this.authenticationService.onDidChangeSessions(e => { - this.proxy.$onDidChangeAuthenticationSessions(e.providerId, e.label); - }); - this.authenticationService.onDidRegisterAuthenticationProvider(info => { - this.proxy.$onDidChangeAuthenticationProviders([info], []); - }); - this.authenticationService.onDidUnregisterAuthenticationProvider(providerId => { - this.proxy.$onDidChangeAuthenticationProviders([], [providerId]); + this.proxy.$onDidChangeAuthenticationSessions({ id: e.providerId, label: e.label }); }); } - $getProviderIds(): Promise { - return Promise.resolve(this.authenticationService.getProviderIds()); - } async $registerAuthenticationProvider(id: string, label: string, supportsMultipleAccounts: boolean): Promise { const provider = new AuthenticationProviderImpl(this.proxy, id, label, supportsMultipleAccounts, this.storageService, this.messageService); @@ -230,15 +219,7 @@ export class AuthenticationMainImpl implements AuthenticationMain { this.storageService.setData(`authentication-session-${extensionName}-${providerId}`, sessionId); } - $ensureProvider(id: string): Promise { - return this.extensionService.$activateByEvent(getAuthenticationProviderActivationEvent(id)); - } - - $removeSession(providerId: string, sessionId: string): Promise { - return this.authenticationService.logout(providerId, sessionId); - } - - $sendDidChangeSessions(providerId: string, event: theia.AuthenticationProviderAuthenticationSessionsChangeEvent): void { + $onDidChangeSessions(providerId: string, event: theia.AuthenticationProviderAuthenticationSessionsChangeEvent): void { this.authenticationService.updateSessions(providerId, event); } } @@ -272,8 +253,10 @@ interface AccountUsage { } export class AuthenticationProviderImpl implements AuthenticationProvider { - private accounts = new Map(); // Map account name to session ids - private sessions = new Map(); // Map account id to name + /** map from account name to session ids */ + private accounts = new Map(); + /** map from session id to account name */ + private sessions = new Map(); readonly onDidChangeSessions: theia.Event; @@ -313,7 +296,7 @@ export class AuthenticationProviderImpl implements AuthenticationProvider { Dialog.CANCEL); if (result && result === nls.localizeByDefault('Sign Out') && sessionsForAccount) { - sessionsForAccount.forEach(sessionId => this.logout(sessionId)); + sessionsForAccount.forEach(sessionId => this.removeSession(sessionId)); removeAccountUsage(this.storageService, this.id, accountName); } } @@ -325,10 +308,10 @@ export class AuthenticationProviderImpl implements AuthenticationProvider { async updateSessionItems(event: theia.AuthenticationProviderAuthenticationSessionsChangeEvent): Promise { const { added, removed } = event; const session = await this.proxy.$getSessions(this.id); - const addedSessions = session.filter(s => added.some(addedSession => this.getSessionId(addedSession) === s.id)); + const addedSessions = added ? session.filter(s => added.some(addedSession => addedSession.id === s.id)) : []; - removed.forEach(removedSession => { - const sessionId = this.getSessionId(removedSession); + removed?.forEach(removedSession => { + const sessionId = removedSession.id; if (sessionId) { const accountName = this.sessions.get(sessionId); if (accountName) { @@ -347,29 +330,21 @@ export class AuthenticationProviderImpl implements AuthenticationProvider { addedSessions.forEach(s => this.registerSession(s)); } - login(scopes: string[]): Promise { - return this.proxy.$createSession(this.id, scopes); + async login(scopes: string[]): Promise { + return this.createSession(scopes); } async logout(sessionId: string): Promise { - await this.proxy.$removeSession(this.id, sessionId); - this.messageService.info('Successfully signed out.'); + return this.removeSession(sessionId); } createSession(scopes: string[]): Thenable { - return this.login(scopes); + return this.proxy.$createSession(this.id, scopes); } removeSession(sessionId: string): Thenable { - return this.logout(sessionId); - } - - // utility method to be backwards compatible with the old AuthenticationProviderAuthenticationSessionsChangeEvent containing only the session id string - private getSessionId(obj: string | theia.AuthenticationSession | undefined): string | undefined { - if (!obj || typeof obj === 'string') { - return obj; - } - return obj.id; + return this.proxy.$removeSession(this.id, sessionId) + .then(() => { this.messageService.info('Successfully signed out.'); }); } } diff --git a/packages/plugin-ext/src/plugin/authentication-ext.ts b/packages/plugin-ext/src/plugin/authentication-ext.ts index 64e403172f9bb..63e34c214808d 100644 --- a/packages/plugin-ext/src/plugin/authentication-ext.ts +++ b/packages/plugin-ext/src/plugin/authentication-ext.ts @@ -34,13 +34,6 @@ export class AuthenticationExtImpl implements AuthenticationExt { private proxy: AuthenticationMain; private authenticationProviders: Map = new Map(); - private _providerIds: string[] = []; - - private _providers: theia.AuthenticationProviderInformation[] = []; - - private onDidChangeAuthenticationProvidersEmitter = new Emitter(); - readonly onDidChangeAuthenticationProviders: Event = this.onDidChangeAuthenticationProvidersEmitter.event; - private onDidChangeSessionsEmitter = new Emitter(); readonly onDidChangeSessions: Event = this.onDidChangeSessionsEmitter.event; @@ -48,18 +41,6 @@ export class AuthenticationExtImpl implements AuthenticationExt { this.proxy = rpc.getProxy(PLUGIN_RPC_CONTEXT.AUTHENTICATION_MAIN); } - getProviderIds(): Promise> { - return this.proxy.$getProviderIds(); - } - - get providerIds(): string[] { - return this._providerIds; - } - - get providers(): ReadonlyArray { - return Object.freeze(this._providers.slice()); - } - async getSession(requestingExtension: InternalPlugin, providerId: string, scopes: readonly string[], options: theia.AuthenticationGetSessionOptions & ({ createIfNone: true } | { forceNewSession: true } | { forceNewSession: { detail: string } })): Promise; @@ -76,29 +57,14 @@ export class AuthenticationExtImpl implements AuthenticationExt { return this.proxy.$getSession(providerId, scopes, extensionId, extensionName, options); } - async logout(providerId: string, sessionId: string): Promise { - return this.proxy.$removeSession(providerId, sessionId); - } - registerAuthenticationProvider(id: string, label: string, provider: theia.AuthenticationProvider, options?: theia.AuthenticationProviderOptions): theia.Disposable { if (this.authenticationProviders.get(id)) { throw new Error(`An authentication provider with id '${id}' is already registered.`); } this.authenticationProviders.set(id, provider); - if (this._providerIds.indexOf(id) === -1) { - this._providerIds.push(id); - } - - if (!this._providers.find(p => p.id === id)) { - this._providers.push({ - id, - label - }); - } - const listener = provider.onDidChangeSessions(e => { - this.proxy.$sendDidChangeSessions(id, e); + this.proxy.$onDidChangeSessions(id, e); }); this.proxy.$registerAuthenticationProvider(id, label, !!options?.supportsMultipleAccounts); @@ -106,16 +72,6 @@ export class AuthenticationExtImpl implements AuthenticationExt { return new Disposable(() => { listener.dispose(); this.authenticationProviders.delete(id); - const index = this._providerIds.findIndex(pid => id === pid); - if (index > -1) { - this._providerIds.splice(index); - } - - const i = this._providers.findIndex(p => p.id === id); - if (i > -1) { - this._providers.splice(i); - } - this.proxy.$unregisterAuthenticationProvider(id); }); } @@ -163,30 +119,7 @@ export class AuthenticationExtImpl implements AuthenticationExt { throw new Error(`Unable to find authentication provider with handle: ${providerId}`); } - $onDidChangeAuthenticationSessions(id: string, label: string): Promise { - this.onDidChangeSessionsEmitter.fire({ provider: { id, label } }); - return Promise.resolve(); - } - - async $onDidChangeAuthenticationProviders(added: theia.AuthenticationProviderInformation[], removed: theia.AuthenticationProviderInformation[]): Promise { - added.forEach(id => { - if (this._providers.indexOf(id) === -1) { - this._providers.push(id); - } - }); - - removed.forEach(p => { - const index = this._providers.findIndex(provider => provider.id === p.id); - if (index > -1) { - this._providers.splice(index); - } - }); - - this.onDidChangeAuthenticationProvidersEmitter.fire({ added, removed }); - } - - $setProviders(providers: theia.AuthenticationProviderInformation[]): Promise { - this._providers.push(...providers); - return Promise.resolve(undefined); + async $onDidChangeAuthenticationSessions(provider: theia.AuthenticationProviderInformation): Promise { + this.onDidChangeSessionsEmitter.fire({ provider }); } } diff --git a/packages/plugin-ext/src/plugin/plugin-context.ts b/packages/plugin-ext/src/plugin/plugin-context.ts index 00fddb1f7c806..88566167e1bd5 100644 --- a/packages/plugin-ext/src/plugin/plugin-context.ts +++ b/packages/plugin-ext/src/plugin/plugin-context.ts @@ -224,50 +224,14 @@ export function createAPIFactory( return function (plugin: InternalPlugin): typeof theia { const authentication: typeof theia.authentication = { - // support older (< 1.53.0) and newer version of authentication provider registration - registerAuthenticationProvider( - id: string | theia.AuthenticationProvider, label?: string, provider?: theia.AuthenticationProvider, options?: theia.AuthenticationProviderOptions): - theia.Disposable { - // collect registration data based on registration type: new (all parameters given) vs old (data stored in provider) - const isNewRegistration = typeof id === 'string'; - const regProvider = isNewRegistration ? provider! : id; - const regId = isNewRegistration ? id : regProvider.id; - const regLabel = isNewRegistration ? label! : regProvider.label; - const regOptions = isNewRegistration ? options : { supportsMultipleAccounts: regProvider.supportsMultipleAccounts }; - - // ensure that all methods of the new AuthenticationProvider are available or delegate otherwise - if (!regProvider['createSession']) { - regProvider['createSession'] = (scopes: string[]) => regProvider.login(scopes); - } - if (!regProvider['removeSession']) { - regProvider['removeSession'] = (sessionId: string) => regProvider.logout(sessionId); - } - return authenticationExt.registerAuthenticationProvider(regId, regLabel, regProvider, regOptions); - }, - get onDidChangeAuthenticationProviders(): theia.Event { - return authenticationExt.onDidChangeAuthenticationProviders; - }, - getProviderIds(): Thenable> { - return Promise.resolve(authenticationExt.providerIds); - }, - get providerIds(): string[] { - return authenticationExt.providerIds; - }, - get providers(): ReadonlyArray { - return authenticationExt.providers; + registerAuthenticationProvider(id: string, label: string, provider: theia.AuthenticationProvider, options?: theia.AuthenticationProviderOptions): theia.Disposable { + return authenticationExt.registerAuthenticationProvider(id, label, provider, options); }, getSession(providerId: string, scopes: string[], options: theia.AuthenticationGetSessionOptions) { return authenticationExt.getSession(plugin, providerId, scopes, options as any); }, - logout(providerId: string, sessionId: string): Thenable { - return authenticationExt.logout(providerId, sessionId); - }, get onDidChangeSessions(): theia.Event { return authenticationExt.onDidChangeSessions; - }, - async hasSession(providerId: string, scopes: readonly string[]): Promise { - const session = await authenticationExt.getSession(plugin, providerId, scopes, { silent: true }); - return !!session; } }; const commands: typeof theia.commands = { diff --git a/packages/plugin/src/theia-proposed.d.ts b/packages/plugin/src/theia-proposed.d.ts index 367c0079ec1f8..509e548003203 100644 --- a/packages/plugin/src/theia-proposed.d.ts +++ b/packages/plugin/src/theia-proposed.d.ts @@ -21,130 +21,6 @@ * These API are NOT stable and subject to change. Use it on own risk. */ export module '@theia/plugin' { - // #region auth provider - - /** - * An [event](#Event) which fires when an [AuthenticationProvider](#AuthenticationProvider) is added or removed. - */ - export interface AuthenticationProvidersChangeEvent { - /** - * The ids of the [authenticationProvider](#AuthenticationProvider)s that have been added. - */ - readonly added: ReadonlyArray; - - /** - * The ids of the [authenticationProvider](#AuthenticationProvider)s that have been removed. - */ - readonly removed: ReadonlyArray; - } - - /** - * An [event](#Event) which fires when an [AuthenticationSession](#AuthenticationSession) is added, removed, or changed. - */ - export interface AuthenticationProviderAuthenticationSessionsChangeEvent { - /** - * The ids of the [AuthenticationSession](#AuthenticationSession)s that have been added. - */ - readonly added: ReadonlyArray; - - /** - * The ids of the [AuthenticationSession](#AuthenticationSession)s that have been removed. - */ - readonly removed: ReadonlyArray; - - /** - * The ids of the [AuthenticationSession](#AuthenticationSession)s that have been changed. - */ - readonly changed: ReadonlyArray; - } - - /** - * **WARNING** When writing an AuthenticationProvider, `id` should be treated as part of your extension's - * API, changing it is a breaking change for all extensions relying on the provider. The id is - * treated case-sensitively. - */ - export interface AuthenticationProvider { - /** - * Used as an identifier for extensions trying to work with a particular - * provider: 'microsoft', 'github', etc. id must be unique, registering - * another provider with the same id will fail. - */ - readonly id: string; - - /** - * The human-readable name of the provider. - */ - readonly label: string; - - /** - * Whether it is possible to be signed into multiple accounts at once with this provider - */ - readonly supportsMultipleAccounts: boolean; - - /** - * Returns an array of current sessions. - */ - getSessions(): Thenable>; - - /** - * Prompts a user to login. - */ - login(scopes: string[]): Thenable; - - /** - * Removes the session corresponding to session id. - * @param sessionId The session id to log out of - */ - logout(sessionId: string): Thenable; - } - - export namespace authentication { - /** - * Register an authentication provider. - * - * There can only be one provider per id and an error is being thrown when an id - * has already been used by another provider. - * - * @param provider The authentication provider provider. - * @return A [disposable](#Disposable) that unregisters this provider when being disposed. - */ - export function registerAuthenticationProvider(provider: AuthenticationProvider): Disposable; - - /** - * Fires with the provider id that was registered or unregistered. - */ - export const onDidChangeAuthenticationProviders: Event; - - /** - * @deprecated - * The ids of the currently registered authentication providers. - * @returns An array of the ids of authentication providers that are currently registered. - */ - export function getProviderIds(): Thenable>; - - /** - * @deprecated - * An array of the ids of authentication providers that are currently registered. - */ - export const providerIds: ReadonlyArray; - - /** - * An array of the information of authentication providers that are currently registered. - */ - export const providers: ReadonlyArray; - - /** - * @deprecated - * Logout of a specific session. - * @param providerId The id of the provider to use - * @param sessionId The session id to remove - * provider - */ - export function logout(providerId: string, sessionId: string): Thenable; - } - - // #endregion - /** * The contiguous set of modified lines in a diff. */ diff --git a/packages/plugin/src/theia.d.ts b/packages/plugin/src/theia.d.ts index 825a44b33a0fc..f80aca2ac1b6a 100644 --- a/packages/plugin/src/theia.d.ts +++ b/packages/plugin/src/theia.d.ts @@ -11815,7 +11815,7 @@ export module '@theia/plugin' { * The permissions granted by the session's access token. Available scopes * are defined by the [AuthenticationProvider](#AuthenticationProvider). */ - readonly scopes: ReadonlyArray; + readonly scopes: readonly string[]; } /** @@ -12050,12 +12050,6 @@ export module '@theia/plugin' { * @return A {@link Disposable} that unregisters this provider when being disposed. */ export function registerAuthenticationProvider(id: string, label: string, provider: AuthenticationProvider, options?: AuthenticationProviderOptions): Disposable; - - /** - * @deprecated Use {@link getSession()} {@link AuthenticationGetSessionOptions.silent} instead. - */ - export function hasSession(providerId: string, scopes: readonly string[]): Thenable; - } }