Allow extensions to create multiple sessions from the same provider (#124640)
This commit is contained in:
parent
21162bfb68
commit
a18ea9c9ec
5 changed files with 163 additions and 14 deletions
|
@ -183,21 +183,15 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
|
|||
return choice === 0;
|
||||
}
|
||||
|
||||
private async setTrustedExtensionAndAccountPreference(providerId: string, accountName: string, extensionId: string, extensionName: string, sessionId: string): Promise<void> {
|
||||
this.authenticationService.updatedAllowedExtension(providerId, accountName, extensionId, extensionName, true);
|
||||
this.storageService.store(`${extensionName}-${providerId}`, sessionId, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
|
||||
}
|
||||
|
||||
private async selectSession(providerId: string, extensionId: string, extensionName: string, scopes: string[], potentialSessions: readonly modes.AuthenticationSession[], clearSessionPreference: boolean, silent: boolean): Promise<modes.AuthenticationSession | undefined> {
|
||||
if (!potentialSessions.length) {
|
||||
throw new Error('No potential sessions found');
|
||||
}
|
||||
|
||||
if (clearSessionPreference) {
|
||||
this.storageService.remove(`${extensionName}-${providerId}`, StorageScope.GLOBAL);
|
||||
this.storageService.remove(`${extensionName}-${providerId}-${scopes.join('-')}`, StorageScope.GLOBAL);
|
||||
} else {
|
||||
const existingSessionPreference = this.storageService.get(`${extensionName}-${providerId}`, StorageScope.GLOBAL);
|
||||
const existingSessionPreference = this.storageService.get(`${extensionName}-${providerId}-${scopes.join('-')}`, StorageScope.GLOBAL);
|
||||
if (existingSessionPreference) {
|
||||
const matchingSession = potentialSessions.find(session => session.id === existingSessionPreference);
|
||||
if (matchingSession) {
|
||||
|
@ -261,7 +255,8 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
|
|||
}
|
||||
|
||||
session = await this.authenticationService.createSession(providerId, scopes, true);
|
||||
await this.setTrustedExtensionAndAccountPreference(providerId, session.account.label, extensionId, extensionName, session.id);
|
||||
await this.authenticationService.updatedAllowedExtension(providerId, session.account.label, extensionId, extensionName, true);
|
||||
this.storageService.store(`${extensionName}-${providerId}-${scopes.join('-')}`, session.id, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
} else {
|
||||
await this.authenticationService.requestNewSession(providerId, scopes, extensionId, extensionName);
|
||||
}
|
||||
|
|
|
@ -68,7 +68,8 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
|||
this._inFlightRequests.set(extensionId, inFlightRequests);
|
||||
|
||||
try {
|
||||
await session;
|
||||
const s = await session;
|
||||
return s;
|
||||
} finally {
|
||||
const requestIndex = inFlightRequests.findIndex(request => request.scopes === sortedScopes);
|
||||
if (requestIndex > -1) {
|
||||
|
@ -76,8 +77,6 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
|||
this._inFlightRequests.set(extensionId, inFlightRequests);
|
||||
}
|
||||
}
|
||||
|
||||
return session;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -503,7 +503,7 @@ export class AuthenticationService extends Disposable implements IAuthentication
|
|||
this.updatedAllowedExtension(providerId, accountName, extensionId, extensionName, true);
|
||||
|
||||
this.removeAccessRequest(providerId, extensionId);
|
||||
this.storageService.store(`${extensionName}-${providerId}`, session.id, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
this.storageService.store(`${extensionName}-${providerId}-${scopes.join('-')}`, session.id, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
|
||||
quickPick.dispose();
|
||||
resolve(session);
|
||||
|
@ -638,7 +638,7 @@ export class AuthenticationService extends Disposable implements IAuthentication
|
|||
this.updatedAllowedExtension(providerId, session.account.label, extensionId, extensionName, true);
|
||||
|
||||
// And also set it as the preferred account for the extension
|
||||
storageService.store(`${extensionName}-${providerId}`, session.id, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
storageService.store(`${extensionName}-${providerId}-${scopes.join('-')}`, session.id, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { AuthenticationProviderInformation } from 'vs/editor/common/modes';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService';
|
||||
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
|
||||
import { IQuickInputHideEvent, IQuickInputService, IQuickPickDidAcceptEvent } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { MainThreadAuthentication } from 'vs/workbench/api/browser/mainThreadAuthentication';
|
||||
import { IExtHostContext } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { IActivityService } from 'vs/workbench/services/activity/common/activity';
|
||||
import { AuthenticationService, IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService';
|
||||
import { ExtensionHostKind, IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { TestRemoteAgentService } from 'vs/workbench/services/remote/test/common/testServices';
|
||||
import { TestQuickInputService } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||
import { TestActivityService, TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
|
||||
|
||||
function createSession(id: string = '1234', scope: string[] = []) {
|
||||
return {
|
||||
accessToken: '1234',
|
||||
account: {
|
||||
id: 'test@test.com',
|
||||
label: 'Test Person'
|
||||
},
|
||||
id: id,
|
||||
scopes: scope
|
||||
};
|
||||
}
|
||||
|
||||
class AuthQuickPick {
|
||||
private listener: ((e: IQuickPickDidAcceptEvent) => any) | undefined;
|
||||
public items = [];
|
||||
public get selectedItems(): string[] {
|
||||
return this.items;
|
||||
}
|
||||
|
||||
onDidAccept(listener: (e: IQuickPickDidAcceptEvent) => any) {
|
||||
this.listener = listener;
|
||||
}
|
||||
onDidHide(listener: (e: IQuickInputHideEvent) => any) {
|
||||
|
||||
}
|
||||
dispose() {
|
||||
|
||||
}
|
||||
show() {
|
||||
this.listener!({
|
||||
inBackground: false
|
||||
});
|
||||
}
|
||||
}
|
||||
class AuthTestQuickInputService extends TestQuickInputService {
|
||||
override createQuickPick() {
|
||||
return <any>new AuthQuickPick();
|
||||
}
|
||||
}
|
||||
|
||||
suite('MainThreadAuthentication', () => {
|
||||
let mainThreadAuthentication: MainThreadAuthentication;
|
||||
suiteSetup(async () => {
|
||||
// extHostContext: IExtHostContext,
|
||||
const services = new ServiceCollection();
|
||||
const dialogService = new TestDialogService();
|
||||
const storageService = new TestStorageService();
|
||||
const quickInputService = new AuthTestQuickInputService();
|
||||
const extensionService = new TestExtensionService();
|
||||
|
||||
const activityService = new TestActivityService();
|
||||
const remoteAgentService = new TestRemoteAgentService();
|
||||
|
||||
services.set(IDialogService, dialogService);
|
||||
services.set(IStorageService, storageService);
|
||||
services.set(INotificationService, new TestNotificationService());
|
||||
services.set(IQuickInputService, quickInputService);
|
||||
services.set(IExtensionService, extensionService);
|
||||
services.set(IActivityService, activityService);
|
||||
services.set(IRemoteAgentService, remoteAgentService);
|
||||
|
||||
const instaService = new InstantiationService(services);
|
||||
services.set(IAuthenticationService, instaService.createInstance(AuthenticationService));
|
||||
|
||||
mainThreadAuthentication = instaService.createInstance(MainThreadAuthentication,
|
||||
new class implements IExtHostContext {
|
||||
remoteAuthority = '';
|
||||
extensionHostKind = ExtensionHostKind.LocalProcess;
|
||||
assertRegistered() { }
|
||||
set(v: any): any { return null; }
|
||||
getProxy(): any {
|
||||
return {
|
||||
$getSessions(id: string, scopes: string[]) {
|
||||
return Promise.resolve([createSession(id, scopes)]);
|
||||
},
|
||||
$createSession(id: string, scopes: string[]) {
|
||||
return Promise.resolve(createSession(id, scopes));
|
||||
},
|
||||
$removeSession(id: string, sessionId: string) { return Promise.resolve(); },
|
||||
$onDidChangeAuthenticationSessions(id: string, label: string) { return Promise.resolve(); },
|
||||
$onDidChangeAuthenticationProviders(added: AuthenticationProviderInformation[], removed: AuthenticationProviderInformation[]) { return Promise.resolve(); },
|
||||
$setProviders(providers: AuthenticationProviderInformation[]) { return Promise.resolve(); }
|
||||
};
|
||||
}
|
||||
drain(): any { return null; }
|
||||
});
|
||||
|
||||
await mainThreadAuthentication.$registerAuthenticationProvider('test', 'test provider', true);
|
||||
});
|
||||
|
||||
suiteTeardown(() => {
|
||||
mainThreadAuthentication.$unregisterAuthenticationProvider('test');
|
||||
});
|
||||
|
||||
test('Can get a session', async () => {
|
||||
const session = await mainThreadAuthentication.$getSession('test', ['foo'], 'testextension', 'test extension', { createIfNone: true, clearSessionPreference: false });
|
||||
assert.strictEqual(session?.id, 'test');
|
||||
assert.strictEqual(session?.scopes[0], 'foo');
|
||||
});
|
||||
|
||||
test('Can get multiple sessions (with different scopes) in one extension', async () => {
|
||||
let session = await mainThreadAuthentication.$getSession('test', ['foo'], 'testextension', 'test extension', { createIfNone: true, clearSessionPreference: false });
|
||||
session = await mainThreadAuthentication.$getSession('test', ['bar'], 'testextension', 'test extension', { createIfNone: true, clearSessionPreference: false });
|
||||
assert.strictEqual(session?.id, 'test');
|
||||
assert.strictEqual(session?.scopes[0], 'bar');
|
||||
|
||||
session = await mainThreadAuthentication.$getSession('test', ['foo'], 'testextension', 'test extension', { createIfNone: false, clearSessionPreference: false });
|
||||
assert.strictEqual(session?.id, 'test');
|
||||
assert.strictEqual(session?.scopes[0], 'foo');
|
||||
});
|
||||
});
|
|
@ -22,6 +22,7 @@ import { IFileStatWithMetadata } from 'vs/platform/files/common/files';
|
|||
import { ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { IActivity, IActivityService } from 'vs/workbench/services/activity/common/activity';
|
||||
|
||||
export class TestTextResourcePropertiesService implements ITextResourcePropertiesService {
|
||||
|
||||
|
@ -214,3 +215,21 @@ export interface Ctor<T> {
|
|||
export class TestExtensionService extends NullExtensionService { }
|
||||
|
||||
export const TestProductService = { _serviceBrand: undefined, ...product };
|
||||
|
||||
export class TestActivityService implements IActivityService {
|
||||
_serviceBrand: undefined;
|
||||
showViewContainerActivity(viewContainerId: string, badge: IActivity): IDisposable {
|
||||
return this;
|
||||
}
|
||||
showViewActivity(viewId: string, badge: IActivity): IDisposable {
|
||||
return this;
|
||||
}
|
||||
showAccountsActivity(activity: IActivity): IDisposable {
|
||||
return this;
|
||||
}
|
||||
showGlobalActivity(activity: IActivity): IDisposable {
|
||||
return this;
|
||||
}
|
||||
|
||||
dispose() { }
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue