Merge branch 'master' of github.com:Budibase/budibase into feature/security-update
This commit is contained in:
commit
b7cb7c59a0
|
@ -15,7 +15,7 @@ exports.fetch = async function(ctx) {
|
||||||
|
|
||||||
exports.create = async function(ctx) {
|
exports.create = async function(ctx) {
|
||||||
const db = new CouchDB(ctx.user.appId)
|
const db = new CouchDB(ctx.user.appId)
|
||||||
const { email, username, password, name, roleId } = ctx.request.body
|
const { email, password, roleId } = ctx.request.body
|
||||||
|
|
||||||
if (!email || !password) {
|
if (!email || !password) {
|
||||||
ctx.throw(400, "email and Password Required.")
|
ctx.throw(400, "email and Password Required.")
|
||||||
|
@ -29,7 +29,6 @@ exports.create = async function(ctx) {
|
||||||
_id: generateUserID(email),
|
_id: generateUserID(email),
|
||||||
email,
|
email,
|
||||||
password: await bcrypt.hash(password),
|
password: await bcrypt.hash(password),
|
||||||
name,
|
|
||||||
type: "user",
|
type: "user",
|
||||||
roleId,
|
roleId,
|
||||||
tableId: ViewNames.USERS,
|
tableId: ViewNames.USERS,
|
||||||
|
@ -43,7 +42,6 @@ exports.create = async function(ctx) {
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
_rev: response.rev,
|
_rev: response.rev,
|
||||||
email,
|
email,
|
||||||
name,
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.status === 409) {
|
if (err.status === 409) {
|
||||||
|
@ -80,7 +78,6 @@ exports.find = async function(ctx) {
|
||||||
const user = await database.get(generateUserID(ctx.params.email))
|
const user = await database.get(generateUserID(ctx.params.email))
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
email: user.email,
|
email: user.email,
|
||||||
name: user.name,
|
|
||||||
_rev: user._rev,
|
_rev: user._rev,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,8 +127,8 @@ describe("/automations", () => {
|
||||||
trigger.id = "wadiawdo34"
|
trigger.id = "wadiawdo34"
|
||||||
let createAction = ACTION_DEFINITIONS["CREATE_ROW"]
|
let createAction = ACTION_DEFINITIONS["CREATE_ROW"]
|
||||||
createAction.inputs.row = {
|
createAction.inputs.row = {
|
||||||
name: "{{trigger.name}}",
|
name: "{{trigger.row.name}}",
|
||||||
description: "{{trigger.description}}"
|
description: "{{trigger.row.description}}"
|
||||||
}
|
}
|
||||||
createAction.id = "awde444wk"
|
createAction.id = "awde444wk"
|
||||||
|
|
||||||
|
@ -167,19 +167,20 @@ describe("/automations", () => {
|
||||||
TEST_AUTOMATION.definition.trigger.inputs.tableId = table._id
|
TEST_AUTOMATION.definition.trigger.inputs.tableId = table._id
|
||||||
TEST_AUTOMATION.definition.steps[0].inputs.row.tableId = table._id
|
TEST_AUTOMATION.definition.steps[0].inputs.row.tableId = table._id
|
||||||
await createAutomation()
|
await createAutomation()
|
||||||
|
await delay(500)
|
||||||
|
const res = await triggerWorkflow(automation._id)
|
||||||
// this looks a bit mad but we don't actually have a way to wait for a response from the automation to
|
// this looks a bit mad but we don't actually have a way to wait for a response from the automation to
|
||||||
// know that it has finished all of its actions - this is currently the best way
|
// know that it has finished all of its actions - this is currently the best way
|
||||||
// also when this runs in CI it is very temper-mental so for now trying to make run stable by repeating until it works
|
// also when this runs in CI it is very temper-mental so for now trying to make run stable by repeating until it works
|
||||||
// TODO: update when workflow logs are a thing
|
// TODO: update when workflow logs are a thing
|
||||||
for (let tries = 0; tries < MAX_RETRIES; tries++) {
|
for (let tries = 0; tries < MAX_RETRIES; tries++) {
|
||||||
const res = await triggerWorkflow(automation._id)
|
|
||||||
expect(res.body.message).toEqual(`Automation ${automation._id} has been triggered.`)
|
expect(res.body.message).toEqual(`Automation ${automation._id} has been triggered.`)
|
||||||
expect(res.body.automation.name).toEqual(TEST_AUTOMATION.name)
|
expect(res.body.automation.name).toEqual(TEST_AUTOMATION.name)
|
||||||
await delay(500)
|
await delay(500)
|
||||||
let elements = await getAllFromTable(request, appId, table._id)
|
let elements = await getAllFromTable(request, appId, table._id)
|
||||||
// don't test it unless there are values to test
|
// don't test it unless there are values to test
|
||||||
if (elements.length === 1) {
|
if (elements.length > 1) {
|
||||||
expect(elements.length).toEqual(1)
|
expect(elements.length).toEqual(5)
|
||||||
expect(elements[0].name).toEqual("Test")
|
expect(elements[0].name).toEqual("Test")
|
||||||
expect(elements[0].description).toEqual("TEST")
|
expect(elements[0].description).toEqual("TEST")
|
||||||
return
|
return
|
||||||
|
|
|
@ -5,14 +5,13 @@ const {
|
||||||
createUser,
|
createUser,
|
||||||
testPermissionsForEndpoint,
|
testPermissionsForEndpoint,
|
||||||
} = require("./couchTestUtils")
|
} = require("./couchTestUtils")
|
||||||
const {
|
const { BUILTIN_ROLE_IDS } = require("../../../utilities/security/roles")
|
||||||
BUILTIN_ROLE_IDS,
|
|
||||||
} = require("../../../utilities/security/roles")
|
|
||||||
const { cloneDeep } = require("lodash/fp")
|
const { cloneDeep } = require("lodash/fp")
|
||||||
|
|
||||||
const baseBody = {
|
const baseBody = {
|
||||||
|
email: "bill@bill.com",
|
||||||
password: "yeeooo",
|
password: "yeeooo",
|
||||||
roleId: BUILTIN_ROLE_IDS.POWER
|
roleId: BUILTIN_ROLE_IDS.POWER,
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("/users", () => {
|
describe("/users", () => {
|
||||||
|
@ -22,7 +21,7 @@ describe("/users", () => {
|
||||||
let appId
|
let appId
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
({ request, server } = await supertest(server))
|
;({ request, server } = await supertest(server))
|
||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
@ -42,7 +41,7 @@ describe("/users", () => {
|
||||||
const res = await request
|
const res = await request
|
||||||
.get(`/api/users`)
|
.get(`/api/users`)
|
||||||
.set(defaultHeaders(appId))
|
.set(defaultHeaders(appId))
|
||||||
.expect('Content-Type', /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(res.body.length).toBe(2)
|
expect(res.body.length).toBe(2)
|
||||||
|
@ -51,7 +50,7 @@ describe("/users", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should apply authorization to endpoint", async () => {
|
it("should apply authorization to endpoint", async () => {
|
||||||
await createUser(request, appId, "brenda", "brendas_password")
|
await createUser(request, appId, "brenda@brenda.com", "brendas_password")
|
||||||
await testPermissionsForEndpoint({
|
await testPermissionsForEndpoint({
|
||||||
request,
|
request,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
@ -61,7 +60,6 @@ describe("/users", () => {
|
||||||
failRole: BUILTIN_ROLE_IDS.PUBLIC,
|
failRole: BUILTIN_ROLE_IDS.PUBLIC,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("create", () => {
|
describe("create", () => {
|
||||||
|
@ -73,7 +71,7 @@ describe("/users", () => {
|
||||||
.set(defaultHeaders(appId))
|
.set(defaultHeaders(appId))
|
||||||
.send(body)
|
.send(body)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.expect('Content-Type', /json/)
|
.expect("Content-Type", /json/)
|
||||||
|
|
||||||
expect(res.res.statusMessage).toEqual("User created successfully.")
|
expect(res.res.statusMessage).toEqual("User created successfully.")
|
||||||
expect(res.body._id).toBeUndefined()
|
expect(res.body._id).toBeUndefined()
|
||||||
|
|
|
@ -58,7 +58,7 @@ module.exports.definition = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.run = async function({ inputs, appId, apiKey }) {
|
module.exports.run = async function({ inputs, appId, apiKey, emitter }) {
|
||||||
// TODO: better logging of when actions are missed due to missing parameters
|
// TODO: better logging of when actions are missed due to missing parameters
|
||||||
if (inputs.row == null || inputs.row.tableId == null) {
|
if (inputs.row == null || inputs.row.tableId == null) {
|
||||||
return
|
return
|
||||||
|
@ -77,6 +77,7 @@ module.exports.run = async function({ inputs, appId, apiKey }) {
|
||||||
body: inputs.row,
|
body: inputs.row,
|
||||||
},
|
},
|
||||||
user: { appId },
|
user: { appId },
|
||||||
|
eventEmitter: emitter,
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -59,7 +59,7 @@ module.exports.definition = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.run = async function({ inputs, appId, apiKey }) {
|
module.exports.run = async function({ inputs, appId, apiKey, emitter }) {
|
||||||
const { email, password, roleId } = inputs
|
const { email, password, roleId } = inputs
|
||||||
const ctx = {
|
const ctx = {
|
||||||
user: {
|
user: {
|
||||||
|
@ -68,6 +68,7 @@ module.exports.run = async function({ inputs, appId, apiKey }) {
|
||||||
request: {
|
request: {
|
||||||
body: { email, password, roleId },
|
body: { email, password, roleId },
|
||||||
},
|
},
|
||||||
|
eventEmitter: emitter,
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -50,7 +50,7 @@ module.exports.definition = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.run = async function({ inputs, appId, apiKey }) {
|
module.exports.run = async function({ inputs, appId, apiKey, emitter }) {
|
||||||
// TODO: better logging of when actions are missed due to missing parameters
|
// TODO: better logging of when actions are missed due to missing parameters
|
||||||
if (inputs.id == null || inputs.revision == null) {
|
if (inputs.id == null || inputs.revision == null) {
|
||||||
return
|
return
|
||||||
|
@ -62,6 +62,7 @@ module.exports.run = async function({ inputs, appId, apiKey }) {
|
||||||
revId: inputs.revision,
|
revId: inputs.revision,
|
||||||
},
|
},
|
||||||
user: { appId },
|
user: { appId },
|
||||||
|
eventEmitter: emitter,
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -53,7 +53,7 @@ module.exports.definition = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.run = async function({ inputs, appId }) {
|
module.exports.run = async function({ inputs, appId, emitter }) {
|
||||||
if (inputs.rowId == null || inputs.row == null) {
|
if (inputs.rowId == null || inputs.row == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,7 @@ module.exports.run = async function({ inputs, appId }) {
|
||||||
body: inputs.row,
|
body: inputs.row,
|
||||||
},
|
},
|
||||||
user: { appId },
|
user: { appId },
|
||||||
|
eventEmitter: emitter,
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -2,6 +2,7 @@ const handlebars = require("handlebars")
|
||||||
const actions = require("./actions")
|
const actions = require("./actions")
|
||||||
const logic = require("./logic")
|
const logic = require("./logic")
|
||||||
const automationUtils = require("./automationUtils")
|
const automationUtils = require("./automationUtils")
|
||||||
|
const AutomationEmitter = require("../events/AutomationEmitter")
|
||||||
|
|
||||||
handlebars.registerHelper("object", value => {
|
handlebars.registerHelper("object", value => {
|
||||||
return new handlebars.SafeString(JSON.stringify(value))
|
return new handlebars.SafeString(JSON.stringify(value))
|
||||||
|
@ -32,12 +33,18 @@ function recurseMustache(inputs, context) {
|
||||||
*/
|
*/
|
||||||
class Orchestrator {
|
class Orchestrator {
|
||||||
constructor(automation, triggerOutput) {
|
constructor(automation, triggerOutput) {
|
||||||
|
this._metadata = triggerOutput.metadata
|
||||||
|
this._chainCount = this._metadata ? this._metadata.automationChainCount : 0
|
||||||
this._appId = triggerOutput.appId
|
this._appId = triggerOutput.appId
|
||||||
// remove from context
|
// remove from context
|
||||||
delete triggerOutput.appId
|
delete triggerOutput.appId
|
||||||
|
delete triggerOutput.metadata
|
||||||
// step zero is never used as the mustache is zero indexed for customer facing
|
// step zero is never used as the mustache is zero indexed for customer facing
|
||||||
this._context = { steps: [{}], trigger: triggerOutput }
|
this._context = { steps: [{}], trigger: triggerOutput }
|
||||||
this._automation = automation
|
this._automation = automation
|
||||||
|
// create an emitter which has the chain count for this automation run in it, so it can block
|
||||||
|
// excessive chaining if required
|
||||||
|
this._emitter = new AutomationEmitter(this._chainCount + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
async getStepFunctionality(type, stepId) {
|
async getStepFunctionality(type, stepId) {
|
||||||
|
@ -68,6 +75,7 @@ class Orchestrator {
|
||||||
inputs: step.inputs,
|
inputs: step.inputs,
|
||||||
appId: this._appId,
|
appId: this._appId,
|
||||||
apiKey: automation.apiKey,
|
apiKey: automation.apiKey,
|
||||||
|
emitter: this._emitter,
|
||||||
})
|
})
|
||||||
if (step.stepId === FILTER_STEP_ID && !outputs.success) {
|
if (step.stepId === FILTER_STEP_ID && !outputs.success) {
|
||||||
break
|
break
|
||||||
|
|
|
@ -174,22 +174,20 @@ async function fillRowOutput(automation, params) {
|
||||||
let table = await db.get(tableId)
|
let table = await db.get(tableId)
|
||||||
let row = {}
|
let row = {}
|
||||||
for (let schemaKey of Object.keys(table.schema)) {
|
for (let schemaKey of Object.keys(table.schema)) {
|
||||||
if (params[schemaKey] != null) {
|
const paramValue = params[schemaKey]
|
||||||
continue
|
|
||||||
}
|
|
||||||
let propSchema = table.schema[schemaKey]
|
let propSchema = table.schema[schemaKey]
|
||||||
switch (propSchema.constraints.type) {
|
switch (propSchema.constraints.type) {
|
||||||
case "string":
|
case "string":
|
||||||
row[schemaKey] = FAKE_STRING
|
row[schemaKey] = paramValue || FAKE_STRING
|
||||||
break
|
break
|
||||||
case "boolean":
|
case "boolean":
|
||||||
row[schemaKey] = FAKE_BOOL
|
row[schemaKey] = paramValue || FAKE_BOOL
|
||||||
break
|
break
|
||||||
case "number":
|
case "number":
|
||||||
row[schemaKey] = FAKE_NUMBER
|
row[schemaKey] = paramValue || FAKE_NUMBER
|
||||||
break
|
break
|
||||||
case "datetime":
|
case "datetime":
|
||||||
row[schemaKey] = FAKE_DATETIME
|
row[schemaKey] = paramValue || FAKE_DATETIME
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
const { rowEmission, tableEmission } = require("./utils")
|
||||||
|
const mainEmitter = require("./index")
|
||||||
|
|
||||||
|
// max number of automations that can chain on top of each other
|
||||||
|
const MAX_AUTOMATION_CHAIN = 5
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special emitter which takes the count of automation runs which have occurred and blocks an
|
||||||
|
* automation from running if it has reached the maximum number of chained automations runs.
|
||||||
|
* This essentially "fakes" the normal emitter to add some functionality in-between to stop automations
|
||||||
|
* from getting stuck endlessly chaining.
|
||||||
|
*/
|
||||||
|
class AutomationEmitter {
|
||||||
|
constructor(chainCount) {
|
||||||
|
this.chainCount = chainCount
|
||||||
|
this.metadata = {
|
||||||
|
automationChainCount: chainCount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emitRow(eventName, appId, row, table = null) {
|
||||||
|
// don't emit even if we've reached max automation chain
|
||||||
|
if (this.chainCount >= MAX_AUTOMATION_CHAIN) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rowEmission({
|
||||||
|
emitter: mainEmitter,
|
||||||
|
eventName,
|
||||||
|
appId,
|
||||||
|
row,
|
||||||
|
table,
|
||||||
|
metadata: this.metadata,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
emitTable(eventName, appId, table = null) {
|
||||||
|
// don't emit even if we've reached max automation chain
|
||||||
|
if (this.chainCount > MAX_AUTOMATION_CHAIN) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tableEmission({
|
||||||
|
emitter: mainEmitter,
|
||||||
|
eventName,
|
||||||
|
appId,
|
||||||
|
table,
|
||||||
|
metadata: this.metadata,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = AutomationEmitter
|
|
@ -1,4 +1,5 @@
|
||||||
const EventEmitter = require("events").EventEmitter
|
const EventEmitter = require("events").EventEmitter
|
||||||
|
const { rowEmission, tableEmission } = require("./utils")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* keeping event emitter in one central location as it might be used for things other than
|
* keeping event emitter in one central location as it might be used for things other than
|
||||||
|
@ -12,36 +13,11 @@ const EventEmitter = require("events").EventEmitter
|
||||||
*/
|
*/
|
||||||
class BudibaseEmitter extends EventEmitter {
|
class BudibaseEmitter extends EventEmitter {
|
||||||
emitRow(eventName, appId, row, table = null) {
|
emitRow(eventName, appId, row, table = null) {
|
||||||
let event = {
|
rowEmission({ emitter: this, eventName, appId, row, table })
|
||||||
row,
|
|
||||||
appId,
|
|
||||||
tableId: row.tableId,
|
|
||||||
}
|
|
||||||
if (table) {
|
|
||||||
event.table = table
|
|
||||||
}
|
|
||||||
event.id = row._id
|
|
||||||
if (row._rev) {
|
|
||||||
event.revision = row._rev
|
|
||||||
}
|
|
||||||
this.emit(eventName, event)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
emitTable(eventName, appId, table = null) {
|
emitTable(eventName, appId, table = null) {
|
||||||
const tableId = table._id
|
tableEmission({ emitter: this, eventName, appId, table })
|
||||||
let event = {
|
|
||||||
table: {
|
|
||||||
...table,
|
|
||||||
tableId: tableId,
|
|
||||||
},
|
|
||||||
appId,
|
|
||||||
tableId: tableId,
|
|
||||||
}
|
|
||||||
event.id = tableId
|
|
||||||
if (table._rev) {
|
|
||||||
event.revision = table._rev
|
|
||||||
}
|
|
||||||
this.emit(eventName, event)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
exports.rowEmission = ({ emitter, eventName, appId, row, table, metadata }) => {
|
||||||
|
let event = {
|
||||||
|
row,
|
||||||
|
appId,
|
||||||
|
tableId: row.tableId,
|
||||||
|
}
|
||||||
|
if (table) {
|
||||||
|
event.table = table
|
||||||
|
}
|
||||||
|
event.id = row._id
|
||||||
|
if (row._rev) {
|
||||||
|
event.revision = row._rev
|
||||||
|
}
|
||||||
|
if (metadata) {
|
||||||
|
event.metadata = metadata
|
||||||
|
}
|
||||||
|
emitter.emit(eventName, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.tableEmission = ({ emitter, eventName, appId, table, metadata }) => {
|
||||||
|
const tableId = table._id
|
||||||
|
let event = {
|
||||||
|
table: {
|
||||||
|
...table,
|
||||||
|
tableId: tableId,
|
||||||
|
},
|
||||||
|
appId,
|
||||||
|
tableId: tableId,
|
||||||
|
}
|
||||||
|
event.id = tableId
|
||||||
|
if (table._rev) {
|
||||||
|
event.revision = table._rev
|
||||||
|
}
|
||||||
|
if (metadata) {
|
||||||
|
event.metadata = metadata
|
||||||
|
}
|
||||||
|
emitter.emit(eventName, event)
|
||||||
|
}
|
Loading…
Reference in New Issue