From 084ade9ba8ddff0f0879c98b9e0a4c67dc909e1f Mon Sep 17 00:00:00 2001 From: fangyangci <133664123+fangyangci@users.noreply.github.com> Date: Thu, 21 Dec 2023 22:55:47 +0800 Subject: [PATCH] fix: USGovSingleTenant OAuthEndpoint (#4588) * fix usgov single tenant * fix js lint --- .../botbuilder/src/botFrameworkAdapter.ts | 44 ++++++++++++------- .../botbuilder/src/botFrameworkHttpClient.ts | 6 +-- .../src/auth/appCredentials.ts | 24 ++++++---- .../src/auth/governmentConstants.ts | 12 +++++ .../botframework-connector/src/auth/index.ts | 1 + .../auth/microsoftGovernmentAppCredentials.ts | 39 ++++++++++++++++ .../msalServiceClientCredentialsFactory.ts | 4 +- .../passwordServiceClientCredentialFactory.ts | 6 +-- .../auth/microsoftAppCredentials.test.js | 23 ++++++++++ .../microsoftGovernmentAppCredentials.test.js | 22 ++++++++++ 10 files changed, 149 insertions(+), 32 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..7df7cc2599 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,19 @@ 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 +289,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 +1632,21 @@ 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..1586cf64f5 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..7e3a82d30b 100644 --- a/libraries/botframework-connector/src/auth/appCredentials.ts +++ b/libraries/botframework-connector/src/auth/appCredentials.ts @@ -45,15 +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 = AuthenticationConstants.ToBotFromChannelTokenIssuer - ) { + constructor(appId: string, channelAuthTenant?: string, 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 +65,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 +187,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..4eb51a887a --- /dev/null +++ b/libraries/botframework-connector/src/auth/microsoftGovernmentAppCredentials.ts @@ -0,0 +1,39 @@ +/** + * @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..83ca0f8059 --- /dev/null +++ b/libraries/botframework-connector/tests/auth/microsoftAppCredentials.test.js @@ -0,0 +1,23 @@ +const { MicrosoftAppCredentials, AuthenticationConstants } = require('../..'); +const assert = require('assert'); + +describe('MicrosoftAppCredentialsTestSuite', function () { + describe('MicrosoftAppCredentialsTestCase', function () { + 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 new file mode 100644 index 0000000000..01e9b7b166 --- /dev/null +++ b/libraries/botframework-connector/tests/auth/microsoftGovernmentAppCredentials.test.js @@ -0,0 +1,22 @@ +const { MicrosoftGovernmentAppCredentials, GovernmentConstants } = require('../..'); +const assert = require('assert'); + +describe('MicrosoftGovernmentAppCredentialsTestSuite', function () { + describe('MicrosoftGovernmentAppCredentialsTestCase', function () { + 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); + }); + }); +});