Merge branch 'master' into BUDI-9038/validate-hbs

This commit is contained in:
Adria Navarro 2025-02-17 12:48:37 +01:00 committed by GitHub
commit 969b81c570
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 111 additions and 1 deletions

View File

@ -1,6 +1,6 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"version": "3.4.9",
"version": "3.4.11",
"npmClient": "yarn",
"concurrency": 20,
"command": {

View File

@ -67,6 +67,15 @@ describe("utils", () => {
})
})
it("gets appId from query params", async () => {
const ctx = structures.koa.newContext()
const expected = db.generateAppID()
ctx.query = { appId: expected }
const actual = await utils.getAppIdFromCtx(ctx)
expect(actual).toBe(expected)
})
it("doesn't get appId from url when previewing", async () => {
const ctx = structures.koa.newContext()
const appId = db.generateAppID()

View File

@ -101,6 +101,11 @@ export async function getAppIdFromCtx(ctx: Ctx) {
appId = confirmAppId(pathId)
}
// look in queryParams
if (!appId && ctx.query?.appId) {
appId = confirmAppId(ctx.query?.appId as string)
}
// lookup using custom url - prod apps only
// filter out the builder preview path which collides with the prod app path
// to ensure we don't load all apps excessively

View File

@ -2,12 +2,14 @@ import Router from "@koa/router"
import * as controller from "../controllers/backup"
import authorized from "../../middleware/authorized"
import { permissions } from "@budibase/backend-core"
import ensureTenantAppOwnership from "../../middleware/ensureTenantAppOwnership"
const router: Router = new Router()
router.post(
"/api/backups/export",
authorized(permissions.BUILDER),
ensureTenantAppOwnership,
controller.exportAppDump
)

View File

@ -0,0 +1,19 @@
import { tenancy, utils, context } from "@budibase/backend-core"
import { UserCtx } from "@budibase/types"
async function ensureTenantAppOwnership(ctx: UserCtx, next: any) {
const appId = await utils.getAppIdFromCtx(ctx)
if (!appId) {
ctx.throw(400, "appId must be provided")
}
const appTenantId = context.getTenantIDFromAppID(appId)
const tenantId = tenancy.getTenantId()
if (appTenantId !== tenantId) {
ctx.throw(403, "Unauthorized")
}
await next()
}
export default ensureTenantAppOwnership

View File

@ -0,0 +1,75 @@
import ensureTenantAppOwnership from "../ensureTenantAppOwnership"
import { tenancy, utils } from "@budibase/backend-core"
jest.mock("@budibase/backend-core", () => ({
...jest.requireActual("@budibase/backend-core"),
tenancy: {
getTenantId: jest.fn(),
},
utils: {
getAppIdFromCtx: jest.fn(),
},
}))
class TestConfiguration {
constructor(appId = "tenant_1") {
this.next = jest.fn()
this.throw = jest.fn()
this.middleware = ensureTenantAppOwnership
this.ctx = {
next: this.next,
throw: this.throw,
}
utils.getAppIdFromCtx.mockResolvedValue(appId)
}
async executeMiddleware() {
return this.middleware(this.ctx, this.next)
}
afterEach() {
jest.clearAllMocks()
}
}
describe("Ensure Tenant Ownership Middleware", () => {
let config
beforeEach(() => {
config = new TestConfiguration()
})
afterEach(() => {
config.afterEach()
})
it("calls next() when appId matches tenant ID", async () => {
tenancy.getTenantId.mockReturnValue("tenant_1")
await config.executeMiddleware()
expect(utils.getAppIdFromCtx).toHaveBeenCalledWith(config.ctx)
expect(config.next).toHaveBeenCalled()
})
it("throws when tenant appId does not match tenant ID", async () => {
const appId = "app_dev_tenant3_fce449c4d75b4e4a9c7a6980d82a3e22"
utils.getAppIdFromCtx.mockResolvedValue(appId)
tenancy.getTenantId.mockReturnValue("tenant_2")
await config.executeMiddleware()
expect(utils.getAppIdFromCtx).toHaveBeenCalledWith(config.ctx)
expect(config.throw).toHaveBeenCalledWith(403, "Unauthorized")
})
it("throws 400 when appId is missing", async () => {
utils.getAppIdFromCtx.mockResolvedValue(null)
await config.executeMiddleware()
expect(config.throw).toHaveBeenCalledWith(400, "appId must be provided")
})
})