Skip to content

Commit

Permalink
Initial getSessions API
Browse files Browse the repository at this point in the history
  • Loading branch information
TylerLeonhardt committed Mar 14, 2023
1 parent 93eaf7c commit e222d92
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 47 deletions.
31 changes: 23 additions & 8 deletions src/vs/workbench/api/browser/mainThreadAuthentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,17 +281,32 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
const session = await this.doGetSession(providerId, scopes, extensionId, extensionName, options);

if (session) {
type AuthProviderUsageClassification = {
owner: 'TylerLeonhardt';
comment: 'Used to see which extensions are using which providers';
extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The extension id.' };
providerId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The provider id.' };
};
this.telemetryService.publicLog2<{ extensionId: string; providerId: string }, AuthProviderUsageClassification>('authentication.providerUsage', { providerId, extensionId });

this.sendProviderUsageTelemetry(extensionId, providerId);
addAccountUsage(this.storageService, providerId, session.account.label, extensionId, extensionName);
}

return session;
}

async $getSessions(providerId: string, scopes: readonly string[], extensionId: string, extensionName: string): Promise<AuthenticationSession[]> {
const sessions = await this.authenticationService.getSessions(providerId, [...scopes], true);
const accessibleSessions = sessions.filter(s => this.authenticationService.isAccessAllowed(providerId, s.account.label, extensionId));
if (accessibleSessions.length) {
this.sendProviderUsageTelemetry(extensionId, providerId);
for (const session of accessibleSessions) {
addAccountUsage(this.storageService, providerId, session.account.label, extensionId, extensionName);
}
}
return accessibleSessions;
}

