Skip to content

Commit

Permalink
Cleanup deprecated and out-dated authentication API
Browse files Browse the repository at this point in the history
- Remove very out-dated backwards compatibility to proposed VS Code API
-- Get rid of provider id-based API
-- Remove deprecated 'hasSession' from API
-- Deprecate login/logout (now: createSession/removeSession) in Theia

Previously we were compatible with a very specific commit of the
proposed authentication API of VS Code 1.53.2. The API was moved from
its proposed state to stable with 1.54.0 to which we are fully
compatible.

Relates to #11556
  • Loading branch information
martin-fleck-at committed Aug 12, 2022
1 parent fe224bf commit 3850a44
Show file tree
Hide file tree
Showing 7 changed files with 48 additions and 300 deletions.
35 changes: 23 additions & 12 deletions packages/core/src/browser/authentication-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ export interface AuthenticationProviderInformation {

/** Should match the definition from the theia/vscode types */
export interface AuthenticationProviderAuthenticationSessionsChangeEvent {
readonly added: ReadonlyArray<AuthenticationSession | string | undefined>;
readonly removed: ReadonlyArray<AuthenticationSession | string | undefined>;
readonly changed: ReadonlyArray<AuthenticationSession | string | undefined>;
readonly added: readonly AuthenticationSession[] | undefined;
readonly removed: readonly AuthenticationSession[] | undefined;
readonly changed: readonly AuthenticationSession[] | undefined;
}

export interface SessionRequest {
Expand All @@ -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<void>;

getSessions(scopes?: string[]): Promise<ReadonlyArray<AuthenticationSession>>;

updateSessionItems(event: AuthenticationProviderAuthenticationSessionsChangeEvent): Promise<void>;

/**
* @deprecated use `createSession` instead.
*/
login(scopes: string[]): Promise<AuthenticationSession>;

/**
* @deprecated use `removeSession` instead.
*/
logout(sessionId: string): Promise<void>;

/**
Expand Down Expand Up @@ -169,15 +180,15 @@ export class AuthenticationServiceImpl implements AuthenticationService {
}

protected async handleSessionChange(changeEvent: SessionChangeEvent): Promise<void> {
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)) {
this.sessionMap.set(session.id, this.createAccountUi(changeEvent.providerId, changeEvent.label, session));
}
});
}
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();
Expand Down Expand Up @@ -400,7 +411,7 @@ export class AuthenticationServiceImpl implements AuthenticationService {
async login(id: string, scopes: string[]): Promise<AuthenticationSession> {
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.`);
}
Expand All @@ -409,7 +420,7 @@ export class AuthenticationServiceImpl implements AuthenticationService {
async logout(id: string, sessionId: string): Promise<void> {
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.`);
}
Expand Down
9 changes: 2 additions & 7 deletions packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1943,20 +1943,15 @@ export interface AuthenticationExt {
$getSessions(id: string, scopes?: string[]): Promise<ReadonlyArray<theia.AuthenticationSession>>;
$createSession(id: string, scopes: string[]): Promise<theia.AuthenticationSession>;
$removeSession(id: string, sessionId: string): Promise<void>;
$onDidChangeAuthenticationSessions(id: string, label: string): Promise<void>;
$onDidChangeAuthenticationProviders(added: theia.AuthenticationProviderInformation[], removed: theia.AuthenticationProviderInformation[]): Promise<void>;
$setProviders(providers: theia.AuthenticationProviderInformation[]): Promise<void>;
$onDidChangeAuthenticationSessions(provider: theia.AuthenticationProviderInformation): Promise<void>;
}

export interface AuthenticationMain {
$registerAuthenticationProvider(id: string, label: string, supportsMultipleAccounts: boolean): void;
$unregisterAuthenticationProvider(id: string): void;
$getProviderIds(): Promise<string[]>;
$ensureProvider(id: string): Promise<void>;
$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<theia.AuthenticationSession | undefined>;
$removeSession(providerId: string, sessionId: string): Promise<void>;
}

export interface RawColorInfo {
Expand Down
59 changes: 17 additions & 42 deletions packages/plugin-ext/src/main/browser/authentication-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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<string[]> {
return Promise.resolve(this.authenticationService.getProviderIds());
}

async $registerAuthenticationProvider(id: string, label: string, supportsMultipleAccounts: boolean): Promise<void> {
const provider = new AuthenticationProviderImpl(this.proxy, id, label, supportsMultipleAccounts, this.storageService, this.messageService);
Expand Down Expand Up @@ -230,15 +219,7 @@ export class AuthenticationMainImpl implements AuthenticationMain {
this.storageService.setData(`authentication-session-${extensionName}-${providerId}`, sessionId);
}

$ensureProvider(id: string): Promise<void> {
return this.extensionService.$activateByEvent(getAuthenticationProviderActivationEvent(id));
}

$removeSession(providerId: string, sessionId: string): Promise<void> {
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);
}
}
Expand Down Expand Up @@ -272,8 +253,10 @@ interface AccountUsage {
}

export class AuthenticationProviderImpl implements AuthenticationProvider {
private accounts = new Map<string, string[]>(); // Map account name to session ids
private sessions = new Map<string, string>(); // Map account id to name
/** map from account name to session ids */
private accounts = new Map<string, string[]>();
/** map from session id to account name */
private sessions = new Map<string, string>();

readonly onDidChangeSessions: theia.Event<theia.AuthenticationProviderAuthenticationSessionsChangeEvent>;

Expand Down Expand Up @@ -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);
}
}
Expand All @@ -325,10 +308,10 @@ export class AuthenticationProviderImpl implements AuthenticationProvider {
async updateSessionItems(event: theia.AuthenticationProviderAuthenticationSessionsChangeEvent): Promise<void> {
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) {
Expand All @@ -347,29 +330,21 @@ export class AuthenticationProviderImpl implements AuthenticationProvider {
addedSessions.forEach(s => this.registerSession(s));
}

login(scopes: string[]): Promise<theia.AuthenticationSession> {
return this.proxy.$createSession(this.id, scopes);
async login(scopes: string[]): Promise<theia.AuthenticationSession> {
return this.createSession(scopes);
}

async logout(sessionId: string): Promise<void> {
await this.proxy.$removeSession(this.id, sessionId);
this.messageService.info('Successfully signed out.');
return this.removeSession(sessionId);
}

createSession(scopes: string[]): Thenable<theia.AuthenticationSession> {
return this.login(scopes);
return this.proxy.$createSession(this.id, scopes);
}

removeSession(sessionId: string): Thenable<void> {
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.'); });
}
}

Expand Down
73 changes: 3 additions & 70 deletions packages/plugin-ext/src/plugin/authentication-ext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,32 +34,13 @@ export class AuthenticationExtImpl implements AuthenticationExt {
private proxy: AuthenticationMain;
private authenticationProviders: Map<string, theia.AuthenticationProvider> = new Map<string, theia.AuthenticationProvider>();

private _providerIds: string[] = [];

private _providers: theia.AuthenticationProviderInformation[] = [];

private onDidChangeAuthenticationProvidersEmitter = new Emitter<theia.AuthenticationProvidersChangeEvent>();
readonly onDidChangeAuthenticationProviders: Event<theia.AuthenticationProvidersChangeEvent> = this.onDidChangeAuthenticationProvidersEmitter.event;

private onDidChangeSessionsEmitter = new Emitter<theia.AuthenticationSessionsChangeEvent>();
readonly onDidChangeSessions: Event<theia.AuthenticationSessionsChangeEvent> = this.onDidChangeSessionsEmitter.event;

constructor(rpc: RPCProtocol) {
this.proxy = rpc.getProxy(PLUGIN_RPC_CONTEXT.AUTHENTICATION_MAIN);
}

getProviderIds(): Promise<ReadonlyArray<string>> {
return this.proxy.$getProviderIds();
}

get providerIds(): string[] {
return this._providerIds;
}

get providers(): ReadonlyArray<theia.AuthenticationProviderInformation> {
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<theia.AuthenticationSession>;
Expand All @@ -76,46 +57,21 @@ export class AuthenticationExtImpl implements AuthenticationExt {
return this.proxy.$getSession(providerId, scopes, extensionId, extensionName, options);
}

async logout(providerId: string, sessionId: string): Promise<void> {
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);

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);
});
}
Expand Down Expand Up @@ -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<void> {
this.onDidChangeSessionsEmitter.fire({ provider: { id, label } });
return Promise.resolve();
}

async $onDidChangeAuthenticationProviders(added: theia.AuthenticationProviderInformation[], removed: theia.AuthenticationProviderInformation[]): Promise<void> {
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<void> {
this._providers.push(...providers);
return Promise.resolve(undefined);
async $onDidChangeAuthenticationSessions(provider: theia.AuthenticationProviderInformation): Promise<void> {
this.onDidChangeSessionsEmitter.fire({ provider });
}
}
Loading

0 comments on commit 3850a44

Please sign in to comment.