diff --git a/libraries/botbuilder-core/src/activityHandler.ts b/libraries/botbuilder-core/src/activityHandler.ts index 1fe6ea9f28..67d1f8f2d9 100644 --- a/libraries/botbuilder-core/src/activityHandler.ts +++ b/libraries/botbuilder-core/src/activityHandler.ts @@ -4,6 +4,7 @@ */ import { ChannelAccount, MessageReaction, TurnContext } from '.'; import { ActivityHandlerBase } from './activityHandlerBase'; +import { verifyStateOperationName, tokenExchangeOperationName, tokenResponseEventName } from './authContants'; /** * Describes a bot activity event handler, for use with an [ActivityHandler](xref:botbuilder-core.ActivityHandler) object. @@ -391,7 +392,6 @@ export class ActivityHandler extends ActivityHandlerBase { * The default logic is to call any handlers registered via [onTurn](xref:botbuilder-core.ActivityHandler.onTurn), * and then continue by calling [ActivityHandlerBase.onTurnActivity](xref:botbuilder-core.ActivityHandlerBase.onTurnActivity). */ - //maybe handle invoke here instead of in base protected async onTurnActivity(context: TurnContext): Promise { await this.handle(context, 'Turn', async () => { await super.onTurnActivity(context); @@ -426,13 +426,14 @@ export class ActivityHandler extends ActivityHandlerBase { * and then continue by calling [defaultNextEvent](xref:botbuilder-core.ActivityHandler.defaultNextEvent). */ protected async onInvokeActivity(context: TurnContext): Promise { - if(context.activity.name && context.activity.name === 'signin/verifyState' || context.activity.name === 'signin/tokenExchange') { + if(context.activity.name && context.activity.name === verifyStateOperationName || context.activity.name === tokenExchangeOperationName) { await this.onSignInInvoke(context); } else { await this.handle(context, 'Invoke', this.defaultNextEvent(context)); } } + /* * Runs all registered _signin invoke activity type_ handlers and then continues the event emission process. * @@ -648,7 +649,7 @@ export class ActivityHandler extends ActivityHandlerBase { * - Continue by calling [defaultNextEvent](xref:botbuilder-core.ActivityHandler.defaultNextEvent). */ protected async dispatchEventActivity(context: TurnContext): Promise { - if (context.activity.name === 'tokens/response') { + if (context.activity.name === tokenResponseEventName) { await this.handle(context, 'TokenResponseEvent', this.defaultNextEvent(context)); } else { await this.defaultNextEvent(context)(); diff --git a/libraries/botbuilder-core/src/authContants.ts b/libraries/botbuilder-core/src/authContants.ts new file mode 100644 index 0000000000..f5b784bcaf --- /dev/null +++ b/libraries/botbuilder-core/src/authContants.ts @@ -0,0 +1,11 @@ +/** + * @module botbuilder + */ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + + export const verifyStateOperationName: string = 'signin/verifyState'; + export const tokenExchangeOperationName: string = 'signin/tokenExchange'; + export const tokenResponseEventName: string = 'tokens/response'; diff --git a/libraries/botbuilder-core/src/credentialTokenProvider.ts b/libraries/botbuilder-core/src/credentialTokenProvider.ts deleted file mode 100644 index 8e918748f9..0000000000 --- a/libraries/botbuilder-core/src/credentialTokenProvider.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * @module botbuilder-core - */ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. - */ - -import { AppCredentials } from './appCredentials'; -import { IUserTokenProvider } from './userTokenProvider'; -import { TurnContext } from './turnContext'; -import { TokenResponse } from 'botframework-schema'; - -export interface CredentialTokenProvider extends IUserTokenProvider { - /** - * Retrieves the OAuth token for a user that is in a sign-in flow. - * @param context Context for the current turn of conversation with the user. - * @param connectionName Name of the auth connection to use. - * @param magicCode (Optional) Optional user entered code to validate. - */ - getUserToken(context: TurnContext, connectionName: string, magicCode?: string, appCredentials?: AppCredentials): Promise; - - /** - * Signs the user out with the token server. - * @param context Context for the current turn of conversation with the user. - * @param connectionName Name of the auth connection to use. - * @param userId User id of user to sign out. - * @param oAuthAppCredentials AppCredentials for OAuth. - */ - signOutUser(context: TurnContext, connectionName: string, userId?: string, appCredentials?: AppCredentials): Promise; - - /** - * Gets a signin link from the token server that can be sent as part of a SigninCard. - * @param context Context for the current turn of conversation with the user. - * @param connectionName Name of the auth connection to use. - * @param oAuthAppCredentials AppCredentials for OAuth. - */ - getSignInLink(context: TurnContext, connectionName: string, appCredentials?: AppCredentials): Promise; - - /** - * Signs the user out with the token server. - * @param context Context for the current turn of conversation with the user. - * @param connectionName Name of the auth connection to use. - * @param oAuthAppCredentials AppCredentials for OAuth. - */ - getAadTokens(context: TurnContext, connectionName: string, resourceUrls: string[], appCredentials?: AppCredentials): Promise<{ - [propertyName: string]: TokenResponse; - }>; -} diff --git a/libraries/botbuilder-core/src/index.ts b/libraries/botbuilder-core/src/index.ts index 6be55123ea..2dd571868b 100644 --- a/libraries/botbuilder-core/src/index.ts +++ b/libraries/botbuilder-core/src/index.ts @@ -7,6 +7,8 @@ */ export * from 'botframework-schema'; +export * from './authContants'; +export * from './appCredentials'; export * from './activityHandler'; export * from './activityHandlerBase'; export * from './autoSaveStateMiddleware'; @@ -35,6 +37,4 @@ export * from './transcriptLogger'; export * from './turnContext'; export * from './userState'; export * from './userTokenProvider'; -export * from './userTokenSettings'; -export * from './appCredentials'; -export * from './credentialTokenProvider'; +export * from './userTokenSettings'; \ No newline at end of file diff --git a/libraries/botbuilder-core/src/testAdapter.ts b/libraries/botbuilder-core/src/testAdapter.ts index d2824826c0..dbf0be9ab7 100644 --- a/libraries/botbuilder-core/src/testAdapter.ts +++ b/libraries/botbuilder-core/src/testAdapter.ts @@ -396,7 +396,8 @@ export class TestAdapter extends BotAdapter implements IExtendedUserTokenProvide } - private _exchangeableTokens : {[key: string]: ExchangeableToken} = {}; + private exchangeableTokens : {[key: string]: ExchangeableToken} = {}; + public addExchangeableToken(connectionName: string, channelId: string, userId: string, exchangeableItem: string, token: string) { const key: ExchangeableToken = new ExchangeableToken(); key.ChannelId = channelId; @@ -404,12 +405,12 @@ export class TestAdapter extends BotAdapter implements IExtendedUserTokenProvide key.UserId = userId; key.exchangeableItem = exchangeableItem; key.Token = token; - this._exchangeableTokens[key.toKey()] = key; + this.exchangeableTokens[key.toKey()] = key; } public async getSignInResource(context: TurnContext, connectionName: string, userId?: string, finalRedirect?: string): Promise { return { - signInLink: `https://fake.com/oauthsignin/${connectionName}/${context.activity.channelId}/${userId}`, + signInLink: `https://botframeworktestadapter.com/oauthsignin/${connectionName}/${context.activity.channelId}/${userId}`, tokenExchangeResource: { id: String(Math.random()), providerId: null, @@ -429,7 +430,7 @@ export class TestAdapter extends BotAdapter implements IExtendedUserTokenProvide key.exchangeableItem = exchangeableValue; key.UserId = userId; - const tokenExchangeResponse = this._exchangeableTokens[key.toKey()]; + const tokenExchangeResponse = this.exchangeableTokens[key.toKey()]; return tokenExchangeResponse ? { channelId: key.ChannelId, diff --git a/libraries/botbuilder-dialogs/package.json b/libraries/botbuilder-dialogs/package.json index 562c22ca1b..a9ff9d2dee 100644 --- a/libraries/botbuilder-dialogs/package.json +++ b/libraries/botbuilder-dialogs/package.json @@ -25,6 +25,7 @@ "@microsoft/recognizers-text-number": "1.1.4", "@microsoft/recognizers-text-suite": "1.1.4", "@types/node": "^10.12.18", + "botbuilder": "4.1.6", "botbuilder-core": "4.1.6", "globalize": "^1.4.2" }, diff --git a/libraries/botbuilder-dialogs/src/prompts/oauthPrompt.ts b/libraries/botbuilder-dialogs/src/prompts/oauthPrompt.ts index b7ea15e16e..850131cd42 100644 --- a/libraries/botbuilder-dialogs/src/prompts/oauthPrompt.ts +++ b/libraries/botbuilder-dialogs/src/prompts/oauthPrompt.ts @@ -5,7 +5,8 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ -import { Activity, ActivityTypes, Attachment, AppCredentials, CardFactory, Channels, InputHints, MessageFactory, OAuthLoginTimeoutKey, TokenResponse, TurnContext, IUserTokenProvider, OAuthCard, ActionTypes, IExtendedUserTokenProvider } from 'botbuilder-core'; +import { Activity, ActivityTypes, Attachment, AppCredentials, CardFactory, Channels, InputHints, MessageFactory, OAuthLoginTimeoutKey, TokenResponse, TurnContext, IUserTokenProvider, OAuthCard, ActionTypes, IExtendedUserTokenProvider, verifyStateOperationName, tokenExchangeOperationName, tokenResponseEventName } from 'botbuilder-core'; +import { StatusCodes} from 'botbuilder'; import { Dialog, DialogTurnResult } from '../dialog'; import { DialogContext } from '../dialogContext'; import { PromptOptions, PromptRecognizerResult, PromptValidator } from './prompt'; @@ -28,7 +29,7 @@ class TokenExchangeInvokeResponse { connectionName: string; failureDetail: string; - constructor(id:string, connectionName:string, failureDetail:string){ + constructor(id:string, connectionName:string, failureDetail:string) { this.id = id; this.connectionName = connectionName; this.failureDetail = failureDetail; @@ -333,7 +334,7 @@ export class OAuthPrompt extends Dialog { try { token = await this.getUserToken(context, code); if (token !== undefined) { - await context.sendActivity({ type: 'invokeResponse', value: { status: 200 }}); + await context.sendActivity({ type: 'invokeResponse', value: { status: StatusCodes }}); } else { await context.sendActivity({ type: 'invokeResponse', value: { status: 404 }}); } @@ -343,39 +344,42 @@ export class OAuthPrompt extends Dialog { await context.sendActivity({ type: 'invokeResponse', value: { status: 500 }}); } } else if (this.isTokenExchangeRequestInvoke(context)) { + // Received activity is not a token exchange request if(!(context.activity.value && this.isTokenExchangeRequest(context.activity.value))) { await context.sendActivity(this.getTokenExchangeInvokeResponse( - 400, + StatusCodes.BAD_REQUEST, 'The bot received an InvokeActivity that is missing a TokenExchangeInvokeRequest value. This is required to be sent with the InvokeActivity.')); - } else if (context.activity.value.connectionName != this.settings.connectionName) { + } else if (context.activity.value.connectionName != this.settings.connectionName) { + // Connection name on activity does not match that of setting await context.sendActivity(this.getTokenExchangeInvokeResponse( - 400, + StatusCodes.BAD_REQUEST, 'The bot received an InvokeActivity with a TokenExchangeInvokeRequest containing a ConnectionName that does not match the ConnectionName' + 'expected by the bots active OAuthPrompt. Ensure these names match when sending the InvokeActivityInvalid ConnectionName in the TokenExchangeInvokeRequest')); } else if (!('exchangeToken' in context.adapter)) { + // Token Exchange not supported in the adapter await context.sendActivity(this.getTokenExchangeInvokeResponse( - 502, + StatusCodes.BAD_GATEWAY, 'The bot\'s BotAdapter does not support token exchange operations. Ensure the bot\'s Adapter supports the ITokenExchangeProvider interface.')); throw new Error('OAuthPrompt.recognize(): not supported by the current adapter'); } else { + // No errors. Proceed with token exchange const extendedUserTokenProvider : IExtendedUserTokenProvider = context.adapter as IExtendedUserTokenProvider; const tokenExchangeResponse = await extendedUserTokenProvider.exchangeToken(context, this.settings.connectionName, context.activity.from.id, {token: context.activity.value.token}); if(!tokenExchangeResponse || !tokenExchangeResponse.token) { await context.sendActivity(this.getTokenExchangeInvokeResponse( - 409, + StatusCodes.CONFLICT, 'The bot is unable to exchange token. Proceed with regular login.')); } else { - await context.sendActivity(this.getTokenExchangeInvokeResponse(200, null, context.activity.value.id)); + await context.sendActivity(this.getTokenExchangeInvokeResponse(StatusCodes.OK, null, context.activity.value.id)); token = { channelId: tokenExchangeResponse.channelId, connectionName: tokenExchangeResponse.connectionName, token : tokenExchangeResponse.token, expiration: null - }; + }; } } - } else if (context.activity.type === ActivityTypes.Message) { const matched: RegExpExecArray = /(\d{6})/.exec(context.activity.text); if (matched && matched.length > 1) { @@ -400,19 +404,19 @@ export class OAuthPrompt extends Dialog { private isTokenResponseEvent(context: TurnContext): boolean { const activity: Activity = context.activity; - return activity.type === ActivityTypes.Event && activity.name === 'tokens/response'; + return activity.type === ActivityTypes.Event && activity.name === tokenResponseEventName; } private isTeamsVerificationInvoke(context: TurnContext): boolean { const activity: Activity = context.activity; - return activity.type === ActivityTypes.Invoke && activity.name === 'signin/verifyState'; + return activity.type === ActivityTypes.Invoke && activity.name === verifyStateOperationName; } private isTokenExchangeRequestInvoke(context: TurnContext): boolean { const activity: Activity = context.activity; - return activity.type === ActivityTypes.Invoke && activity.name === 'signin/tokenExchange'; + return activity.type === ActivityTypes.Invoke && activity.name === tokenExchangeOperationName; } private isTokenExchangeRequest(obj: unknown) : obj is TokenExchangeInvokeRequest { diff --git a/libraries/botbuilder/src/botFrameworkAdapter.ts b/libraries/botbuilder/src/botFrameworkAdapter.ts index 9d8f4fe19b..f4191eb11a 100644 --- a/libraries/botbuilder/src/botFrameworkAdapter.ts +++ b/libraries/botbuilder/src/botFrameworkAdapter.ts @@ -22,9 +22,11 @@ export enum StatusCodes { UNAUTHORIZED = 401, NOT_FOUND = 404, METHOD_NOT_ALLOWED = 405, + CONFLICT = 409, UPGRADE_REQUIRED = 426, INTERNAL_SERVER_ERROR = 500, NOT_IMPLEMENTED = 501, + BAD_GATEWAY = 502 } export class StatusCodeError extends Error { @@ -597,7 +599,8 @@ export class BotFrameworkAdapter extends BotAdapter implements IExtendedUserToke const state: any = { ConnectionName: connectionName, Conversation: conversation, - MsAppId: (client.credentials as AppCredentials).appId + MsAppId: (client.credentials as AppCredentials).appId, + RelatesTo: context.activity.relatesTo }; const finalState: string = Buffer.from(JSON.stringify(state)).toString('base64'); @@ -688,7 +691,7 @@ export class BotFrameworkAdapter extends BotAdapter implements IExtendedUserToke const state: any = { ConnectionName: connectionName, Conversation: conversation, - //RelatesTo = context.activity.RelatesTo, + relatesTo: context.activity.relatesTo, MSAppId: (client.credentials as AppCredentials).appId }; const finalState: string = Buffer.from(JSON.stringify(state)).toString('base64'); diff --git a/libraries/botbuilder/src/botFrameworkHttpClient.ts b/libraries/botbuilder/src/botFrameworkHttpClient.ts index ab49b2a474..56ee0c67c1 100644 --- a/libraries/botbuilder/src/botFrameworkHttpClient.ts +++ b/libraries/botbuilder/src/botFrameworkHttpClient.ts @@ -18,6 +18,7 @@ import { import { USER_AGENT } from './botFrameworkAdapter'; import { InvokeResponse } from './interfaces'; +import { ConversationReference } from 'botframework-connector/lib/connectorApi/models/mappers'; /** * HttpClient for calling skills from a Node.js BotBuilder V4 SDK bot. @@ -68,6 +69,22 @@ export class BotFrameworkHttpClient { const originalServiceUrl = activity.serviceUrl; const originalCallerId = activity.callerId; try { + activity.relatesTo = { + serviceUrl: activity.serviceUrl, + activityId: activity.id, + channelId: activity.channelId, + conversation: { + id: activity.conversation.id, + name: activity.conversation.name, + conversationType: activity.conversation.conversationType, + aadObjectId: activity.conversation.aadObjectId, + isGroup: activity.conversation.isGroup, + properties: activity.conversation.properties, + role: activity.conversation.role, + tenantId: activity.conversation.tenantId + }, + bot: null + }; activity.conversation.id = conversationId; activity.serviceUrl = serviceUrl; activity.callerId = fromBotId; diff --git a/libraries/botbuilder/src/streaming/tokenResolver.ts b/libraries/botbuilder/src/streaming/tokenResolver.ts index 7f33d46fd1..5716358b5d 100644 --- a/libraries/botbuilder/src/streaming/tokenResolver.ts +++ b/libraries/botbuilder/src/streaming/tokenResolver.ts @@ -19,6 +19,7 @@ import { TokenPollingSettings, TokenPollingSettingsKey, TurnContext, + tokenResponseEventName, } from 'botbuilder-core'; /** @@ -104,7 +105,7 @@ export class TokenResolver { replyToId: relatesTo.activityId, channelId: relatesTo.channelId, conversation: relatesTo.conversation, - name: 'tokens/response', + name: tokenResponseEventName, relatesTo: relatesTo, value: { token: token, diff --git a/libraries/botbuilder/src/teamsActivityHandler.ts b/libraries/botbuilder/src/teamsActivityHandler.ts index cf47091295..eeb2e3d262 100644 --- a/libraries/botbuilder/src/teamsActivityHandler.ts +++ b/libraries/botbuilder/src/teamsActivityHandler.ts @@ -25,7 +25,9 @@ import { TeamsChannelData, TeamsChannelAccount, TeamInfo, - TurnContext + TurnContext, + tokenExchangeOperationName, + verifyStateOperationName } from 'botbuilder-core'; import { InvokeResponse } from './interfaces'; import { TeamsInfo } from './teamsInfo'; @@ -62,11 +64,11 @@ export class TeamsActivityHandler extends ActivityHandler { return await this.handleTeamsCardActionInvoke(context); } else { switch (context.activity.name) { - case 'signin/verifyState': + case verifyStateOperationName: await this.handleTeamsSigninVerifyState(context, context.activity.value); return TeamsActivityHandler.createInvokeResponse(); - case 'signin/tokenExchange': + case tokenExchangeOperationName: await this.handleTeamsSigninTokenExchange(context, context.activity.value); return TeamsActivityHandler.createInvokeResponse();