diff --git a/Composer/packages/server/src/services/auth/electronAuthProvider.ts b/Composer/packages/server/src/services/auth/electronAuthProvider.ts index b137853b40..76756585fd 100644 --- a/Composer/packages/server/src/services/auth/electronAuthProvider.ts +++ b/Composer/packages/server/src/services/auth/electronAuthProvider.ts @@ -34,7 +34,8 @@ export class ElectronAuthProvider extends AuthProvider { async getAccessToken(params: AuthParameters): Promise { const { getAccessToken } = this.electronContext; - const { targetResource = '' } = params; + const { targetResource = '', authority } = params; + const authParams = { targetResource, authority }; if (isLinux()) { log('Auth login flow is currently unsupported in Linux.'); @@ -44,7 +45,7 @@ export class ElectronAuthProvider extends AuthProvider { log('Getting access token.'); // try to get a cached token - const cachedToken = this.getCachedToken(params); + const cachedToken = this.getCachedToken(authParams); if (!!cachedToken && Date.now() <= cachedToken.expiryTime.valueOf()) { log('Returning cached token.'); return cachedToken.accessToken; @@ -53,8 +54,8 @@ export class ElectronAuthProvider extends AuthProvider { try { // otherwise get a fresh token log('Did not find cached token. Getting fresh token.'); - const { accessToken, acquiredAt, expiryTime } = await getAccessToken({ targetResource }); - this.cacheTokens({ accessToken, acquiredAt, expiryTime }, params); + const { accessToken, acquiredAt, expiryTime } = await getAccessToken(authParams); + this.cacheTokens({ accessToken, acquiredAt, expiryTime }, authParams); return accessToken; } catch (e) { @@ -140,6 +141,6 @@ export class ElectronAuthProvider extends AuthProvider { } private getTokenHash(params: AuthParameters): string { - return params.targetResource || ''; + return JSON.stringify(params); } } diff --git a/Composer/packages/types/src/publish.ts b/Composer/packages/types/src/publish.ts index a75a7b873f..60ce68974f 100644 --- a/Composer/packages/types/src/publish.ts +++ b/Composer/packages/types/src/publish.ts @@ -101,7 +101,7 @@ export type PublishPlugin = { config: Config, project: IBotProject, user?: UserIdentity, - getAccessToken?: GetAccessToken + getAccessToken?: GetAccessToken, ) => Promise; getProvisionStatus?: ( target: string, diff --git a/extensions/azurePublish/src/node/index.ts b/extensions/azurePublish/src/node/index.ts index b3bf71a1c9..f6867d81e4 100644 --- a/extensions/azurePublish/src/node/index.ts +++ b/extensions/azurePublish/src/node/index.ts @@ -16,6 +16,7 @@ import { PublishResult, SDKKinds, PublishProfile, + AuthParameters, } from '@botframework-composer/types'; import { parseRuntimeKey, applyPublishingProfileToSettings } from '@bfc/shared'; import { indexer } from '@bfc/indexers'; @@ -268,7 +269,13 @@ export default async (composer: IExtensionRegistration): Promise => { /*******************************************************************************************************************************/ /* These methods provision resources to azure async */ /*******************************************************************************************************************************/ - asyncProvision = async (jobId: string, config: ProvisionConfig, project: IBotProject, user): Promise => { + asyncProvision = async ( + jobId: string, + config: ProvisionConfig, + project: IBotProject, + user, + geAccessToken: (params: AuthParameters) => Promise + ): Promise => { const { runtimeLanguage } = parseRuntimeKey(project.settings?.runtime?.key); // map runtime language/platform to worker runtime @@ -286,13 +293,16 @@ export default async (composer: IExtensionRegistration): Promise => { const { name } = provisionConfig; // Create the object responsible for actually taking the provision actions. - const azureProvisioner = new BotProjectProvision({ - ...provisionConfig, - logger: (msg: any) => { - this.logger(msg); - BackgroundProcessManager.updateProcess(jobId, 202, msg.message); + const azureProvisioner = new BotProjectProvision( + { + ...provisionConfig, + logger: (msg: any) => { + this.logger(msg); + BackgroundProcessManager.updateProcess(jobId, 202, msg.message); + }, }, - }); + geAccessToken + ); // perform the provision using azureProvisioner.create. // this will start the process, then return. @@ -514,7 +524,7 @@ export default async (composer: IExtensionRegistration): Promise => { *************************************************************************************************/ provision = async (config: any, project: IBotProject, user, getAccessToken): Promise => { const jobId = BackgroundProcessManager.startProcess(202, project.id, config.name, 'Creating Azure resources...'); - this.asyncProvision(jobId, config, project, user); + this.asyncProvision(jobId, config, project, user, getAccessToken); return BackgroundProcessManager.getStatus(jobId); }; diff --git a/extensions/azurePublish/src/node/provision.ts b/extensions/azurePublish/src/node/provision.ts index 0b98396df5..524f588c6f 100644 --- a/extensions/azurePublish/src/node/provision.ts +++ b/extensions/azurePublish/src/node/provision.ts @@ -3,6 +3,7 @@ import { TokenCredentials } from '@azure/ms-rest-js'; import axios, { AxiosRequestConfig } from 'axios'; +import { AuthParameters } from '@botframework-composer/types'; import { BotProjectDeployLoggerType } from './types'; import { AzureResourceManangerConfig } from './azureResourceManager/azureResourceManagerConfig'; @@ -48,7 +49,7 @@ export class BotProjectProvision { // Will be assigned by create or deploy private tenantId = ''; - constructor(config: ProvisionConfig) { + constructor(config: ProvisionConfig, private getAccessToken?: (params: AuthParameters) => Promise) { this.subscriptionId = config.subscription; this.logger = config.logger; this.accessToken = config.accessToken; @@ -97,8 +98,26 @@ export class BotProjectProvision { message: '> Creating App Registration ...', }); + this.logger({ + status: BotProjectDeployLoggerType.PROVISION_INFO, + message: `> TenantId: ${this.tenantId}`, + }); + + const accessToken = await this.getAccessToken?.({ + scopes: ['https://graph.microsoft.com/Application.ReadWrite.All'], // TODO: figure out if we need to pass scopes + targetResource: 'https://graph.microsoft.com/', + authority: `https://login.microsoftonline.com/${this.tenantId}/oauth2/v2.0/authorize`, + }); + + this.logger({ + status: BotProjectDeployLoggerType.PROVISION_INFO, + message: `> Retrieved tenant specific graph access token ...: ${accessToken?.length} ${ + accessToken === this.graphToken + }`, + }); + const appCreateOptions: AxiosRequestConfig = { - headers: { Authorization: `Bearer ${this.graphToken}` }, + headers: { Authorization: `Bearer ${accessToken ?? this.graphToken}` }, }; // This call if successful returns an object in the form @@ -152,7 +171,7 @@ export class BotProjectProvision { }, }; const setSecretOptions: AxiosRequestConfig = { - headers: { Authorization: `Bearer ${this.graphToken}` }, + headers: { Authorization: appCreateOptions.headers.Authorization }, }; let passwordSet;