From c8030b61da98ed821a64d6c2d9a1a166c62faa8a Mon Sep 17 00:00:00 2001 From: fangyangci Date: Mon, 18 Dec 2023 20:54:03 +0800 Subject: [PATCH 1/4] aseChannelValidation --- ...configurationBotFrameworkAuthentication.ts | 12 ++ .../botbuilder/src/botFrameworkAdapter.ts | 36 ++-- .../botbuilder/src/botFrameworkHttpClient.ts | 6 +- .../src/auth/appCredentials.ts | 22 ++- .../src/auth/aseChannelValidation.ts | 162 ++++++++++++++++++ .../src/auth/governmentConstants.ts | 12 ++ .../botframework-connector/src/auth/index.ts | 3 + .../src/auth/jwtTokenValidation.ts | 10 +- .../auth/microsoftGovernmentAppCredentials.ts | 40 +++++ .../msalServiceClientCredentialsFactory.ts | 4 +- ...parameterizedBotFrameworkAuthentication.ts | 6 + .../passwordServiceClientCredentialFactory.ts | 6 +- .../src/auth/tokenValidationParameters.ts | 11 ++ .../tests/auth/aseChannelValidation.test.js | 46 +++++ .../auth/microsoftAppCredentials.test.js | 17 ++ .../microsoftGovernmentAppCredentials.test.js | 17 ++ 16 files changed, 379 insertions(+), 31 deletions(-) create mode 100644 libraries/botframework-connector/src/auth/aseChannelValidation.ts create mode 100644 libraries/botframework-connector/src/auth/microsoftGovernmentAppCredentials.ts create mode 100644 libraries/botframework-connector/tests/auth/aseChannelValidation.test.js create mode 100644 libraries/botframework-connector/tests/auth/microsoftAppCredentials.test.js create mode 100644 libraries/botframework-connector/tests/auth/microsoftGovernmentAppCredentials.test.js diff --git a/libraries/botbuilder-core/src/configurationBotFrameworkAuthentication.ts b/libraries/botbuilder-core/src/configurationBotFrameworkAuthentication.ts index b13b4174f5..f80d6a62e4 100644 --- a/libraries/botbuilder-core/src/configurationBotFrameworkAuthentication.ts +++ b/libraries/botbuilder-core/src/configurationBotFrameworkAuthentication.ts @@ -17,6 +17,7 @@ import { ConnectorFactory, ServiceClientCredentialsFactory, UserTokenClient, + AseChannelValidation, } from 'botframework-connector'; import { @@ -26,6 +27,16 @@ import { const TypedOptions = z .object({ + /** + * The ID assigned to your bot in the [Bot Framework Portal](https://dev.botframework.com/). + */ + MicrosoftAppId: z.string(), + + /** + * The tenant id assigned to your bot in the [Bot Framework Portal](https://dev.botframework.com/). + */ + MicrosoftAppTenantId: z.string(), + /** * (Optional) The OAuth URL used to get a token from OAuthApiClient. The "OAuthUrl" member takes precedence over this value. */ @@ -131,6 +142,7 @@ export class ConfigurationBotFrameworkAuthentication extends BotFrameworkAuthent super(); try { + AseChannelValidation.init(botFrameworkAuthConfig); const typedBotFrameworkAuthConfig = TypedOptions.nonstrict().parse(botFrameworkAuthConfig); const { diff --git a/libraries/botbuilder/src/botFrameworkAdapter.ts b/libraries/botbuilder/src/botFrameworkAdapter.ts index cbe01ab5f7..eaa4f01a18 100644 --- a/libraries/botbuilder/src/botFrameworkAdapter.ts +++ b/libraries/botbuilder/src/botFrameworkAdapter.ts @@ -56,6 +56,7 @@ import { GovernmentConstants, JwtTokenValidation, MicrosoftAppCredentials, + MicrosoftGovernmentAppCredentials, SignInUrlResponse, SimpleCredentialProvider, SkillValidation, @@ -254,11 +255,20 @@ export class BotFrameworkAdapter ); this.credentialsProvider = new SimpleCredentialProvider(this.credentials.appId, ''); } else { - this.credentials = new MicrosoftAppCredentials( - this.settings.appId, - this.settings.appPassword || '', - this.settings.channelAuthTenant - ); + if (JwtTokenValidation.isGovernment(this.settings.channelService)) { + this.credentials = new MicrosoftGovernmentAppCredentials( + this.settings.appId, + this.settings.appPassword || '', + this.settings.channelAuthTenant + ); + } + else{ + this.credentials = new MicrosoftAppCredentials( + this.settings.appId, + this.settings.appPassword || '', + this.settings.channelAuthTenant + ); + } this.credentialsProvider = new SimpleCredentialProvider( this.credentials.appId, this.settings.appPassword || '' @@ -280,10 +290,6 @@ export class BotFrameworkAdapter 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) { @@ -1627,12 +1633,12 @@ export class BotFrameworkAdapter this.settings.channelAuthTenant ); } else { - credentials = new MicrosoftAppCredentials(appId, appPassword, this.settings.channelAuthTenant, oAuthScope); - } - - if (JwtTokenValidation.isGovernment(this.settings.channelService)) { - credentials.oAuthEndpoint = GovernmentConstants.ToChannelFromBotLoginUrl; - credentials.oAuthScope = oAuthScope || GovernmentConstants.ToChannelFromBotOAuthScope; + if (JwtTokenValidation.isGovernment(this.settings.channelService)) { + credentials = new MicrosoftGovernmentAppCredentials(appId, appPassword, this.settings.channelAuthTenant, oAuthScope); + } + else{ + credentials = new MicrosoftAppCredentials(appId, appPassword, this.settings.channelAuthTenant, oAuthScope); + } } return credentials; diff --git a/libraries/botbuilder/src/botFrameworkHttpClient.ts b/libraries/botbuilder/src/botFrameworkHttpClient.ts index fc2adebebe..fd4a91eacd 100644 --- a/libraries/botbuilder/src/botFrameworkHttpClient.ts +++ b/libraries/botbuilder/src/botFrameworkHttpClient.ts @@ -13,10 +13,10 @@ import { AppCredentials, AuthenticationConstants, ConversationConstants, - GovernmentConstants, ICredentialProvider, JwtTokenValidation, MicrosoftAppCredentials, + MicrosoftGovernmentAppCredentials } from 'botframework-connector'; import { USER_AGENT } from './botFrameworkAdapter'; @@ -158,9 +158,7 @@ export class BotFrameworkHttpClient implements BotFrameworkClient { protected async buildCredentials(appId: string, oAuthScope?: string): Promise { const appPassword = await this.credentialProvider.getAppPassword(appId); if (JwtTokenValidation.isGovernment(this.channelService)) { - const appCredentials = new MicrosoftAppCredentials(appId, appPassword, undefined, oAuthScope); - appCredentials.oAuthEndpoint = GovernmentConstants.ToChannelFromBotLoginUrl; - return appCredentials; + return new MicrosoftGovernmentAppCredentials(appId, appPassword, undefined, oAuthScope); } else { return new MicrosoftAppCredentials(appId, appPassword, undefined, oAuthScope); } diff --git a/libraries/botframework-connector/src/auth/appCredentials.ts b/libraries/botframework-connector/src/auth/appCredentials.ts index 4df398456f..5caf20ebf9 100644 --- a/libraries/botframework-connector/src/auth/appCredentials.ts +++ b/libraries/botframework-connector/src/auth/appCredentials.ts @@ -48,12 +48,14 @@ export abstract class AppCredentials implements ServiceClientCredentials { constructor( appId: string, channelAuthTenant?: string, - oAuthScope: string = AuthenticationConstants.ToBotFromChannelTokenIssuer + oAuthScope: string = null ) { this.appId = appId; this.tenant = channelAuthTenant; - this.oAuthEndpoint = AuthenticationConstants.ToChannelFromBotLoginUrlPrefix + this.tenant; - this.oAuthScope = oAuthScope; + this.oAuthEndpoint = this.GetToChannelFromBotLoginUrlPrefix() + this.tenant; + this.oAuthScope = (oAuthScope && oAuthScope.length > 0) + ? oAuthScope + : this.GetToChannelFromBotOAuthScope(); } /** @@ -69,7 +71,7 @@ export abstract class AppCredentials implements ServiceClientCredentials { * Sets tenant to be used for channel authentication. */ private set tenant(value: string) { - this._tenant = value && value.length > 0 ? value : AuthenticationConstants.DefaultChannelAuthTenant; + this._tenant = value && value.length > 0 ? value : this.GetDefaultChannelAuthTenant(); } /** @@ -191,6 +193,18 @@ export abstract class AppCredentials implements ServiceClientCredentials { } } + protected GetToChannelFromBotOAuthScope(): string { + return AuthenticationConstants.ToChannelFromBotOAuthScope; + } + + protected GetToChannelFromBotLoginUrlPrefix(): string { + return AuthenticationConstants.ToChannelFromBotLoginUrlPrefix; + } + + protected GetDefaultChannelAuthTenant(): string { + return AuthenticationConstants.DefaultChannelAuthTenant; + } + protected abstract refreshToken(): Promise; /** diff --git a/libraries/botframework-connector/src/auth/aseChannelValidation.ts b/libraries/botframework-connector/src/auth/aseChannelValidation.ts new file mode 100644 index 0000000000..a580b8189e --- /dev/null +++ b/libraries/botframework-connector/src/auth/aseChannelValidation.ts @@ -0,0 +1,162 @@ +/** + * @module botframework-connector + */ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +/* eslint-disable @typescript-eslint/no-namespace */ + +import { ClaimsIdentity } from './claimsIdentity'; +import { AuthenticationConstants } from './authenticationConstants'; +import { AuthenticationConfiguration } from './authenticationConfiguration'; +import { GovernmentConstants } from './governmentConstants'; +import { ICredentialProvider } from './credentialProvider'; +import { JwtTokenExtractor } from './jwtTokenExtractor'; +import { JwtTokenValidation } from './jwtTokenValidation'; +import { AuthenticationError } from './authenticationError'; +import { SimpleCredentialProvider } from './credentialProvider'; +import { StatusCodes } from 'botframework-schema'; +import { BetweenBotAndAseChannelTokenValidationParameters } from './tokenValidationParameters'; + +/** + * @deprecated Use `ConfigurationBotFrameworkAuthentication` instead to perform AseChannel validation. + * Validates and Examines JWT tokens from the Bot Framework AseChannel + */ +export namespace AseChannelValidation { + const ChannelId = "AseChannel"; + let _creadentialProvider: ICredentialProvider; + let _channelService: string; + export let MetadataUrl: string; + + export function init(configuration: any) + { + let appId = configuration.MicrosoftAppId; + let tenantId = configuration.MicrosoftAppTenantId; + _channelService = configuration.ChannelService; + MetadataUrl = _channelService !== undefined && JwtTokenValidation.isGovernment(_channelService) + ? GovernmentConstants.ToBotFromEmulatorOpenIdMetadataUrl + : AuthenticationConstants.ToBotFromEmulatorOpenIdMetadataUrl; + + _creadentialProvider = new SimpleCredentialProvider(appId, ""); + + let tenantIds: string[] = [ + tenantId, + "f8cdef31-a31e-4b4a-93e4-5f571e91255a", // US Gov MicrosoftServices.onmicrosoft.us + "d6d49420-f39b-4df7-a1dc-d59a935871db" // Public botframework.com + ]; + let validIssuers: string[] = []; + tenantIds.forEach((tmpId: string) => { + validIssuers.push(`https://sts.windows.net/${tmpId}/`); // Auth Public/US Gov, 1.0 token + validIssuers.push(`https://login.microsoftonline.com/${tmpId}/v2.0`); // Auth Public, 2.0 token + validIssuers.push(`https://login.microsoftonline.us/${tmpId}/v2.0`); // Auth for US Gov, 2.0 token + }) + BetweenBotAndAseChannelTokenValidationParameters.issuer = validIssuers; + } + + /** + * Determines if a given Auth header is from the Bot Framework AseChannel + * + * @param {string} channelId. + * @returns {boolean} True, if the token was issued by the AseChannel. Otherwise, false. + */ + export function isTokenFromAseChannel(channelId: string): boolean { + return channelId === ChannelId; + } + + /** + * Validate the incoming Auth Header as a token sent from the Bot Framework AseChannel. + * A token issued by the Bot Framework will FAIL this check. Only AseChannel tokens will pass. + * + * @param {string} authHeader The raw HTTP header in the format: "Bearer [longString]" + * @param {ICredentialProvider} credentials The user defined set of valid credentials, such as the AppId. + * @param {string} channelService The channelService value that distinguishes public Azure from US Government Azure. + * @param {AuthenticationConfiguration} authConfig The authentication configuration. + * @returns {Promise} A valid ClaimsIdentity. + */ + export async function authenticateAseChannelToken( + authHeader: string, + authConfig: AuthenticationConfiguration = new AuthenticationConfiguration() + ): Promise { + + const tokenExtractor: JwtTokenExtractor = new JwtTokenExtractor( + BetweenBotAndAseChannelTokenValidationParameters, + MetadataUrl, + AuthenticationConstants.AllowedSigningAlgorithms + ); + + const identity: ClaimsIdentity = await tokenExtractor.getIdentityFromAuthHeader( + authHeader, + ChannelId, + authConfig.requiredEndorsements + ); + if (!identity) { + // No valid identity. Not Authorized. + throw new AuthenticationError('Unauthorized. No valid identity.', StatusCodes.UNAUTHORIZED); + } + + if (!identity.isAuthenticated) { + // The token is in some way invalid. Not Authorized. + throw new AuthenticationError('Unauthorized. Is not authenticated', StatusCodes.UNAUTHORIZED); + } + + // Now check that the AppID in the claimset matches + // what we're looking for. Note that in a multi-tenant bot, this value + // comes from developer code that may be reaching out to a service, hence the + // Async validation. + const versionClaim: string = identity.getClaimValue(AuthenticationConstants.VersionClaim); + if (versionClaim === null) { + throw new AuthenticationError( + 'Unauthorized. "ver" claim is required on AseChannel Tokens.', + StatusCodes.UNAUTHORIZED + ); + } + + let appId = ''; + + // The AseChannel, depending on Version, sends the AppId via either the + // appid claim (Version 1) or the Authorized Party claim (Version 2). + if (!versionClaim || versionClaim === '1.0') { + // either no Version or a version of "1.0" means we should look for + // the claim in the "appid" claim. + const appIdClaim: string = identity.getClaimValue(AuthenticationConstants.AppIdClaim); + if (!appIdClaim) { + // No claim around AppID. Not Authorized. + throw new AuthenticationError( + 'Unauthorized. "appid" claim is required on AseChannel Token version "1.0".', + StatusCodes.UNAUTHORIZED + ); + } + + appId = appIdClaim; + } else if (versionClaim === '2.0') { + // AseChannel, "2.0" puts the AppId in the "azp" claim. + const appZClaim: string = identity.getClaimValue(AuthenticationConstants.AuthorizedParty); + if (!appZClaim) { + // No claim around AppID. Not Authorized. + throw new AuthenticationError( + 'Unauthorized. "azp" claim is required on AseChannel Token version "2.0".', + StatusCodes.UNAUTHORIZED + ); + } + + appId = appZClaim; + } else { + // Unknown Version. Not Authorized. + throw new AuthenticationError( + `Unauthorized. Unknown AseChannel Token version "${versionClaim}".`, + StatusCodes.UNAUTHORIZED + ); + } + + if (!(await _creadentialProvider.isValidAppId(appId))) { + throw new AuthenticationError( + `Unauthorized. Invalid AppId passed on token: ${appId}`, + StatusCodes.UNAUTHORIZED + ); + } + + return identity; + } +} diff --git a/libraries/botframework-connector/src/auth/governmentConstants.ts b/libraries/botframework-connector/src/auth/governmentConstants.ts index 4a422bcd7a..dc873237f6 100644 --- a/libraries/botframework-connector/src/auth/governmentConstants.ts +++ b/libraries/botframework-connector/src/auth/governmentConstants.ts @@ -15,9 +15,21 @@ export namespace GovernmentConstants { /** * TO CHANNEL FROM BOT: Login URL + * + * DEPRECATED: DO NOT USE */ export const ToChannelFromBotLoginUrl = 'https://login.microsoftonline.us/MicrosoftServices.onmicrosoft.us'; + /** + * TO CHANNEL FROM BOT: Login URL prefix + */ + export const ToChannelFromBotLoginUrlPrefix = 'https://login.microsoftonline.us/'; + + /** + * TO CHANNEL FROM BOT: Default tenant from which to obtain a token for bot to channel communication + */ + export const DefaultChannelAuthTenant = 'MicrosoftServices.onmicrosoft.us'; + /** * TO CHANNEL FROM BOT: OAuth scope to request */ diff --git a/libraries/botframework-connector/src/auth/index.ts b/libraries/botframework-connector/src/auth/index.ts index 38a5cb890e..5367b217b3 100644 --- a/libraries/botframework-connector/src/auth/index.ts +++ b/libraries/botframework-connector/src/auth/index.ts @@ -22,6 +22,7 @@ export * from './claimsIdentity'; export * from './connectorFactory'; export * from './credentialProvider'; export * from './emulatorValidation'; +export * from './aseChannelValidation'; export * from './endorsementsValidator'; export * from './enterpriseChannelValidation'; export * from './governmentChannelValidation'; @@ -32,9 +33,11 @@ export * from './managedIdentityAppCredentials'; export * from './managedIdentityAuthenticator'; export * from './managedIdentityServiceClientCredentialsFactory'; export * from './microsoftAppCredentials'; +export * from './microsoftGovernmentAppCredentials'; export * from './passwordServiceClientCredentialFactory'; export * from './serviceClientCredentialsFactory'; export * from './skillValidation'; +export * from './tokenValidationParameters'; export * from './userTokenClient'; export { MsalAppCredentials } from './msalAppCredentials'; diff --git a/libraries/botframework-connector/src/auth/jwtTokenValidation.ts b/libraries/botframework-connector/src/auth/jwtTokenValidation.ts index 8cf1858e35..6892598485 100644 --- a/libraries/botframework-connector/src/auth/jwtTokenValidation.ts +++ b/libraries/botframework-connector/src/auth/jwtTokenValidation.ts @@ -19,6 +19,7 @@ import { EnterpriseChannelValidation } from './enterpriseChannelValidation'; import { GovernmentChannelValidation } from './governmentChannelValidation'; import { GovernmentConstants } from './governmentConstants'; import { SkillValidation } from './skillValidation'; +import { AseChannelValidation } from './aseChannelValidation'; /** * @deprecated Use `ConfigurationBotFrameworkAuthentication` instead to perform JWT token validation. @@ -128,6 +129,11 @@ export namespace JwtTokenValidation { authConfig: AuthenticationConfiguration, serviceUrl: string ): Promise { + + if (AseChannelValidation.isTokenFromAseChannel(channelId)) { + return AseChannelValidation.authenticateAseChannelToken(authHeader); + } + if (SkillValidation.isSkillToken(authHeader)) { return await SkillValidation.authenticateChannelToken( authHeader, @@ -138,9 +144,7 @@ export namespace JwtTokenValidation { ); } - const usingEmulator = EmulatorValidation.isTokenFromEmulator(authHeader); - - if (usingEmulator) { + if (EmulatorValidation.isTokenFromEmulator(authHeader)) { return await EmulatorValidation.authenticateEmulatorToken( authHeader, credentials, diff --git a/libraries/botframework-connector/src/auth/microsoftGovernmentAppCredentials.ts b/libraries/botframework-connector/src/auth/microsoftGovernmentAppCredentials.ts new file mode 100644 index 0000000000..85d4affa6e --- /dev/null +++ b/libraries/botframework-connector/src/auth/microsoftGovernmentAppCredentials.ts @@ -0,0 +1,40 @@ +/** + * @module botframework-connector + */ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +import { GovernmentConstants } from './governmentConstants'; +import { MicrosoftAppCredentials } from './microsoftAppCredentials'; + +/** + * MicrosoftGovermentAppCredentials auth implementation + */ +export class MicrosoftGovernmentAppCredentials extends MicrosoftAppCredentials { + + /** + * Initializes a new instance of the [MicrosoftGovernmentAppCredentials](xref:botframework-connector.MicrosoftGovernmentAppCredentials) class. + * + * @param {string} appId The Microsoft app ID. + * @param {string} appPassword The Microsoft app password. + * @param {string} channelAuthTenant Optional. The oauth token tenant. + * @param {string} oAuthScope Optional. The scope for the token. + */ + constructor(appId: string, public appPassword: string, channelAuthTenant?: string, oAuthScope?: string) { + super(appId, appPassword, channelAuthTenant, oAuthScope); + } + + protected GetToChannelFromBotOAuthScope(): string { + return GovernmentConstants.ToChannelFromBotOAuthScope; + } + + protected GetToChannelFromBotLoginUrlPrefix(): string { + return GovernmentConstants.ToChannelFromBotLoginUrlPrefix; + } + + protected GetDefaultChannelAuthTenant(): string { + return GovernmentConstants.DefaultChannelAuthTenant; + } +} diff --git a/libraries/botframework-connector/src/auth/msalServiceClientCredentialsFactory.ts b/libraries/botframework-connector/src/auth/msalServiceClientCredentialsFactory.ts index e807aa513e..997915d4c7 100644 --- a/libraries/botframework-connector/src/auth/msalServiceClientCredentialsFactory.ts +++ b/libraries/botframework-connector/src/auth/msalServiceClientCredentialsFactory.ts @@ -70,11 +70,11 @@ export class MsalServiceClientCredentialsFactory implements ServiceClientCredent ); } - if (normalizedEndpoint === GovernmentConstants.ToChannelFromBotLoginUrl.toLowerCase()) { + if (normalizedEndpoint.startsWith(GovernmentConstants.ToChannelFromBotLoginUrlPrefix)) { return new MsalAppCredentials( this.clientApplication, appId, - GovernmentConstants.ToChannelFromBotLoginUrl, + undefined, audience || GovernmentConstants.ToChannelFromBotOAuthScope ); } diff --git a/libraries/botframework-connector/src/auth/parameterizedBotFrameworkAuthentication.ts b/libraries/botframework-connector/src/auth/parameterizedBotFrameworkAuthentication.ts index d6e8f20dd9..b8bf0e81c7 100644 --- a/libraries/botframework-connector/src/auth/parameterizedBotFrameworkAuthentication.ts +++ b/libraries/botframework-connector/src/auth/parameterizedBotFrameworkAuthentication.ts @@ -22,6 +22,7 @@ import { ToBotFromBotOrEmulatorTokenValidationParameters } from './tokenValidati import { UserTokenClientImpl } from './userTokenClientImpl'; import type { UserTokenClient } from './userTokenClient'; import { VerifyOptions } from 'jsonwebtoken'; +import { AseChannelValidation } from './aseChannelValidation'; function getAppId(claimsIdentity: ClaimsIdentity): string | undefined { // For requests from channel App Id is in Audience claim of JWT token. For emulator it is in AppId claim. For @@ -270,6 +271,11 @@ export class ParameterizedBotFrameworkAuthentication extends BotFrameworkAuthent channelId: string, serviceUrl: string ): Promise { + + if (AseChannelValidation.isTokenFromAseChannel(channelId)) { + return AseChannelValidation.authenticateAseChannelToken(authHeader); + } + if (SkillValidation.isSkillToken(authHeader)) { return this.SkillValidation_authenticateChannelToken(authHeader, channelId); } diff --git a/libraries/botframework-connector/src/auth/passwordServiceClientCredentialFactory.ts b/libraries/botframework-connector/src/auth/passwordServiceClientCredentialFactory.ts index ec01185056..11e60bce7a 100644 --- a/libraries/botframework-connector/src/auth/passwordServiceClientCredentialFactory.ts +++ b/libraries/botframework-connector/src/auth/passwordServiceClientCredentialFactory.ts @@ -8,6 +8,7 @@ import type { ServiceClientCredentials } from '@azure/core-http'; import { AuthenticationConstants } from './authenticationConstants'; import { GovernmentConstants } from './governmentConstants'; import { MicrosoftAppCredentials } from './microsoftAppCredentials'; +import { MicrosoftGovernmentAppCredentials } from './microsoftGovernmentAppCredentials'; import { ServiceClientCredentialsFactory } from './serviceClientCredentialsFactory'; import { stringExt } from 'botbuilder-stdlib'; @@ -111,9 +112,8 @@ export class PasswordServiceClientCredentialFactory implements ServiceClientCred if (normalizedEndpoint?.startsWith(AuthenticationConstants.ToChannelFromBotLoginUrlPrefix)) { credentials = new MicrosoftAppCredentials(appId, this.password, this.tenantId, audience); - } else if (normalizedEndpoint === GovernmentConstants.ToChannelFromBotLoginUrl.toLowerCase()) { - credentials = new MicrosoftAppCredentials(appId, this.password, this.tenantId, audience); - credentials.oAuthEndpoint = loginEndpoint; + } else if (normalizedEndpoint?.startsWith(GovernmentConstants.ToChannelFromBotLoginUrlPrefix)) { + credentials = new MicrosoftGovernmentAppCredentials(appId, this.password, this.tenantId, audience); } else { credentials = new PrivateCloudAppCredentials( appId, diff --git a/libraries/botframework-connector/src/auth/tokenValidationParameters.ts b/libraries/botframework-connector/src/auth/tokenValidationParameters.ts index f0bd53b89f..6bc439402c 100644 --- a/libraries/botframework-connector/src/auth/tokenValidationParameters.ts +++ b/libraries/botframework-connector/src/auth/tokenValidationParameters.ts @@ -23,3 +23,14 @@ export const ToBotFromBotOrEmulatorTokenValidationParameters: VerifyOptions = { clockTolerance: 5 * 60, ignoreExpiration: false, }; + +// Internal +/** + * Default options for validating incoming tokens from the Bot Ase Channel. + */ +export const BetweenBotAndAseChannelTokenValidationParameters: VerifyOptions = { + issuer: [], + audience: undefined, // Audience validation takes place manually in code. + clockTolerance: 5 * 60, + ignoreExpiration: false, +}; diff --git a/libraries/botframework-connector/tests/auth/aseChannelValidation.test.js b/libraries/botframework-connector/tests/auth/aseChannelValidation.test.js new file mode 100644 index 0000000000..d886689762 --- /dev/null +++ b/libraries/botframework-connector/tests/auth/aseChannelValidation.test.js @@ -0,0 +1,46 @@ +const { AseChannelValidation, GovernmentConstants, AuthenticationConstants, BetweenBotAndAseChannelTokenValidationParameters } = require('../..'); +const assert = require('assert'); + +describe('AseChannelTestSuite', function () { + describe('AseChannelTestCase', function () { + + it('ValidationMetadataUrlTest_AseChannel_USGov', function () { + let config = { + ChannelService: GovernmentConstants.ChannelService, + } + AseChannelValidation.init(config); + assert.strictEqual(AseChannelValidation.MetadataUrl, GovernmentConstants.ToBotFromEmulatorOpenIdMetadataUrl); + + }); + + it('ValidationMetadataUrlTest_AseChannel_Public', function () { + let config = {} + AseChannelValidation.init(config); + assert.strictEqual(AseChannelValidation.MetadataUrl, AuthenticationConstants.ToBotFromEmulatorOpenIdMetadataUrl); + }); + + it('ValidationIssueUrlTest_AseChannel', function () { + let config = { + MicrosoftAppTenantId: "testTenantId" + } + AseChannelValidation.init(config); + let tenantIds = + [ + "testTenantId", + "f8cdef31-a31e-4b4a-93e4-5f571e91255a", // US Gov MicrosoftServices.onmicrosoft.us + "d6d49420-f39b-4df7-a1dc-d59a935871db" // Public botframework.com + ]; + tenantIds.forEach(function (tmpId) { + assert.strictEqual(true, BetweenBotAndAseChannelTokenValidationParameters.issuer.includes(`https://sts.windows.net/${tmpId}/`)); + assert.strictEqual(true, BetweenBotAndAseChannelTokenValidationParameters.issuer.includes(`https://login.microsoftonline.com/${tmpId}/v2.0`)); + assert.strictEqual(true, BetweenBotAndAseChannelTokenValidationParameters.issuer.includes(`https://login.microsoftonline.us/${tmpId}/v2.0`)); + }) + }); + + it('ValidationChannelIdTest_AseChannel', function () { + assert.strictEqual(true, AseChannelValidation.isTokenFromAseChannel("AseChannel")); + }); + }); +}); + + diff --git a/libraries/botframework-connector/tests/auth/microsoftAppCredentials.test.js b/libraries/botframework-connector/tests/auth/microsoftAppCredentials.test.js new file mode 100644 index 0000000000..a69184a9c3 --- /dev/null +++ b/libraries/botframework-connector/tests/auth/microsoftAppCredentials.test.js @@ -0,0 +1,17 @@ +const { MicrosoftAppCredentials, AuthenticationConstants } = require('../..'); +const assert = require('assert'); + +describe('MicrosoftAppCredentialsTestSuite', function () { + describe('MicrosoftAppCredentialsTestCase', function () { + it('AssertOAuthEndpointAndOAuthScope', function() { + var credentials = new MicrosoftAppCredentials("appId", "password", "tenantId", "audience"); + assert.strictEqual(AuthenticationConstants.ToChannelFromBotLoginUrlPrefix + "tenantId", credentials.oAuthEndpoint); + assert.strictEqual("audience", credentials.oAuthScope); + + var credentials = new MicrosoftAppCredentials("appId", "password"); + assert.strictEqual(AuthenticationConstants.ToChannelFromBotLoginUrlPrefix + AuthenticationConstants.DefaultChannelAuthTenant, credentials.oAuthEndpoint); + assert.strictEqual(AuthenticationConstants.ToChannelFromBotOAuthScope, credentials.oAuthScope); + }); + + }); +}); diff --git a/libraries/botframework-connector/tests/auth/microsoftGovernmentAppCredentials.test.js b/libraries/botframework-connector/tests/auth/microsoftGovernmentAppCredentials.test.js new file mode 100644 index 0000000000..8c0743711b --- /dev/null +++ b/libraries/botframework-connector/tests/auth/microsoftGovernmentAppCredentials.test.js @@ -0,0 +1,17 @@ +const { MicrosoftGovernmentAppCredentials, GovernmentConstants } = require('../..'); +const assert = require('assert'); + +describe('MicrosoftGovernmentAppCredentialsTestSuite', function () { + describe('MicrosoftGovernmentAppCredentialsTestCase', function () { + it('AssertOAuthEndpointAndOAuthScope', function() { + var credentials = new MicrosoftGovernmentAppCredentials("appId", "password", "tenantId", "audience"); + assert.strictEqual(GovernmentConstants.ToChannelFromBotLoginUrlPrefix + "tenantId", credentials.oAuthEndpoint); + assert.strictEqual("audience", credentials.oAuthScope); + + var credentials = new MicrosoftGovernmentAppCredentials("appId", "password"); + assert.strictEqual(GovernmentConstants.ToChannelFromBotLoginUrlPrefix + GovernmentConstants.DefaultChannelAuthTenant, credentials.oAuthEndpoint); + assert.strictEqual(GovernmentConstants.ToChannelFromBotOAuthScope, credentials.oAuthScope); + }); + + }); +}); From c33ec8a2cce82c2e6dc33c2702c182b0d3d781ce Mon Sep 17 00:00:00 2001 From: fangyangci Date: Mon, 18 Dec 2023 21:00:25 +0800 Subject: [PATCH 2/4] fix usgov single tenant --- .../botbuilder/src/botFrameworkAdapter.ts | 36 ++++++++++------- .../botbuilder/src/botFrameworkHttpClient.ts | 6 +-- .../src/auth/appCredentials.ts | 22 ++++++++-- .../src/auth/governmentConstants.ts | 12 ++++++ .../botframework-connector/src/auth/index.ts | 1 + .../auth/microsoftGovernmentAppCredentials.ts | 40 +++++++++++++++++++ .../msalServiceClientCredentialsFactory.ts | 4 +- .../passwordServiceClientCredentialFactory.ts | 6 +-- .../auth/microsoftAppCredentials.test.js | 17 ++++++++ .../microsoftGovernmentAppCredentials.test.js | 17 ++++++++ 10 files changed, 133 insertions(+), 28 deletions(-) create mode 100644 libraries/botframework-connector/src/auth/microsoftGovernmentAppCredentials.ts create mode 100644 libraries/botframework-connector/tests/auth/microsoftAppCredentials.test.js create mode 100644 libraries/botframework-connector/tests/auth/microsoftGovernmentAppCredentials.test.js diff --git a/libraries/botbuilder/src/botFrameworkAdapter.ts b/libraries/botbuilder/src/botFrameworkAdapter.ts index cbe01ab5f7..eaa4f01a18 100644 --- a/libraries/botbuilder/src/botFrameworkAdapter.ts +++ b/libraries/botbuilder/src/botFrameworkAdapter.ts @@ -56,6 +56,7 @@ import { GovernmentConstants, JwtTokenValidation, MicrosoftAppCredentials, + MicrosoftGovernmentAppCredentials, SignInUrlResponse, SimpleCredentialProvider, SkillValidation, @@ -254,11 +255,20 @@ export class BotFrameworkAdapter ); this.credentialsProvider = new SimpleCredentialProvider(this.credentials.appId, ''); } else { - this.credentials = new MicrosoftAppCredentials( - this.settings.appId, - this.settings.appPassword || '', - this.settings.channelAuthTenant - ); + if (JwtTokenValidation.isGovernment(this.settings.channelService)) { + this.credentials = new MicrosoftGovernmentAppCredentials( + this.settings.appId, + this.settings.appPassword || '', + this.settings.channelAuthTenant + ); + } + else{ + this.credentials = new MicrosoftAppCredentials( + this.settings.appId, + this.settings.appPassword || '', + this.settings.channelAuthTenant + ); + } this.credentialsProvider = new SimpleCredentialProvider( this.credentials.appId, this.settings.appPassword || '' @@ -280,10 +290,6 @@ export class BotFrameworkAdapter 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) { @@ -1627,12 +1633,12 @@ export class BotFrameworkAdapter this.settings.channelAuthTenant ); } else { - credentials = new MicrosoftAppCredentials(appId, appPassword, this.settings.channelAuthTenant, oAuthScope); - } - - if (JwtTokenValidation.isGovernment(this.settings.channelService)) { - credentials.oAuthEndpoint = GovernmentConstants.ToChannelFromBotLoginUrl; - credentials.oAuthScope = oAuthScope || GovernmentConstants.ToChannelFromBotOAuthScope; + if (JwtTokenValidation.isGovernment(this.settings.channelService)) { + credentials = new MicrosoftGovernmentAppCredentials(appId, appPassword, this.settings.channelAuthTenant, oAuthScope); + } + else{ + credentials = new MicrosoftAppCredentials(appId, appPassword, this.settings.channelAuthTenant, oAuthScope); + } } return credentials; diff --git a/libraries/botbuilder/src/botFrameworkHttpClient.ts b/libraries/botbuilder/src/botFrameworkHttpClient.ts index fc2adebebe..fd4a91eacd 100644 --- a/libraries/botbuilder/src/botFrameworkHttpClient.ts +++ b/libraries/botbuilder/src/botFrameworkHttpClient.ts @@ -13,10 +13,10 @@ import { AppCredentials, AuthenticationConstants, ConversationConstants, - GovernmentConstants, ICredentialProvider, JwtTokenValidation, MicrosoftAppCredentials, + MicrosoftGovernmentAppCredentials } from 'botframework-connector'; import { USER_AGENT } from './botFrameworkAdapter'; @@ -158,9 +158,7 @@ export class BotFrameworkHttpClient implements BotFrameworkClient { protected async buildCredentials(appId: string, oAuthScope?: string): Promise { const appPassword = await this.credentialProvider.getAppPassword(appId); if (JwtTokenValidation.isGovernment(this.channelService)) { - const appCredentials = new MicrosoftAppCredentials(appId, appPassword, undefined, oAuthScope); - appCredentials.oAuthEndpoint = GovernmentConstants.ToChannelFromBotLoginUrl; - return appCredentials; + return new MicrosoftGovernmentAppCredentials(appId, appPassword, undefined, oAuthScope); } else { return new MicrosoftAppCredentials(appId, appPassword, undefined, oAuthScope); } diff --git a/libraries/botframework-connector/src/auth/appCredentials.ts b/libraries/botframework-connector/src/auth/appCredentials.ts index 4df398456f..5caf20ebf9 100644 --- a/libraries/botframework-connector/src/auth/appCredentials.ts +++ b/libraries/botframework-connector/src/auth/appCredentials.ts @@ -48,12 +48,14 @@ export abstract class AppCredentials implements ServiceClientCredentials { constructor( appId: string, channelAuthTenant?: string, - oAuthScope: string = AuthenticationConstants.ToBotFromChannelTokenIssuer + oAuthScope: string = null ) { this.appId = appId; this.tenant = channelAuthTenant; - this.oAuthEndpoint = AuthenticationConstants.ToChannelFromBotLoginUrlPrefix + this.tenant; - this.oAuthScope = oAuthScope; + this.oAuthEndpoint = this.GetToChannelFromBotLoginUrlPrefix() + this.tenant; + this.oAuthScope = (oAuthScope && oAuthScope.length > 0) + ? oAuthScope + : this.GetToChannelFromBotOAuthScope(); } /** @@ -69,7 +71,7 @@ export abstract class AppCredentials implements ServiceClientCredentials { * Sets tenant to be used for channel authentication. */ private set tenant(value: string) { - this._tenant = value && value.length > 0 ? value : AuthenticationConstants.DefaultChannelAuthTenant; + this._tenant = value && value.length > 0 ? value : this.GetDefaultChannelAuthTenant(); } /** @@ -191,6 +193,18 @@ export abstract class AppCredentials implements ServiceClientCredentials { } } + protected GetToChannelFromBotOAuthScope(): string { + return AuthenticationConstants.ToChannelFromBotOAuthScope; + } + + protected GetToChannelFromBotLoginUrlPrefix(): string { + return AuthenticationConstants.ToChannelFromBotLoginUrlPrefix; + } + + protected GetDefaultChannelAuthTenant(): string { + return AuthenticationConstants.DefaultChannelAuthTenant; + } + protected abstract refreshToken(): Promise; /** diff --git a/libraries/botframework-connector/src/auth/governmentConstants.ts b/libraries/botframework-connector/src/auth/governmentConstants.ts index 4a422bcd7a..dc873237f6 100644 --- a/libraries/botframework-connector/src/auth/governmentConstants.ts +++ b/libraries/botframework-connector/src/auth/governmentConstants.ts @@ -15,9 +15,21 @@ export namespace GovernmentConstants { /** * TO CHANNEL FROM BOT: Login URL + * + * DEPRECATED: DO NOT USE */ export const ToChannelFromBotLoginUrl = 'https://login.microsoftonline.us/MicrosoftServices.onmicrosoft.us'; + /** + * TO CHANNEL FROM BOT: Login URL prefix + */ + export const ToChannelFromBotLoginUrlPrefix = 'https://login.microsoftonline.us/'; + + /** + * TO CHANNEL FROM BOT: Default tenant from which to obtain a token for bot to channel communication + */ + export const DefaultChannelAuthTenant = 'MicrosoftServices.onmicrosoft.us'; + /** * TO CHANNEL FROM BOT: OAuth scope to request */ diff --git a/libraries/botframework-connector/src/auth/index.ts b/libraries/botframework-connector/src/auth/index.ts index 38a5cb890e..c4c275cb07 100644 --- a/libraries/botframework-connector/src/auth/index.ts +++ b/libraries/botframework-connector/src/auth/index.ts @@ -32,6 +32,7 @@ export * from './managedIdentityAppCredentials'; export * from './managedIdentityAuthenticator'; export * from './managedIdentityServiceClientCredentialsFactory'; export * from './microsoftAppCredentials'; +export * from './microsoftGovernmentAppCredentials'; export * from './passwordServiceClientCredentialFactory'; export * from './serviceClientCredentialsFactory'; export * from './skillValidation'; diff --git a/libraries/botframework-connector/src/auth/microsoftGovernmentAppCredentials.ts b/libraries/botframework-connector/src/auth/microsoftGovernmentAppCredentials.ts new file mode 100644 index 0000000000..85d4affa6e --- /dev/null +++ b/libraries/botframework-connector/src/auth/microsoftGovernmentAppCredentials.ts @@ -0,0 +1,40 @@ +/** + * @module botframework-connector + */ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +import { GovernmentConstants } from './governmentConstants'; +import { MicrosoftAppCredentials } from './microsoftAppCredentials'; + +/** + * MicrosoftGovermentAppCredentials auth implementation + */ +export class MicrosoftGovernmentAppCredentials extends MicrosoftAppCredentials { + + /** + * Initializes a new instance of the [MicrosoftGovernmentAppCredentials](xref:botframework-connector.MicrosoftGovernmentAppCredentials) class. + * + * @param {string} appId The Microsoft app ID. + * @param {string} appPassword The Microsoft app password. + * @param {string} channelAuthTenant Optional. The oauth token tenant. + * @param {string} oAuthScope Optional. The scope for the token. + */ + constructor(appId: string, public appPassword: string, channelAuthTenant?: string, oAuthScope?: string) { + super(appId, appPassword, channelAuthTenant, oAuthScope); + } + + protected GetToChannelFromBotOAuthScope(): string { + return GovernmentConstants.ToChannelFromBotOAuthScope; + } + + protected GetToChannelFromBotLoginUrlPrefix(): string { + return GovernmentConstants.ToChannelFromBotLoginUrlPrefix; + } + + protected GetDefaultChannelAuthTenant(): string { + return GovernmentConstants.DefaultChannelAuthTenant; + } +} diff --git a/libraries/botframework-connector/src/auth/msalServiceClientCredentialsFactory.ts b/libraries/botframework-connector/src/auth/msalServiceClientCredentialsFactory.ts index e807aa513e..997915d4c7 100644 --- a/libraries/botframework-connector/src/auth/msalServiceClientCredentialsFactory.ts +++ b/libraries/botframework-connector/src/auth/msalServiceClientCredentialsFactory.ts @@ -70,11 +70,11 @@ export class MsalServiceClientCredentialsFactory implements ServiceClientCredent ); } - if (normalizedEndpoint === GovernmentConstants.ToChannelFromBotLoginUrl.toLowerCase()) { + if (normalizedEndpoint.startsWith(GovernmentConstants.ToChannelFromBotLoginUrlPrefix)) { return new MsalAppCredentials( this.clientApplication, appId, - GovernmentConstants.ToChannelFromBotLoginUrl, + undefined, audience || GovernmentConstants.ToChannelFromBotOAuthScope ); } diff --git a/libraries/botframework-connector/src/auth/passwordServiceClientCredentialFactory.ts b/libraries/botframework-connector/src/auth/passwordServiceClientCredentialFactory.ts index ec01185056..11e60bce7a 100644 --- a/libraries/botframework-connector/src/auth/passwordServiceClientCredentialFactory.ts +++ b/libraries/botframework-connector/src/auth/passwordServiceClientCredentialFactory.ts @@ -8,6 +8,7 @@ import type { ServiceClientCredentials } from '@azure/core-http'; import { AuthenticationConstants } from './authenticationConstants'; import { GovernmentConstants } from './governmentConstants'; import { MicrosoftAppCredentials } from './microsoftAppCredentials'; +import { MicrosoftGovernmentAppCredentials } from './microsoftGovernmentAppCredentials'; import { ServiceClientCredentialsFactory } from './serviceClientCredentialsFactory'; import { stringExt } from 'botbuilder-stdlib'; @@ -111,9 +112,8 @@ export class PasswordServiceClientCredentialFactory implements ServiceClientCred if (normalizedEndpoint?.startsWith(AuthenticationConstants.ToChannelFromBotLoginUrlPrefix)) { credentials = new MicrosoftAppCredentials(appId, this.password, this.tenantId, audience); - } else if (normalizedEndpoint === GovernmentConstants.ToChannelFromBotLoginUrl.toLowerCase()) { - credentials = new MicrosoftAppCredentials(appId, this.password, this.tenantId, audience); - credentials.oAuthEndpoint = loginEndpoint; + } else if (normalizedEndpoint?.startsWith(GovernmentConstants.ToChannelFromBotLoginUrlPrefix)) { + credentials = new MicrosoftGovernmentAppCredentials(appId, this.password, this.tenantId, audience); } else { credentials = new PrivateCloudAppCredentials( appId, diff --git a/libraries/botframework-connector/tests/auth/microsoftAppCredentials.test.js b/libraries/botframework-connector/tests/auth/microsoftAppCredentials.test.js new file mode 100644 index 0000000000..a69184a9c3 --- /dev/null +++ b/libraries/botframework-connector/tests/auth/microsoftAppCredentials.test.js @@ -0,0 +1,17 @@ +const { MicrosoftAppCredentials, AuthenticationConstants } = require('../..'); +const assert = require('assert'); + +describe('MicrosoftAppCredentialsTestSuite', function () { + describe('MicrosoftAppCredentialsTestCase', function () { + it('AssertOAuthEndpointAndOAuthScope', function() { + var credentials = new MicrosoftAppCredentials("appId", "password", "tenantId", "audience"); + assert.strictEqual(AuthenticationConstants.ToChannelFromBotLoginUrlPrefix + "tenantId", credentials.oAuthEndpoint); + assert.strictEqual("audience", credentials.oAuthScope); + + var credentials = new MicrosoftAppCredentials("appId", "password"); + assert.strictEqual(AuthenticationConstants.ToChannelFromBotLoginUrlPrefix + AuthenticationConstants.DefaultChannelAuthTenant, credentials.oAuthEndpoint); + assert.strictEqual(AuthenticationConstants.ToChannelFromBotOAuthScope, credentials.oAuthScope); + }); + + }); +}); diff --git a/libraries/botframework-connector/tests/auth/microsoftGovernmentAppCredentials.test.js b/libraries/botframework-connector/tests/auth/microsoftGovernmentAppCredentials.test.js new file mode 100644 index 0000000000..8c0743711b --- /dev/null +++ b/libraries/botframework-connector/tests/auth/microsoftGovernmentAppCredentials.test.js @@ -0,0 +1,17 @@ +const { MicrosoftGovernmentAppCredentials, GovernmentConstants } = require('../..'); +const assert = require('assert'); + +describe('MicrosoftGovernmentAppCredentialsTestSuite', function () { + describe('MicrosoftGovernmentAppCredentialsTestCase', function () { + it('AssertOAuthEndpointAndOAuthScope', function() { + var credentials = new MicrosoftGovernmentAppCredentials("appId", "password", "tenantId", "audience"); + assert.strictEqual(GovernmentConstants.ToChannelFromBotLoginUrlPrefix + "tenantId", credentials.oAuthEndpoint); + assert.strictEqual("audience", credentials.oAuthScope); + + var credentials = new MicrosoftGovernmentAppCredentials("appId", "password"); + assert.strictEqual(GovernmentConstants.ToChannelFromBotLoginUrlPrefix + GovernmentConstants.DefaultChannelAuthTenant, credentials.oAuthEndpoint); + assert.strictEqual(GovernmentConstants.ToChannelFromBotOAuthScope, credentials.oAuthScope); + }); + + }); +}); From 1bf5c808c04e2f888a2d6452ba742a853882b1de Mon Sep 17 00:00:00 2001 From: fangyangci Date: Tue, 19 Dec 2023 10:20:51 +0800 Subject: [PATCH 3/4] fix js lint --- .../botbuilder/src/botFrameworkAdapter.ts | 20 +++++++++++----- .../botbuilder/src/botFrameworkHttpClient.ts | 2 +- .../src/auth/appCredentials.ts | 10 ++------ .../auth/microsoftGovernmentAppCredentials.ts | 3 +-- .../auth/microsoftAppCredentials.test.js | 24 ++++++++++++------- .../microsoftGovernmentAppCredentials.test.js | 23 +++++++++++------- 6 files changed, 47 insertions(+), 35 deletions(-) diff --git a/libraries/botbuilder/src/botFrameworkAdapter.ts b/libraries/botbuilder/src/botFrameworkAdapter.ts index eaa4f01a18..7df7cc2599 100644 --- a/libraries/botbuilder/src/botFrameworkAdapter.ts +++ b/libraries/botbuilder/src/botFrameworkAdapter.ts @@ -261,8 +261,7 @@ export class BotFrameworkAdapter this.settings.appPassword || '', this.settings.channelAuthTenant ); - } - else{ + } else { this.credentials = new MicrosoftAppCredentials( this.settings.appId, this.settings.appPassword || '', @@ -1634,10 +1633,19 @@ export class BotFrameworkAdapter ); } else { if (JwtTokenValidation.isGovernment(this.settings.channelService)) { - credentials = new MicrosoftGovernmentAppCredentials(appId, appPassword, this.settings.channelAuthTenant, oAuthScope); - } - else{ - credentials = new MicrosoftAppCredentials(appId, appPassword, this.settings.channelAuthTenant, oAuthScope); + credentials = new MicrosoftGovernmentAppCredentials( + appId, + appPassword, + this.settings.channelAuthTenant, + oAuthScope + ); + } else { + credentials = new MicrosoftAppCredentials( + appId, + appPassword, + this.settings.channelAuthTenant, + oAuthScope + ); } } diff --git a/libraries/botbuilder/src/botFrameworkHttpClient.ts b/libraries/botbuilder/src/botFrameworkHttpClient.ts index fd4a91eacd..1586cf64f5 100644 --- a/libraries/botbuilder/src/botFrameworkHttpClient.ts +++ b/libraries/botbuilder/src/botFrameworkHttpClient.ts @@ -16,7 +16,7 @@ import { ICredentialProvider, JwtTokenValidation, MicrosoftAppCredentials, - MicrosoftGovernmentAppCredentials + MicrosoftGovernmentAppCredentials, } from 'botframework-connector'; import { USER_AGENT } from './botFrameworkAdapter'; diff --git a/libraries/botframework-connector/src/auth/appCredentials.ts b/libraries/botframework-connector/src/auth/appCredentials.ts index 5caf20ebf9..7e3a82d30b 100644 --- a/libraries/botframework-connector/src/auth/appCredentials.ts +++ b/libraries/botframework-connector/src/auth/appCredentials.ts @@ -45,17 +45,11 @@ export abstract class AppCredentials implements ServiceClientCredentials { * @param channelAuthTenant Optional. The oauth token tenant. * @param oAuthScope The scope for the token. */ - constructor( - appId: string, - channelAuthTenant?: string, - oAuthScope: string = null - ) { + constructor(appId: string, channelAuthTenant?: string, oAuthScope: string = null) { this.appId = appId; this.tenant = channelAuthTenant; this.oAuthEndpoint = this.GetToChannelFromBotLoginUrlPrefix() + this.tenant; - this.oAuthScope = (oAuthScope && oAuthScope.length > 0) - ? oAuthScope - : this.GetToChannelFromBotOAuthScope(); + this.oAuthScope = oAuthScope && oAuthScope.length > 0 ? oAuthScope : this.GetToChannelFromBotOAuthScope(); } /** diff --git a/libraries/botframework-connector/src/auth/microsoftGovernmentAppCredentials.ts b/libraries/botframework-connector/src/auth/microsoftGovernmentAppCredentials.ts index 85d4affa6e..4eb51a887a 100644 --- a/libraries/botframework-connector/src/auth/microsoftGovernmentAppCredentials.ts +++ b/libraries/botframework-connector/src/auth/microsoftGovernmentAppCredentials.ts @@ -13,8 +13,7 @@ import { MicrosoftAppCredentials } from './microsoftAppCredentials'; * MicrosoftGovermentAppCredentials auth implementation */ export class MicrosoftGovernmentAppCredentials extends MicrosoftAppCredentials { - - /** + /** * Initializes a new instance of the [MicrosoftGovernmentAppCredentials](xref:botframework-connector.MicrosoftGovernmentAppCredentials) class. * * @param {string} appId The Microsoft app ID. diff --git a/libraries/botframework-connector/tests/auth/microsoftAppCredentials.test.js b/libraries/botframework-connector/tests/auth/microsoftAppCredentials.test.js index a69184a9c3..83ca0f8059 100644 --- a/libraries/botframework-connector/tests/auth/microsoftAppCredentials.test.js +++ b/libraries/botframework-connector/tests/auth/microsoftAppCredentials.test.js @@ -3,15 +3,21 @@ const assert = require('assert'); describe('MicrosoftAppCredentialsTestSuite', function () { describe('MicrosoftAppCredentialsTestCase', function () { - it('AssertOAuthEndpointAndOAuthScope', function() { - var credentials = new MicrosoftAppCredentials("appId", "password", "tenantId", "audience"); - assert.strictEqual(AuthenticationConstants.ToChannelFromBotLoginUrlPrefix + "tenantId", credentials.oAuthEndpoint); - assert.strictEqual("audience", credentials.oAuthScope); - - var credentials = new MicrosoftAppCredentials("appId", "password"); - assert.strictEqual(AuthenticationConstants.ToChannelFromBotLoginUrlPrefix + AuthenticationConstants.DefaultChannelAuthTenant, credentials.oAuthEndpoint); - assert.strictEqual(AuthenticationConstants.ToChannelFromBotOAuthScope, credentials.oAuthScope); - }); + it('AssertOAuthEndpointAndOAuthScope', function () { + const credentials1 = new MicrosoftAppCredentials('appId', 'password', 'tenantId', 'audience'); + assert.strictEqual( + AuthenticationConstants.ToChannelFromBotLoginUrlPrefix + 'tenantId', + credentials1.oAuthEndpoint + ); + assert.strictEqual('audience', credentials1.oAuthScope); + const credentials2 = new MicrosoftAppCredentials('appId', 'password'); + assert.strictEqual( + AuthenticationConstants.ToChannelFromBotLoginUrlPrefix + + AuthenticationConstants.DefaultChannelAuthTenant, + credentials2.oAuthEndpoint + ); + assert.strictEqual(AuthenticationConstants.ToChannelFromBotOAuthScope, credentials2.oAuthScope); + }); }); }); diff --git a/libraries/botframework-connector/tests/auth/microsoftGovernmentAppCredentials.test.js b/libraries/botframework-connector/tests/auth/microsoftGovernmentAppCredentials.test.js index 8c0743711b..01e9b7b166 100644 --- a/libraries/botframework-connector/tests/auth/microsoftGovernmentAppCredentials.test.js +++ b/libraries/botframework-connector/tests/auth/microsoftGovernmentAppCredentials.test.js @@ -3,15 +3,20 @@ const assert = require('assert'); describe('MicrosoftGovernmentAppCredentialsTestSuite', function () { describe('MicrosoftGovernmentAppCredentialsTestCase', function () { - it('AssertOAuthEndpointAndOAuthScope', function() { - var credentials = new MicrosoftGovernmentAppCredentials("appId", "password", "tenantId", "audience"); - assert.strictEqual(GovernmentConstants.ToChannelFromBotLoginUrlPrefix + "tenantId", credentials.oAuthEndpoint); - assert.strictEqual("audience", credentials.oAuthScope); - - var credentials = new MicrosoftGovernmentAppCredentials("appId", "password"); - assert.strictEqual(GovernmentConstants.ToChannelFromBotLoginUrlPrefix + GovernmentConstants.DefaultChannelAuthTenant, credentials.oAuthEndpoint); - assert.strictEqual(GovernmentConstants.ToChannelFromBotOAuthScope, credentials.oAuthScope); - }); + it('AssertOAuthEndpointAndOAuthScope', function () { + const credentials1 = new MicrosoftGovernmentAppCredentials('appId', 'password', 'tenantId', 'audience'); + assert.strictEqual( + GovernmentConstants.ToChannelFromBotLoginUrlPrefix + 'tenantId', + credentials1.oAuthEndpoint + ); + assert.strictEqual('audience', credentials1.oAuthScope); + const credentials2 = new MicrosoftGovernmentAppCredentials('appId', 'password'); + assert.strictEqual( + GovernmentConstants.ToChannelFromBotLoginUrlPrefix + GovernmentConstants.DefaultChannelAuthTenant, + credentials2.oAuthEndpoint + ); + assert.strictEqual(GovernmentConstants.ToChannelFromBotOAuthScope, credentials2.oAuthScope); + }); }); }); From 91293f3e4feda0adc75d5a242690693f462e58a8 Mon Sep 17 00:00:00 2001 From: fangyangci Date: Tue, 19 Dec 2023 11:18:27 +0800 Subject: [PATCH 4/4] fix js lint --- .../src/auth/aseChannelValidation.ts | 54 ++++++++------- .../src/auth/jwtTokenValidation.ts | 1 - ...parameterizedBotFrameworkAuthentication.ts | 1 - .../tests/auth/aseChannelValidation.test.js | 69 ++++++++++++------- 4 files changed, 73 insertions(+), 52 deletions(-) diff --git a/libraries/botframework-connector/src/auth/aseChannelValidation.ts b/libraries/botframework-connector/src/auth/aseChannelValidation.ts index a580b8189e..4d326e6279 100644 --- a/libraries/botframework-connector/src/auth/aseChannelValidation.ts +++ b/libraries/botframework-connector/src/auth/aseChannelValidation.ts @@ -25,40 +25,45 @@ import { BetweenBotAndAseChannelTokenValidationParameters } from './tokenValidat * Validates and Examines JWT tokens from the Bot Framework AseChannel */ export namespace AseChannelValidation { - const ChannelId = "AseChannel"; + const ChannelId = 'AseChannel'; let _creadentialProvider: ICredentialProvider; let _channelService: string; export let MetadataUrl: string; - export function init(configuration: any) - { - let appId = configuration.MicrosoftAppId; - let tenantId = configuration.MicrosoftAppTenantId; + /** + * init authentication from user .env configuration. + * + * @param configuration The user .env configuration. + */ + export function init(configuration: any) { + const appId = configuration.MicrosoftAppId; + const tenantId = configuration.MicrosoftAppTenantId; _channelService = configuration.ChannelService; - MetadataUrl = _channelService !== undefined && JwtTokenValidation.isGovernment(_channelService) - ? GovernmentConstants.ToBotFromEmulatorOpenIdMetadataUrl - : AuthenticationConstants.ToBotFromEmulatorOpenIdMetadataUrl; + MetadataUrl = + _channelService !== undefined && JwtTokenValidation.isGovernment(_channelService) + ? GovernmentConstants.ToBotFromEmulatorOpenIdMetadataUrl + : AuthenticationConstants.ToBotFromEmulatorOpenIdMetadataUrl; - _creadentialProvider = new SimpleCredentialProvider(appId, ""); + _creadentialProvider = new SimpleCredentialProvider(appId, ''); - let tenantIds: string[] = [ + const tenantIds: string[] = [ tenantId, - "f8cdef31-a31e-4b4a-93e4-5f571e91255a", // US Gov MicrosoftServices.onmicrosoft.us - "d6d49420-f39b-4df7-a1dc-d59a935871db" // Public botframework.com + 'f8cdef31-a31e-4b4a-93e4-5f571e91255a', // US Gov MicrosoftServices.onmicrosoft.us + 'd6d49420-f39b-4df7-a1dc-d59a935871db', // Public botframework.com ]; - let validIssuers: string[] = []; + const validIssuers: string[] = []; tenantIds.forEach((tmpId: string) => { validIssuers.push(`https://sts.windows.net/${tmpId}/`); // Auth Public/US Gov, 1.0 token validIssuers.push(`https://login.microsoftonline.com/${tmpId}/v2.0`); // Auth Public, 2.0 token validIssuers.push(`https://login.microsoftonline.us/${tmpId}/v2.0`); // Auth for US Gov, 2.0 token - }) + }); BetweenBotAndAseChannelTokenValidationParameters.issuer = validIssuers; } /** * Determines if a given Auth header is from the Bot Framework AseChannel * - * @param {string} channelId. + * @param {string} channelId The channelId. * @returns {boolean} True, if the token was issued by the AseChannel. Otherwise, false. */ export function isTokenFromAseChannel(channelId: string): boolean { @@ -69,17 +74,14 @@ export namespace AseChannelValidation { * Validate the incoming Auth Header as a token sent from the Bot Framework AseChannel. * A token issued by the Bot Framework will FAIL this check. Only AseChannel tokens will pass. * - * @param {string} authHeader The raw HTTP header in the format: "Bearer [longString]" - * @param {ICredentialProvider} credentials The user defined set of valid credentials, such as the AppId. - * @param {string} channelService The channelService value that distinguishes public Azure from US Government Azure. - * @param {AuthenticationConfiguration} authConfig The authentication configuration. + * @param {string} authHeader The raw HTTP header in the format: 'Bearer [longString]' + * @param {AuthenticationConfiguration} authConfig The authentication configuration. * @returns {Promise} A valid ClaimsIdentity. */ export async function authenticateAseChannelToken( authHeader: string, authConfig: AuthenticationConfiguration = new AuthenticationConfiguration() ): Promise { - const tokenExtractor: JwtTokenExtractor = new JwtTokenExtractor( BetweenBotAndAseChannelTokenValidationParameters, MetadataUrl, @@ -108,14 +110,14 @@ export namespace AseChannelValidation { const versionClaim: string = identity.getClaimValue(AuthenticationConstants.VersionClaim); if (versionClaim === null) { throw new AuthenticationError( - 'Unauthorized. "ver" claim is required on AseChannel Tokens.', + 'Unauthorized. "ver" claim is required on Emulator Tokens.', StatusCodes.UNAUTHORIZED ); } let appId = ''; - // The AseChannel, depending on Version, sends the AppId via either the + // The Emulator, depending on Version, sends the AppId via either the // appid claim (Version 1) or the Authorized Party claim (Version 2). if (!versionClaim || versionClaim === '1.0') { // either no Version or a version of "1.0" means we should look for @@ -124,19 +126,19 @@ export namespace AseChannelValidation { if (!appIdClaim) { // No claim around AppID. Not Authorized. throw new AuthenticationError( - 'Unauthorized. "appid" claim is required on AseChannel Token version "1.0".', + 'Unauthorized. "appid" claim is required on Emulator Token version "1.0".', StatusCodes.UNAUTHORIZED ); } appId = appIdClaim; } else if (versionClaim === '2.0') { - // AseChannel, "2.0" puts the AppId in the "azp" claim. + // Emulator, "2.0" puts the AppId in the "azp" claim. const appZClaim: string = identity.getClaimValue(AuthenticationConstants.AuthorizedParty); if (!appZClaim) { // No claim around AppID. Not Authorized. throw new AuthenticationError( - 'Unauthorized. "azp" claim is required on AseChannel Token version "2.0".', + 'Unauthorized. "azp" claim is required on Emulator Token version "2.0".', StatusCodes.UNAUTHORIZED ); } @@ -145,7 +147,7 @@ export namespace AseChannelValidation { } else { // Unknown Version. Not Authorized. throw new AuthenticationError( - `Unauthorized. Unknown AseChannel Token version "${versionClaim}".`, + `Unauthorized. Unknown Emulator Token version "${versionClaim}".`, StatusCodes.UNAUTHORIZED ); } diff --git a/libraries/botframework-connector/src/auth/jwtTokenValidation.ts b/libraries/botframework-connector/src/auth/jwtTokenValidation.ts index 6892598485..be593f6b6f 100644 --- a/libraries/botframework-connector/src/auth/jwtTokenValidation.ts +++ b/libraries/botframework-connector/src/auth/jwtTokenValidation.ts @@ -129,7 +129,6 @@ export namespace JwtTokenValidation { authConfig: AuthenticationConfiguration, serviceUrl: string ): Promise { - if (AseChannelValidation.isTokenFromAseChannel(channelId)) { return AseChannelValidation.authenticateAseChannelToken(authHeader); } diff --git a/libraries/botframework-connector/src/auth/parameterizedBotFrameworkAuthentication.ts b/libraries/botframework-connector/src/auth/parameterizedBotFrameworkAuthentication.ts index b8bf0e81c7..21d14f02ce 100644 --- a/libraries/botframework-connector/src/auth/parameterizedBotFrameworkAuthentication.ts +++ b/libraries/botframework-connector/src/auth/parameterizedBotFrameworkAuthentication.ts @@ -271,7 +271,6 @@ export class ParameterizedBotFrameworkAuthentication extends BotFrameworkAuthent channelId: string, serviceUrl: string ): Promise { - if (AseChannelValidation.isTokenFromAseChannel(channelId)) { return AseChannelValidation.authenticateAseChannelToken(authHeader); } diff --git a/libraries/botframework-connector/tests/auth/aseChannelValidation.test.js b/libraries/botframework-connector/tests/auth/aseChannelValidation.test.js index d886689762..5d2c167d1c 100644 --- a/libraries/botframework-connector/tests/auth/aseChannelValidation.test.js +++ b/libraries/botframework-connector/tests/auth/aseChannelValidation.test.js @@ -1,46 +1,67 @@ -const { AseChannelValidation, GovernmentConstants, AuthenticationConstants, BetweenBotAndAseChannelTokenValidationParameters } = require('../..'); +const { + AseChannelValidation, + GovernmentConstants, + AuthenticationConstants, + BetweenBotAndAseChannelTokenValidationParameters, +} = require('../..'); const assert = require('assert'); describe('AseChannelTestSuite', function () { describe('AseChannelTestCase', function () { - it('ValidationMetadataUrlTest_AseChannel_USGov', function () { - let config = { + const config = { ChannelService: GovernmentConstants.ChannelService, - } + }; AseChannelValidation.init(config); - assert.strictEqual(AseChannelValidation.MetadataUrl, GovernmentConstants.ToBotFromEmulatorOpenIdMetadataUrl); - + assert.strictEqual( + AseChannelValidation.MetadataUrl, + GovernmentConstants.ToBotFromEmulatorOpenIdMetadataUrl + ); }); it('ValidationMetadataUrlTest_AseChannel_Public', function () { - let config = {} + const config = {}; AseChannelValidation.init(config); - assert.strictEqual(AseChannelValidation.MetadataUrl, AuthenticationConstants.ToBotFromEmulatorOpenIdMetadataUrl); + assert.strictEqual( + AseChannelValidation.MetadataUrl, + AuthenticationConstants.ToBotFromEmulatorOpenIdMetadataUrl + ); }); it('ValidationIssueUrlTest_AseChannel', function () { - let config = { - MicrosoftAppTenantId: "testTenantId" - } + const config = { + MicrosoftAppTenantId: 'testTenantId', + }; AseChannelValidation.init(config); - let tenantIds = - [ - "testTenantId", - "f8cdef31-a31e-4b4a-93e4-5f571e91255a", // US Gov MicrosoftServices.onmicrosoft.us - "d6d49420-f39b-4df7-a1dc-d59a935871db" // Public botframework.com - ]; + const tenantIds = [ + 'testTenantId', + 'f8cdef31-a31e-4b4a-93e4-5f571e91255a', // US Gov MicrosoftServices.onmicrosoft.us + 'd6d49420-f39b-4df7-a1dc-d59a935871db', // Public botframework.com + ]; tenantIds.forEach(function (tmpId) { - assert.strictEqual(true, BetweenBotAndAseChannelTokenValidationParameters.issuer.includes(`https://sts.windows.net/${tmpId}/`)); - assert.strictEqual(true, BetweenBotAndAseChannelTokenValidationParameters.issuer.includes(`https://login.microsoftonline.com/${tmpId}/v2.0`)); - assert.strictEqual(true, BetweenBotAndAseChannelTokenValidationParameters.issuer.includes(`https://login.microsoftonline.us/${tmpId}/v2.0`)); - }) + assert.strictEqual( + true, + BetweenBotAndAseChannelTokenValidationParameters.issuer.includes( + `https://sts.windows.net/${tmpId}/` + ) + ); + assert.strictEqual( + true, + BetweenBotAndAseChannelTokenValidationParameters.issuer.includes( + `https://login.microsoftonline.com/${tmpId}/v2.0` + ) + ); + assert.strictEqual( + true, + BetweenBotAndAseChannelTokenValidationParameters.issuer.includes( + `https://login.microsoftonline.us/${tmpId}/v2.0` + ) + ); + }); }); it('ValidationChannelIdTest_AseChannel', function () { - assert.strictEqual(true, AseChannelValidation.isTokenFromAseChannel("AseChannel")); + assert.strictEqual(true, AseChannelValidation.isTokenFromAseChannel('AseChannel')); }); }); }); - -