diff --git a/packages/admin.gbapp/services/GBAdminService.ts b/packages/admin.gbapp/services/GBAdminService.ts index e6df1903..c12305b2 100644 --- a/packages/admin.gbapp/services/GBAdminService.ts +++ b/packages/admin.gbapp/services/GBAdminService.ts @@ -223,10 +223,6 @@ export class GBAdminService implements IGBAdminService { await deployer.undeployPackageFromLocalPath(min.instance, urlJoin(GBDeployer.workFolder, packageName)); } - public static async broadcastCommand(text: any, min: GBMinInstance) { - const packageName = text.split(' ')[1]; - } - public static isSharePointPath(path: string) { return path.indexOf('sharepoint.com') > 0; } diff --git a/packages/core.gbapp/dialogs/BroadcastDialog.ts b/packages/core.gbapp/dialogs/BroadcastDialog.ts new file mode 100644 index 00000000..8b560869 --- /dev/null +++ b/packages/core.gbapp/dialogs/BroadcastDialog.ts @@ -0,0 +1,71 @@ +/*****************************************************************************\ +| ( )_ _ | +| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ | +| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' v `\ /'_`\ | +| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) | +| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' | +| | | ( )_) | | +| (_) \___/' | +| | +| General Bots Copyright (c) Pragmatismo.io. All rights reserved. | +| Licensed under the AGPL-3.0. | +| | +| According to our dual licensing model, this program can be used either | +| under the terms of the GNU Affero General Public License, version 3, | +| or under a proprietary license. | +| | +| The texts of the GNU Affero General Public License with an additional | +| permission and of our proprietary license can be found at and | +| in the LICENSE file you have received along with this program. | +| | +| This program is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY, without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| GNU Affero General Public License for more details. | +| | +| "General Bots" is a registered trademark of Pragmatismo.io. | +| The licensing of the program under the AGPLv3 does not imply a | +| trademark license. Therefore any rights, title and interest in | +| our trademarks remain entirely with us. | +| | +\*****************************************************************************/ + +/** + * @fileoverview General Bots server core. + */ + +'use strict'; + +import { BotAdapter } from 'botbuilder'; +import { WaterfallDialog } from 'botbuilder-dialogs'; +import { GBMinInstance, IGBDialog } from 'botlib'; +import { Messages } from '../strings'; +import { SecService } from '../../security.gbapp/services/SecService'; +import { GBServer } from '../../../src/app'; +import { GBConversationalService } from '../services/GBConversationalService'; +/** + * Dialog for the bot explains about itself. + */ +export class BroadcastDialog extends IGBDialog { + /** + * Setup dialogs flows and define services call. + * + * @param bot The bot adapter. + * @param min The minimal bot instance data. + */ + public static setup(bot: BotAdapter, min: GBMinInstance) { + min.dialogs.add( + new WaterfallDialog('/gb-broadcast', [ + async step => { + const locale = step.context.activity.locale; + + return await min.conversationalService.prompt(min, step, 'Type the message and the broadcast will start.'); + }, + async step => { + await min.conversationalService['broadcast'](min, step.result); + return await step.next(); + } + ]) + ); + } +} diff --git a/packages/core.gbapp/index.ts b/packages/core.gbapp/index.ts index 0f203816..d34dcb6d 100644 --- a/packages/core.gbapp/index.ts +++ b/packages/core.gbapp/index.ts @@ -38,6 +38,7 @@ import { GBDialogStep, GBLog, GBMinInstance, IGBCoreService, IGBPackage } from 'botlib'; import { Sequelize } from 'sequelize-typescript'; +import { BroadcastDialog } from './dialogs/BroadcastDialog'; import { SwitchBotDialog } from './dialogs/SwitchBot'; import { WelcomeDialog } from './dialogs/WelcomeDialog'; import { WhoAmIDialog } from './dialogs/WhoAmIDialog'; @@ -76,5 +77,6 @@ export class GBCorePackage implements IGBPackage { WhoAmIDialog.setup(min.bot, min); SwitchBotDialog.setup(min.bot, min); DialogClass.setup(min.bot, min); + BroadcastDialog.setup(min.bot, min); } } diff --git a/packages/core.gbapp/services/GBConversationalService.ts b/packages/core.gbapp/services/GBConversationalService.ts index f796259c..9782584b 100644 --- a/packages/core.gbapp/services/GBConversationalService.ts +++ b/packages/core.gbapp/services/GBConversationalService.ts @@ -44,6 +44,9 @@ import { Readable } from 'stream'; import { GBAdminService } from '../../admin.gbapp/services/GBAdminService'; import { SecService } from '../../security.gbapp/services/SecService'; import { AnalyticsService } from '../../analytics.gblib/services/AnalyticsService'; +import { CollectionUtil } from 'pragmatismo-io-framework'; +import { WaterfallStep, WaterfallStepContext } from 'botbuilder-dialogs'; +import { MicrosoftAppCredentials } from 'botframework-connector'; const urlJoin = require('url-join'); const PasswordGenerator = require('strict-password-generator').default; const Nexmo = require('nexmo'); @@ -625,4 +628,31 @@ export class GBConversationalService { } } } + + public async broadcast(min: GBMinInstance, message: string) { + GBLog.info(`Sending broadcast notifications...`); + + let sleep = ms => { + return new Promise(resolve => { + setTimeout(resolve, ms); + }); + }; + + const service = new SecService(); + const users = await service.getAllUsers(min.instance.instanceId); + await CollectionUtil.asyncForEach(users, async user => { + if (user.conversationReference) { + const ref = JSON.parse(user.conversationReference); + MicrosoftAppCredentials.trustServiceUrl(ref.serviceUrl); + await min.bot['createConversation'](ref, async t1 => { + const ref2 = TurnContext.getConversationReference(t1.activity); + await min.bot.continueConversation(ref2, async t2 => { + await t2.sendActivity(message); + }); + }); + } else { + GBLog.info(`User: ${user.systemUserId} with no conversation ID while broadcasting.`); + } + }); + } } diff --git a/packages/core.gbapp/services/GBMinService.ts b/packages/core.gbapp/services/GBMinService.ts index a879b359..299ea803 100644 --- a/packages/core.gbapp/services/GBMinService.ts +++ b/packages/core.gbapp/services/GBMinService.ts @@ -43,9 +43,16 @@ const request = require('request-promise-native'); const removeRoute = require('express-remove-route'); const AuthenticationContext = require('adal-node').AuthenticationContext; const wash = require('washyourmouthoutwithsoap'); -import { AutoSaveStateMiddleware, BotFrameworkAdapter, ConversationState, MemoryStorage, UserState } from 'botbuilder'; +import { + AutoSaveStateMiddleware, + BotFrameworkAdapter, + ConversationState, + MemoryStorage, + TurnContext, + UserState +} from 'botbuilder'; import { CollectionUtil, AzureText } from 'pragmatismo-io-framework'; -import { ConfirmPrompt, WaterfallDialog } from 'botbuilder-dialogs'; +import { ConfirmPrompt, OAuthPrompt, WaterfallDialog } from 'botbuilder-dialogs'; import { GBDialogStep, GBLog, @@ -227,7 +234,7 @@ export class GBMinService { GBServer.globals.minInstances.push(min); await this.deployer.deployPackage(min, 'packages/default.gbtheme'); - + // Install per bot deployed packages. let packagePath = `work/${min.botId}.gbai/${min.botId}.gbdialog`; @@ -527,6 +534,14 @@ export class GBMinService { min.dialogs = new DialogSet(dialogState); min.dialogs.add(new TextPrompt('textPrompt')); min.dialogs.add(new ConfirmPrompt('confirmPrompt')); + min.dialogs.add( + new OAuthPrompt('oAuthPrompt', { + connectionName: 'OAuth2', + text: 'Please sign in.', + title: 'Sign in', + timeout: 300000 + }) + ); return { min, adapter, conversationState }; } @@ -582,6 +597,7 @@ export class GBMinService { // First time processing. + const sec = new SecService(); if (!user.loaded) { await min.conversationalService.sendEvent(min, step, 'loadInstance', { instanceId: instance.instanceId, @@ -594,7 +610,6 @@ export class GBMinService { user.cb = undefined; if (context.activity.from.id !== min.botId) { - let sec = new SecService(); const member = context.activity.from; const persistedUser = await sec.ensureUser( @@ -605,7 +620,10 @@ export class GBMinService { 'web', member.name ); - + if (step.context.activity.channelId === "msteams"){ + persistedUser.conversationReference = JSON.stringify(TurnContext.getConversationReference(context.activity)); + await persistedUser.save(); + } const analytics = new AnalyticsService(); user.systemUser = persistedUser; diff --git a/packages/security.gbapp/dialogs/OAuthDialog.ts b/packages/security.gbapp/dialogs/OAuthDialog.ts new file mode 100644 index 00000000..86ac6b21 --- /dev/null +++ b/packages/security.gbapp/dialogs/OAuthDialog.ts @@ -0,0 +1,69 @@ +/*****************************************************************************\ +| ( )_ _ | +| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ | +| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' v `\ /'_`\ | +| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) | +| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' | +| | | ( )_) | | +| (_) \___/' | +| | +| General Bots Copyright (c) Pragmatismo.io. All rights reserved. | +| Licensed under the AGPL-3.0. | +| | +| According to our dual licensing model, this program can be used either | +| under the terms of the GNU Affero General Public License, version 3, | +| or under a proprietary license. | +| | +| The texts of the GNU Affero General Public License with an additional | +| permission and of our proprietary license can be found at and | +| in the LICENSE file you have received along with this program. | +| | +| This program is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY, without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| GNU Affero General Public License for more details. | +| | +| "General Bots" is a registered trademark of Pragmatismo.io. | +| The licensing of the program under the AGPLv3 does not imply a | +| trademark license. Therefore any rights, title and interest in | +| our trademarks remain entirely with us. | +| | +\*****************************************************************************/ + +/** + * @fileoverview Dialog for handling OAuth scenarios. + */ + +'use strict'; + +import { TokenResponse } from 'botbuilder'; +import { IGBDialog, GBLog, GBMinInstance } from 'botlib'; +import { Messages } from '../strings'; + +/** + * Dialogs for handling Menu control. + */ +export class OAuthDialog extends IGBDialog { + public static getOAuthDialog(min: GBMinInstance) { + return { + id: '/auth', + waterfall: [ + async step => { + step.activeDialog.state.options = step.options; + return await step.beginDialog('oAuthPrompt'); + }, + async step => { + const tokenResponse: TokenResponse = step.result; + if (tokenResponse) { + GBLog.info('Token acquired.'); + + return await step.endDialog(tokenResponse); + } else { + await step.context.sendActivity('Please sign in so I can show you your profile.'); + return await step.replaceDialog('/auth'); + } + } + ] + }; + } +} diff --git a/packages/security.gbapp/index.ts b/packages/security.gbapp/index.ts index 15609d57..92e19701 100644 --- a/packages/security.gbapp/index.ts +++ b/packages/security.gbapp/index.ts @@ -42,6 +42,7 @@ import { GBDialogStep, GBLog, GBMinInstance, IGBCoreService, IGBPackage } from ' import {ProfileDialog} from './dialogs/ProfileDialog' import { Sequelize } from 'sequelize-typescript'; import { GuaribasGroup, GuaribasUser, GuaribasUserGroup } from './models'; +import { OAuthDialog } from './dialogs/OAuthDialog'; /** * Package for the security module. @@ -54,8 +55,9 @@ export class GBSecurityPackage implements IGBPackage { ProfileDialog.getEmailDialog(min), ProfileDialog.getMobileDialog(min), ProfileDialog.getMobileConfirmDialog(min), + OAuthDialog.getOAuthDialog(min), ]; - GBLog.verbose(`getDialogs called.`); + } public async unloadPackage(core: IGBCoreService): Promise { GBLog.verbose(`unloadPackage called.`); @@ -72,8 +74,7 @@ export class GBSecurityPackage implements IGBPackage { public async onExchangeData(min: GBMinInstance, kind: string, data: any) { GBLog.verbose(`onExchangeData called.`); } - - + public async loadPackage(core: IGBCoreService, sequelize: Sequelize): Promise { core.sequelize.addModels([GuaribasGroup, GuaribasUser, GuaribasUserGroup]); } diff --git a/packages/security.gbapp/services/SecService.ts b/packages/security.gbapp/services/SecService.ts index 29c659a5..4ca370cb 100644 --- a/packages/security.gbapp/services/SecService.ts +++ b/packages/security.gbapp/services/SecService.ts @@ -6,12 +6,10 @@ import { GuaribasGroup, GuaribasUser, GuaribasUserGroup } from '../models'; import { ConversationReference } from 'botbuilder'; import { CollectionUtil } from 'pragmatismo-io-framework'; - /** * Security service layer. */ export class SecService extends GBService { - public async importSecurityFile(localPath: string, instance: IGBInstance) { const security = JSON.parse(Fs.readFileSync(urlJoin(localPath, 'security.json'), 'utf8')); await CollectionUtil.asyncForEach(security.groups, async group => { @@ -31,7 +29,6 @@ export class SecService extends GBService { userGroup.groupId = g1.groupId; userGroup.userId = user2.userId; await userGroup.save(); - }); }); } @@ -45,7 +42,6 @@ export class SecService extends GBService { displayName: string ): Promise { let user = await GuaribasUser.findOne({ - where: { userSystemId: userSystemId } @@ -85,13 +81,7 @@ export class SecService extends GBService { await user.save(); } - public async updateUserInstance( - userSystemId: string, - instanceId: number - ): Promise { - - - + public async updateUserInstance(userSystemId: string, instanceId: number): Promise { let user = await GuaribasUser.findOne({ where: { userSystemId: userSystemId @@ -114,7 +104,6 @@ export class SecService extends GBService { }); if (agentSystemId === null) { - const agent = await GuaribasUser.findOne({ where: { userSystemId: user.agentSystemId @@ -122,16 +111,15 @@ export class SecService extends GBService { }); if (agent !== null && agent !== undefined) { - agent.agentMode = "bot"; + agent.agentMode = 'bot'; agent.agentSystemId = null; await agent.save(); } - user.agentMode = "bot"; + user.agentMode = 'bot'; user.agentSystemId = null; - } else { - user.agentMode = "human"; + user.agentMode = 'human'; user.agentSystemId = agentSystemId; const agent = await GuaribasUser.findOne({ where: { @@ -140,7 +128,7 @@ export class SecService extends GBService { }); agent.instanceId = user.instanceId; - agent.agentMode = "self"; + agent.agentMode = 'self'; agent.agentSystemId = null; await agent.save(); } @@ -160,35 +148,30 @@ export class SecService extends GBService { throw `TRANSFER_TO phones must talk first to the bot before becoming an agent.`; } - return (user.agentMode === "self"); + return user.agentMode === 'self'; } - - public async assignHumanAgent( - userSystemId: string, - instanceId: number - ): Promise { - + public async assignHumanAgent(userSystemId: string, instanceId: number): Promise { let agentSystemId; const list = process.env.TRANSFER_TO.split(';'); await CollectionUtil.asyncForEach(list, async item => { - - if (!await this.isAgentSystemId(item) && item !== undefined && - agentSystemId === undefined && item !== userSystemId) { // TODO: Optimize loop. + if ( + !(await this.isAgentSystemId(item)) && + item !== undefined && + agentSystemId === undefined && + item !== userSystemId + ) { + // TODO: Optimize loop. agentSystemId = item; } - }); await this.updateCurrentAgent(userSystemId, instanceId, agentSystemId); return agentSystemId; - } - public async getUserFromSystemId( - systemId: string - ): Promise { + public async getUserFromSystemId(systemId: string): Promise { return await GuaribasUser.findOne({ where: { userSystemId: systemId @@ -196,15 +179,19 @@ export class SecService extends GBService { }); } - public async getUserFromAgentSystemId( - systemId: string - ): Promise { + public async getUserFromAgentSystemId(systemId: string): Promise { return await GuaribasUser.findOne({ where: { - agentSystemId: systemId, - + agentSystemId: systemId } }); } + public async getAllUsers(instanceId: number): Promise { + return await GuaribasUser.findAll({ + where: { + instanceId: instanceId + } + }); + } }