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 Aug 31, 2020
1 parent 9c36df8 commit 43329f9
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 105 deletions.
29 changes: 17 additions & 12 deletions packages/core/src/browser/authentication-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ import { ACCOUNTS_MENU, ACCOUNTS_SUBMENU, MenuModelRegistry } from '../common/me
import { CommandRegistry } from '../common/command';
import { DisposableCollection } from '../common/disposable';

export interface AuthenticationSessionsChangeEvent {
added: ReadonlyArray<string>;
removed: ReadonlyArray<string>;
changed: ReadonlyArray<string>;
}

export interface AuthenticationSession {
id: string;
accessToken: string;
Expand All @@ -38,12 +44,6 @@ export interface AuthenticationSession {
scopes: ReadonlyArray<string>;
}

export interface AuthenticationSessionsChangeEvent {
added: ReadonlyArray<string>;
removed: ReadonlyArray<string>;
changed: ReadonlyArray<string>;
}

export interface AuthenticationProviderInformation {
id: string;
label: string;
Expand Down Expand Up @@ -85,7 +85,7 @@ export interface AuthenticationService {
registerAuthenticationProvider(id: string, provider: AuthenticationProvider): void;
unregisterAuthenticationProvider(id: string): void;
requestNewSession(id: string, scopes: string[], extensionId: string, extensionName: string): void;
sessionsUpdate(providerId: string, event: AuthenticationSessionsChangeEvent): void;
updateSessions(providerId: string, event: AuthenticationSessionsChangeEvent): void;

readonly onDidRegisterAuthenticationProvider: Event<AuthenticationProviderInformation>;
readonly onDidUnregisterAuthenticationProvider: Event<AuthenticationProviderInformation>;
Expand Down Expand Up @@ -197,10 +197,14 @@ export class AuthenticationServiceImpl implements AuthenticationService {
}

registerAuthenticationProvider(id: string, authenticationProvider: AuthenticationProvider): void {
if (this.authenticationProviders.get(id)) {
throw new Error(`An authentication provider with id '${id}' is already registered.`);
}
this.authenticationProviders.set(id, authenticationProvider);
this.onDidRegisterAuthenticationProviderEmitter.fire({ id, label: authenticationProvider.label });

this.updateAccountsMenuItem();
console.log(`An authentication provider with id '${id}' was registered.`);
}

unregisterAuthenticationProvider(id: string): void {
Expand All @@ -209,10 +213,11 @@ 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.`);
}
}

async sessionsUpdate(id: string, event: AuthenticationSessionsChangeEvent): Promise<void> {
async updateSessions(id: string, event: AuthenticationSessionsChangeEvent): Promise<void> {
const provider = this.authenticationProviders.get(id);
if (provider) {
await provider.updateSessionItems(event);
Expand Down Expand Up @@ -290,11 +295,11 @@ export class AuthenticationServiceImpl implements AuthenticationService {
const allowList = await readAllowedExtensions(this.storageService, providerId, session.account.label);
if (!allowList.find(allowed => allowed.id === extensionId)) {
allowList.push({ id: extensionId, name: extensionName });
this.storageService.setData(`${providerId}-${session.account.label}`, JSON.stringify(allowList));
this.storageService.setData(`authentication-trusted-extensions-${providerId}-${session.account.label}`, JSON.stringify(allowList));
}

// And also set it as the preferred account for the extension
this.storageService.setData(`${extensionName}-${providerId}`, session.id);
this.storageService.setData(`authentication-${extensionName}-${providerId}`, session.id);
}
});

Expand Down Expand Up @@ -380,12 +385,12 @@ export interface AllowedExtension {
export async function readAllowedExtensions(storageService: StorageService, providerId: string, accountName: string): Promise<AllowedExtension[]> {
let trustedExtensions: AllowedExtension[] = [];
try {
const trustedExtensionSrc: string | undefined = await storageService.getData(`${providerId}-${accountName}`);
const trustedExtensionSrc: string | undefined = await storageService.getData(`authentication-trusted-extensions-${providerId}-${accountName}`);
if (trustedExtensionSrc) {
trustedExtensions = JSON.parse(trustedExtensionSrc);
}
} catch (err) {
console.log(err);
console.error(err);
}

return trustedExtensions;
Expand Down
18 changes: 18 additions & 0 deletions packages/plugin-ext/src/common/plugin-api-rpc-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -540,3 +540,21 @@ export interface LinePreview {
text: string;
character: number;
}

export interface AuthenticationSession {
id: string;
accessToken: string;
account: { id: string, label: string };
scopes: ReadonlyArray<string>;
}

export interface AuthenticationSessionsChangeEvent {
added: ReadonlyArray<string>;
removed: ReadonlyArray<string>;
changed: ReadonlyArray<string>;
}

export interface AuthenticationProviderInformation {
id: string;
label: string;
}
24 changes: 9 additions & 15 deletions packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ import {
SelectionRange,
CallHierarchyDefinition,
CallHierarchyReference,
SearchInWorkspaceResult
SearchInWorkspaceResult,
AuthenticationSession,
AuthenticationSessionsChangeEvent,
AuthenticationProviderInformation
} from './plugin-api-rpc-model';
import { ExtPluginApi } from './plugin-ext-api-contribution';
import { KeysToAnyValues, KeysToKeysToAnyValue } from './types';
Expand All @@ -78,10 +81,6 @@ import { QuickTitleButton } from '@theia/core/lib/common/quick-open-model';
import * as files from '@theia/filesystem/lib/common/files';
import { BinaryBuffer } from '@theia/core/lib/common/buffer';
import { ResourceLabelFormatter } from '@theia/core/lib/common/label-protocol';
import {
AuthenticationProviderInformation,
AuthenticationSessionsChangeEvent
} from '@theia/core/lib/browser/authentication-service';

export interface PreferenceData {
[scope: number]: any;
Expand Down Expand Up @@ -1503,30 +1502,25 @@ export interface TasksMain {
}

export interface AuthenticationExt {
$getSessions(id: string): Promise<ReadonlyArray<theia.AuthenticationSession>>;
$getSessionAccessToken(id: string, sessionId: string): Promise<string>;
$login(id: string, scopes: string[]): Promise<theia.AuthenticationSession>;
$getSessions(id: string): Promise<ReadonlyArray<AuthenticationSession>>;
$login(id: string, scopes: string[]): Promise<AuthenticationSession>;
$logout(id: string, sessionId: string): Promise<void>;
$onDidChangeAuthenticationSessions(id: string, label: string, event: AuthenticationSessionsChangeEvent): Promise<void>;
$onDidChangeAuthenticationProviders(added: AuthenticationProviderInformation[], removed: theia.AuthenticationProviderInformation[]): Promise<void>;
$onDidChangeAuthenticationProviders(added: AuthenticationProviderInformation[], removed: AuthenticationProviderInformation[]): Promise<void>;
}

export interface AuthenticationMain {
$registerAuthenticationProvider(id: string, label: string, supportsMultipleAccounts: boolean): void;
$unregisterAuthenticationProvider(id: string): void;
$getProviderIds(): Promise<string[]>;
$sendDidChangeSessions(providerId: string, event: AuthenticationSessionsChangeEvent): void;
$getSession(providerId: string, scopes: string[], extensionId: string, extensionName: string,
options: { createIfNone?: boolean, clearSessionPreference?: boolean }): Promise<theia.AuthenticationSession | undefined>;
$updateSessions(providerId: string, event: AuthenticationSessionsChangeEvent): void;
$selectSession(providerId: string, providerName: string, extensionId: string, extensionName: string,
potentialSessions: theia.AuthenticationSession[], scopes: string[], clearSessionPreference: boolean): Promise<theia.AuthenticationSession>;
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>;

$getSessions(providerId: string): Promise<ReadonlyArray<theia.AuthenticationSession>>;
$login(providerId: string, scopes: string[]): Promise<theia.AuthenticationSession>;
$logout(providerId: string, sessionId: string): Promise<void>;
}

Expand Down
77 changes: 17 additions & 60 deletions packages/plugin-ext/src/main/browser/authentication-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@ import { StorageService } from '@theia/core/lib/browser';
import {
AuthenticationProvider,
AuthenticationService,
AuthenticationSession,
AuthenticationSessionsChangeEvent,
readAllowedExtensions
} from '@theia/core/lib/browser/authentication-service';
import { QuickPickItem, QuickPickService } from '@theia/core/lib/common/quick-pick-service';
import {
AuthenticationSession,
AuthenticationSessionsChangeEvent
} from '../../common/plugin-api-rpc-model';

export class AuthenticationMainImpl implements AuthenticationMain {
private readonly proxy: AuthenticationExt;
Expand Down Expand Up @@ -62,24 +64,16 @@ export class AuthenticationMainImpl implements AuthenticationMain {
}

async $registerAuthenticationProvider(id: string, label: string, supportsMultipleAccounts: boolean): Promise<void> {
const provider = new AuthenticationProviderImp(this.proxy, id, label, supportsMultipleAccounts, this.storageService, this.messageService);
const provider = new AuthenticationProviderImpl(this.proxy, id, label, supportsMultipleAccounts, this.storageService, this.messageService);
this.authenticationService.registerAuthenticationProvider(id, provider);
}

async $unregisterAuthenticationProvider(id: string): Promise<void> {
this.authenticationService.unregisterAuthenticationProvider(id);
}

async $sendDidChangeSessions(id: string, event: AuthenticationSessionsChangeEvent): Promise<void> {
this.authenticationService.sessionsUpdate(id, event);
}

$getSessions(id: string): Promise<ReadonlyArray<AuthenticationSession>> {
return this.authenticationService.getSessions(id);
}

$login(providerId: string, scopes: string[]): Promise<AuthenticationSession> {
return this.authenticationService.login(providerId, scopes);
async $updateSessions(id: string, event: AuthenticationSessionsChangeEvent): Promise<void> {
this.authenticationService.updateSessions(id, event);
}

$logout(providerId: string, sessionId: string): Promise<void> {
Expand All @@ -90,53 +84,16 @@ export class AuthenticationMainImpl implements AuthenticationMain {
return this.authenticationService.requestNewSession(providerId, scopes, extensionId, extensionName);
}

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.$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;
}
}
}

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');
}

if (clearSessionPreference) {
await this.storageService.setData(`${extensionName}-${providerId}`, undefined);
await this.storageService.setData(`authentication-session-${extensionName}-${providerId}`, undefined);
} else {
const existingSessionPreference = await this.storageService.getData(`${extensionName}-${providerId}`);
const existingSessionPreference = await this.storageService.getData(`authentication-session-${extensionName}-${providerId}`);
if (existingSessionPreference) {
const matchingSession = potentialSessions.find(session => session.id === existingSessionPreference);
if (matchingSession) {
Expand Down Expand Up @@ -169,10 +126,10 @@ export class AuthenticationMainImpl implements AuthenticationMain {
const allowList = await readAllowedExtensions(this.storageService, providerId, accountName);
if (!allowList.find(allowed => allowed.id === extensionId)) {
allowList.push({ id: extensionId, name: extensionName });
this.storageService.setData(`${providerId}-${accountName}`, JSON.stringify(allowList));
this.storageService.setData(`authentication-trusted-extensions-${providerId}-${accountName}`, JSON.stringify(allowList));
}

this.storageService.setData(`${extensionName}-${providerId}`, session.id);
this.storageService.setData(`authentication-session-${extensionName}-${providerId}`, session.id);

resolve(session);

Expand All @@ -195,7 +152,7 @@ export class AuthenticationMainImpl implements AuthenticationMain {
if (allow) {
await addAccountUsage(this.storageService, providerId, accountName, extensionId, extensionName);
allowList.push({ id: extensionId, name: extensionName });
this.storageService.setData(`${providerId}-${accountName}`, JSON.stringify(allowList));
this.storageService.setData(`authentication-trusted-extensions-${providerId}-${accountName}`, JSON.stringify(allowList));
}

return allow;
Expand All @@ -210,13 +167,13 @@ export class AuthenticationMainImpl implements AuthenticationMain {
const allowList = await readAllowedExtensions(this.storageService, providerId, accountName);
if (!allowList.find(allowed => allowed.id === extensionId)) {
allowList.push({ id: extensionId, name: extensionName });
this.storageService.setData(`${providerId}-${accountName}`, JSON.stringify(allowList));
this.storageService.setData(`authentication-trusted-extensions-${providerId}-${accountName}`, JSON.stringify(allowList));
}
}
}

async function addAccountUsage(storageService: StorageService, providerId: string, accountName: string, extensionId: string, extensionName: string): Promise<void> {
const accountKey = `${providerId}-${accountName}-usages`;
const accountKey = `authentication-${providerId}-${accountName}-usages`;
const usages = await readAccountUsages(storageService, providerId, accountName);

const existingUsageIndex = usages.findIndex(usage => usage.extensionId === extensionId);
Expand All @@ -243,7 +200,7 @@ interface AccountUsage {
lastUsed: number;
}

export class AuthenticationProviderImp implements AuthenticationProvider {
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

Expand Down Expand Up @@ -322,7 +279,7 @@ export class AuthenticationProviderImp implements AuthenticationProvider {
}

async function readAccountUsages(storageService: StorageService, providerId: string, accountName: string): Promise<AccountUsage[]> {
const accountKey = `${providerId}-${accountName}-usages`;
const accountKey = `authentication-${providerId}-${accountName}-usages`;
const storedUsages: string | undefined = await storageService.getData(accountKey);
let usages: AccountUsage[] = [];
if (storedUsages) {
Expand All @@ -337,6 +294,6 @@ async function readAccountUsages(storageService: StorageService, providerId: str
}

function removeAccountUsage(storageService: StorageService, providerId: string, accountName: string): void {
const accountKey = `${providerId}-${accountName}-usages`;
const accountKey = `authentication-${providerId}-${accountName}-usages`;
storageService.setData(accountKey, undefined);
}
Loading

0 comments on commit 43329f9

Please sign in to comment.