Path: blob/main/extensions/microsoft-authentication/src/node/flows.ts
5226 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { AuthenticationResult } from '@azure/msal-node';6import { Uri, LogOutputChannel, env } from 'vscode';7import { ICachedPublicClientApplication } from '../common/publicClientCache';8import { UriHandlerLoopbackClient } from '../common/loopbackClientAndOpener';9import { UriEventHandler } from '../UriEventHandler';10import { loopbackTemplate } from './loopbackTemplate';11import { Config } from '../common/config';1213const DEFAULT_REDIRECT_URI = 'https://vscode.dev/redirect';1415export const enum ExtensionHost {16Remote,17Local18}1920interface IMsalFlowOptions {21supportsRemoteExtensionHost: boolean;22supportsUnsupportedClient: boolean;23supportsBroker: boolean;24supportsPortableMode: boolean;25}2627interface IMsalFlowTriggerOptions {28cachedPca: ICachedPublicClientApplication;29authority: string;30scopes: string[];31callbackUri: Uri;32loginHint?: string;33windowHandle?: Buffer;34logger: LogOutputChannel;35uriHandler: UriEventHandler;36claims?: string;37}3839interface IMsalFlow {40readonly label: string;41readonly options: IMsalFlowOptions;42trigger(options: IMsalFlowTriggerOptions): Promise<AuthenticationResult>;43}4445class DefaultLoopbackFlow implements IMsalFlow {46label = 'default';47options: IMsalFlowOptions = {48supportsRemoteExtensionHost: false,49supportsUnsupportedClient: true,50supportsBroker: true,51supportsPortableMode: true52};5354async trigger({ cachedPca, authority, scopes, claims, loginHint, windowHandle, logger }: IMsalFlowTriggerOptions): Promise<AuthenticationResult> {55logger.info('Trying default msal flow...');56let redirectUri: string | undefined;57if (cachedPca.isBrokerAvailable && process.platform === 'darwin') {58redirectUri = Config.macOSBrokerRedirectUri;59}60return await cachedPca.acquireTokenInteractive({61openBrowser: async (url: string) => { await env.openExternal(Uri.parse(url)); },62scopes,63authority,64successTemplate: loopbackTemplate,65errorTemplate: loopbackTemplate,66loginHint,67prompt: loginHint ? undefined : 'select_account',68windowHandle,69claims,70redirectUri71});72}73}7475class UrlHandlerFlow implements IMsalFlow {76label = 'protocol handler';77options: IMsalFlowOptions = {78supportsRemoteExtensionHost: true,79supportsUnsupportedClient: false,80supportsBroker: false,81supportsPortableMode: false82};8384async trigger({ cachedPca, authority, scopes, claims, loginHint, windowHandle, logger, uriHandler, callbackUri }: IMsalFlowTriggerOptions): Promise<AuthenticationResult> {85logger.info('Trying protocol handler flow...');86const loopbackClient = new UriHandlerLoopbackClient(uriHandler, DEFAULT_REDIRECT_URI, callbackUri, logger);87let redirectUri: string | undefined;88if (cachedPca.isBrokerAvailable && process.platform === 'darwin') {89redirectUri = Config.macOSBrokerRedirectUri;90}91return await cachedPca.acquireTokenInteractive({92openBrowser: (url: string) => loopbackClient.openBrowser(url),93scopes,94authority,95loopbackClient,96loginHint,97prompt: loginHint ? undefined : 'select_account',98windowHandle,99claims,100redirectUri101});102}103}104105class DeviceCodeFlow implements IMsalFlow {106label = 'device code';107options: IMsalFlowOptions = {108supportsRemoteExtensionHost: true,109supportsUnsupportedClient: true,110supportsBroker: false,111supportsPortableMode: true112};113114async trigger({ cachedPca, authority, scopes, claims, logger }: IMsalFlowTriggerOptions): Promise<AuthenticationResult> {115logger.info('Trying device code flow...');116const result = await cachedPca.acquireTokenByDeviceCode({ scopes, authority, claims });117if (!result) {118throw new Error('Device code flow did not return a result');119}120return result;121}122}123124const allFlows: IMsalFlow[] = [125new DefaultLoopbackFlow(),126new UrlHandlerFlow(),127new DeviceCodeFlow()128];129130export interface IMsalFlowQuery {131extensionHost: ExtensionHost;132supportedClient: boolean;133isBrokerSupported: boolean;134isPortableMode: boolean;135}136137export function getMsalFlows(query: IMsalFlowQuery): IMsalFlow[] {138const flows = [];139for (const flow of allFlows) {140let useFlow: boolean = true;141if (query.extensionHost === ExtensionHost.Remote) {142useFlow &&= flow.options.supportsRemoteExtensionHost;143}144useFlow &&= flow.options.supportsBroker || !query.isBrokerSupported;145useFlow &&= flow.options.supportsUnsupportedClient || query.supportedClient;146useFlow &&= flow.options.supportsPortableMode || !query.isPortableMode;147if (useFlow) {148flows.push(flow);149}150}151return flows;152}153154155