Skip to content

Commit

Permalink
Merge pull request #574 from codeforequity-at/develop
Browse files Browse the repository at this point in the history
Botium Core 1.10.0
  • Loading branch information
Botium authored Oct 26, 2020
2 parents f709ae9 + a1c81d3 commit 9c088fa
Show file tree
Hide file tree
Showing 21 changed files with 176 additions and 231 deletions.
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module.exports = {
Plugins: require('./src/Plugins'),
BotiumError: require('./src/scripting/BotiumError').BotiumError,
ScriptingMemory: require('./src/scripting/ScriptingMemory'),
HookUtils: require('./src/helpers/HookUtils'),
Lib: {
tryLoadPlugin: require('./src/containers/plugins/index').tryLoadPlugin
},
Expand Down
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "botium-core",
"version": "1.9.12",
"version": "1.10.0",
"description": "The Selenium for Chatbots",
"main": "index.js",
"module": "dist/botium-es.js",
Expand Down Expand Up @@ -64,6 +64,7 @@
"swagger-jsdoc": "^4.0.0",
"swagger-ui-express": "^4.1.4",
"uuid": "^8.3.0",
"vm2": "^3.9.2",
"write-yaml": "^1.0.0",
"xlsx": "^0.16.7",
"xregexp": "^4.3.0",
Expand Down
43 changes: 7 additions & 36 deletions src/containers/BaseContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@ const rimraf = require('rimraf')
const Bottleneck = require('bottleneck')
const _ = require('lodash')
const debug = require('debug')('botium-connector-BaseContainer')
const path = require('path')

const Events = require('../Events')
const Capabilities = require('../Capabilities')
const Queue = require('../helpers/Queue')
const { executeHook, getHook } = require('../helpers/HookUtils')
const BotiumMockMessage = require('../mocks/BotiumMockMessage')
const { BotiumError } = require('../scripting/BotiumError')

module.exports = class BaseContainer {
constructor (eventEmitter, tempDirectory, repo, caps, envs) {
Expand All @@ -26,39 +24,12 @@ module.exports = class BaseContainer {
}

Validate () {
this.onBuildHook = getHook(this.caps[Capabilities.CUSTOMHOOK_ONBUILD])
this.onStartHook = getHook(this.caps[Capabilities.CUSTOMHOOK_ONSTART])
this.onUserSaysHook = getHook(this.caps[Capabilities.CUSTOMHOOK_ONUSERSAYS])
this.onBotResponseHook = getHook(this.caps[Capabilities.CUSTOMHOOK_ONBOTRESPONSE])
this.onStopHook = getHook(this.caps[Capabilities.CUSTOMHOOK_ONSTOP])
this.onCleanHook = getHook(this.caps[Capabilities.CUSTOMHOOK_ONCLEAN])

if (!this.caps[Capabilities.SECURITY_ALLOW_UNSAFE] &&
(
this.caps[Capabilities.CUSTOMHOOK_ONBUILD] ||
this.caps[Capabilities.CUSTOMHOOK_ONSTART] ||
this.caps[Capabilities.CUSTOMHOOK_ONUSERSAYS] ||
this.caps[Capabilities.CUSTOMHOOK_ONBOTRESPONSE] ||
this.caps[Capabilities.CUSTOMHOOK_ONSTOP] ||
this.caps[Capabilities.CUSTOMHOOK_ONCLEAN])) {
throw new BotiumError(
'Security Error. Using unsafe custom hooks is not allowed',
{
type: 'security',
subtype: 'allow unsafe',
source: path.basename(__filename),
cause: {
SECURITY_ALLOW_UNSAFE: this.caps[Capabilities.SECURITY_ALLOW_UNSAFE],
onBuildHook: !!this.caps[Capabilities.CUSTOMHOOK_ONBUILD],
onStartHook: !!this.caps[Capabilities.CUSTOMHOOK_ONSTART],
onUserSaysHook: !!this.caps[Capabilities.CUSTOMHOOK_ONUSERSAYS],
onBotResponseHook: !!this.caps[Capabilities.CUSTOMHOOK_ONBOTRESPONSE],
onStopHook: !!this.caps[Capabilities.CUSTOMHOOK_ONSTOP],
onCleanHook: !!this.caps[Capabilities.CUSTOMHOOK_ONCLEAN]
}
}
)
}
this.onBuildHook = getHook(this.caps, this.caps[Capabilities.CUSTOMHOOK_ONBUILD])
this.onStartHook = getHook(this.caps, this.caps[Capabilities.CUSTOMHOOK_ONSTART])
this.onUserSaysHook = getHook(this.caps, this.caps[Capabilities.CUSTOMHOOK_ONUSERSAYS])
this.onBotResponseHook = getHook(this.caps, this.caps[Capabilities.CUSTOMHOOK_ONBOTRESPONSE])
this.onStopHook = getHook(this.caps, this.caps[Capabilities.CUSTOMHOOK_ONSTOP])
this.onCleanHook = getHook(this.caps, this.caps[Capabilities.CUSTOMHOOK_ONCLEAN])

return Promise.resolve()
}
Expand Down Expand Up @@ -245,7 +216,7 @@ module.exports = class BaseContainer {

async _RunCustomHook (name, hook, args) {
try {
await executeHook(hook, Object.assign({}, { container: this }, args))
await executeHook(this.caps, hook, Object.assign({}, { container: this }, args))
debug(`_RunCustomHook ${name} finished`)
} catch (err) {
debug(`_RunCustomHook ${name} finished with error: ${err.message || util.inspect(err)}`)
Expand Down
18 changes: 9 additions & 9 deletions src/containers/plugins/SimpleRestContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ module.exports = class SimpleRestContainer {
}
if (this.caps[Capabilities.SIMPLEREST_CONTEXT_MERGE_OR_REPLACE] !== 'MERGE' && this.caps[Capabilities.SIMPLEREST_CONTEXT_MERGE_OR_REPLACE] !== 'REPLACE') throw new Error('SIMPLEREST_CONTEXT_MERGE_OR_REPLACE capability only MERGE or REPLACE allowed')

this.startHook = getHook(this.caps[Capabilities.SIMPLEREST_START_HOOK])
this.stopHook = getHook(this.caps[Capabilities.SIMPLEREST_STOP_HOOK])
this.requestHook = getHook(this.caps[Capabilities.SIMPLEREST_REQUEST_HOOK])
this.responseHook = getHook(this.caps[Capabilities.SIMPLEREST_RESPONSE_HOOK])
this.startHook = getHook(this.caps, this.caps[Capabilities.SIMPLEREST_START_HOOK])
this.stopHook = getHook(this.caps, this.caps[Capabilities.SIMPLEREST_STOP_HOOK])
this.requestHook = getHook(this.caps, this.caps[Capabilities.SIMPLEREST_REQUEST_HOOK])
this.responseHook = getHook(this.caps, this.caps[Capabilities.SIMPLEREST_RESPONSE_HOOK])
}

Build () {
Expand Down Expand Up @@ -91,7 +91,7 @@ module.exports = class SimpleRestContainer {
},

(startHookComplete) => {
executeHook(this.startHook, this.view).then(() => startHookComplete()).catch(startHookComplete)
executeHook(this.caps, this.startHook, this.view).then(() => startHookComplete()).catch(startHookComplete)
},

(pingComplete) => {
Expand Down Expand Up @@ -160,7 +160,7 @@ module.exports = class SimpleRestContainer {
throw new Error(`Failed to call url ${this.caps[Capabilities.SIMPLEREST_STOP_URL]} to stop session: ${err.message}`)
}
}
await executeHook(this.stopHook, this.view)
await executeHook(this.caps, this.stopHook, this.view)
await this._unsubscribeInbound()
await this._stopPolling()
this.view = {}
Expand Down Expand Up @@ -289,14 +289,14 @@ module.exports = class SimpleRestContainer {

hasMessageText = true
const botMsg = { sourceData: body, messageText, media, buttons }
await executeHook(this.responseHook, Object.assign({ botMsg, botMsgRoot: jsonPathRoot, messageTextIndex }, this.view))
await executeHook(this.caps, this.responseHook, Object.assign({ botMsg, botMsgRoot: jsonPathRoot, messageTextIndex }, this.view))
result.push(botMsg)
}
}

if (!hasMessageText) {
const botMsg = { messageText: '', sourceData: body, media, buttons }
await executeHook(this.responseHook, Object.assign({ botMsg, botMsgRoot: jsonPathRoot }, this.view))
await executeHook(this.caps, this.responseHook, Object.assign({ botMsg, botMsgRoot: jsonPathRoot }, this.view))
result.push(botMsg)
}
}
Expand Down Expand Up @@ -423,7 +423,7 @@ module.exports = class SimpleRestContainer {
}
this._addRequestOptions(requestOptions)

await executeHook(this.requestHook, Object.assign({ requestOptions }, this.view))
await executeHook(this.caps, this.requestHook, Object.assign({ requestOptions }, this.view))

return requestOptions
}
Expand Down
101 changes: 65 additions & 36 deletions src/helpers/HookUtils.js
Original file line number Diff line number Diff line change
@@ -1,68 +1,82 @@
const util = require('util')
const path = require('path')
const fs = require('fs')
const vm = require('vm')
const { NodeVM } = require('vm2')
const esprima = require('esprima')
const _ = require('lodash')
const debug = require('debug')('botium-core-HookUtils')

const executeHook = async (hook, args) => {
return executeHookSync(hook, args)
const Capabilities = require('../Capabilities')
const { BotiumError } = require('../scripting/BotiumError')

const executeHook = async (caps, hook, args) => {
return executeHookSync(caps, hook, args)
}

const executeHookSync = (hook, args) => {
const executeHookSync = (caps, hook, args) => {
if (!hook) {
return
}
if (_.isFunction(hook)) {
try {
return hook(args)
} catch (err) {
throw new Error(`Calling Hook function failed: ${err.message}`)
const allowUnsafe = !!caps[Capabilities.SECURITY_ALLOW_UNSAFE]
if (allowUnsafe) {
if (_.isFunction(hook)) {
try {
return hook(args)
} catch (err) {
throw new Error(`Calling Hook function failed: ${err.message}`)
}
}
}

if (_.isString(hook)) {
try {
const sandbox = vm.createContext({ debug, console, process, ...args })
vm.runInContext(hook, sandbox)
return sandbox.result
const vm = new NodeVM({
eval: false,
require: false,
sandbox: args
})
return vm.run(hook)
} catch (err) {
throw new Error(`Calling Hook Javascript code failed: ${err.message}`)
}
}
throw new Error(`Unknown hook ${typeof hook}`)
}
const getHook = (data) => {
const getHook = (caps, data) => {
if (!data) {
return null
}

if (_.isFunction(data)) {
debug('found hook, type: function definition')
return data
}
const allowUnsafe = !!caps[Capabilities.SECURITY_ALLOW_UNSAFE]

let resultWithRequire
let tryLoadFile = path.resolve(process.cwd(), data)
if (fs.existsSync(tryLoadFile)) {
try {
resultWithRequire = require(tryLoadFile)
} catch (err) {
if (allowUnsafe) {
if (_.isFunction(data)) {
debug('found hook, type: function definition')
return data
}
} else {
tryLoadFile = data
try {
resultWithRequire = require(data)
} catch (err) {
}
}

if (resultWithRequire) {
if (_.isFunction(resultWithRequire)) {
debug(`found hook, type: require, in ${tryLoadFile}`)
return resultWithRequire
let resultWithRequire
let tryLoadFile = path.resolve(process.cwd(), data)
if (fs.existsSync(tryLoadFile)) {
try {
resultWithRequire = require(tryLoadFile)
} catch (err) {
}
} else {
throw new Error(`Cant load hook ${tryLoadFile} because it is not a function`)
tryLoadFile = data
try {
resultWithRequire = require(data)
} catch (err) {
}
}

if (resultWithRequire) {
if (_.isFunction(resultWithRequire)) {
debug(`found hook, type: require, in ${tryLoadFile}`)
return resultWithRequire
} else {
throw new Error(`Cant load hook ${tryLoadFile} because it is not a function`)
}
}
}

Expand All @@ -77,7 +91,22 @@ const getHook = (data) => {
return data
}

throw new Error(`Not valid hook ${util.inspect(data)}`)
if (!allowUnsafe) {
throw new BotiumError(
'Security Error. Using unsafe custom hook is not allowed',
{
type: 'security',
subtype: 'allow unsafe',
source: path.basename(__filename),
cause: {
SECURITY_ALLOW_UNSAFE: caps[Capabilities.SECURITY_ALLOW_UNSAFE],
hookData: data
}
}
)
} else {
throw new Error(`Not valid hook ${util.inspect(data)}`)
}
}

module.exports = {
Expand Down
2 changes: 1 addition & 1 deletion src/scripting/CompilerTxt.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ module.exports = class CompilerTxt extends CompilerBase {
if (line && line.startsWith('#')) {
pushPrev()

convoStepSender = line.substr(1)
convoStepSender = line.substr(1).trim()
convoStepChannel = null
convoStepLineIndex = currentLineIndex
if (convoStepSender.indexOf(' ') > 0) {
Expand Down
1 change: 1 addition & 0 deletions src/scripting/Convo.js
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ class Convo {
continue
} else {
debug(`${this.header.name}/${convoStep.stepTag}: message not found in #me section, message not sent to container ${util.inspect(convoStep)}`)
transcriptStep.botEnd = new Date()
await this.scriptingEvents.onMeEnd({ convo: this, convoStep, container, scriptingMemory, meMsg, transcript: [...transcriptSteps] })
continue
}
Expand Down
12 changes: 8 additions & 4 deletions src/scripting/ScriptingMemory.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const debug = require('debug')('botium-core-ScriptingMemory')
const randomize = require('randomatic')
const { v1: uuidv1 } = require('uuid')
const moment = require('moment')
const vm = require('vm')
const { NodeVM } = require('vm2')
const _ = require('lodash')
const path = require('path')

Expand Down Expand Up @@ -118,12 +118,16 @@ const SCRIPTING_FUNCTIONS_RAW = {
throw Error('func function used without args!')
}
try {
return vm.runInNewContext(code, { process: process, debug: debug, console: console, require: require })
const vm = new NodeVM({
eval: false,
require: false,
sandbox: {}
})
return vm.run(`module.exports = (${code})`)
} catch (err) {
throw Error(`func function execution failed - ${err}`)
}
},
unsafe: true
}
}
}

Expand Down
13 changes: 8 additions & 5 deletions src/scripting/logichook/LogicHookUtils.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const util = require('util')
const vm = require('vm')
const { NodeVM } = require('vm2')
const path = require('path')
const fs = require('fs')
const isClass = require('is-class')
Expand Down Expand Up @@ -196,18 +196,21 @@ module.exports = class LogicHookUtils {
}
}
if (_.isObject(src) && !_.isString(src)) {
_checkUnsafe()
try {
const hookObject = Object.keys(src).reduce((result, key) => {
result[key] = (args) => {
const script = src[key]
if (_.isFunction(script)) {
_checkUnsafe()
return script(args)
} else if (_.isString(script)) {
try {
const sandbox = vm.createContext({ debug, console, process, ...args })
vm.runInContext(script, sandbox)
return sandbox.result || Promise.resolve()
const vm = new NodeVM({
eval: false,
require: false,
sandbox: args
})
return vm.run(script)
} catch (err) {
throw new Error(`Script "${key}" is not valid - ${util.inspect(err)}`)
}
Expand Down
2 changes: 1 addition & 1 deletion src/scripting/precompilers/JsonToJson.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const _ensureList = (queryResult) => {
return [queryResult]
}

module.exports.precompile = (scriptBuffer, options, filename) => {
module.exports.precompile = (caps, scriptBuffer, options, filename) => {
if (!filename.endsWith('.json')) {
return
}
Expand Down
Loading

0 comments on commit 9c088fa

Please sign in to comment.