private sendProviderUsageTelemetry(extensionId: string, providerId: string): void {
type AuthProviderUsageClassification = {
owner: 'TylerLeonhardt';
comment: 'Used to see which extensions are using which providers';
extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The extension id.' };
providerId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The provider id.' };
};
this.telemetryService.publicLog2<{ extensionId: string; providerId: string }, AuthProviderUsageClassification>('authentication.providerUsage', { providerId, extensionId });
}
}
4 changes: 4 additions & 0 deletions src/vs/workbench/api/common/extHost.api.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
getSession(providerId: string, scopes: readonly string[], options?: vscode.AuthenticationGetSessionOptions) {
return extHostAuthentication.getSession(extension, providerId, scopes, options as any);
},
getSessions(providerId: string, scopes: readonly string[]) {
checkProposedApiEnabled(extension, 'getSessions');
return extHostAuthentication.getSessions(extension, providerId, scopes);
},
// TODO: remove this after GHPR and Codespaces move off of it
async hasSession(providerId: string, scopes: readonly string[]) {
checkProposedApiEnabled(extension, 'authSession');
Expand Down
1 change: 1 addition & 0 deletions src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export interface MainThreadAuthenticationShape extends IDisposable {
$ensureProvider(id: string): Promise<void>;
$sendDidChangeSessions(providerId: string, event: AuthenticationSessionsChangeEvent): void;
$getSession(providerId: string, scopes: readonly string[], extensionId: string, extensionName: string, options: { createIfNone?: boolean; forceNewSession?: boolean | { detail: string }; clearSessionPreference?: boolean }): Promise<AuthenticationSession | undefined>;
$getSessions(providerId: string, scopes: readonly string[], extensionId: string, extensionName: string): Promise<AuthenticationSession[]>;
$removeSession(providerId: string, sessionId: string): Promise<void>;
}

Expand Down
69 changes: 30 additions & 39 deletions src/vs/workbench/api/common/extHostAuthentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,6 @@ import { IMainContext, MainContext, MainThreadAuthenticationShape, ExtHostAuthen
import { Disposable } from 'vs/workbench/api/common/extHostTypes';
import { IExtensionDescription, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';

interface GetSessionsRequest {
scopes: string;
providerId: string;
result: Promise<vscode.AuthenticationSession | undefined>;
}

interface ProviderWithMetadata {
label: string;
provider: vscode.AuthenticationProvider;
Expand All @@ -30,7 +24,8 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
private _onDidChangeSessions = new Emitter<vscode.AuthenticationSessionsChangeEvent>();
readonly onDidChangeSessions: Event<vscode.AuthenticationSessionsChangeEvent> = this._onDidChangeSessions.event;

private _inFlightRequests = new Map<string, GetSessionsRequest[]>();
private _getSessionTaskSingler = new TaskSingler<vscode.AuthenticationSession | undefined>();
private _getSessionsTaskSingler = new TaskSingler<ReadonlyArray<vscode.AuthenticationSession>>();

constructor(mainContext: IMainContext) {
this._proxy = mainContext.getProxy(MainContext.MainThreadAuthentication);
Expand All @@ -47,41 +42,22 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
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) || [];
const sortedScopes = [...scopes].sort().join(' ');
let inFlightRequest: GetSessionsRequest | undefined = inFlightRequests.find(request => request.providerId === providerId && request.scopes === sortedScopes);

if (inFlightRequest) {
return inFlightRequest.result;
} else {
const session = this._getSession(requestingExtension, extensionId, providerId, scopes, options);
inFlightRequest = {
providerId,
scopes: sortedScopes,
result: session
};

inFlightRequests.push(inFlightRequest);
this._inFlightRequests.set(extensionId, inFlightRequests);

try {
await session;
} finally {
const requestIndex = inFlightRequests.findIndex(request => request.providerId === providerId && request.scopes === sortedScopes);
if (requestIndex > -1) {
inFlightRequests.splice(requestIndex);
this._inFlightRequests.set(extensionId, inFlightRequests);
}
}

return session;
}
return await this._getSessionTaskSingler.getOrCreate(`${extensionId} ${sortedScopes}`, async () => {
await this._proxy.$ensureProvider(providerId);
const extensionName = requestingExtension.displayName || requestingExtension.name;
return this._proxy.$getSession(providerId, scopes, extensionId, extensionName, options);
});
}

private async _getSession(requestingExtension: IExtensionDescription, extensionId: string, providerId: string, scopes: readonly string[], options: vscode.AuthenticationGetSessionOptions = {}): Promise<vscode.AuthenticationSession | undefined> {
await this._proxy.$ensureProvider(providerId);
const extensionName = requestingExtension.displayName || requestingExtension.name;
return this._proxy.$getSession(providerId, scopes, extensionId, extensionName, options);
async getSessions(requestingExtension: IExtensionDescription, providerId: string, scopes: readonly string[]): Promise<ReadonlyArray<vscode.AuthenticationSession>> {
const extensionId = ExtensionIdentifier.toKey(requestingExtension.identifier);
const sortedScopes = [...scopes].sort().join(' ');
return await this._getSessionsTaskSingler.getOrCreate(`${extensionId} ${sortedScopes}`, async () => {
await this._proxy.$ensureProvider(providerId);
const extensionName = requestingExtension.displayName || requestingExtension.name;
return this._proxy.$getSessions(providerId, scopes, extensionId, extensionName);
});
}

async removeSession(providerId: string, sessionId: string): Promise<void> {
Expand Down Expand Up @@ -162,3 +138,18 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
return Promise.resolve();
}
}

class TaskSingler<T> {
private _inFlightPromises = new Map<string, Promise<T>>();
getOrCreate(key: string, promiseFactory: () => Promise<T>) {
const inFlight = this._inFlightPromises.get(key);
if (inFlight) {
return inFlight;
}

const promise = promiseFactory().finally(() => this._inFlightPromises.delete(key));
this._inFlightPromises.set(key, promise);

return promise;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const allApiProposals = Object.freeze({
fileSearchProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fileSearchProvider.d.ts',
findTextInFiles: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findTextInFiles.d.ts',
fsChunks: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fsChunks.d.ts',
getSessions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.getSessions.d.ts',
idToken: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.idToken.d.ts',
indentSize: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.indentSize.d.ts',
inlineCompletionsAdditions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts',
Expand Down
25 changes: 25 additions & 0 deletions src/vscode-dts/vscode.proposed.getSessions.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

declare module 'vscode' {

// https://github.com/microsoft/vscode/issues/152399

export namespace authentication {
/**
* Get all authentication sessions matching the desired scopes that this extension has access to. In order to request access,
* use {@link getSession}. To request an additional account, specify {@link AuthenticationGetSessionOptions.clearSessionPreference}
* and {@link AuthenticationGetSessionOptions.createIfNone} together.
*
* Currently, there are only two authentication providers that are contributed from built in extensions
* to the editor that implement GitHub and Microsoft authentication: their providerId's are 'github' and 'microsoft'.
*
* @param providerId The id of the provider to use
* @param scopes A list of scopes representing the permissions requested. These are dependent on the authentication provider
* @returns A thenable that resolves to a readonly array of authentication sessions.
*/
export function getSessions(providerId: string, scopes: readonly string[]): Thenable<readonly AuthenticationSession[]>;
}
}

0 comments on commit e222d92

Please sign in to comment.