mirror of
https://github.com/microsoft/vscode.git
synced 2026-09-11 21:51:59 +01:00
Plumbs an optional 'resource' field through IAuthenticationService get/createSession options and the authIssuers proposal so MCP authentication can request audience-restricted tokens. mainThreadMcp now forwards authDetails.resourceMetadata.resource into both calls. In the microsoft-authentication extension, the resource is threaded into MSAL's acquireTokenInteractive, acquireTokenByDeviceCode, and acquireTokenSilent. Bumps @azure/msal-node and @azure/msal-node-extensions to ^5.1.5; adapts to ServerAuthorizationCodeResponse -> AuthorizeResponse and fromNativeBroker -> fromPlatformBroker renames. Adds tests verifying that getSessions/createSession forward 'resource' to the provider, and that each MSAL flow (default, protocol handler, device code) forwards 'resource' to the underlying MSAL call.
162 lines
5.0 KiB
TypeScript
162 lines
5.0 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import { AuthenticationResult } from '@azure/msal-node';
|
|
import { Uri, LogOutputChannel, env } from 'vscode';
|
|
import { ICachedPublicClientApplication } from '../common/publicClientCache';
|
|
import { UriHandlerLoopbackClient } from '../common/loopbackClientAndOpener';
|
|
import { UriEventHandler } from '../UriEventHandler';
|
|
import { loopbackTemplate } from './loopbackTemplate';
|
|
import { Config } from '../common/config';
|
|
|
|
const DEFAULT_REDIRECT_URI = 'https://vscode.dev/redirect';
|
|
|
|
export const enum ExtensionHost {
|
|
Remote,
|
|
Local
|
|
}
|
|
|
|
interface IMsalFlowOptions {
|
|
supportsRemoteExtensionHost: boolean;
|
|
supportsUnsupportedClient: boolean;
|
|
supportsBroker: boolean;
|
|
supportsPortableMode: boolean;
|
|
}
|
|
|
|
interface IMsalFlowTriggerOptions {
|
|
cachedPca: ICachedPublicClientApplication;
|
|
authority: string;
|
|
scopes: string[];
|
|
callbackUri: Uri;
|
|
loginHint?: string;
|
|
windowHandle?: Buffer;
|
|
logger: LogOutputChannel;
|
|
uriHandler: UriEventHandler;
|
|
claims?: string;
|
|
/**
|
|
* Resource indicator (RFC 8707) for MCP-style flows. When provided, MSAL forwards
|
|
* this as the `resource` parameter to the authorization & token endpoints so the
|
|
* issued token is bound to the requested resource.
|
|
*/
|
|
resource?: string;
|
|
}
|
|
|
|
interface IMsalFlow {
|
|
readonly label: string;
|
|
readonly options: IMsalFlowOptions;
|
|
trigger(options: IMsalFlowTriggerOptions): Promise<AuthenticationResult>;
|
|
}
|
|
|
|
class DefaultLoopbackFlow implements IMsalFlow {
|
|
label = 'default';
|
|
options: IMsalFlowOptions = {
|
|
supportsRemoteExtensionHost: false,
|
|
supportsUnsupportedClient: true,
|
|
supportsBroker: true,
|
|
supportsPortableMode: true
|
|
};
|
|
|
|
async trigger({ cachedPca, authority, scopes, claims, resource, loginHint, windowHandle, logger }: IMsalFlowTriggerOptions): Promise<AuthenticationResult> {
|
|
logger.info('Trying default msal flow...');
|
|
let redirectUri: string | undefined;
|
|
if (cachedPca.isBrokerAvailable && process.platform === 'darwin') {
|
|
redirectUri = Config.macOSBrokerRedirectUri;
|
|
}
|
|
return await cachedPca.acquireTokenInteractive({
|
|
openBrowser: async (url: string) => { await env.openExternal(Uri.parse(url)); },
|
|
scopes,
|
|
authority,
|
|
successTemplate: loopbackTemplate,
|
|
errorTemplate: loopbackTemplate,
|
|
loginHint,
|
|
prompt: loginHint ? undefined : 'select_account',
|
|
windowHandle,
|
|
claims,
|
|
resource,
|
|
redirectUri
|
|
});
|
|
}
|
|
}
|
|
|
|
class UrlHandlerFlow implements IMsalFlow {
|
|
label = 'protocol handler';
|
|
options: IMsalFlowOptions = {
|
|
supportsRemoteExtensionHost: true,
|
|
supportsUnsupportedClient: false,
|
|
supportsBroker: false,
|
|
supportsPortableMode: false
|
|
};
|
|
|
|
async trigger({ cachedPca, authority, scopes, claims, resource, loginHint, windowHandle, logger, uriHandler, callbackUri }: IMsalFlowTriggerOptions): Promise<AuthenticationResult> {
|
|
logger.info('Trying protocol handler flow...');
|
|
const loopbackClient = new UriHandlerLoopbackClient(uriHandler, DEFAULT_REDIRECT_URI, callbackUri, logger);
|
|
let redirectUri: string | undefined;
|
|
if (cachedPca.isBrokerAvailable && process.platform === 'darwin') {
|
|
redirectUri = Config.macOSBrokerRedirectUri;
|
|
}
|
|
return await cachedPca.acquireTokenInteractive({
|
|
openBrowser: (url: string) => loopbackClient.openBrowser(url),
|
|
scopes,
|
|
authority,
|
|
loopbackClient,
|
|
loginHint,
|
|
prompt: loginHint ? undefined : 'select_account',
|
|
windowHandle,
|
|
claims,
|
|
resource,
|
|
redirectUri
|
|
});
|
|
}
|
|
}
|
|
|
|
class DeviceCodeFlow implements IMsalFlow {
|
|
label = 'device code';
|
|
options: IMsalFlowOptions = {
|
|
supportsRemoteExtensionHost: true,
|
|
supportsUnsupportedClient: true,
|
|
supportsBroker: false,
|
|
supportsPortableMode: true
|
|
};
|
|
|
|
async trigger({ cachedPca, authority, scopes, claims, resource, logger }: IMsalFlowTriggerOptions): Promise<AuthenticationResult> {
|
|
logger.info('Trying device code flow...');
|
|
const result = await cachedPca.acquireTokenByDeviceCode({ scopes, authority, claims, resource });
|
|
if (!result) {
|
|
throw new Error('Device code flow did not return a result');
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
export const allFlows: IMsalFlow[] = [
|
|
new DefaultLoopbackFlow(),
|
|
new UrlHandlerFlow(),
|
|
new DeviceCodeFlow()
|
|
];
|
|
|
|
export interface IMsalFlowQuery {
|
|
extensionHost: ExtensionHost;
|
|
supportedClient: boolean;
|
|
isBrokerSupported: boolean;
|
|
isPortableMode: boolean;
|
|
}
|
|
|
|
export function getMsalFlows(query: IMsalFlowQuery): IMsalFlow[] {
|
|
const flows = [];
|
|
for (const flow of allFlows) {
|
|
let useFlow: boolean = true;
|
|
if (query.extensionHost === ExtensionHost.Remote) {
|
|
useFlow &&= flow.options.supportsRemoteExtensionHost;
|
|
}
|
|
useFlow &&= flow.options.supportsBroker || !query.isBrokerSupported;
|
|
useFlow &&= flow.options.supportsUnsupportedClient || query.supportedClient;
|
|
useFlow &&= flow.options.supportsPortableMode || !query.isPortableMode;
|
|
if (useFlow) {
|
|
flows.push(flow);
|
|
}
|
|
}
|
|
return flows;
|
|
}
|