提交 4a875e0d 编写于 作者: R Rachel Macfarlane

Add getSession and hasSessions to authentication namespace

上级 4da345ea
......@@ -108,6 +108,40 @@ declare module 'vscode' {
*/
export const providerIds: string[];
/**
* Returns whether a provider has any sessions matching the requested scopes. This request
* is transparent to the user, not UI is shown. Rejects if a provider with providerId is not
* registered.
* @param providerId The id of the provider
* @param scopes A list of scopes representing the permissions requested. These are dependent on the authentication
* provider
*/
export function hasSessions(providerId: string, scopes: string[]): Thenable<boolean>;
export interface GetSessionOptions {
/**
* Whether login should be performed if there is no matching session. Defaults to false.
*/
createIfNone?: boolean;
/**
* Whether the existing user session preference should be cleared. Set to allow the user to switch accounts.
* Defaults to false.
*/
clearSessionPreference?: boolean;
}
/**
* Get an authentication session matching the desired scopes. Rejects if a provider with providerId is not
* registered, or if the user does not consent to sharing authentication information with
* the extension. If there are multiple sessions with the same scopes, the user will be shown a
* quickpick to select which account they would like to use.
* @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
* @param options The [getSessionOptions](#GetSessionOptions) to use
*/
export function getSession(providerId: string, scopes: string[], options: GetSessionOptions): Thenable<AuthenticationSession | undefined>;
/**
* Get existing authentication sessions. Rejects if a provider with providerId is not
* registered, or if the user does not consent to sharing authentication information with
......
......@@ -294,7 +294,8 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
@IStorageService private readonly storageService: IStorageService,
@INotificationService private readonly notificationService: INotificationService,
@IStorageKeysSyncRegistryService private readonly storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
@IQuickInputService private readonly quickInputService: IQuickInputService
) {
super();
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostAuthentication);
......@@ -314,6 +315,72 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
this.authenticationService.sessionsUpdate(id, event);
}
async $getSession(providerId: string, providerName: string, extensionId: string, extensionName: string, potentialSessions: modes.AuthenticationSession[], scopes: string[], clearSessionPreference: boolean): Promise<modes.AuthenticationSession> {
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 = await this.$getSessionsPrompt(providerId, matchingSession.account.displayName, providerName, extensionId, extensionName);
if (allowed) {
return matchingSession;
}
}
}
}
return new Promise((resolve, reject) => {
const quickPick = this.quickInputService.createQuickPick<{ label: string, session?: modes.AuthenticationSession }>();
quickPick.ignoreFocusOut = true;
const items: { label: string, session?: modes.AuthenticationSession }[] = potentialSessions.map(session => {
return {
label: session.account.displayName,
session
};
});
items.push({
label: nls.localize('useOtherAccount', "Sign in to another account")
});
quickPick.items = items;
quickPick.title = nls.localize('selectAccount', "The extension '{0}' wants to access a {1} account", extensionName, providerName);
quickPick.placeholder = nls.localize('getSessionPlateholder', "Select an account for '{0}' to use or Esc to cancel", extensionName);
quickPick.onDidAccept(async _ => {
const selected = quickPick.selectedItems[0];
const session = selected.session ?? await this.authenticationService.login(providerId, scopes);
const accountName = session.account.displayName;
const allowList = readAllowedExtensions(this.storageService, providerId, accountName);
allowList.push({ id: extensionId, name: extensionName });
this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL);
this.storageService.store(`${extensionName}-${providerId}`, session.id, StorageScope.GLOBAL);
quickPick.dispose();
resolve(session);
});
quickPick.onDidHide(_ => {
if (!quickPick.selectedItems[0]) {
reject('User did not consent to account access');
}
quickPick.dispose();
});
quickPick.show();
});
}
async $getSessionsPrompt(providerId: string, accountName: string, providerName: string, extensionId: string, extensionName: string): Promise<boolean> {
addAccountUsage(providerId, accountName, extensionName);
......
......@@ -196,6 +196,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
get providerIds(): string[] {
return extHostAuthentication.providerIds;
},
hasSessions(providerId: string, scopes: string[]): Thenable<boolean> {
return extHostAuthentication.hasSessions(providerId, scopes);
},
getSession(providerId: string, scopes: string[], options: vscode.authentication.GetSessionOptions): Thenable<vscode.AuthenticationSession | undefined> {
return extHostAuthentication.getSession(extension, providerId, scopes, options);
},
getSessions(providerId: string, scopes: string[]): Thenable<readonly vscode.AuthenticationSession[]> {
return extHostAuthentication.getSessions(extension, providerId, scopes);
},
......
......@@ -160,6 +160,7 @@ export interface MainThreadAuthenticationShape extends IDisposable {
$registerAuthenticationProvider(id: string, displayName: string): void;
$unregisterAuthenticationProvider(id: string): void;
$onDidChangeSessions(providerId: string, event: modes.AuthenticationSessionsChangeEvent): void;
$getSession(providerId: string, providerName: string, extensionId: string, extensionName: string, potentialSessions: modes.AuthenticationSession[], scopes: string[], clearSessionPreference: boolean): Promise<modes.AuthenticationSession>;
$getSessionsPrompt(providerId: string, accountName: string, providerName: string, extensionId: string, extensionName: string): Promise<boolean>;
$loginPrompt(providerName: string, extensionName: string): Promise<boolean>;
$setTrustedExtension(providerId: string, accountName: string, extensionId: string, extensionName: string): Promise<void>;
......
......@@ -33,6 +33,46 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
return ids;
}
async hasSessions(providerId: string, scopes: string[]): Promise<boolean> {
const provider = this._authenticationProviders.get(providerId);
if (!provider) {
throw new Error(`No authentication provider with id '${providerId}' is currently registered.`);
}
const orderedScopes = scopes.sort().join(' ');
return !!(await provider.getSessions()).filter(session => session.scopes.sort().join(' ') === orderedScopes).length;
}
async getSession(requestingExtension: IExtensionDescription, providerId: string, scopes: string[], options: vscode.authentication.GetSessionOptions): Promise<vscode.AuthenticationSession | undefined> {
const provider = this._authenticationProviders.get(providerId);
if (!provider) {
throw new Error(`No authentication provider with id '${providerId}' is currently registered.`);
}
const orderedScopes = scopes.sort().join(' ');
const sessions = (await provider.getSessions()).filter(session => session.scopes.sort().join(' ') === orderedScopes);
if (sessions.length) {
// On renderer side, confirm consent, ask user to choose between accounts if multiple sessions are valid
const extensionName = requestingExtension.displayName || requestingExtension.name;
const selected = await this._proxy.$getSession(provider.id, provider.displayName, ExtensionIdentifier.toKey(requestingExtension.identifier), extensionName, sessions, scopes, !!options.clearSessionPreference);
return sessions.find(session => session.id === selected.id);
} else {
if (options.createIfNone) {
const extensionName = requestingExtension.displayName || requestingExtension.name;
const isAllowed = await this._proxy.$loginPrompt(provider.displayName, extensionName);
if (!isAllowed) {
throw new Error('User did not consent to login.');
}
const session = await provider.login(scopes);
await this._proxy.$setTrustedExtension(provider.id, session.account.displayName, ExtensionIdentifier.toKey(requestingExtension.identifier), extensionName);
return session;
} else {
return undefined;
}
}
}
async getSessions(requestingExtension: IExtensionDescription, providerId: string, scopes: string[]): Promise<readonly vscode.AuthenticationSession[]> {
const provider = this._authenticationProviders.get(providerId);
if (!provider) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册