Merge pull request #15556 from Budibase/app-export-vuln
ensure app ownership middleware
This commit is contained in:
commit
630ed8c1ea
|
@ -2,12 +2,14 @@ import Router from "@koa/router"
|
||||||
import * as controller from "../controllers/backup"
|
import * as controller from "../controllers/backup"
|
||||||
import authorized from "../../middleware/authorized"
|
import authorized from "../../middleware/authorized"
|
||||||
import { permissions } from "@budibase/backend-core"
|
import { permissions } from "@budibase/backend-core"
|
||||||
|
import ensureTenantAppOwnership from "../../middleware/ensureTenantAppOwnership"
|
||||||
|
|
||||||
const router: Router = new Router()
|
const router: Router = new Router()
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/api/backups/export",
|
"/api/backups/export",
|
||||||
authorized(permissions.BUILDER),
|
authorized(permissions.BUILDER),
|
||||||
|
ensureTenantAppOwnership,
|
||||||
controller.exportAppDump
|
controller.exportAppDump
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { tenancy, utils } 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 tenantId = tenancy.getTenantId()
|
||||||
|
if (appId !== tenantId) {
|
||||||
|
ctx.throw(403, `App does not belong to tenant`)
|
||||||
|
}
|
||||||
|
await next()
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ensureTenantAppOwnership
|
|
@ -0,0 +1,75 @@
|
||||||
|
import ensureTenantAppOwnership from "../ensureTenantAppOwnership"
|
||||||
|
import { tenancy, utils } from "@budibase/backend-core"
|
||||||
|
|
||||||
|
jest.mock("@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 403 when appId does not match tenant ID", async () => {
|
||||||
|
tenancy.getTenantId.mockReturnValue("tenant_2")
|
||||||
|
|
||||||
|
await config.executeMiddleware()
|
||||||
|
|
||||||
|
expect(utils.getAppIdFromCtx).toHaveBeenCalledWith(config.ctx)
|
||||||
|
expect(config.throw).toHaveBeenCalledWith(
|
||||||
|
403,
|
||||||
|
"App does not belong to tenant"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("throws 400 when appId is missing", async () => {
|
||||||
|
utils.getAppIdFromCtx.mockResolvedValue(null)
|
||||||
|
|
||||||
|
await config.executeMiddleware()
|
||||||
|
|
||||||
|
expect(config.throw).toHaveBeenCalledWith(400, "appId must be provided")
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in New Issue