diff --git a/packages/server/src/api/routes/rowAction.ts b/packages/server/src/api/routes/rowAction.ts index 846e568420..54154e3ee8 100644 --- a/packages/server/src/api/routes/rowAction.ts +++ b/packages/server/src/api/routes/rowAction.ts @@ -2,9 +2,10 @@ import Router from "@koa/router" import Joi from "joi" import { middleware, permissions } from "@budibase/backend-core" import * as rowActionController from "../controllers/rowAction" -import authorized, { authorizedResource } from "../../middleware/authorized" +import authorized from "../../middleware/authorized" +import { triggerRowActionAuthorised } from "../../middleware/triggerRowActionAuthorised" -const { PermissionLevel, PermissionType, BUILDER } = permissions +const { BUILDER } = permissions function rowActionValidator() { return middleware.joiValidator.body( @@ -65,7 +66,7 @@ router .post( "/api/tables/:sourceId/actions/:actionId/trigger", rowTriggerValidator(), - authorizedResource(PermissionType.TABLE, PermissionLevel.READ, "tableId"), + triggerRowActionAuthorised("sourceId", "actionId"), rowActionController.run ) diff --git a/packages/server/src/api/routes/tests/rowAction.spec.ts b/packages/server/src/api/routes/tests/rowAction.spec.ts index a21163a978..0cf41cb36f 100644 --- a/packages/server/src/api/routes/tests/rowAction.spec.ts +++ b/packages/server/src/api/routes/tests/rowAction.spec.ts @@ -726,5 +726,47 @@ describe("/rowsActions", () => { }), ]) }) + + it("rejects triggering from a non-allowed view", async () => { + const viewId = ( + await config.api.viewV2.create( + setup.structures.viewV2.createRequest(tableId) + ) + ).id + + await config.api.rowAction.trigger( + viewId, + rowAction.id, + { + rowId: row._id!, + }, + { + status: 403, + body: { + message: `Row action '${rowAction.id}' is not enabled for view '${viewId}'"`, + }, + } + ) + + const { data: automationLogs } = await config.doInContext( + config.getProdAppId(), + async () => + automations.logs.logSearch({ + startDate: await automations.logs.oldestLogDate(), + }) + ) + expect(automationLogs).toEqual([ + expect.objectContaining({ + automationId: rowAction.automationId, + trigger: expect.objectContaining({ + outputs: { + fields: {}, + row: await config.api.row.get(tableId, row._id!), + table: await config.api.table.get(tableId), + }, + }), + }), + ]) + }) }) }) diff --git a/packages/server/src/middleware/triggerRowActionAuthorised.ts b/packages/server/src/middleware/triggerRowActionAuthorised.ts new file mode 100644 index 0000000000..4cc654c139 --- /dev/null +++ b/packages/server/src/middleware/triggerRowActionAuthorised.ts @@ -0,0 +1,42 @@ +import { Next } from "koa" +import { Ctx } from "@budibase/types" +import { paramSubResource } from "./resourceId" +import { docIds } from "@budibase/backend-core" +import * as utils from "../db/utils" +import sdk from "../sdk" + +export function triggerRowActionAuthorised( + sourcePath: string, + actionPath: string +) { + return async (ctx: Ctx, next: Next) => { + // Reusing the existing middleware to extract the value + paramSubResource(sourcePath, actionPath)(ctx, () => {}) + const { resourceId: sourceId, subResourceId: rowActionId } = ctx + + const isTableId = docIds.isTableId(sourceId) + const isViewId = utils.isViewID(sourceId) + if (!isTableId && !isViewId) { + ctx.throw(400, `'${sourceId}' is not a valid source id`) + } + + const tableId = isTableId + ? sourceId + : utils.extractViewInfoFromID(sourceId).tableId + const rowAction = await sdk.rowActions.get(tableId, rowActionId) + + if (isTableId && !rowAction.permissions.table.runAllowed) { + ctx.throw( + 403, + `Row action '${rowActionId}' is not enabled for table '${sourceId}'` + ) + } else if (isViewId && !rowAction.permissions.views[sourceId]?.runAllowed) { + ctx.throw( + 403, + `Row action '${rowActionId}' is not enabled for view '${sourceId}'` + ) + } + + return next() + } +}