ensure app ownership middleware

This commit is contained in:
Martin McKeaveney 2025-02-15 18:54:16 +00:00
parent 511d3200ee
commit c26357c347
3 changed files with 80 additions and 0 deletions

View File

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

View File

@ -0,0 +1,16 @@
import { tenancy } from "@budibase/backend-core"
import { UserCtx } from "@budibase/types"
function ensureTenantAppOwnership(appIdGetter: (ctx: UserCtx) => string) {
return async (ctx: UserCtx, next: any) => {
const appId = appIdGetter(ctx)
const exportAppId = tenancy.getTenantIDFromAppID(appId)
const tenantId = tenancy.getTenantId()
if (exportAppId !== tenantId) {
ctx.throw(403, `Cannot export app from another tenant`)
}
await next()
}
}
export default ensureTenantAppOwnership

View File

@ -0,0 +1,61 @@
import ensureTenantAppOwnership from "../ensureTenantAppOwnership"
import { tenancy } from "@budibase/backend-core"
jest.mock("@budibase/backend-core", () => ({
tenancy: {
getTenantId: jest.fn(),
getTenantIDFromAppID: jest.fn(),
},
}))
class TestConfiguration {
constructor() {
this.next = jest.fn()
this.throw = jest.fn()
this.middleware = ensureTenantAppOwnership(() => "app_123")
this.ctx = {
next: this.next,
throw: this.throw,
}
}
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 tenant IDs match", async () => {
tenancy.getTenantIDFromAppID.mockReturnValue("tenant_1")
tenancy.getTenantId.mockReturnValue("tenant_1")
await config.executeMiddleware()
expect(config.next).toHaveBeenCalled()
})
it("throws 403 when tenant IDs do not match", async () => {
tenancy.getTenantIDFromAppID.mockReturnValue("tenant_2")
tenancy.getTenantId.mockReturnValue("tenant_1")
await config.executeMiddleware()
expect(config.throw).toHaveBeenCalledWith(
403,
"Cannot export app from another tenant"
)
})
})