UNPKG

botbuilder

Version:

Bot Builder is a framework for building rich bots on virtually any platform.

1,126 lines (1,033 loc) • 96.6 kB
/** * @module botbuilder */ /** * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ import * as z from 'zod'; import { BotFrameworkHttpAdapter } from './botFrameworkHttpAdapter'; import { ConnectorClientBuilder, Request, Response, ResponseT, WebRequest, WebResponse } from './interfaces'; import { HttpClient, RequestPolicyFactory, userAgentPolicy } from '@azure/ms-rest-js'; import { INodeBufferT, INodeSocketT, LogicT } from './zod'; import { arch, release, type } from 'os'; import { delay, retry } from 'botbuilder-stdlib'; import { validateAndFixActivity } from './activityValidator'; import { Activity, ActivityEventNames, ActivityTypes, BotAdapter, BotCallbackHandlerKey, CallerIdConstants, ChannelAccount, Channels, ConversationParameters, ConversationReference, ConversationsResult, CoreAppCredentials, DeliveryModes, ExpectedReplies, ExtendedUserTokenProvider, INVOKE_RESPONSE_KEY, InvokeResponse, ResourceResponse, StatusCodes, TokenResponse, TurnContext, } from 'botbuilder-core'; import { AppCredentials, AuthenticationConfiguration, AuthenticationConstants, AuthenticationError, CertificateAppCredentials, ChannelValidation, Claim, ClaimsIdentity, ConnectorClient, ConnectorClientOptions, EmulatorApiClient, GovernmentChannelValidation, GovernmentConstants, JwtTokenValidation, MicrosoftAppCredentials, SignInUrlResponse, SimpleCredentialProvider, SkillValidation, TokenApiClient, TokenApiModels, TokenExchangeRequest, TokenStatus, } from 'botframework-connector'; import { INodeBuffer, INodeSocket, IReceiveRequest, ISocket, IStreamingTransportServer, NamedPipeServer, NodeWebSocketFactory, NodeWebSocketFactoryBase, RequestHandler, StreamingResponse, WebSocketServer, } from 'botframework-streaming'; import { defaultPipeName, GET, POST, MESSAGES_PATH, StreamingHttpClient, TokenResolver, VERSION_PATH, } from './streaming'; /** * @deprecated Use `CloudAdapter` with `ConfigurationBotFrameworkAuthentication` instead to configure bot runtime. * Contains settings used to configure a [BotFrameworkAdapter](xref:botbuilder.BotFrameworkAdapter) instance. */ export interface BotFrameworkAdapterSettings { /** * The ID assigned to your bot in the [Bot Framework Portal](https://dev.botframework.com/). */ appId: string; /** * The password assigned to your bot in the [Bot Framework Portal](https://dev.botframework.com/). */ appPassword: string; /** * Optional. The tenant to acquire the bot-to-channel token from. */ channelAuthTenant?: string; /** * Optional. The OAuth API endpoint for your bot to use. */ oAuthEndpoint?: string; /** * Optional. The OpenID Metadata endpoint for your bot to use. */ openIdMetadata?: string; /** * Optional. The channel service option for this bot to validate connections from Azure or other channel locations. */ channelService?: string; /** * Optional. Used to pass in a NodeWebSocketFactoryBase instance. */ webSocketFactory?: NodeWebSocketFactoryBase; /** * Optional. Certificate thumbprint to authenticate the appId against AAD. */ certificateThumbprint?: string; /** * Optional. Certificate key to authenticate the appId against AAD. */ certificatePrivateKey?: string; /** * Optional. Used to require specific endorsements and verify claims. Recommended for Skills. */ authConfig?: AuthenticationConfiguration; /** * Optional. Used when creating new ConnectorClients. */ clientOptions?: ConnectorClientOptions; } // Retrieve additional information, i.e., host operating system, host OS release, architecture, Node.js version const ARCHITECTURE = arch(); const TYPE = type(); const RELEASE = release(); const NODE_VERSION = process.version; const pjson: Record<'version', string> = require('../package.json'); // eslint-disable-line @typescript-eslint/no-var-requires export const USER_AGENT = `Microsoft-BotFramework/3.1 BotBuilder/${pjson.version} (Node.js,Version=${NODE_VERSION}; ${TYPE} ${RELEASE}; ${ARCHITECTURE})`; const OAUTH_ENDPOINT = 'https://api.botframework.com'; const US_GOV_OAUTH_ENDPOINT = 'https://api.botframework.azure.us'; /** * A [BotAdapter](xref:botbuilder-core.BotAdapter) that can connect a bot to a service endpoint. * Implements [IUserTokenProvider](xref:botbuilder-core.IUserTokenProvider). * * @remarks * The bot adapter encapsulates authentication processes and sends activities to and receives * activities from the Bot Connector Service. When your bot receives an activity, the adapter * creates a turn context object, passes it to your bot application logic, and sends responses * back to the user's channel. * * The adapter processes and directs incoming activities in through the bot middleware pipeline to * your bot logic and then back out again. As each activity flows in and out of the bot, each * piece of middleware can inspect or act upon the activity, both before and after the bot logic runs. * Use the [use](xref:botbuilder-core.BotAdapter.use) method to add [Middleware](xref:botbuilder-core.Middleware) * objects to your adapter's middleware collection. * * For more information, see the articles on * [How bots work](https://docs.microsoft.com/azure/bot-service/bot-builder-basics) and * [Middleware](https://docs.microsoft.com/azure/bot-service/bot-builder-concept-middleware). * * For example: * ```JavaScript * const { BotFrameworkAdapter } = require('botbuilder'); * * const adapter = new BotFrameworkAdapter({ * appId: process.env.MicrosoftAppId, * appPassword: process.env.MicrosoftAppPassword * }); * * adapter.onTurnError = async (context, error) => { * // Catch-all logic for errors. * }; * ``` */ /** * @deprecated Use `CloudAdapter` instead. */ export class BotFrameworkAdapter extends BotAdapter implements BotFrameworkHttpAdapter, ConnectorClientBuilder, ExtendedUserTokenProvider, RequestHandler { // These keys are public to permit access to the keys from the adapter when it's a being // from library that does not have access to static properties off of BotFrameworkAdapter. // E.g. botbuilder-dialogs readonly TokenApiClientCredentialsKey: symbol = Symbol('TokenApiClientCredentials'); protected readonly credentials: AppCredentials; protected readonly credentialsProvider: SimpleCredentialProvider; protected readonly settings: BotFrameworkAdapterSettings; private isEmulatingOAuthCards: boolean; // Streaming-specific properties: private logic: (context: TurnContext) => Promise<void>; private streamingServer: IStreamingTransportServer; private webSocketFactory: NodeWebSocketFactoryBase; private authConfiguration: AuthenticationConfiguration; private namedPipeName?: string; /** * Creates a new instance of the [BotFrameworkAdapter](xref:botbuilder.BotFrameworkAdapter) class. * * @param settings Optional. The settings to use for this adapter instance. * * @remarks * If the `settings` parameter does not include * [channelService](xref:botbuilder.BotFrameworkAdapterSettings.channelService) or * [openIdMetadata](xref:botbuilder.BotFrameworkAdapterSettings.openIdMetadata) values, the * constructor checks the process' environment variables for these values. These values may be * set when a bot is provisioned on Azure and if so are required for the bot to work properly * in the global cloud or in a national cloud. * * The [BotFrameworkAdapterSettings](xref:botbuilder.BotFrameworkAdapterSettings) class defines * the available adapter settings. */ constructor(settings?: Partial<BotFrameworkAdapterSettings>) { super(); this.settings = { appId: '', appPassword: '', ...settings }; // If settings.certificateThumbprint & settings.certificatePrivateKey are provided, // use CertificateAppCredentials. if (this.settings.certificateThumbprint && this.settings.certificatePrivateKey) { this.credentials = new CertificateAppCredentials( this.settings.appId, settings.certificateThumbprint, settings.certificatePrivateKey, this.settings.channelAuthTenant ); this.credentialsProvider = new SimpleCredentialProvider(this.credentials.appId, ''); } else { this.credentials = new MicrosoftAppCredentials( this.settings.appId, this.settings.appPassword || '', this.settings.channelAuthTenant ); this.credentialsProvider = new SimpleCredentialProvider( this.credentials.appId, this.settings.appPassword || '' ); } this.isEmulatingOAuthCards = false; // If no channelService or openIdMetadata values were passed in the settings, check the process' Environment Variables for values. // These values may be set when a bot is provisioned on Azure and if so are required for the bot to properly work in Public Azure or a National Cloud. this.settings.channelService = this.settings.channelService || process.env[AuthenticationConstants.ChannelService]; this.settings.openIdMetadata = this.settings.openIdMetadata || process.env[AuthenticationConstants.BotOpenIdMetadataKey]; this.authConfiguration = this.settings.authConfig || new AuthenticationConfiguration(); if (this.settings.openIdMetadata) { ChannelValidation.OpenIdMetadataEndpoint = this.settings.openIdMetadata; GovernmentChannelValidation.OpenIdMetadataEndpoint = this.settings.openIdMetadata; } if (JwtTokenValidation.isGovernment(this.settings.channelService)) { this.credentials.oAuthEndpoint = GovernmentConstants.ToChannelFromBotLoginUrl; this.credentials.oAuthScope = GovernmentConstants.ToChannelFromBotOAuthScope; } // If a NodeWebSocketFactoryBase was passed in, set it on the BotFrameworkAdapter. if (this.settings.webSocketFactory) { this.webSocketFactory = this.settings.webSocketFactory; } // Relocate the tenantId field used by MS Teams to a new location (from channelData to conversation) // This will only occur on activities from teams that include tenant info in channelData but NOT in conversation, // thus should be future friendly. However, once the the transition is complete. we can remove this. this.use( async (context, next): Promise<void> => { if ( context.activity.channelId === 'msteams' && context.activity && context.activity.conversation && !context.activity.conversation.tenantId && context.activity.channelData && context.activity.channelData.tenant ) { context.activity.conversation.tenantId = context.activity.channelData.tenant.id; } await next(); } ); } /** * Used in streaming contexts to check if the streaming connection is still open for the bot to send activities. * * @returns True if the streaming connection is open, otherwise false. */ get isStreamingConnectionOpen(): boolean { return this.streamingServer?.isConnected ?? false; } /** * Asynchronously resumes a conversation with a user, possibly after some time has gone by. * * @param reference A reference to the conversation to continue. * @param oAuthScope The intended recipient of any sent activities. * @param logic The asynchronous method to call after the adapter middleware runs. * * @remarks * This is often referred to as a _proactive notification_, the bot can proactively * send a message to a conversation or user without waiting for an incoming message. * For example, a bot can use this method to send notifications or coupons to a user. * * To send a proactive message: * 1. Save a copy of a [ConversationReference](xref:botframework-schema.ConversationReference) * from an incoming activity. For example, you can store the conversation reference in a database. * 1. Call this method to resume the conversation at a later time. Use the saved reference to access the conversation. * 1. On success, the adapter generates a [TurnContext](xref:botbuilder-core.TurnContext) object and calls the `logic` function handler. * Use the `logic` function to send the proactive message. * * To copy the reference from any incoming activity in the conversation, use the * [TurnContext.getConversationReference](xref:botbuilder-core.TurnContext.getConversationReference) method. * * This method is similar to the [processActivity](xref:botbuilder.BotFrameworkAdapter.processActivity) method. * The adapter creates a [TurnContext](xref:botbuilder-core.TurnContext) and routes it through * its middleware before calling the `logic` handler. The created activity will have a * [type](xref:botframework-schema.Activity.type) of 'event' and a * [name](xref:botframework-schema.Activity.name) of 'continueConversation'. * * For example: * ```JavaScript * server.post('/api/notifyUser', async (req, res) => { * // Lookup previously saved conversation reference. * const reference = await findReference(req.body.refId); * * // Proactively notify the user. * if (reference) { * await adapter.continueConversation(reference, async (context) => { * await context.sendActivity(req.body.message); * }); * res.send(200); * } else { * res.send(404); * } * }); * ``` */ async continueConversation( reference: Partial<ConversationReference>, logic: (context: TurnContext) => Promise<void> ): Promise<void>; /** * Asynchronously resumes a conversation with a user, possibly after some time has gone by. * * @param reference [ConversationReference](xref:botframework-schema.ConversationReference) of the conversation to continue. * @param oAuthScope The intended recipient of any sent activities or the function to call to continue the conversation. * @param logic Optional. The asynchronous method to call after the adapter middleware runs. */ async continueConversation( reference: Partial<ConversationReference>, oAuthScope: string, logic: (context: TurnContext) => Promise<void> ): Promise<void>; /** * @internal */ async continueConversation( reference: Partial<ConversationReference>, oAuthScopeOrlogic: string | ((context: TurnContext) => Promise<void>), maybeLogic?: (context: TurnContext) => Promise<void> ): Promise<void> { let audience: string; if (LogicT.check(oAuthScopeOrlogic)) { // Because the OAuthScope parameter was not provided, get the correct value via the channelService. // In this scenario, the ConnectorClient for the continued conversation can only communicate with // official channels, not with other bots. audience = JwtTokenValidation.isGovernment(this.settings.channelService) ? GovernmentConstants.ToChannelFromBotOAuthScope : AuthenticationConstants.ToChannelFromBotOAuthScope; } else { audience = z.string().parse(oAuthScopeOrlogic); } const logic = LogicT.check(oAuthScopeOrlogic) ? oAuthScopeOrlogic : LogicT.parse(maybeLogic); let credentials = this.credentials; // For authenticated flows (where the bot has an AppId), the ConversationReference's serviceUrl needs // to be trusted for the bot to acquire a token when sending activities to the conversation. // For anonymous flows, the serviceUrl should not be trusted. if (credentials.appId) { // If the provided OAuthScope doesn't match the current one on the instance's credentials, create // a new AppCredentials with the correct OAuthScope. if (credentials.oAuthScope !== audience) { // The BotFrameworkAdapter JavaScript implementation supports one Bot per instance, so get // the botAppId from the credentials. credentials = await this.buildCredentials(credentials.appId, audience); } } const connectorClient = this.createConnectorClientInternal(reference.serviceUrl, credentials); const request = TurnContext.applyConversationReference( { type: ActivityTypes.Event, name: ActivityEventNames.ContinueConversation }, reference, true ); const context = this.createContext(request); context.turnState.set(this.OAuthScopeKey, audience); context.turnState.set(this.ConnectorClientKey, connectorClient); await this.runMiddleware(context, logic); } /** * Asynchronously creates and starts a conversation with a user on a channel. * * @param {Partial<ConversationReference>} reference A reference for the conversation to create. * @param {(context: TurnContext) => Promise<void>} logic The asynchronous method to call after the adapter middleware runs. * @returns {Promise<void>} a promise representing the asynchronous operation * * @summary * To use this method, you need both the bot's and the user's account information on a channel. * The Bot Connector service supports the creating of group conversations; however, this * method and most channels only support initiating a direct message (non-group) conversation. * * To create and start a new conversation: * 1. Get a copy of a [ConversationReference](xref:botframework-schema.ConversationReference) from an incoming activity. * 1. Set the [user](xref:botframework-schema.ConversationReference.user) property to the * [ChannelAccount](xref:botframework-schema.ChannelAccount) value for the intended recipient. * 1. Call this method to request that the channel create a new conversation with the specified user. * 1. On success, the adapter generates a turn context and calls the `logic` function handler. * * To get the initial reference, use the * [TurnContext.getConversationReference](xref:botbuilder-core.TurnContext.getConversationReference) * method on any incoming activity in the conversation. * * If the channel establishes the conversation, the generated event activity's * [conversation](xref:botframework-schema.Activity.conversation) property will contain the * ID of the new conversation. * * This method is similar to the [processActivity](xref:botbuilder.BotFrameworkAdapter.processActivity) method. * The adapter creates a [TurnContext](xref:botbuilder-core.TurnContext) and routes it through * middleware before calling the `logic` handler. The created activity will have a * [type](xref:botframework-schema.Activity.type) of 'event' and a * [name](xref:botframework-schema.Activity.name) of 'createConversation'. * * For example: * ```JavaScript * // Get group members conversation reference * const reference = TurnContext.getConversationReference(context.activity); * * // ... * // Start a new conversation with the user * await adapter.createConversation(reference, async (ctx) => { * await ctx.sendActivity(`Hi (in private)`); * }); * ``` */ createConversation( reference: Partial<ConversationReference>, logic: (context: TurnContext) => Promise<void> ): Promise<void>; /** * Asynchronously creates and starts a conversation with a user on a channel. * * @param {Partial<ConversationReference>} reference A reference for the conversation to create. * @param {Partial<ConversationParameters>} parameters Parameters used when creating the conversation * @param {(context: TurnContext) => Promise<void>} logic The asynchronous method to call after the adapter middleware runs. * @returns {Promise<void>} a promise representing the asynchronous operation */ createConversation( reference: Partial<ConversationReference>, parameters: Partial<ConversationParameters>, logic: (context: TurnContext) => Promise<void> ): Promise<void>; /** * @internal */ async createConversation( reference: Partial<ConversationReference>, parametersOrLogic: Partial<ConversationParameters> | ((context: TurnContext) => Promise<void>), maybeLogic?: (context: TurnContext) => Promise<void> ): Promise<void> { if (!reference.serviceUrl) { throw new Error('BotFrameworkAdapter.createConversation(): missing serviceUrl.'); } const parameters = LogicT.check(parametersOrLogic) ? {} : parametersOrLogic; const logic = LogicT.check(parametersOrLogic) ? parametersOrLogic : LogicT.parse(maybeLogic); // Create conversation parameters, taking care to provide defaults that can be // overridden by passed in parameters const conversationParameters = Object.assign( {}, { bot: reference.bot, members: [reference.user], isGroup: false, activity: null, channelData: null, }, parameters ); const client = this.createConnectorClient(reference.serviceUrl); // Mix in the tenant ID if specified. This is required for MS Teams. if (reference.conversation && reference.conversation.tenantId) { // Putting tenantId in channelData is a temporary solution while we wait for the Teams API to be updated conversationParameters.channelData = { tenant: { id: reference.conversation.tenantId } }; // Permanent solution is to put tenantId in parameters.tenantId conversationParameters.tenantId = reference.conversation.tenantId; } const response = await client.conversations.createConversation(conversationParameters); // Initialize request and copy over new conversation ID and updated serviceUrl. const request = TurnContext.applyConversationReference( { type: ActivityTypes.Event, name: ActivityEventNames.CreateConversation }, reference, true ); request.conversation = { id: response.id, isGroup: conversationParameters.isGroup, conversationType: null, tenantId: reference.conversation.tenantId, name: null, }; request.channelData = conversationParameters.channelData; if (response.serviceUrl) { request.serviceUrl = response.serviceUrl; } // Create context and run middleware const context = this.createContext(request); await this.runMiddleware(context, logic); } /** * Asynchronously deletes an existing activity. * * This interface supports the framework and is not intended to be called directly for your code. * Use [TurnContext.deleteActivity](xref:botbuilder-core.TurnContext.deleteActivity) to delete * an activity from your bot code. * * @param context The context object for the turn. * @param reference Conversation reference information for the activity to delete. * * @remarks * Not all channels support this operation. For channels that don't, this call may throw an exception. */ async deleteActivity(context: TurnContext, reference: Partial<ConversationReference>): Promise<void> { if (!reference.serviceUrl) { throw new Error('BotFrameworkAdapter.deleteActivity(): missing serviceUrl'); } if (!reference.conversation || !reference.conversation.id) { throw new Error('BotFrameworkAdapter.deleteActivity(): missing conversation or conversation.id'); } if (!reference.activityId) { throw new Error('BotFrameworkAdapter.deleteActivity(): missing activityId'); } const client: ConnectorClient = this.getOrCreateConnectorClient( context, reference.serviceUrl, this.credentials ); await client.conversations.deleteActivity(reference.conversation.id, reference.activityId); } /** * Asynchronously removes a member from the current conversation. * * @param context The context object for the turn. * @param memberId The ID of the member to remove from the conversation. * * @remarks * Remove a member's identity information from the conversation. * * Not all channels support this operation. For channels that don't, this call may throw an exception. */ async deleteConversationMember(context: TurnContext, memberId: string): Promise<void> { if (!context.activity.serviceUrl) { throw new Error('BotFrameworkAdapter.deleteConversationMember(): missing serviceUrl'); } if (!context.activity.conversation || !context.activity.conversation.id) { throw new Error('BotFrameworkAdapter.deleteConversationMember(): missing conversation or conversation.id'); } const serviceUrl: string = context.activity.serviceUrl; const conversationId: string = context.activity.conversation.id; const client: ConnectorClient = this.getOrCreateConnectorClient(context, serviceUrl, this.credentials); await client.conversations.deleteConversationMember(conversationId, memberId); } /** * Asynchronously lists the members of a given activity. * * @param context The context object for the turn. * @param activityId Optional. The ID of the activity to get the members of. If not specified, the current activity ID is used. * * @returns An array of [ChannelAccount](xref:botframework-schema.ChannelAccount) objects for * the users involved in a given activity. * * @remarks * Returns an array of [ChannelAccount](xref:botframework-schema.ChannelAccount) objects for * the users involved in a given activity. * * This is different from [getConversationMembers](xref:botbuilder.BotFrameworkAdapter.getConversationMembers) * in that it will return only those users directly involved in the activity, not all members of the conversation. */ async getActivityMembers(context: TurnContext, activityId?: string): Promise<ChannelAccount[]> { if (!activityId) { activityId = context.activity.id; } if (!context.activity.serviceUrl) { throw new Error('BotFrameworkAdapter.getActivityMembers(): missing serviceUrl'); } if (!context.activity.conversation || !context.activity.conversation.id) { throw new Error('BotFrameworkAdapter.getActivityMembers(): missing conversation or conversation.id'); } if (!activityId) { throw new Error( 'BotFrameworkAdapter.getActivityMembers(): missing both activityId and context.activity.id' ); } const serviceUrl: string = context.activity.serviceUrl; const conversationId: string = context.activity.conversation.id; const client: ConnectorClient = this.getOrCreateConnectorClient(context, serviceUrl, this.credentials); return await client.conversations.getActivityMembers(conversationId, activityId); } /** * Asynchronously lists the members of the current conversation. * * @param context The context object for the turn. * * @returns An array of [ChannelAccount](xref:botframework-schema.ChannelAccount) objects for * all users currently involved in a conversation. * * @remarks * Returns an array of [ChannelAccount](xref:botframework-schema.ChannelAccount) objects for * all users currently involved in a conversation. * * This is different from [getActivityMembers](xref:botbuilder.BotFrameworkAdapter.getActivityMembers) * in that it will return all members of the conversation, not just those directly involved in a specific activity. */ async getConversationMembers(context: TurnContext): Promise<ChannelAccount[]> { if (!context.activity.serviceUrl) { throw new Error('BotFrameworkAdapter.getConversationMembers(): missing serviceUrl'); } if (!context.activity.conversation || !context.activity.conversation.id) { throw new Error('BotFrameworkAdapter.getConversationMembers(): missing conversation or conversation.id'); } const serviceUrl: string = context.activity.serviceUrl; const conversationId: string = context.activity.conversation.id; const client: ConnectorClient = this.getOrCreateConnectorClient(context, serviceUrl, this.credentials); return await client.conversations.getConversationMembers(conversationId); } /** * For the specified channel, asynchronously gets a page of the conversations in which this bot has participated. * * @param contextOrServiceUrl The URL of the channel server to query or a * [TurnContext](xref:botbuilder-core.TurnContext) object from a conversation on the channel. * @param continuationToken Optional. The continuation token from the previous page of results. * Omit this parameter or use `undefined` to retrieve the first page of results. * * @returns A [ConversationsResult](xref:botframework-schema.ConversationsResult) object containing a page of results * and a continuation token. * * @remarks * The the return value's [conversations](xref:botframework-schema.ConversationsResult.conversations) property contains a page of * [ConversationMembers](xref:botframework-schema.ConversationMembers) objects. Each object's * [id](xref:botframework-schema.ConversationMembers.id) is the ID of a conversation in which the bot has participated on this channel. * This method can be called from outside the context of a conversation, as only the bot's service URL and credentials are required. * * The channel batches results in pages. If the result's * [continuationToken](xref:botframework-schema.ConversationsResult.continuationToken) property is not empty, then * there are more pages to get. Use the returned token to get the next page of results. * If the `contextOrServiceUrl` parameter is a [TurnContext](xref:botbuilder-core.TurnContext), the URL of the channel server is * retrieved from * `contextOrServiceUrl`.[activity](xref:botbuilder-core.TurnContext.activity).[serviceUrl](xref:botframework-schema.Activity.serviceUrl). */ async getConversations( contextOrServiceUrl: TurnContext | string, continuationToken?: string ): Promise<ConversationsResult> { let client: ConnectorClient; if (typeof contextOrServiceUrl === 'object') { const context: TurnContext = contextOrServiceUrl as TurnContext; client = this.getOrCreateConnectorClient(context, context.activity.serviceUrl, this.credentials); } else { client = this.createConnectorClient(contextOrServiceUrl as string); } return await client.conversations.getConversations( continuationToken ? { continuationToken: continuationToken } : undefined ); } /** * Asynchronously attempts to retrieve the token for a user that's in a login flow. * * @param context The context object for the turn. * @param connectionName The name of the auth connection to use. * @param magicCode Optional. The validation code the user entered. * @param oAuthAppCredentials AppCredentials for OAuth. * * @returns A [TokenResponse](xref:botframework-schema.TokenResponse) object that contains the user token. */ async getUserToken(context: TurnContext, connectionName: string, magicCode?: string): Promise<TokenResponse>; async getUserToken( context: TurnContext, connectionName: string, magicCode?: string, oAuthAppCredentials?: CoreAppCredentials ): Promise<TokenResponse>; /** * Asynchronously attempts to retrieve the token for a user that's in a login flow. * * @param context The [TurnContext](xref:botbuilder-core.TurnContext) for the turn. * @param connectionName The name of the auth connection to use. * @param magicCode Optional. The validation code the user entered. * @param oAuthAppCredentials Optional. [AppCredentials](xref:botframework-connector.AppCredentials) for OAuth. * * @returns A [TokenResponse](xref:botframework-schema.TokenResponse) object that contains the user token. */ async getUserToken( context: TurnContext, connectionName: string, magicCode?: string, oAuthAppCredentials?: AppCredentials ): Promise<TokenResponse> { if (!context.activity.from || !context.activity.from.id) { throw new Error('BotFrameworkAdapter.getUserToken(): missing from or from.id'); } if (!connectionName) { throw new Error('getUserToken() requires a connectionName but none was provided.'); } this.checkEmulatingOAuthCards(context); const userId: string = context.activity.from.id; const url: string = this.oauthApiUrl(context); const client: TokenApiClient = this.createTokenApiClient(url, oAuthAppCredentials); context.turnState.set(this.TokenApiClientCredentialsKey, client); const result: TokenApiModels.UserTokenGetTokenResponse = await client.userToken.getToken( userId, connectionName, { code: magicCode, channelId: context.activity.channelId } ); if (!result || !result.token || result._response.status == 404) { return undefined; } else { return result as TokenResponse; } } /** * Asynchronously signs out the user from the token server. * * @param context The context object for the turn. * @param connectionName The name of the auth connection to use. * @param userId The ID of user to sign out. * @param oAuthAppCredentials AppCredentials for OAuth. */ async signOutUser(context: TurnContext, connectionName?: string, userId?: string): Promise<void>; async signOutUser( context: TurnContext, connectionName?: string, userId?: string, oAuthAppCredentials?: CoreAppCredentials ): Promise<void>; /** * Asynchronously signs out the user from the token server. * * @param context The [TurnContext](xref:botbuilder-core.TurnContext) for the turn. * @param connectionName Optional. The name of the auth connection to use. * @param userId Optional. The ID of the user to sign out. * @param oAuthAppCredentials Optional. [AppCredentials](xref:botframework-connector.AppCredentials) for OAuth. */ async signOutUser( context: TurnContext, connectionName?: string, userId?: string, oAuthAppCredentials?: AppCredentials ): Promise<void> { if (!context.activity.from || !context.activity.from.id) { throw new Error('BotFrameworkAdapter.signOutUser(): missing from or from.id'); } if (!userId) { userId = context.activity.from.id; } this.checkEmulatingOAuthCards(context); const url: string = this.oauthApiUrl(context); const client: TokenApiClient = this.createTokenApiClient(url, oAuthAppCredentials); context.turnState.set(this.TokenApiClientCredentialsKey, client); await client.userToken.signOut(userId, { connectionName: connectionName, channelId: context.activity.channelId, }); } /** * Asynchronously gets a sign-in link from the token server that can be sent as part * of a [SigninCard](xref:botframework-schema.SigninCard). * * @param context The context object for the turn. * @param connectionName The name of the auth connection to use. * @param oAuthAppCredentials AppCredentials for OAuth. * @param userId The user id that will be associated with the token. * @param finalRedirect The final URL that the OAuth flow will redirect to. */ async getSignInLink( context: TurnContext, connectionName: string, oAuthAppCredentials?: AppCredentials, userId?: string, finalRedirect?: string ): Promise<string>; async getSignInLink( context: TurnContext, connectionName: string, oAuthAppCredentials?: CoreAppCredentials, userId?: string, finalRedirect?: string ): Promise<string>; /** * Asynchronously gets a sign-in link from the token server that can be sent as part * of a [SigninCard](xref:botframework-schema.SigninCard). * * @param context The [TurnContext](xref:botbuilder-core.TurnContext) for the turn. * @param connectionName The name of the auth connection to use. * @param oAuthAppCredentials Optional. [AppCredentials](xref:botframework-connector.AppCredentials) for OAuth. * @param userId Optional. The user id that will be associated with the token. * @param finalRedirect Optional. The final URL that the OAuth flow will redirect to. * @returns The sign in link. */ async getSignInLink( context: TurnContext, connectionName: string, oAuthAppCredentials?: AppCredentials, userId?: string, finalRedirect?: string ): Promise<string> { if (userId && userId != context.activity.from.id) { throw new ReferenceError( "cannot retrieve OAuth signin link for a user that's different from the conversation" ); } this.checkEmulatingOAuthCards(context); const conversation: Partial<ConversationReference> = TurnContext.getConversationReference(context.activity); const url: string = this.oauthApiUrl(context); const client: TokenApiClient = this.createTokenApiClient(url, oAuthAppCredentials); context.turnState.set(this.TokenApiClientCredentialsKey, client); const state: any = { ConnectionName: connectionName, Conversation: conversation, MsAppId: (client.credentials as AppCredentials).appId, RelatesTo: context.activity.relatesTo, }; const finalState: string = Buffer.from(JSON.stringify(state)).toString('base64'); return ( await client.botSignIn.getSignInUrl(finalState, { channelId: context.activity.channelId, finalRedirect }) )._response.bodyAsText; } /** * Asynchronously retrieves the token status for each configured connection for the given user. * * @param context The context object for the turn. * @param userId Optional. If present, the ID of the user to retrieve the token status for. * Otherwise, the ID of the user who sent the current activity is used. * @param includeFilter Optional. A comma-separated list of connection's to include. If present, * the `includeFilter` parameter limits the tokens this method returns. * @param oAuthAppCredentials AppCredentials for OAuth. * * @returns The [TokenStatus](xref:botframework-connector.TokenStatus) objects retrieved. */ async getTokenStatus(context: TurnContext, userId?: string, includeFilter?: string): Promise<TokenStatus[]>; async getTokenStatus( context: TurnContext, userId?: string, includeFilter?: string, oAuthAppCredentials?: CoreAppCredentials ): Promise<TokenStatus[]>; /** * Asynchronously retrieves the token status for each configured connection for the given user. * * @param context The [TurnContext](xref:botbuilder-core.TurnContext) for the turn. * @param userId Optional. If present, the ID of the user to retrieve the token status for. * Otherwise, the ID of the user who sent the current activity is used. * @param includeFilter Optional. A comma-separated list of connection's to include. If present, * the `includeFilter` parameter limits the tokens this method returns. * @param oAuthAppCredentials Optional. [AppCredentials](xref:botframework-connector.AppCredentials) for OAuth. * * @returns The [TokenStatus](xref:botframework-connector.TokenStatus) objects retrieved. */ async getTokenStatus( context: TurnContext, userId?: string, includeFilter?: string, oAuthAppCredentials?: AppCredentials ): Promise<TokenStatus[]> { if (!userId && (!context.activity.from || !context.activity.from.id)) { throw new Error('BotFrameworkAdapter.getTokenStatus(): missing from or from.id'); } this.checkEmulatingOAuthCards(context); userId = userId || context.activity.from.id; const url: string = this.oauthApiUrl(context); const client: TokenApiClient = this.createTokenApiClient(url, oAuthAppCredentials); context.turnState.set(this.TokenApiClientCredentialsKey, client); return ( await client.userToken.getTokenStatus(userId, { channelId: context.activity.channelId, include: includeFilter, }) )._response.parsedBody; } /** * Asynchronously signs out the user from the token server. * * @param context The context object for the turn. * @param connectionName The name of the auth connection to use. * @param resourceUrls The list of resource URLs to retrieve tokens for. * @param oAuthAppCredentials AppCredentials for OAuth. * * @returns A map of the [TokenResponse](xref:botframework-schema.TokenResponse) objects by resource URL. */ async getAadTokens( context: TurnContext, connectionName: string, resourceUrls: string[] ): Promise<{ [propertyName: string]: TokenResponse }>; async getAadTokens( context: TurnContext, connectionName: string, resourceUrls: string[], oAuthAppCredentials?: CoreAppCredentials ): Promise<{ [propertyName: string]: TokenResponse }>; /** * Asynchronously signs out the user from the token server. * * @param context The [TurnContext](xref:botbuilder-core.TurnContext) for the turn. * @param connectionName The name of the auth connection to use. * @param resourceUrls The list of resource URLs to retrieve tokens for. * @param oAuthAppCredentials Optional. [AppCredentials](xref:botframework-connector.AppCredentials) for OAuth. * * @returns A map of the [TokenResponse](xref:botframework-schema.TokenResponse) objects by resource URL. */ async getAadTokens( context: TurnContext, connectionName: string, resourceUrls: string[], oAuthAppCredentials?: AppCredentials ): Promise<{ [propertyName: string]: TokenResponse }> { if (!context.activity.from || !context.activity.from.id) { throw new Error('BotFrameworkAdapter.getAadTokens(): missing from or from.id'); } this.checkEmulatingOAuthCards(context); const userId: string = context.activity.from.id; const url: string = this.oauthApiUrl(context); const client: TokenApiClient = this.createTokenApiClient(url, oAuthAppCredentials); context.turnState.set(this.TokenApiClientCredentialsKey, client); return ( await client.userToken.getAadTokens( userId, connectionName, { resourceUrls: resourceUrls }, { channelId: context.activity.channelId } ) )._response.parsedBody as { [propertyName: string]: TokenResponse }; } /** * Asynchronously Get the raw signin resource to be sent to the user for signin. * * @param context The context object for the turn. * @param connectionName The name of the auth connection to use. * @param userId The user id that will be associated with the token. * @param finalRedirect The final URL that the OAuth flow will redirect to. * @param appCredentials Optional. The CoreAppCredentials for OAuth. * @returns The [BotSignInGetSignInResourceResponse](xref:botframework-connector.BotSignInGetSignInResourceResponse) object. */ async getSignInResource( context: TurnContext, connectionName: string, userId?: string, finalRedirect?: string, appCredentials?: CoreAppCredentials ): Promise<SignInUrlResponse> { if (!connectionName) { throw new Error('getUserToken() requires a connectionName but none was provided.'); } if (!context.activity.from || !context.activity.from.id) { throw new Error('BotFrameworkAdapter.getSignInResource(): missing from or from.id'); } // The provided userId doesn't match the from.id on the activity. (same for finalRedirect) if (userId && context.activity.from.id !== userId) { throw new Error( 'BotFrameworkAdapter.getSiginInResource(): cannot get signin resource for a user that is different from the conversation' ); } const url: string = this.oauthApiUrl(context); const credentials = appCredentials as AppCredentials; const client: TokenApiClient = this.createTokenApiClient(url, credentials); const conversation: Partial<ConversationReference> = TurnContext.getConversationReference(context.activity); const state: any = { ConnectionName: connectionName, Conversation: conversation, relatesTo: context.activity.relatesTo, MSAppId: (client.credentials as AppCredentials).appId, }; const finalState: string = Buffer.from(JSON.stringify(state)).toString('base64'); const options: TokenApiModels.BotSignInGetSignInResourceOptionalParams = { finalRedirect: finalRedirect }; return await client.botSignIn.getSignInResource(finalState, options); } /** * Asynchronously Performs a token exchange operation such as for single sign-on. * * @param context Context for the current turn of conversation with the user. * @param connectionName Name of the auth connection to use. * @param userId The user id that will be associated with the token. * @param tokenExchangeRequest The exchange request details, either a token to exchange or a uri to exchange. * @param appCredentials Optional. The CoreAppCredentials for OAuth. */ async exchangeToken( context: TurnContext, connectionName: string, userId: string, tokenExchangeRequest: TokenExchangeRequest, appCredentials?: CoreAppCredentials ): Promise<TokenResponse>; /** * Asynchronously Performs a token exchange operation such as for single sign-on. * * @param context The [TurnContext](xref:botbuilder-core.TurnContext) for the turn. * @param connectionName Name of the auth connection to use. * @param userId The user id that will be associated with the token. * @param tokenExchangeRequest The [TokenExchangeRequest](xref:botbuilder-schema.TokenExchangeRequest), either a token to exchange or a uri to exchange. * @param appCredentials Optional. [AppCredentials](xref:botframework-connector.AppCredentials) for OAuth. * @returns A `Promise` representing the exchanged [TokenResponse](xref:botframework-schema.TokenResponse). */ async exchangeToken( context: TurnContext, connectionName: string, userId: string, tokenExchangeRequest: TokenExchangeRequest, appCredentials?: AppCredentials ): Promise<TokenResponse> { if (!connectionName) { throw new Error('exchangeToken() requires a connectionName but none was provided.'); } if (!userId) { throw new Error('exchangeToken() requires an userId but none was provided.'); } if (tokenExchangeRequest && !tokenExchangeRequest.token && !tokenExchangeRequest.uri) { throw new Error( 'BotFrameworkAdapter.exchangeToken(): Either a Token or Uri property is required on the TokenExchangeRequest' ); } const url: string = this.oauthApiUrl(context); const client: TokenApiClient = this.createTokenApiClient(url, appCredentials); return ( await client.userToken.exchangeAsync( userId, connectionName, context.activity.channelId, tokenExchangeRequest ) )._response.parsedBody as TokenResponse; } /** * Asynchronously sends an emulated OAuth card for a channel. * * This method supports the framework a