Guard permission

This commit is contained in:
Adria Navarro 2024-09-04 10:16:59 +02:00
parent 623b385d8a
commit 00119f9d73
3 changed files with 81 additions and 27 deletions

View File

@ -742,5 +742,37 @@ describe("/rowsActions", () => {
}),
])
})
const { PUBLIC, ...nonPublicRoles } = roles.BUILTIN_ROLE_IDS
it.each(Object.values(nonPublicRoles))(
"rejects if the user does not have table read permission",
async role => {
await config.api.permission.add({
level: PermissionLevel.READ,
resourceId: tableId,
roleId: role,
})
const normalUser = await config.createUser({
admin: { global: false },
builder: {},
})
await config.withUser(normalUser, async () => {
await config.publish()
await config.api.rowAction.trigger(
tableId,
rowAction.id,
{
rowId: row._id!,
},
{ status: 403, body: { message: "User does not have permission" } }
)
const automationLogs = await getAutomationLogs()
expect(automationLogs).toBeEmpty()
})
}
)
})
})

View File

@ -88,7 +88,7 @@ const authorized =
opts = { schema: false },
resourcePath?: string
) =>
async (ctx: any, next: any) => {
async (ctx: UserCtx, next: any) => {
// webhooks don't need authentication, each webhook unique
// also internal requests (between services) don't need authorized
if (isWebhookEndpoint(ctx) || ctx.internal) {

View File

@ -1,44 +1,64 @@
import { Next } from "koa"
import { UserCtx } from "@budibase/types"
import { PermissionLevel, PermissionType, UserCtx } from "@budibase/types"
import { paramSubResource } from "./resourceId"
import { docIds } from "@budibase/backend-core"
import * as utils from "../db/utils"
import sdk from "../sdk"
async function executeMiddleware(
ctx: UserCtx,
delegate: (ctx: UserCtx, next: any) => any
) {
await new Promise<void>(r => delegate(ctx, () => r()))
}
import { authorizedResource } from "./authorized"
export function triggerRowActionAuthorised(
sourcePath: string,
actionPath: string
) {
async function extractResourceIds(ctx: UserCtx) {
ctx = { ...ctx }
// Reusing the existing middleware to extract the value
await executeMiddleware(ctx, paramSubResource(sourcePath, actionPath))
return async (ctx: UserCtx, next: Next) => {
async function getResourceIds() {
// Reusing the existing middleware to extract the value
await paramSubResource(sourcePath, actionPath)(ctx, () => {})
const sourceId: string = ctx.resourceId
const rowActionId: String = ctx.subResourceId
const sourceId: string = ctx.resourceId
const rowActionId: string = ctx.subResourceId
const isTableId = docIds.isTableId(sourceId)
const isViewId = utils.isViewID(sourceId)
if (!isTableId && !isViewId) {
ctx.throw(400, `'${sourceId}' is not a valid source id`)
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 viewId = isTableId ? undefined : sourceId
return { tableId, viewId, rowActionId }
}
const tableId = isTableId
? sourceId
: utils.extractViewInfoFromID(sourceId).tableId
const viewId = isTableId ? undefined : sourceId
return { tableId, viewId, rowActionId }
}
async function guardResourcePermissions(
ctx: UserCtx,
tableId: string,
viewId?: string
) {
const { params } = ctx
try {
if (!viewId) {
ctx.params = { tableId }
await authorizedResource(
PermissionType.TABLE,
PermissionLevel.READ,
"tableId"
)(ctx, () => {})
} else {
ctx.params = { viewId }
await authorizedResource(
PermissionType.VIEW,
PermissionLevel.READ,
"__viewId"
)(ctx, () => {})
}
} finally {
ctx.params = params
}
}
return async (ctx: UserCtx, next: Next) => {
const { tableId, viewId, rowActionId } = extractResourceIds(ctx)
const { tableId, viewId, rowActionId } = await getResourceIds()
const rowAction = await sdk.rowActions.get(tableId, rowActionId)
@ -54,6 +74,8 @@ export function triggerRowActionAuthorised(
)
}
await guardResourcePermissions(ctx, tableId, viewId)
// Enrich tableId
ctx.params.tableId = tableId
return next()