Skip to content

Commit

Permalink
fixup! Apply vscode Authentication plugin API
Browse files Browse the repository at this point in the history
  • Loading branch information
vinokurig committed Sep 4, 2020
1 parent 407e6ff commit d5ab07b
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 65 deletions.
7 changes: 5 additions & 2 deletions packages/core/src/browser/authentication-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,8 @@ export class AuthenticationServiceImpl implements AuthenticationService {
this.authenticationProviders.delete(id);
this.onDidUnregisterAuthenticationProviderEmitter.fire({ id, label: provider.label });
this.updateAccountsMenuItem();
console.log(`An authentication provider with id '${id}' was unregistered.`);
} else {
console.error(`Failed to unregister an authentication provider. A provider with id '${id}' was not found.`);
}
}

Expand All @@ -230,6 +231,8 @@ export class AuthenticationServiceImpl implements AuthenticationService {
if (event.added) {
await this.updateNewSessionRequests(provider);
}
} else {
console.error(`Failed to update an authentication session. An authentication provider with id '${id}' was not found.`);
}
}

Expand Down Expand Up @@ -346,7 +349,7 @@ export class AuthenticationServiceImpl implements AuthenticationService {
async getSessions(id: string): Promise<ReadonlyArray<AuthenticationSession>> {
const authProvider = this.authenticationProviders.get(id);
if (authProvider) {
return await authProvider.getSessions();
return authProvider.getSessions();
} 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 @@ -1514,13 +1514,8 @@ export interface AuthenticationMain {
$unregisterAuthenticationProvider(id: string): void;
$getProviderIds(): Promise<string[]>;
$updateSessions(providerId: string, event: AuthenticationSessionsChangeEvent): void;
$selectSession(providerId: string, providerName: string, extensionId: string, extensionName: string,
potentialSessions: AuthenticationSession[], scopes: string[], clearSessionPreference: boolean): Promise<AuthenticationSession>;
$getSessionsPrompt(providerId: string, accountName: string, providerName: string, extensionId: string, extensionName: string): Promise<boolean>;
$loginPrompt(providerName: string, extensionName: string): Promise<boolean>;
$setTrustedExtensionAndAccountPreference(providerId: string, accountName: string, extensionId: string, extensionName: string, sessionId: string): Promise<void>;
$requestNewSession(providerId: string, scopes: string[], extensionId: string, extensionName: string): Promise<void>;

$getSession(providerId: string, scopes: string[], extensionId: string, extensionName: string,
options: { createIfNone?: boolean, clearSessionPreference?: boolean }): Promise<theia.AuthenticationSession | undefined>;
$logout(providerId: string, sessionId: string): Promise<void>;
}

Expand Down
51 changes: 44 additions & 7 deletions packages/plugin-ext/src/main/browser/authentication-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,49 @@ export class AuthenticationMainImpl implements AuthenticationMain {
return this.authenticationService.logout(providerId, sessionId);
}

async $requestNewSession(providerId: string, scopes: string[], extensionId: string, extensionName: string): Promise<void> {
protected async requestNewSession(providerId: string, scopes: string[], extensionId: string, extensionName: string): Promise<void> {
return this.authenticationService.requestNewSession(providerId, scopes, extensionId, extensionName);
}

async $selectSession(providerId: string, providerName: string, extensionId: string, extensionName: string,
potentialSessions: AuthenticationSession[], scopes: string[], clearSessionPreference: boolean): Promise<AuthenticationSession> {
async $getSession(providerId: string, scopes: string[], extensionId: string, extensionName: string,
options: { createIfNone: boolean, clearSessionPreference: boolean }): Promise<AuthenticationSession | undefined> {
const orderedScopes = scopes.sort().join(' ');
const sessions = (await this.authenticationService.getSessions(providerId)).filter(session => session.scopes.slice().sort().join(' ') === orderedScopes);
const label = this.authenticationService.getLabel(providerId);

if (sessions.length) {
if (!this.authenticationService.supportsMultipleAccounts(providerId)) {
const session = sessions[0];
const allowed = await this.getSessionsPrompt(providerId, session.account.label, label, extensionId, extensionName);
if (allowed) {
return session;
} else {
throw new Error('User did not consent to login.');
}
}

// On renderer side, confirm consent, ask user to choose between accounts if multiple sessions are valid
const selected = await this.selectSession(providerId, label, extensionId, extensionName, sessions, scopes, !!options.clearSessionPreference);
return sessions.find(session => session.id === selected.id);
} else {
if (options.createIfNone) {
const isAllowed = await this.loginPrompt(label, extensionName);
if (!isAllowed) {
throw new Error('User did not consent to login.');
}

const session = await this.authenticationService.login(providerId, scopes);
await this.setTrustedExtensionAndAccountPreference(providerId, session.account.label, extensionId, extensionName, session.id);
return session;
} else {
await this.requestNewSession(providerId, scopes, extensionId, extensionName);
return undefined;
}
}
}

protected async selectSession(providerId: string, providerName: string, extensionId: string, extensionName: string,
potentialSessions: AuthenticationSession[], scopes: string[], clearSessionPreference: boolean): Promise<AuthenticationSession> {
if (!potentialSessions.length) {
throw new Error('No potential sessions found');
}
Expand All @@ -97,7 +134,7 @@ export class AuthenticationMainImpl implements AuthenticationMain {
if (existingSessionPreference) {
const matchingSession = potentialSessions.find(session => session.id === existingSessionPreference);
if (matchingSession) {
const allowed = await this.$getSessionsPrompt(providerId, matchingSession.account.label, providerName, extensionId, extensionName);
const allowed = await this.getSessionsPrompt(providerId, matchingSession.account.label, providerName, extensionId, extensionName);
if (allowed) {
return matchingSession;
}
Expand Down Expand Up @@ -139,7 +176,7 @@ export class AuthenticationMainImpl implements AuthenticationMain {
});
}

async $getSessionsPrompt(providerId: string, accountName: string, providerName: string, extensionId: string, extensionName: string): Promise<boolean> {
protected async getSessionsPrompt(providerId: string, accountName: string, providerName: string, extensionId: string, extensionName: string): Promise<boolean> {
const allowList = await readAllowedExtensions(this.storageService, providerId, accountName);
const extensionData = allowList.find(extension => extension.id === extensionId);
if (extensionData) {
Expand All @@ -158,12 +195,12 @@ export class AuthenticationMainImpl implements AuthenticationMain {
return allow;
}

async $loginPrompt(providerName: string, extensionName: string): Promise<boolean> {
protected async loginPrompt(providerName: string, extensionName: string): Promise<boolean> {
const choice = await this.messageService.info(`The extension '${extensionName}' wants to sign in using ${providerName}.`, 'Allow', 'Cancel');
return choice === 'Allow';
}

async $setTrustedExtensionAndAccountPreference(providerId: string, accountName: string, extensionId: string, extensionName: string, sessionId: string): Promise<void> {
protected async setTrustedExtensionAndAccountPreference(providerId: string, accountName: string, extensionId: string, extensionName: string, sessionId: string): Promise<void> {
const allowList = await readAllowedExtensions(this.storageService, providerId, accountName);
if (!allowList.find(allowed => allowed.id === extensionId)) {
allowList.push({ id: extensionId, name: extensionName });
Expand Down
69 changes: 22 additions & 47 deletions packages/plugin-ext/src/plugin/authentication-ext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
import { RPCProtocol } from '../common/rpc-protocol';
import { Emitter, Event } from '@theia/core/lib/common/event';
import * as theia from '@theia/plugin';
import { AuthenticationSessionsChangeEvent } from '../common/plugin-api-rpc-model';
import { AuthenticationSession, AuthenticationSessionsChangeEvent } from '../common/plugin-api-rpc-model';

export class AuthenticationExtImpl implements AuthenticationExt {
private proxy: AuthenticationMain;
Expand Down Expand Up @@ -65,55 +65,14 @@ export class AuthenticationExtImpl implements AuthenticationExt {
options: theia.AuthenticationGetSessionOptions & { createIfNone: true }): Promise<theia.AuthenticationSession>;
async getSession(requestingExtension: InternalPlugin, providerId: string, scopes: string[],
options: theia.AuthenticationGetSessionOptions = {}): Promise<theia.AuthenticationSession | undefined> {
const provider = this.authenticationProviders.get(providerId);
const extensionName = requestingExtension.model.displayName || requestingExtension.model.name;
const extensionId = requestingExtension.model.id.toLowerCase();

if (!provider) {
throw new Error(`An authentication provider with id '${providerId}' was not found.`);
}

const orderedScopes = scopes.sort().join(' ');
const sessions = (await provider.getSessions()).filter(s => s.scopes.slice().sort().join(' ') === orderedScopes);

if (sessions.length > 0) {
if (!provider.supportsMultipleAccounts) {
const session = sessions[0];
const allowed = await this.proxy.$getSessionsPrompt(providerId, session.account.label, provider.label, extensionId, extensionName);
if (allowed) {
return session;
} else {
throw new Error('User did not consent to login.');
}
}

// On renderer side, confirm consent, ask user to choose between accounts if multiple sessions are valid
const selected = await this.proxy.$selectSession(providerId, provider.label, extensionId, extensionName, sessions, scopes, !!options.clearSessionPreference);
return sessions.find(session => session.id === selected.id);
} else {
if (options.createIfNone) {
const isAllowed = await this.proxy.$loginPrompt(provider.label, extensionName);
if (!isAllowed) {
throw new Error('User did not consent to login.');
}

const session = await provider.login(scopes);
await this.proxy.$setTrustedExtensionAndAccountPreference(providerId, session.account.label, extensionId, extensionName, session.id);
return session;
} else {
await this.proxy.$requestNewSession(providerId, scopes, extensionId, extensionName);
return undefined;
}
}
return this.proxy.$getSession(providerId, scopes, extensionId, extensionName, options);
}

async logout(providerId: string, sessionId: string): Promise<void> {
const provider = this.authenticationProviders.get(providerId);
if (!provider) {
return this.proxy.$logout(providerId, sessionId);
}

return provider.logout(sessionId);
return this.proxy.$logout(providerId, sessionId);
}

registerAuthenticationProvider(provider: theia.AuthenticationProvider): theia.Disposable {
Expand Down Expand Up @@ -156,7 +115,7 @@ export class AuthenticationExtImpl implements AuthenticationExt {
});
}

$login(providerId: string, scopes: string[]): Promise<theia.AuthenticationSession> {
$login(providerId: string, scopes: string[]): Promise<AuthenticationSession> {
const authProvider = this.authenticationProviders.get(providerId);
if (authProvider) {
return Promise.resolve(authProvider.login(scopes));
Expand All @@ -174,10 +133,26 @@ export class AuthenticationExtImpl implements AuthenticationExt {
throw new Error(`Unable to find authentication provider with handle: ${providerId}`);
}

$getSessions(providerId: string): Promise<ReadonlyArray<theia.AuthenticationSession>> {
async $getSessions(providerId: string): Promise<ReadonlyArray<AuthenticationSession>> {
const authProvider = this.authenticationProviders.get(providerId);
if (authProvider) {
return Promise.resolve(authProvider.getSessions());
const sessions = await authProvider.getSessions();

/* Wrap the session object received from the plugin to prevent serialization mismatches
e.g. if the plugin object is constructed with the help of getters they won't be serialized:
class SessionImpl implements AuthenticationSession {
private _id;
get id() {
return _id;
}
...
} will translate to JSON as { _id: '<sessionid>' } not { id: '<sessionid>' } */
return sessions.map(session => ({
id: session.id,
accessToken: session.accessToken,
account: { id: session.account.id, label: session.account.label },
scopes: session.scopes
}));
}

throw new Error(`Unable to find authentication provider with handle: ${providerId}`);
Expand Down
2 changes: 0 additions & 2 deletions packages/plugin/src/theia.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9821,8 +9821,6 @@ declare module '@theia/plugin' {
* the extension. If there are multiple sessions with the same scopes, the user will be shown a
* quickpick to select which account they would like to use.
*
* Currently, there are only two authentication providers that are contributed from built in extensions
* to VS Code that implement GitHub and Microsoft authentication: their providerId's are 'github' and 'microsoft'.
* @param providerId The id of the provider to use
* @param scopes A list of scopes representing the permissions requested. These are dependent on the authentication provider
* @param options The [getSessionOptions](#GetSessionOptions) to use
Expand Down

0 comments on commit d5ab07b

Please sign in to comment.