extHostAuthentication.ts 6.6 KB
Newer Older
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6
import type * as vscode from 'vscode';
7 8 9
import * as modes from 'vs/editor/common/modes';
import { Emitter, Event } from 'vs/base/common/event';
import { IMainContext, MainContext, MainThreadAuthenticationShape, ExtHostAuthenticationShape } from 'vs/workbench/api/common/extHost.protocol';
10 11
import { Disposable } from 'vs/workbench/api/common/extHostTypes';
import { IExtensionDescription, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
12

13 14 15
export class ExtHostAuthentication implements ExtHostAuthenticationShape {
	private _proxy: MainThreadAuthenticationShape;
	private _authenticationProviders: Map<string, vscode.AuthenticationProvider> = new Map<string, vscode.AuthenticationProvider>();
16

17 18
	private _onDidChangeAuthenticationProviders = new Emitter<vscode.AuthenticationProvidersChangeEvent>();
	readonly onDidChangeAuthenticationProviders: Event<vscode.AuthenticationProvidersChangeEvent> = this._onDidChangeAuthenticationProviders.event;
19

20 21
	private _onDidChangeSessions = new Emitter<{ [providerId: string]: vscode.AuthenticationSessionsChangeEvent }>();
	readonly onDidChangeSessions: Event<{ [providerId: string]: vscode.AuthenticationSessionsChangeEvent }> = this._onDidChangeSessions.event;
22

23
	constructor(mainContext: IMainContext) {
24
		this._proxy = mainContext.getProxy(MainContext.MainThreadAuthentication);
25 26
	}

27 28 29 30 31 32 33
	get providerIds(): string[] {
		const ids: string[] = [];
		this._authenticationProviders.forEach(provider => {
			ids.push(provider.id);
		});

		return ids;
34 35
	}

36
	async getSessions(requestingExtension: IExtensionDescription, providerId: string, scopes: string[]): Promise<readonly vscode.AuthenticationSession[]> {
37 38 39 40 41
		const provider = this._authenticationProviders.get(providerId);
		if (!provider) {
			throw new Error(`No authentication provider with id '${providerId}' is currently registered.`);
		}

42
		const extensionId = ExtensionIdentifier.toKey(requestingExtension.identifier);
43
		const orderedScopes = scopes.sort().join(' ');
44

45 46
		return (await provider.getSessions())
			.filter(session => session.scopes.sort().join(' ') === orderedScopes)
47 48 49
			.map(session => {
				return {
					id: session.id,
50
					account: session.account,
51 52 53 54
					scopes: session.scopes,
					getAccessToken: async () => {
						const isAllowed = await this._proxy.$getSessionsPrompt(
							provider.id,
55
							session.account.displayName,
56
							provider.displayName,
57
							extensionId,
58 59 60 61 62 63 64
							requestingExtension.displayName || requestingExtension.name);

						if (!isAllowed) {
							throw new Error('User did not consent to token access.');
						}

						return session.getAccessToken();
65
					}
66 67
				};
			});
68 69
	}

70 71 72 73 74 75
	async login(requestingExtension: IExtensionDescription, providerId: string, scopes: string[]): Promise<vscode.AuthenticationSession> {
		const provider = this._authenticationProviders.get(providerId);
		if (!provider) {
			throw new Error(`No authentication provider with id '${providerId}' is currently registered.`);
		}

76 77
		const extensionName = requestingExtension.displayName || requestingExtension.name;
		const isAllowed = await this._proxy.$loginPrompt(provider.displayName, extensionName);
78 79 80 81
		if (!isAllowed) {
			throw new Error('User did not consent to login.');
		}

82
		const session = await provider.login(scopes);
83
		await this._proxy.$setTrustedExtension(provider.id, session.account.displayName, ExtensionIdentifier.toKey(requestingExtension.identifier), extensionName);
84 85
		return {
			id: session.id,
86
			account: session.account,
87 88 89 90
			scopes: session.scopes,
			getAccessToken: async () => {
				const isAllowed = await this._proxy.$getSessionsPrompt(
					provider.id,
91
					session.account.displayName,
92 93 94 95 96 97 98 99 100 101 102
					provider.displayName,
					ExtensionIdentifier.toKey(requestingExtension.identifier),
					requestingExtension.displayName || requestingExtension.name);

				if (!isAllowed) {
					throw new Error('User did not consent to token access.');
				}

				return session.getAccessToken();
			}
		};
103
	}
104

105
	registerAuthenticationProvider(provider: vscode.AuthenticationProvider): vscode.Disposable {
106 107 108 109
		if (this._authenticationProviders.get(provider.id)) {
			throw new Error(`An authentication provider with id '${provider.id}' is already registered.`);
		}

110 111
		this._authenticationProviders.set(provider.id, provider);

112 113 114
		const listener = provider.onDidChangeSessions(e => {
			this._proxy.$onDidChangeSessions(provider.id, e);
			this._onDidChangeSessions.fire({ [provider.id]: e });
115
		});
116

117
		this._proxy.$registerAuthenticationProvider(provider.id, provider.displayName);
118
		this._onDidChangeAuthenticationProviders.fire({ added: [provider.id], removed: [] });
119 120 121 122 123

		return new Disposable(() => {
			listener.dispose();
			this._authenticationProviders.delete(provider.id);
			this._proxy.$unregisterAuthenticationProvider(provider.id);
124
			this._onDidChangeAuthenticationProviders.fire({ added: [], removed: [provider.id] });
125
		});
126 127
	}

128
	$login(providerId: string, scopes: string[]): Promise<modes.AuthenticationSession> {
129
		const authProvider = this._authenticationProviders.get(providerId);
130
		if (authProvider) {
131
			return Promise.resolve(authProvider.login(scopes));
132 133
		}

134
		throw new Error(`Unable to find authentication provider with handle: ${providerId}`);
135 136
	}

137 138
	$logout(providerId: string, sessionId: string): Promise<void> {
		const authProvider = this._authenticationProviders.get(providerId);
139
		if (authProvider) {
140
			return Promise.resolve(authProvider.logout(sessionId));
141 142
		}

143
		throw new Error(`Unable to find authentication provider with handle: ${providerId}`);
144 145
	}

146
	$getSessions(providerId: string): Promise<ReadonlyArray<modes.AuthenticationSession>> {
147
		const authProvider = this._authenticationProviders.get(providerId);
148
		if (authProvider) {
149
			return Promise.resolve(authProvider.getSessions());
150 151
		}

152 153 154 155 156 157 158 159 160
		throw new Error(`Unable to find authentication provider with handle: ${providerId}`);
	}

	async $getSessionAccessToken(providerId: string, sessionId: string): Promise<string> {
		const authProvider = this._authenticationProviders.get(providerId);
		if (authProvider) {
			const sessions = await authProvider.getSessions();
			const session = sessions.find(session => session.id === sessionId);
			if (session) {
161
				return session.getAccessToken();
162 163 164 165 166 167
			}

			throw new Error(`Unable to find session with id: ${sessionId}`);
		}

		throw new Error(`Unable to find authentication provider with handle: ${providerId}`);
168 169
	}
}