UNPKG

@codingame/monaco-vscode-extensions-service-override

Version:

VSCode public API plugged on the monaco editor - extensions service-override

608 lines (604 loc) 29.2 kB
import { __decorate, __param } from '@codingame/monaco-vscode-api/external/tslib/tslib.es6'; import { Disposable, DisposableMap } from '@codingame/monaco-vscode-api/vscode/vs/base/common/lifecycle'; import { localize } from '@codingame/monaco-vscode-api/vscode/vs/nls'; import { extHostNamedCustomer } from '../../services/extensions/common/extHostCustomers.js'; import { isAuthenticationWwwAuthenticateRequest } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/authentication/common/authentication'; import { IAuthenticationService, IAuthenticationExtensionsService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/authentication/common/authentication.service'; import { ExtHostContext, MainContext } from '@codingame/monaco-vscode-api/vscode/vs/workbench/api/common/extHost.protocol'; import { IDialogService } from '@codingame/monaco-vscode-api/vscode/vs/platform/dialogs/common/dialogs.service'; import Severity from '@codingame/monaco-vscode-api/vscode/vs/base/common/severity'; import { INotificationService } from '@codingame/monaco-vscode-api/vscode/vs/platform/notification/common/notification.service'; import { ActivationKind } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/extensions/common/extensions'; import { IExtensionService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/extensions/common/extensions.service'; import { ITelemetryService } from '@codingame/monaco-vscode-api/vscode/vs/platform/telemetry/common/telemetry.service'; import { Emitter } from '@codingame/monaco-vscode-api/vscode/vs/base/common/event'; import { IAuthenticationAccessService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/authentication/browser/authenticationAccessService.service'; import { IAuthenticationUsageService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/authentication/browser/authenticationUsageService.service'; import { getAuthenticationProviderActivationEvent } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/authentication/browser/authenticationService'; import { URI } from '@codingame/monaco-vscode-api/vscode/vs/base/common/uri'; import { IOpenerService } from '@codingame/monaco-vscode-api/vscode/vs/platform/opener/common/opener.service'; import { CancellationError } from '@codingame/monaco-vscode-api/vscode/vs/base/common/errors'; import { ILogService } from '@codingame/monaco-vscode-api/vscode/vs/platform/log/common/log.service'; import { ExtensionHostKind } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/extensions/common/extensionHostKind'; import { IURLService } from '@codingame/monaco-vscode-api/vscode/vs/platform/url/common/url.service'; import { DeferredPromise, raceTimeout } from '@codingame/monaco-vscode-api/vscode/vs/base/common/async'; import { IDynamicAuthenticationProviderStorageService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/authentication/common/dynamicAuthenticationProviderStorage.service'; import { IClipboardService } from '@codingame/monaco-vscode-api/vscode/vs/platform/clipboard/common/clipboardService.service'; import { IQuickInputService } from '@codingame/monaco-vscode-api/vscode/vs/platform/quickinput/common/quickInput.service'; import { IProductService } from '@codingame/monaco-vscode-api/vscode/vs/platform/product/common/productService.service'; class MainThreadAuthenticationProvider extends Disposable { constructor( _proxy, id, label, supportsMultipleAccounts, authorizationServers, resourceServer, onDidChangeSessionsEmitter ) { super(); this._proxy = _proxy; this.id = id; this.label = label; this.supportsMultipleAccounts = supportsMultipleAccounts; this.authorizationServers = authorizationServers; this.resourceServer = resourceServer; this.onDidChangeSessions = onDidChangeSessionsEmitter.event; } async getSessions(scopes, options) { return this._proxy.$getSessions(this.id, scopes, options); } createSession(scopes, options) { return this._proxy.$createSession(this.id, scopes, options); } async removeSession(sessionId) { await this._proxy.$removeSession(this.id, sessionId); } } class MainThreadAuthenticationProviderWithChallenges extends MainThreadAuthenticationProvider { constructor( proxy, id, label, supportsMultipleAccounts, authorizationServers, resourceServer, onDidChangeSessionsEmitter ) { super( proxy, id, label, supportsMultipleAccounts, authorizationServers, resourceServer, onDidChangeSessionsEmitter ); } getSessionsFromChallenges(constraint, options) { return this._proxy.$getSessionsFromChallenges(this.id, constraint, options); } createSessionFromChallenges(constraint, options) { return this._proxy.$createSessionFromChallenges(this.id, constraint, options); } } let MainThreadAuthentication = class MainThreadAuthentication extends Disposable { constructor( extHostContext, productService, authenticationService, authenticationExtensionsService, authenticationAccessService, authenticationUsageService, dialogService, notificationService, extensionService, telemetryService, openerService, logService, urlService, dynamicAuthProviderStorageService, clipboardService, quickInputService ) { super(); this.productService = productService; this.authenticationService = authenticationService; this.authenticationExtensionsService = authenticationExtensionsService; this.authenticationAccessService = authenticationAccessService; this.authenticationUsageService = authenticationUsageService; this.dialogService = dialogService; this.notificationService = notificationService; this.extensionService = extensionService; this.telemetryService = telemetryService; this.openerService = openerService; this.logService = logService; this.urlService = urlService; this.dynamicAuthProviderStorageService = dynamicAuthProviderStorageService; this.clipboardService = clipboardService; this.quickInputService = quickInputService; this._registrations = this._register(( new DisposableMap())); this._sentProviderUsageEvents = ( new Set()); this._suppressUnregisterEvent = false; this._sentClientIdUsageEvents = ( new Set()); this._proxy = ( extHostContext.getProxy(ExtHostContext.ExtHostAuthentication)); this._register( this.authenticationService.onDidChangeSessions(e => this._proxy.$onDidChangeAuthenticationSessions(e.providerId, e.label)) ); this._register(this.authenticationService.onDidUnregisterAuthenticationProvider(e => { if (!this._suppressUnregisterEvent) { this._proxy.$onDidUnregisterAuthenticationProvider(e.id); } })); this._register(this.authenticationExtensionsService.onDidChangeAccountPreference(e => { const providerInfo = this.authenticationService.getProvider(e.providerId); this._proxy.$onDidChangeAuthenticationSessions(providerInfo.id, providerInfo.label, e.extensionIds); })); this._register(this.dynamicAuthProviderStorageService.onDidChangeTokens(e => { this._proxy.$onDidChangeDynamicAuthProviderTokens(e.authProviderId, e.clientId, e.tokens); })); this._register(authenticationService.registerAuthenticationProviderHostDelegate({ priority: extHostContext.extensionHostKind === ExtensionHostKind.LocalWebWorker ? 0 : 1, create: async (authorizationServer, serverMetadata, resource, overrideClientId) => { const authProviderId = resource ? `${( authorizationServer.toString(true))} ${resource.resource}` : ( authorizationServer.toString(true)); const clientDetails = await this.dynamicAuthProviderStorageService.getClientRegistration(authProviderId); let clientId = overrideClientId ?? clientDetails?.clientId; const clientSecret = overrideClientId ? undefined : clientDetails?.clientSecret; let initialTokens = undefined; if (clientId) { initialTokens = await this.dynamicAuthProviderStorageService.getSessionsForDynamicAuthProvider(authProviderId, clientId); } else if (serverMetadata.client_id_metadata_document_supported) { clientId = this.productService.authClientIdMetadataUrl; } return await this._proxy.$registerDynamicAuthProvider( authorizationServer, serverMetadata, resource, clientId, clientSecret, initialTokens ); } })); } async $registerAuthenticationProvider( { id, label, supportsMultipleAccounts, resourceServer, supportedAuthorizationServers, supportsChallenges } ) { if (!this.authenticationService.declaredProviders.find(p => p.id === id)) { this.logService.warn( `Authentication provider ${id} was not declared in the Extension Manifest.` ); this.telemetryService.publicLog2("authentication.providerNotDeclared", { id }); } const emitter = ( new Emitter()); this._registrations.set(id, emitter); const supportedAuthorizationServerUris = ( (supportedAuthorizationServers ?? []).map(i => URI.revive(i))); const provider = supportsChallenges ? ( new MainThreadAuthenticationProviderWithChallenges( this._proxy, id, label, supportsMultipleAccounts, supportedAuthorizationServerUris, resourceServer ? URI.revive(resourceServer) : undefined, emitter )) : ( new MainThreadAuthenticationProvider( this._proxy, id, label, supportsMultipleAccounts, supportedAuthorizationServerUris, resourceServer ? URI.revive(resourceServer) : undefined, emitter )); this.authenticationService.registerAuthenticationProvider(id, provider); } async $unregisterAuthenticationProvider(id) { this._registrations.deleteAndDispose(id); this._suppressUnregisterEvent = true; try { this.authenticationService.unregisterAuthenticationProvider(id); } finally { this._suppressUnregisterEvent = false; } } async $ensureProvider(id) { if (!this.authenticationService.isAuthenticationProviderRegistered(id)) { return await this.extensionService.activateByEvent(getAuthenticationProviderActivationEvent(id), ActivationKind.Immediate); } } async $sendDidChangeSessions(providerId, event) { const obj = this._registrations.get(providerId); if (obj instanceof Emitter) { obj.fire(event); } } $removeSession(providerId, sessionId) { return this.authenticationService.removeSession(providerId, sessionId); } async $waitForUriHandler(expectedUri) { const deferredPromise = ( new DeferredPromise()); const disposable = this.urlService.registerHandler({ handleURL: async uri => { if (uri.scheme !== expectedUri.scheme || uri.authority !== expectedUri.authority || uri.path !== expectedUri.path) { return false; } deferredPromise.complete(uri); disposable.dispose(); return true; } }); const result = await raceTimeout(deferredPromise.p, 5 * 60 * 1000); if (!result) { throw ( new Error("Timed out waiting for URI handler")); } return await deferredPromise.p; } $showContinueNotification(message) { const yes = ( localize(2562, "Yes")); const no = ( localize(2563, "No")); const deferredPromise = ( new DeferredPromise()); let result = false; const handle = this.notificationService.prompt(Severity.Warning, message, [{ label: yes, run: () => result = true }, { label: no, run: () => result = false }]); const disposable = handle.onDidClose(() => { deferredPromise.complete(result); disposable.dispose(); }); return deferredPromise.p; } async $registerDynamicAuthenticationProvider(details) { await this.$registerAuthenticationProvider({ id: details.id, label: details.label, supportsMultipleAccounts: true, supportedAuthorizationServers: [details.authorizationServer], resourceServer: details.resourceServer }); await this.dynamicAuthProviderStorageService.storeClientRegistration(details.id, ( URI.revive(details.authorizationServer).toString(true)), details.clientId, details.clientSecret, details.label); } async $setSessionsForDynamicAuthProvider(authProviderId, clientId, sessions) { await this.dynamicAuthProviderStorageService.setSessionsForDynamicAuthProvider(authProviderId, clientId, sessions); } async $sendDidChangeDynamicProviderInfo( { providerId, clientId, authorizationServer, label, clientSecret } ) { this.logService.info( `Client ID for authentication provider ${providerId} changed to ${clientId}` ); const existing = this.dynamicAuthProviderStorageService.getInteractedProviders().find(p => p.providerId === providerId); if (!existing) { throw ( new Error( `Dynamic authentication provider ${providerId} not found. Has it been registered?` )); } await this.dynamicAuthProviderStorageService.storeClientRegistration(providerId || existing.providerId, authorizationServer ? ( URI.revive(authorizationServer).toString(true)) : existing.authorizationServer, clientId || existing.clientId, clientSecret, label || existing.label); } async loginPrompt(provider, extensionName, recreatingSession, options) { let message; const customMessage = provider.confirmation?.(extensionName, recreatingSession); if (customMessage) { message = customMessage; } else { message = recreatingSession ? ( localize( 2564, "The extension '{0}' wants you to sign in again using {1}.", extensionName, provider.label )) : ( localize( 2565, "The extension '{0}' wants to sign in using {1}.", extensionName, provider.label )); } const buttons = [{ label: ( localize(2566, "&&Allow")), run() { return true; } }]; if (options?.learnMore) { buttons.push({ label: ( localize(2567, "Learn more")), run: async () => { const result = this.loginPrompt(provider, extensionName, recreatingSession, options); await this.openerService.open(URI.revive(options.learnMore), { allowCommands: true }); return await result; } }); } const { result } = await this.dialogService.prompt({ type: Severity.Info, message, buttons, detail: options?.detail, cancelButton: true }); return result ?? false; } async continueWithIncorrectAccountPrompt(chosenAccountLabel, requestedAccountLabel) { const result = await this.dialogService.prompt({ message: ( localize(2568, "Incorrect account detected")), detail: ( localize( 2569, "The chosen account, {0}, does not match the requested account, {1}.", chosenAccountLabel, requestedAccountLabel )), type: Severity.Warning, cancelButton: true, buttons: [{ label: ( localize(2570, "Keep {0}", chosenAccountLabel)), run: () => chosenAccountLabel }, { label: ( localize(2571, "Login with {0}", requestedAccountLabel)), run: () => requestedAccountLabel }] }); if (!result.result) { throw ( new CancellationError()); } return result.result === chosenAccountLabel; } async doGetSession(providerId, scopeListOrRequest, extensionId, extensionName, options) { const authorizationServer = URI.revive(options.authorizationServer); const sessions = await this.authenticationService.getSessions(providerId, scopeListOrRequest, { account: options.account, authorizationServer }, true); const provider = this.authenticationService.getProvider(providerId); 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" )); } if (options.clearSessionPreference) { this.authenticationExtensionsService.removeAccountPreference(extensionId, providerId); } const matchingAccountPreferenceSession = options.account ? sessions[0] : this._getAccountPreference(extensionId, providerId, sessions); if (!options.forceNewSession && sessions.length) { if (matchingAccountPreferenceSession && this.authenticationAccessService.isAccessAllowed(providerId, matchingAccountPreferenceSession.account.label, extensionId)) { return matchingAccountPreferenceSession; } if (!provider.supportsMultipleAccounts && this.authenticationAccessService.isAccessAllowed(providerId, sessions[0].account.label, extensionId)) { return sessions[0]; } } if (options.createIfNone || options.forceNewSession) { let uiOptions; if (typeof options.forceNewSession === "object") { uiOptions = options.forceNewSession; } else if (typeof options.createIfNone === "object") { uiOptions = options.createIfNone; } const recreatingSession = !!(options.forceNewSession && sessions.length); const isAllowed = await this.loginPrompt(provider, extensionName, recreatingSession, uiOptions); if (!isAllowed) { throw ( new Error("User did not consent to login.")); } let session; if (sessions?.length && !options.forceNewSession) { session = provider.supportsMultipleAccounts && !options.account ? await this.authenticationExtensionsService.selectSession(providerId, extensionId, extensionName, scopeListOrRequest, sessions) : sessions[0]; } else { const accountToCreate = options.account ?? matchingAccountPreferenceSession?.account; do { session = await this.authenticationService.createSession(providerId, scopeListOrRequest, { activateImmediate: true, account: accountToCreate, authorizationServer }); } while (accountToCreate && accountToCreate.label !== session.account.label && !(await this.continueWithIncorrectAccountPrompt(session.account.label, accountToCreate.label))); } this.authenticationAccessService.updateAllowedExtensions(providerId, session.account.label, [{ id: extensionId, name: extensionName, allowed: true }]); this.authenticationExtensionsService.updateNewSessionRequests(providerId, [session]); this.authenticationExtensionsService.updateAccountPreference(extensionId, providerId, session.account); return session; } if (!matchingAccountPreferenceSession) { const validSessions = sessions.filter( session => this.authenticationAccessService.isAccessAllowed(providerId, session.account.label, extensionId) ); if (validSessions.length === 1) { return validSessions[0]; } } if (!options.silent) { sessions.length ? this.authenticationExtensionsService.requestSessionAccess(providerId, extensionId, extensionName, scopeListOrRequest, sessions) : await this.authenticationExtensionsService.requestNewSession(providerId, scopeListOrRequest, extensionId, extensionName); } return undefined; } async $getSession(providerId, scopeListOrRequest, extensionId, extensionName, options) { const scopes = isAuthenticationWwwAuthenticateRequest(scopeListOrRequest) ? scopeListOrRequest.fallbackScopes : scopeListOrRequest; if (scopes) { this.sendClientIdUsageTelemetry(extensionId, providerId, scopes); } const session = await this.doGetSession(providerId, scopeListOrRequest, extensionId, extensionName, options); if (session) { this.sendProviderUsageTelemetry(extensionId, providerId); this.authenticationUsageService.addAccountUsage( providerId, session.account.label, session.scopes, extensionId, extensionName ); } return session; } async $getAccounts(providerId) { const accounts = await this.authenticationService.getAccounts(providerId); return accounts; } sendClientIdUsageTelemetry(extensionId, providerId, scopes) { const containsVSCodeClientIdScope = ( scopes.some(scope => scope.startsWith("VSCODE_CLIENT_ID:"))); const key = `${extensionId}|${providerId}|${containsVSCodeClientIdScope}`; if (( this._sentClientIdUsageEvents.has(key))) { return; } this._sentClientIdUsageEvents.add(key); if (containsVSCodeClientIdScope) { this.telemetryService.publicLog2("authentication.clientIdUsage", { extensionId }); } } sendProviderUsageTelemetry(extensionId, providerId) { const key = `${extensionId}|${providerId}`; if (( this._sentProviderUsageEvents.has(key))) { return; } this._sentProviderUsageEvents.add(key); this.telemetryService.publicLog2("authentication.providerUsage", { providerId, extensionId }); } _getAccountPreference(extensionId, providerId, sessions) { if (sessions.length === 0) { return undefined; } const accountNamePreference = this.authenticationExtensionsService.getAccountPreference(extensionId, providerId); if (accountNamePreference) { const session = sessions.find(session => session.account.label === accountNamePreference); return session; } return undefined; } async $showDeviceCodeModal(userCode, verificationUri) { const { result } = await this.dialogService.prompt({ type: Severity.Info, message: ( localize(2572, "Device Code Authentication")), detail: ( localize( 2573, "Your code: {0}\n\nTo complete authentication, navigate to {1} and enter the code above.", userCode, verificationUri )), buttons: [{ label: ( localize(2574, "Copy & Continue")), run: () => true }], cancelButton: true }); if (result) { try { await this.clipboardService.writeText(userCode); return await this.openerService.open(( URI.parse(verificationUri))); } catch (error) { this.notificationService.error(( localize(2575, "Failed to open {0}", verificationUri))); } } return false; } async $promptForClientRegistration(authorizationServerUrl) { const redirectUrls = "http://127.0.0.1:33418\nhttps://vscode.dev/redirect"; const result = await this.dialogService.prompt({ type: Severity.Info, message: ( localize(2576, "Dynamic Client Registration not supported")), detail: ( localize( 2577, "The authorization server '{0}' does not support automatic client registration. Do you want to proceed by manually providing a client registration (client ID)?\n\nNote: When registering your OAuth application, make sure to include these redirect URIs:\n{1}", authorizationServerUrl, redirectUrls )), buttons: [{ label: ( localize(2578, "Copy URIs & Proceed")), run: async () => { try { await this.clipboardService.writeText(redirectUrls); } catch (error) { this.notificationService.error(( localize(2579, "Failed to copy redirect URIs to clipboard."))); } return true; } }], cancelButton: { label: ( localize(2580, "Cancel")), run: () => false } }); if (!result) { return undefined; } const sharedTitle = ( localize(2581, "Add Client Registration Details")); const clientId = await this.quickInputService.input({ title: sharedTitle, prompt: ( localize( 2582, "Enter an existing client ID that has been registered with the following redirect URIs: http://127.0.0.1:33418, https://vscode.dev/redirect" )), placeHolder: ( localize(2583, "OAuth client ID (azye39d...)")), ignoreFocusLost: true, validateInput: async value => { if (!value || value.trim().length === 0) { return localize(2584, "Client ID is required"); } return undefined; } }); if (!clientId || clientId.trim().length === 0) { return undefined; } const clientSecret = await this.quickInputService.input({ title: sharedTitle, prompt: ( localize( 2585, "(optional) Enter an existing client secret associated with the client id '{0}' or leave this field blank", clientId )), placeHolder: ( localize(2586, "OAuth client secret (wer32o50f...) or leave it blank")), password: true, ignoreFocusLost: true }); return { clientId: clientId.trim(), clientSecret: clientSecret?.trim() || undefined }; } }; MainThreadAuthentication = __decorate( [extHostNamedCustomer(MainContext.MainThreadAuthentication), ( __param(1, IProductService)), ( __param(2, IAuthenticationService)), ( __param(3, IAuthenticationExtensionsService)), ( __param(4, IAuthenticationAccessService)), ( __param(5, IAuthenticationUsageService)), ( __param(6, IDialogService)), ( __param(7, INotificationService)), ( __param(8, IExtensionService)), ( __param(9, ITelemetryService)), ( __param(10, IOpenerService)), ( __param(11, ILogService)), ( __param(12, IURLService)), ( __param(13, IDynamicAuthenticationProviderStorageService)), ( __param(14, IClipboardService)), ( __param(15, IQuickInputService))], MainThreadAuthentication ); export { MainThreadAuthentication };