diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 581913ee356..4fae0a8b955 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -184,6 +184,15 @@ declare module 'vscode' { * Defaults to false. */ forceNewSession?: boolean | { detail: string }; + /** + * Whether we should show the indication to sign in in the Accounts menu. + * + * If false, the user will be shown a badge on the Accounts menu with an option to sign in for the extension. + * If true, no indication will be shown. + * + * Defaults to false. + */ + silent?: boolean; } export namespace authentication { diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index 6e06452dbd7..ef09b13eb17 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -17,6 +17,7 @@ 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'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import type { AuthenticationGetSessionOptions } from 'vscode'; interface TrustedExtensionsQuickPickItem { label: string; @@ -204,99 +205,67 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu } - private async selectSession(providerId: string, extensionId: string, extensionName: string, scopes: string[], potentialSessions: readonly modes.AuthenticationSession[], clearSessionPreference: boolean, silent: boolean, showSilentPrompt: boolean): Promise { - if (!potentialSessions.length) { - throw new Error('No potential sessions found'); - } - - if (clearSessionPreference) { - this.storageService.remove(`${extensionName}-${providerId}`, StorageScope.GLOBAL); - } else { - const existingSessionPreference = this.storageService.get(`${extensionName}-${providerId}`, StorageScope.GLOBAL); - if (existingSessionPreference) { - const matchingSession = potentialSessions.find(session => session.id === existingSessionPreference); - if (matchingSession) { - const allowed = this.authenticationService.isAccessAllowed(providerId, matchingSession.account.label, extensionId); - if (!allowed) { - if (!silent) { - const didAcceptPrompt = await this.authenticationService.showGetSessionPrompt(providerId, matchingSession.account.label, extensionId, extensionName); - if (!didAcceptPrompt) { - throw new Error('User did not consent to login.'); - } - } else { - if (showSilentPrompt) { - this.authenticationService.requestSessionAccess(providerId, extensionId, extensionName, scopes, potentialSessions); - } - return undefined; - } - } - - return matchingSession; - } - } - } - - if (silent) { - this.authenticationService.requestSessionAccess(providerId, extensionId, extensionName, scopes, potentialSessions); - return undefined; - } - - return this.authenticationService.selectSession(providerId, extensionId, extensionName, scopes, potentialSessions); - } - - async $getSession(providerId: string, scopes: string[], extensionId: string, extensionName: string, options: { createIfNone: boolean, forceNewSession: boolean | { detail: string }, clearSessionPreference: boolean, solelyCheckExistence?: boolean }): Promise { + private async doGetSession(providerId: string, scopes: string[], extensionId: string, extensionName: string, options: AuthenticationGetSessionOptions): Promise { const sessions = await this.authenticationService.getSessions(providerId, scopes, true); - let silent = !options.createIfNone; - // TODO: remove this property and implement a proper $hasSession function. - let showSilentPrompt = !options.solelyCheckExistence; + // Error cases if (options.forceNewSession && !sessions.length) { throw new Error('No existing sessions found.'); } + if (options.forceNewSession && options.createIfNone) { + throw new Error('Invalid combination of options. Please remove one of the following: forceNewSession, createIfNone'); + } + if (options.forceNewSession && options.silent) { + throw new Error('Invalid combination of options. Please remove one of the following: forceNewSession, silent'); + } + if (options.createIfNone && options.silent) { + throw new Error('Invalid combination of options. Please remove one of the following: createIfNone, silent'); + } - let session: modes.AuthenticationSession | undefined; - // Ignore existing sessions if we are forceRecreating + // Check if the sessions we have are valid if (!options.forceNewSession && sessions.length) { - if (!this.authenticationService.supportsMultipleAccounts(providerId)) { - session = sessions[0]; - const allowed = this.authenticationService.isAccessAllowed(providerId, session.account.label, extensionId); - if (!allowed) { - if (!silent) { - const didAcceptPrompt = await this.authenticationService.showGetSessionPrompt(providerId, session.account.label, extensionId, extensionName); - if (!didAcceptPrompt) { - throw new Error('User did not consent to login.'); + if (this.authenticationService.supportsMultipleAccounts(providerId)) { + if (options.clearSessionPreference) { + this.storageService.remove(`${extensionName}-${providerId}`, StorageScope.GLOBAL); + } else { + const existingSessionPreference = this.storageService.get(`${extensionName}-${providerId}`, StorageScope.GLOBAL); + if (existingSessionPreference) { + const matchingSession = sessions.find(session => session.id === existingSessionPreference); + if (matchingSession && this.authenticationService.isAccessAllowed(providerId, matchingSession.account.label, extensionId)) { + return matchingSession; } - } else if (allowed !== false) { - if (showSilentPrompt) { - this.authenticationService.requestSessionAccess(providerId, extensionId, extensionName, scopes, [session]); - } - return undefined; - } else { - return undefined; } } - } else { - return this.selectSession(providerId, extensionId, extensionName, scopes, sessions, !!options.clearSessionPreference, silent, showSilentPrompt); - } - } else { - // If we are forceRecreating, we need to show the prompt. - if (options.forceNewSession || !silent) { - const providerName = this.authenticationService.getLabel(providerId); - const detail = (typeof options.forceNewSession === 'object') ? options.forceNewSession!.detail : undefined; - const isAllowed = await this.loginPrompt(providerName, extensionName, !!options.forceNewSession, detail); - if (!isAllowed) { - throw new Error('User did not consent to login.'); - } - - session = await this.authenticationService.createSession(providerId, scopes, true); - await this.setTrustedExtensionAndAccountPreference(providerId, session.account.label, extensionId, extensionName, session.id); - } else { - if (showSilentPrompt) { - await this.authenticationService.requestNewSession(providerId, scopes, extensionId, extensionName); - } + } else if (this.authenticationService.isAccessAllowed(providerId, sessions[0].account.label, extensionId)) { + return sessions[0]; } } + // We may need to prompt because we don't have a valid session + // modal flows + if (options.createIfNone || options.forceNewSession) { + const providerName = this.authenticationService.getLabel(providerId); + const detail = (typeof options.forceNewSession === 'object') ? options.forceNewSession!.detail : undefined; + const isAllowed = await this.loginPrompt(providerName, extensionName, !!options.forceNewSession, detail); + if (!isAllowed) { + throw new Error('User did not consent to login.'); + } + + const session = await this.authenticationService.createSession(providerId, scopes, true); + await this.setTrustedExtensionAndAccountPreference(providerId, session.account.label, extensionId, extensionName, session.id); + return session; + } + // passive flows + if (!options.silent) { + await this.authenticationService.requestNewSession(providerId, scopes, extensionId, extensionName); + } + + return undefined; + } + + async $getSession(providerId: string, scopes: string[], extensionId: string, extensionName: string, options: AuthenticationGetSessionOptions): Promise { + const session = await this.doGetSession(providerId, scopes, extensionId, extensionName, options); + if (session) { type AuthProviderUsageClassification = { extensionId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 94645dd95c8..4eb2e047241 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -226,12 +226,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const authentication: typeof vscode.authentication = { getSession(providerId: string, scopes: readonly string[], options?: vscode.AuthenticationGetSessionOptions) { - if (options?.forceNewSession) { + if (options?.forceNewSession || options?.silent) { checkProposedApiEnabled(extension); } return extHostAuthentication.getSession(extension, providerId, scopes, options as any); }, - // TODO: optimize this API to only return the boolean over the wire + // TODO: remove this after GHPR and Codespaces move off of it async hasSession(providerId: string, scopes: readonly string[]) { checkProposedApiEnabled(extension); return !!(await extHostAuthentication.getSession(extension, providerId, scopes, { solelyCheckExistence: true } as any)); diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index efc1cb33487..4b2ef8d5d74 100644 --- a/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -44,6 +44,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { async getSession(requestingExtension: IExtensionDescription, providerId: string, scopes: readonly string[], options: vscode.AuthenticationGetSessionOptions & ({ createIfNone: true } | { forceNewSession: true } | { forceNewSession: { detail: string } })): Promise; async getSession(requestingExtension: IExtensionDescription, providerId: string, scopes: readonly string[], options: vscode.AuthenticationGetSessionOptions & { forceNewSession: true }): Promise; async getSession(requestingExtension: IExtensionDescription, providerId: string, scopes: readonly string[], options: vscode.AuthenticationGetSessionOptions & { forceNewSession: { detail: string } }): Promise; + async getSession(requestingExtension: IExtensionDescription, providerId: string, scopes: readonly string[], options: vscode.AuthenticationGetSessionOptions): Promise; async getSession(requestingExtension: IExtensionDescription, providerId: string, scopes: readonly string[], options: vscode.AuthenticationGetSessionOptions = {}): Promise { const extensionId = ExtensionIdentifier.toKey(requestingExtension.identifier); const inFlightRequests = this._inFlightRequests.get(extensionId) || []; diff --git a/src/vs/workbench/test/browser/api/extHostAuthentication.test.ts b/src/vs/workbench/test/browser/api/extHostAuthentication.test.ts new file mode 100644 index 00000000000..be6599dc213 --- /dev/null +++ b/src/vs/workbench/test/browser/api/extHostAuthentication.test.ts @@ -0,0 +1,358 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { DisposableStore } from 'vs/base/common/lifecycle'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +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 { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; +import { MainThreadAuthentication } from 'vs/workbench/api/browser/mainThreadAuthentication'; +import { ExtHostContext, MainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication'; +import { IActivityService } from 'vs/workbench/services/activity/common/activity'; +import { AuthenticationService, IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; +import { 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 { TestRPCProtocol } from 'vs/workbench/test/browser/api/testRPCProtocol'; +import { TestQuickInputService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestActivityService, TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; +import type { AuthenticationProvider, AuthenticationSession } from 'vscode'; + +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(); + } +} + +class TestAuthProvider implements AuthenticationProvider { + private sessions = new Map(); + onDidChangeSessions = () => { return { dispose() { } }; }; + async getSessions(scopes?: readonly string[]): Promise { + if (!scopes) { + return [...this.sessions.values()]; + } + + const sessions = this.sessions.get(scopes.join(' ')); + return sessions ? [sessions] : []; + } + async createSession(scopes: readonly string[]): Promise { + const scopesStr = scopes.join(' '); + const session = { + scopes, + id: 'test', + account: { + label: scopesStr, + id: scopesStr, + }, + accessToken: Math.random() + '', + }; + this.sessions.set(scopesStr, session); + return session; + } + async removeSession(sessionId: string): Promise { + this.sessions.delete(sessionId); + } + +} + +suite('ExtHostAuthentication', () => { + let disposables: DisposableStore; + let nullExtensionDescription: IExtensionDescription = { + identifier: new ExtensionIdentifier('nullExtensionDescription'), + name: 'Null Extension Description', + publisher: 'vscode', + enableProposedApi: true, + engines: undefined!, + extensionLocation: undefined!, + isBuiltin: false, + isUserBuiltin: false, + isUnderDevelopment: false, + version: undefined! + }; + + let extHostAuthentication: ExtHostAuthentication; + let instantiationService: TestInstantiationService; + + suiteSetup(async () => { + instantiationService = new TestInstantiationService(); + instantiationService.stub(IDialogService, new TestDialogService()); + instantiationService.stub(IStorageService, new TestStorageService()); + instantiationService.stub(IQuickInputService, new AuthTestQuickInputService()); + instantiationService.stub(IExtensionService, new TestExtensionService()); + + instantiationService.stub(IActivityService, new TestActivityService()); + instantiationService.stub(IRemoteAgentService, new TestRemoteAgentService()); + instantiationService.stub(INotificationService, new TestNotificationService()); + instantiationService.stub(ITelemetryService, NullTelemetryService); + const rpcProtocol = new TestRPCProtocol(); + + instantiationService.stub(IAuthenticationService, instantiationService.createInstance(AuthenticationService)); + rpcProtocol.set(MainContext.MainThreadAuthentication, instantiationService.createInstance(MainThreadAuthentication, rpcProtocol)); + extHostAuthentication = new ExtHostAuthentication(rpcProtocol); + rpcProtocol.set(ExtHostContext.ExtHostAuthentication, extHostAuthentication); + }); + + setup(async () => { + disposables = new DisposableStore(); + disposables.add(extHostAuthentication.registerAuthenticationProvider('test', 'test provider', new TestAuthProvider())); + disposables.add(extHostAuthentication.registerAuthenticationProvider( + 'test-multiple', + 'test multiple provider', + new TestAuthProvider(), + { supportsMultipleAccounts: true })); + }); + + teardown(() => { + disposables.dispose(); + }); + + test('createIfNone - true', async () => { + const session = await extHostAuthentication.getSession( + nullExtensionDescription, + 'test', + ['foo'], + { + createIfNone: true + }); + assert.strictEqual(session?.id, 'test'); + assert.strictEqual(session?.scopes[0], 'foo'); + }); + + test('createIfNone - false', async () => { + const nosession = await extHostAuthentication.getSession( + nullExtensionDescription, + 'test', + ['foo'], + {}); + assert.strictEqual(nosession, undefined); + + // Now create the session + const session = await extHostAuthentication.getSession( + nullExtensionDescription, + 'test', + ['foo'], + { + createIfNone: true + }); + + assert.strictEqual(session?.id, 'test'); + assert.strictEqual(session?.scopes[0], 'foo'); + + const session2 = await extHostAuthentication.getSession( + nullExtensionDescription, + 'test', + ['foo'], + {}); + + assert.strictEqual(session.id, session2?.id); + assert.strictEqual(session.scopes[0], session2?.scopes[0]); + assert.strictEqual(session.accessToken, session2?.accessToken); + }); + + // should behave the same as createIfNone: false + test('silent - true', async () => { + const nosession = await extHostAuthentication.getSession( + nullExtensionDescription, + 'test', + ['foo'], + { + silent: true + }); + assert.strictEqual(nosession, undefined); + + // Now create the session + const session = await extHostAuthentication.getSession( + nullExtensionDescription, + 'test', + ['foo'], + { + createIfNone: true + }); + + assert.strictEqual(session?.id, 'test'); + assert.strictEqual(session?.scopes[0], 'foo'); + + const session2 = await extHostAuthentication.getSession( + nullExtensionDescription, + 'test', + ['foo'], + { + silent: true + }); + + assert.strictEqual(session.id, session2?.id); + assert.strictEqual(session.scopes[0], session2?.scopes[0]); + }); + + test('forceNewSession - true', async () => { + const session1 = await extHostAuthentication.getSession( + nullExtensionDescription, + 'test', + ['foo'], + { + createIfNone: true + }); + + // Now create the session + const session2 = await extHostAuthentication.getSession( + nullExtensionDescription, + 'test', + ['foo'], + { + forceNewSession: true + }); + + assert.strictEqual(session2?.id, 'test'); + assert.strictEqual(session2?.scopes[0], 'foo'); + assert.notStrictEqual(session1.accessToken, session2?.accessToken); + }); + + test('forceNewSession - detail', async () => { + const session1 = await extHostAuthentication.getSession( + nullExtensionDescription, + 'test', + ['foo'], + { + createIfNone: true + }); + + // Now create the session + const session2 = await extHostAuthentication.getSession( + nullExtensionDescription, + 'test', + ['foo'], + { + forceNewSession: { detail: 'bar' } + }); + + assert.strictEqual(session2?.id, 'test'); + assert.strictEqual(session2?.scopes[0], 'foo'); + assert.notStrictEqual(session1.accessToken, session2?.accessToken); + }); + + test('clearSessionPreference - true', async () => { + // Now create the session + const session = await extHostAuthentication.getSession( + nullExtensionDescription, + 'test-multiple', + ['foo'], + { + createIfNone: true + }); + + assert.strictEqual(session?.id, 'test'); + assert.strictEqual(session?.scopes[0], 'foo'); + + const session2 = await extHostAuthentication.getSession( + nullExtensionDescription, + 'test-multiple', + ['foo'], + { + clearSessionPreference: true, + createIfNone: true + }); + + assert.strictEqual(session.id, session2?.id); + assert.strictEqual(session.scopes[0], session2?.scopes[0]); + assert.notStrictEqual(session.accessToken, session2?.accessToken); + }); + + //#region error cases + + test('forceNewSession with no sessions', async () => { + try { + await extHostAuthentication.getSession( + nullExtensionDescription, + 'test', + ['foo'], + { + forceNewSession: true + }); + assert.fail('should have thrown an Error.'); + } catch (e) { + assert.strictEqual(e.message, 'No existing sessions found.'); + } + }); + + test('createIfNone and forceNewSession', async () => { + try { + await extHostAuthentication.getSession( + nullExtensionDescription, + 'test', + ['foo'], + { + createIfNone: true, + forceNewSession: true + }); + assert.fail('should have thrown an Error.'); + } catch (e) { + assert.ok(e); + } + }); + + test('forceNewSession and silent', async () => { + try { + await extHostAuthentication.getSession( + nullExtensionDescription, + 'test', + ['foo'], + { + forceNewSession: true, + silent: true + }); + assert.fail('should have thrown an Error.'); + } catch (e) { + assert.ok(e); + } + }); + + test('createIfNone and silent', async () => { + try { + await extHostAuthentication.getSession( + nullExtensionDescription, + 'test', + ['foo'], + { + createIfNone: true, + silent: true + }); + assert.fail('should have thrown an Error.'); + } catch (e) { + assert.ok(e); + } + }); + + //#endregion +}); diff --git a/src/vs/workbench/test/browser/api/mainThreadAuthentication.test.ts b/src/vs/workbench/test/browser/api/mainThreadAuthentication.test.ts deleted file mode 100644 index ac22ea20a8d..00000000000 --- a/src/vs/workbench/test/browser/api/mainThreadAuthentication.test.ts +++ /dev/null @@ -1,162 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * 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 { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -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 { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -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'; - -let i = 0; -function createSession(id: string = '1234', scope: string[] = []) { - return { - accessToken: (++i) + '', - 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; - let instantiationService: TestInstantiationService; - suiteSetup(async () => { - instantiationService = new TestInstantiationService(); - // extHostContext: IExtHostContext, - instantiationService.stub(IDialogService, new TestDialogService()); - instantiationService.stub(IStorageService, new TestStorageService()); - instantiationService.stub(IQuickInputService, new AuthTestQuickInputService()); - instantiationService.stub(IExtensionService, new TestExtensionService()); - - instantiationService.stub(IActivityService, new TestActivityService()); - instantiationService.stub(IRemoteAgentService, new TestRemoteAgentService()); - instantiationService.stub(INotificationService, new TestNotificationService()); - instantiationService.stub(ITelemetryService, NullTelemetryService); - - instantiationService.stub(IAuthenticationService, instantiationService.createInstance(AuthenticationService)); - mainThreadAuthentication = instantiationService.createInstance(MainThreadAuthentication, - new class implements IExtHostContext { - remoteAuthority = ''; - extensionHostKind = ExtensionHostKind.LocalProcess; - assertRegistered() { } - set(v: any): any { return null; } - getProxy(): any { - return { - async $getSessions(id: string, scopes: string[]) { - // if we get the empty auth provider, return no sessions - return id === 'empty' ? [] : [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(); }, - $setProviders(providers: AuthenticationProviderInformation[]) { return Promise.resolve(); } - }; - } - drain(): any { return null; } - }); - }); - - setup(async () => { - await mainThreadAuthentication.$registerAuthenticationProvider('test', 'test provider', true); - await mainThreadAuthentication.$registerAuthenticationProvider('empty', 'test provider', true); - }); - - teardown(() => { - mainThreadAuthentication.$unregisterAuthenticationProvider('test'); - mainThreadAuthentication.$unregisterAuthenticationProvider('empty'); - }); - - test('Can get a session', async () => { - const session = await mainThreadAuthentication.$getSession('test', ['foo'], 'testextension', 'test extension', { - createIfNone: true, - clearSessionPreference: false, - forceNewSession: false - }); - assert.strictEqual(session?.id, 'test'); - assert.strictEqual(session?.scopes[0], 'foo'); - }); - - test('Can recreate a session', async () => { - const session = await mainThreadAuthentication.$getSession('test', ['foo'], 'testextension', 'test extension', { - createIfNone: true, - clearSessionPreference: false, - forceNewSession: false - }); - - assert.strictEqual(session?.id, 'test'); - assert.strictEqual(session?.scopes[0], 'foo'); - - const session2 = await mainThreadAuthentication.$getSession('test', ['foo'], 'testextension', 'test extension', { - createIfNone: false, - clearSessionPreference: false, - forceNewSession: true - }); - - assert.strictEqual(session.id, session2?.id); - assert.strictEqual(session.scopes[0], session2?.scopes[0]); - assert.notStrictEqual(session.accessToken, session2?.accessToken); - }); - - test('Can not recreate a session if none exists', async () => { - try { - await mainThreadAuthentication.$getSession('empty', ['foo'], 'testextension', 'test extension', { - createIfNone: false, - clearSessionPreference: false, - forceNewSession: true - }); - assert.fail('should have thrown an Error.'); - } catch (e) { - assert.strictEqual(e.message, 'No existing sessions found.'); - } - }); -});