From db21ad65746defa918775c883b3eb5e99e06d6e8 Mon Sep 17 00:00:00 2001 From: Rodrigo Rodriguez Date: Fri, 17 Nov 2023 14:27:12 -0300 Subject: [PATCH] new(core.gbapp): #387 adding /setupSecurity multiple tokens. --- packages/admin.gbapp/dialogs/AdminDialog.ts | 40 +++++++++++++++---- .../admin.gbapp/services/GBAdminService.ts | 32 +++++++++------ packages/basic.gblib/services/GBVMService.ts | 29 ++++++++++++-- packages/core.gbapp/services/GBCoreService.ts | 29 ++++++++++++-- packages/core.gbapp/services/GBMinService.ts | 28 +++++++++---- .../services/WhatsappDirectLine.ts | 5 --- 6 files changed, 121 insertions(+), 42 deletions(-) diff --git a/packages/admin.gbapp/dialogs/AdminDialog.ts b/packages/admin.gbapp/dialogs/AdminDialog.ts index 0422987a..8b1ae94e 100644 --- a/packages/admin.gbapp/dialogs/AdminDialog.ts +++ b/packages/admin.gbapp/dialogs/AdminDialog.ts @@ -383,6 +383,12 @@ export class AdminDialog extends IGBDialog { min.dialogs.add( new WaterfallDialog('/setupSecurity', [ async step => { + const tokenName = step.activeDialog.state.tokenName = step.options['tokenName']; + step.activeDialog.state.clientId = min.core.getParam(min.instance, `${tokenName} Client ID`, null), + step.activeDialog.state.clientSecret = min.core.getParam(min.instance, `${tokenName} Client Secret`, null), + step.activeDialog.state.host = min.core.getParam(min.instance, `${tokenName} Host`, null), + step.activeDialog.state.tenant = min.core.getParam(min.instance, `${tokenName} Tenant`, null) + if (step.context.activity.channelId !== 'msteams' && process.env.ENABLE_AUTH) { return await step.beginDialog('/auth'); } else { @@ -391,12 +397,19 @@ export class AdminDialog extends IGBDialog { }, async step => { + if (step.activeDialog.state.tokenName) { + return await step.next(step.options); + } + const locale = step.context.activity.locale; const prompt = Messages[locale].enter_authenticator_tenant; return await min.conversationalService.prompt(min, step, prompt); }, async step => { + if (step.activeDialog.state.tokenName) { + return await step.next(step.options); + } step.activeDialog.state.authenticatorTenant = step.result; const locale = step.context.activity.locale; const prompt = Messages[locale].enter_authenticator_authority_host_url; @@ -404,25 +417,36 @@ export class AdminDialog extends IGBDialog { return await min.conversationalService.prompt(min, step, prompt); }, async step => { + step.activeDialog.state.authenticatorAuthorityHostUrl = step.result; + + const tokenName = step.activeDialog.state.tokenName; - min.instance.authenticatorTenant = step.activeDialog.state.authenticatorTenant; - min.instance.authenticatorAuthorityHostUrl = step.activeDialog.state.authenticatorAuthorityHostUrl; + if (tokenName){ + step.activeDialog.state.clientId + step.activeDialog.state.clientSecret + step.activeDialog.state.tenant + } + else{ + min.instance.authenticatorAuthorityHostUrl = step.activeDialog.state.authenticatorAuthorityHostUrl; + min.instance.authenticatorTenant = step.activeDialog.state.authenticatorTenant; + } await min.adminService.updateSecurityInfo( min.instance.instanceId, - step.activeDialog.state.authenticatorTenant, - step.activeDialog.state.authenticatorAuthorityHostUrl + tokenName? step.activeDialog.state.tenant:step.activeDialog.state.authenticatorTenant, + tokenName?step.activeDialog.state.host:step.activeDialog.state.authenticatorAuthorityHostUrl ); - + const locale = step.context.activity.locale; const buf = Buffer.alloc(16); const state = `${min.instance.instanceId}${crypto.randomFillSync(buf).toString('hex')}`; - min.adminService.setValue(min.instance.instanceId, 'AntiCSRFAttackState', state); + min.adminService.setValue(min.instance.instanceId, `${tokenName}AntiCSRFAttackState`, state); - const redirectUri = urlJoin(process.env.BOT_URL, min.instance.botId, '/token'); - const url = `https://login.microsoftonline.com/${step.activeDialog.state.authenticatorTenant}/oauth2/authorize?client_id=${min.instance.marketplaceId}&response_type=code&redirect_uri=${redirectUri}&scope=https://graph.microsoft.com/.default&state=${state}&response_mode=query`; + const redirectUri = urlJoin(process.env.BOT_URL, min.instance.botId, `/token?value=${tokenName}`); + const scope = tokenName?'': 'https://graph.microsoft.com/.default'; + const url = `https://login.microsoftonline.com/${step.activeDialog.state.authenticatorTenant}/oauth2/authorize?client_id=${min.instance.marketplaceId}&response_type=code&redirect_uri=${redirectUri}&scope=${scope}&state=${state}&response_mode=query`; await min.conversationalService.sendText(min, step, Messages[locale].consent(url)); diff --git a/packages/admin.gbapp/services/GBAdminService.ts b/packages/admin.gbapp/services/GBAdminService.ts index a2a45048..801ffad4 100644 --- a/packages/admin.gbapp/services/GBAdminService.ts +++ b/packages/admin.gbapp/services/GBAdminService.ts @@ -230,17 +230,23 @@ export class GBAdminService implements IGBAdminService { return obj.value; } - public async acquireElevatedToken(instanceId: number, root: boolean = false): Promise { + public async acquireElevatedToken(instanceId: number, root: boolean = false, + tokenName: string = null, + clientId: string = null, + clientSecret: string = null, + host: string = null, + tenant: string = null + ): Promise { if (root) { const minBoot = GBServer.globals.minBoot; instanceId = minBoot.instance.instanceId; } - GBLog.info(`Acquiring token for instanceId: ${instanceId} (root: ${root}).`); + GBLog.info(`Acquiring token for instanceId: ${instanceId} ${tokenName} (root: ${root}).`); let expiresOnV; try { - expiresOnV = await this.getValue(instanceId, 'expiresOn'); + expiresOnV = await this.getValue(instanceId, `${tokenName}expiresOn`); } catch (error) { throw new Error(`/setupSecurity is required before running /publish.`); } @@ -250,23 +256,23 @@ export class GBAdminService implements IGBAdminService { const expiresOn = new Date(expiresOnV); if (expiresOn.getTime() > new Date().getTime()) { - const accessToken = await this.getValue(instanceId, 'accessToken'); + const accessToken = await this.getValue(instanceId, `${tokenName}accessToken`); resolve(accessToken); } else { const authorizationUrl = urlJoin( - instance.authenticatorAuthorityHostUrl, - instance.authenticatorTenant, + host ? host : instance.authenticatorAuthorityHostUrl, + tenant ? tenant : instance.authenticatorTenant, '/oauth2/authorize' ); - const refreshToken = await this.getValue(instanceId, 'refreshToken'); - const resource = 'https://graph.microsoft.com'; + const refreshToken = await this.getValue(instanceId, `${tokenName}refreshToken`); + const resource = clientId? '': 'https://graph.microsoft.com'; const authenticationContext = new AuthenticationContext(authorizationUrl); authenticationContext.acquireTokenWithRefreshToken( refreshToken, - instance.marketplaceId, - instance.marketplacePassword, + clientId ? clientId : instance.marketplaceId, + clientId ? clientSecret : instance.marketplacePassword, resource, async (err, res) => { if (err !== null) { @@ -274,9 +280,9 @@ export class GBAdminService implements IGBAdminService { } else { const token = res as TokenResponse; try { - await this.setValue(instanceId, 'accessToken', token.accessToken); - await this.setValue(instanceId, 'refreshToken', token.refreshToken); - await this.setValue(instanceId, 'expiresOn', token.expiresOn.toString()); + await this.setValue(instanceId, `${tokenName}accessToken`, token.accessToken); + await this.setValue(instanceId, `${tokenName}refreshToken`, token.refreshToken); + await this.setValue(instanceId, `${tokenName}expiresOn`, token.expiresOn.toString()); resolve(token.accessToken); } catch (error) { reject(err); diff --git a/packages/basic.gblib/services/GBVMService.ts b/packages/basic.gblib/services/GBVMService.ts index cbecdb98..c7348adc 100644 --- a/packages/basic.gblib/services/GBVMService.ts +++ b/packages/basic.gblib/services/GBVMService.ts @@ -719,15 +719,36 @@ export class GBVMService extends GBService { GBConfigService.get('DEFAULT_CONTENT_LANGUAGE') ); - // These variables will be automatically be available as normal BASIC variables. - let variables = []; + // Find all tokens in .gbot Config. + + const strFind = ' Client ID'; + const tokens = await min.core['findParam'](min.instance, strFind); + await CollectionUtil.asyncForEach(tokens,async t => { + const tokenName = t.replace(strFind, ''); + try { + variables[t] = await (min.adminService as any)['acquireElevatedToken'] + (min.instance.instanceId, false, + min.core.getParam(min.instance, `${tokenName} Client ID`, null), + min.core.getParam(min.instance, `${tokenName} Client Secret`, null), + min.core.getParam(min.instance, `${tokenName} Host`, null), + min.core.getParam(min.instance, `${tokenName} Tenant`, null) + + ); + } catch (error) { + variables[t] = 'ERROR: Configure /setupSecurity before using token variables.'; + } + }); + + // These variables will be automatically be available as normal BASIC variables. + try { - variables['aadToken'] = await (min.adminService as any)['acquireElevatedToken'](min.instance.instanceId, false); + variables['aadToken'] = await (min.adminService as any)['acquireElevatedToken'] + (min.instance.instanceId, false); } catch (error) { variables['aadToken'] = 'ERROR: Configure /setupSecurity before using aadToken variable.'; - } + } // Adds all .gbot params as variables. diff --git a/packages/core.gbapp/services/GBCoreService.ts b/packages/core.gbapp/services/GBCoreService.ts index 66eef279..35e5c2a0 100644 --- a/packages/core.gbapp/services/GBCoreService.ts +++ b/packages/core.gbapp/services/GBCoreService.ts @@ -400,7 +400,7 @@ ENDPOINT_UPDATE=true public async getApplicationsByInstanceId(appPackages, instanceId: number) { const options = { where: { instanceId: instanceId } }; const apps = await GuaribasApplications.findAll(options); - + let matchingAppPackages = []; await CollectionUtil.asyncForEach(appPackages, async appPackage => { const filenameOnly = Path.basename(appPackage.name); @@ -409,10 +409,10 @@ ENDPOINT_UPDATE=true matchingAppPackages.push(appPackage); } }); - + return matchingAppPackages; } - + /** * Loads all bot instances from object storage, if it's formatted. */ @@ -429,7 +429,7 @@ ENDPOINT_UPDATE=true await CollectionUtil.asyncForEach(instances, async instance => { GBLog.info(`Updating bot endpoint for ${instance.botId}...`); try { - + await installationDeployer.updateBotProxy( instance.botId, GBConfigService.get('CLOUD_GROUP'), @@ -698,4 +698,25 @@ ENDPOINT_UPDATE=true return value; } + + /** + * Finds a dynamic param from instance. * + */ + public async findParam(instance: IGBInstance, criteria: string) { + let params = null; + const list = []; + if (instance.params) { + params = JSON.parse(instance.params); + } + + Object.keys(params).forEach(e => { + if (criteria.toLowerCase().indexOf(e.toLowerCase())) { + list.push(e); + } + }); + + return list; + } + + } diff --git a/packages/core.gbapp/services/GBMinService.ts b/packages/core.gbapp/services/GBMinService.ts index 9199ab44..9029a750 100644 --- a/packages/core.gbapp/services/GBMinService.ts +++ b/packages/core.gbapp/services/GBMinService.ts @@ -491,16 +491,27 @@ export class GBMinService { */ private handleOAuthTokenRequests(server: any, min: GBMinInstance, instance: IGBInstance) { server.get(`/${min.instance.botId}/token`, async (req, res) => { + + const tokenName = req.params['value']; + // Checks request state by reading AntiCSRFAttackState from GB Admin infrastructure. - const state = await min.adminService.getValue(instance.instanceId, 'AntiCSRFAttackState'); + const state = await min.adminService.getValue(instance.instanceId, `${tokenName}AntiCSRFAttackState`); if (req.query.state !== state) { const msg = 'WARNING: state field was not provided as anti-CSRF token'; GBLog.error(msg); throw new Error(msg); } + + const clientId = min.core.getParam(min.instance, `${tokenName} Client ID`, null), + const clientSecret = min.core.getParam(min.instance, `${tokenName} Client Secret`, null), + const host = min.core.getParam(min.instance, `${tokenName} Host`, null), + const tenant = min.core.getParam(min.instance, `${tokenName} Tenant`, null) + const authenticationContext = new AuthenticationContext.AuthenticationContext( - urlJoin(min.instance.authenticatorAuthorityHostUrl, min.instance.authenticatorTenant) + urlJoin( + tokenName? host: min.instance.authenticatorAuthorityHostUrl, + tokenName? tenant: min.instance.authenticatorTenant) ); const resource = 'https://graph.microsoft.com'; @@ -510,20 +521,21 @@ export class GBMinService { req.query.code, urlJoin(process.env.BOT_URL, min.instance.botId, '/token'), resource, - instance.marketplaceId, - instance.marketplacePassword, + tokenName? clientId: instance.marketplaceId, + tokenName? clientSecret: instance.marketplacePassword, async (err, token) => { if (err) { const msg = `handleOAuthTokenRequests: Error acquiring token: ${err}`; GBLog.error(msg); res.send(msg); } else { + // Saves token to the database. - await this.adminService.setValue(instance.instanceId, 'accessToken', token['accessToken']); - await this.adminService.setValue(instance.instanceId, 'refreshToken', token['refreshToken']); - await this.adminService.setValue(instance.instanceId, 'expiresOn', token['expiresOn'].toString()); - await this.adminService.setValue(instance.instanceId, 'AntiCSRFAttackState', null); + await this.adminService.setValue(instance.instanceId, `${tokenName}accessToken`, token['accessToken']); + await this.adminService.setValue(instance.instanceId, `${tokenName}refreshToken`, token['refreshToken']); + await this.adminService.setValue(instance.instanceId, `${tokenName}expiresOn`, token['expiresOn'].toString()); + await this.adminService.setValue(instance.instanceId, `${tokenName}AntiCSRFAttackState`, null); // Inform the home for default .gbui after finishing token retrival. diff --git a/packages/whatsapp.gblib/services/WhatsappDirectLine.ts b/packages/whatsapp.gblib/services/WhatsappDirectLine.ts index c4bf8378..69e4e8bd 100644 --- a/packages/whatsapp.gblib/services/WhatsappDirectLine.ts +++ b/packages/whatsapp.gblib/services/WhatsappDirectLine.ts @@ -1149,11 +1149,6 @@ export class WhatsappDirectLine extends GBService { await sec.updateUserHearOnDialog(user.userId, null); await (activeMin as any).whatsAppDirectLine.received(req, res); } else { - await (activeMin as any).whatsAppDirectLine.sendToDevice( - id, - `Olá! Seja bem-vinda(o)!\nMe chamo ${activeMin.instance.title}. Como posso ajudar? Pode me falar que eu te ouço, me manda um aúdio.`, - null - ); if (res) { res.end(); }