mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-08 07:44:33 +01:00
refactored mainthreadauth with silent option and tests (#135503)
This commit is contained in:
committed by
GitHub
parent
fb0e4870b3
commit
c71e201bed
Vendored
+9
@@ -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 {
|
||||
|
||||
@@ -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<modes.AuthenticationSession | undefined> {
|
||||
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<modes.AuthenticationSession | undefined> {
|
||||
private async doGetSession(providerId: string, scopes: string[], extensionId: string, extensionName: string, options: AuthenticationGetSessionOptions): Promise<modes.AuthenticationSession | undefined> {
|
||||
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<modes.AuthenticationSession | undefined> {
|
||||
const session = await this.doGetSession(providerId, scopes, extensionId, extensionName, options);
|
||||
|
||||
if (session) {
|
||||
type AuthProviderUsageClassification = {
|
||||
extensionId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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<vscode.AuthenticationSession>;
|
||||
async getSession(requestingExtension: IExtensionDescription, providerId: string, scopes: readonly string[], options: vscode.AuthenticationGetSessionOptions & { forceNewSession: true }): Promise<vscode.AuthenticationSession>;
|
||||
async getSession(requestingExtension: IExtensionDescription, providerId: string, scopes: readonly string[], options: vscode.AuthenticationGetSessionOptions & { forceNewSession: { detail: string } }): Promise<vscode.AuthenticationSession>;
|
||||
async getSession(requestingExtension: IExtensionDescription, providerId: string, scopes: readonly string[], options: vscode.AuthenticationGetSessionOptions): Promise<vscode.AuthenticationSession | undefined>;
|
||||
async getSession(requestingExtension: IExtensionDescription, providerId: string, scopes: readonly string[], options: vscode.AuthenticationGetSessionOptions = {}): Promise<vscode.AuthenticationSession | undefined> {
|
||||
const extensionId = ExtensionIdentifier.toKey(requestingExtension.identifier);
|
||||
const inFlightRequests = this._inFlightRequests.get(extensionId) || [];
|
||||
|
||||
@@ -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 <any>new AuthQuickPick();
|
||||
}
|
||||
}
|
||||
|
||||
class TestAuthProvider implements AuthenticationProvider {
|
||||
private sessions = new Map<string, AuthenticationSession>();
|
||||
onDidChangeSessions = () => { return { dispose() { } }; };
|
||||
async getSessions(scopes?: readonly string[]): Promise<AuthenticationSession[]> {
|
||||
if (!scopes) {
|
||||
return [...this.sessions.values()];
|
||||
}
|
||||
|
||||
const sessions = this.sessions.get(scopes.join(' '));
|
||||
return sessions ? [sessions] : [];
|
||||
}
|
||||
async createSession(scopes: readonly string[]): Promise<AuthenticationSession> {
|
||||
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<void> {
|
||||
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
|
||||
});
|
||||
@@ -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 <any>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.');
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user