From 66ecd8f6a8fc78be6e128654687d227b4c9ee855 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Fri, 11 Jun 2021 17:21:37 -0700 Subject: [PATCH] make state of trusted extensions correct in storage. Fixes #118486 --- .../api/browser/mainThreadAuthentication.ts | 21 ++- .../api/mainThreadAuthentication.test.ts | 126 ++++++++++++++++++ .../test/common/workbenchTestServices.ts | 19 +++ 3 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 src/vs/workbench/test/browser/api/mainThreadAuthentication.test.ts diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index 797e8c2ba2f..c54c35799ef 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -17,6 +17,12 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { fromNow } from 'vs/base/common/date'; import { ActivationKind, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +interface TrustedExtensionsQuickPickItem { + label: string; + description: string; + extension: AllowedExtension; +} + export class MainThreadAuthenticationProvider extends Disposable { constructor( private readonly _proxy: ExtHostAuthenticationShape, @@ -38,7 +44,7 @@ export class MainThreadAuthenticationProvider extends Disposable { return; } - const quickPick = this.quickInputService.createQuickPick<{ label: string, description: string, extension: AllowedExtension }>(); + const quickPick = this.quickInputService.createQuickPick(); quickPick.canSelectMany = true; quickPick.customButton = true; quickPick.customLabel = nls.localize('manageTrustedExtensions.cancel', 'Cancel'); @@ -60,12 +66,23 @@ export class MainThreadAuthenticationProvider extends Disposable { quickPick.placeholder = nls.localize('manageExensions', "Choose which extensions can access this account"); quickPick.onDidAccept(() => { - const updatedAllowedList = quickPick.selectedItems.map(item => item.extension); + const updatedAllowedList = quickPick.items + .map(i => (i as TrustedExtensionsQuickPickItem).extension); this.storageService.store(`${this.id}-${accountName}`, JSON.stringify(updatedAllowedList), StorageScope.GLOBAL, StorageTarget.USER); quickPick.dispose(); }); + quickPick.onDidChangeSelection((changed) => { + quickPick.items.forEach(item => { + if ((item as TrustedExtensionsQuickPickItem).extension) { + (item as TrustedExtensionsQuickPickItem).extension.allowed = false; + } + }); + + changed.forEach((item) => item.extension.allowed = true); + }); + quickPick.onDidHide(() => { quickPick.dispose(); }); diff --git a/src/vs/workbench/test/browser/api/mainThreadAuthentication.test.ts b/src/vs/workbench/test/browser/api/mainThreadAuthentication.test.ts new file mode 100644 index 00000000000..92a924aed86 --- /dev/null +++ b/src/vs/workbench/test/browser/api/mainThreadAuthentication.test.ts @@ -0,0 +1,126 @@ +/*--------------------------------------------------------------------------------------------- + * 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 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'); + mainThreadAuthentication.dispose(); + }); + + 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'); + }); +}); diff --git a/src/vs/workbench/test/common/workbenchTestServices.ts b/src/vs/workbench/test/common/workbenchTestServices.ts index b2ddf026545..f4450ecfcd5 100644 --- a/src/vs/workbench/test/common/workbenchTestServices.ts +++ b/src/vs/workbench/test/common/workbenchTestServices.ts @@ -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 { 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() { } +}