budibase/packages/server/src/middleware/tests/authorized.spec.ts

273 lines
6.7 KiB
TypeScript

jest.mock("@budibase/backend-core", () => ({
...jest.requireActual("@budibase/backend-core"),
roles: {
...jest.requireActual("@budibase/backend-core").roles,
getRequiredResourceRole: jest.fn().mockResolvedValue([]),
},
}))
jest.mock("../../environment", () => ({
prod: false,
isTest: () => true,
// @ts-ignore
isProd: () => this.prod,
_set: function (_key: string, value: string) {
this.prod = value === "production"
},
}))
import { PermissionType, PermissionLevel } from "@budibase/types"
import authorizedMiddleware from "../authorized"
import env from "../../environment"
import { generateTableID, generateViewID } from "../../db/utils"
import { roles } from "@budibase/backend-core"
import { mocks } from "@budibase/backend-core/tests"
import { initProMocks } from "../../tests/utilities/mocks/pro"
const APP_ID = ""
initProMocks()
class TestConfiguration {
middleware: (ctx: any, next: any) => Promise<void>
next: () => void
throw: () => void
headers: Record<string, any>
ctx: any
constructor() {
this.middleware = authorizedMiddleware(PermissionType.APP)
this.next = jest.fn()
this.throw = jest.fn()
this.headers = {}
this.ctx = {
headers: {},
request: {
url: "",
},
appId: APP_ID,
auth: {},
next: this.next,
throw: this.throw,
get: (name: string) => this.headers[name],
}
}
executeMiddleware() {
return this.middleware(this.ctx, this.next)
}
setUser(user: any) {
this.ctx.user = user
}
setMiddlewareRequiredPermission(...perms: any[]) {
// @ts-ignore
this.middleware = authorizedMiddleware(...perms)
}
setResourceId(id?: string) {
this.ctx.resourceId = id
}
setAuthenticated(isAuthed: boolean) {
this.ctx.isAuthenticated = isAuthed
}
setRequestUrl(url: string) {
this.ctx.request.url = url
}
setEnvironment(isProd: boolean) {
env._set("NODE_ENV", isProd ? "production" : "jest")
}
setRequestHeaders(headers: Record<string, any>) {
this.ctx.headers = headers
}
afterEach() {
jest.clearAllMocks()
}
}
describe("Authorization middleware", () => {
let config: TestConfiguration
afterEach(() => {
config.afterEach()
})
beforeEach(() => {
jest.clearAllMocks()
mocks.licenses.useCloudFree()
config = new TestConfiguration()
})
describe("non-webhook call", () => {
beforeEach(() => {
config = new TestConfiguration()
config.setEnvironment(true)
config.setAuthenticated(true)
})
it("throws when no user data is present in context", async () => {
await config.executeMiddleware()
expect(config.throw).toHaveBeenCalledWith(403, "No user info found")
})
it("passes on to next() middleware if user is an admin", async () => {
config.setUser({
_id: "user",
role: {
_id: "ADMIN",
},
})
await config.executeMiddleware()
expect(config.next).toHaveBeenCalled()
})
it("throws if the user does not have builder permissions", async () => {
config.setEnvironment(false)
config.setMiddlewareRequiredPermission(PermissionType.BUILDER)
config.setUser({
role: {
_id: "",
},
})
await config.executeMiddleware()
expect(config.throw).toHaveBeenCalledWith(403, "Not Authorized")
})
it("passes on to next() middleware if the user has resource permission", async () => {
config.setResourceId(PermissionType.QUERY)
config.setUser({
role: {
_id: "",
},
})
config.setMiddlewareRequiredPermission(PermissionType.QUERY)
await config.executeMiddleware()
expect(config.next).toHaveBeenCalled()
})
it("throws if the user session is not authenticated", async () => {
config.setUser({
role: {
_id: "",
},
})
config.setAuthenticated(false)
await config.executeMiddleware()
expect(config.throw).toHaveBeenCalledWith(
403,
"Session not authenticated"
)
})
it("throws if the user does not have base permissions to perform the operation", async () => {
config.setUser({
role: {
_id: "",
},
})
config.setMiddlewareRequiredPermission(
PermissionType.APP,
PermissionLevel.READ
)
await config.executeMiddleware()
expect(config.throw).toHaveBeenCalledWith(
403,
"User does not have permission"
)
})
describe("view type", () => {
const tableId = generateTableID()
const viewId = generateViewID(tableId)
const mockedGetRequiredResourceRole =
roles.getRequiredResourceRole as jest.MockedFunction<
typeof roles.getRequiredResourceRole
>
beforeEach(() => {
config.setMiddlewareRequiredPermission(
PermissionType.VIEW,
PermissionLevel.READ
)
config.setResourceId(viewId)
mockedGetRequiredResourceRole.mockResolvedValue(["PUBLIC"])
config.setUser({
_id: "user",
role: {
_id: "PUBLIC",
},
})
})
it("will ignore view permissions if flag is off", async () => {
await config.executeMiddleware()
expect(config.throw).not.toBeCalled()
expect(config.next).toHaveBeenCalled()
expect(mockedGetRequiredResourceRole).toBeCalledTimes(1)
expect(mockedGetRequiredResourceRole).toBeCalledWith(
PermissionLevel.READ,
expect.objectContaining({
resourceId: tableId,
subResourceId: undefined,
})
)
})
it("will use view permissions if flag is on", async () => {
mocks.licenses.useViewPermissions()
await config.executeMiddleware()
expect(config.throw).not.toBeCalled()
expect(config.next).toHaveBeenCalled()
expect(mockedGetRequiredResourceRole).toBeCalledTimes(1)
expect(mockedGetRequiredResourceRole).toBeCalledWith(
PermissionLevel.READ,
expect.objectContaining({
resourceId: tableId,
subResourceId: viewId,
})
)
})
it("throw an exception if the resource id is not provided", async () => {
config.setResourceId(undefined)
await config.executeMiddleware()
expect(config.throw).toHaveBeenNthCalledWith(
1,
400,
"Cannot obtain the view id"
)
})
it("throw an exception if the resource id is not a valid view id", async () => {
config.setResourceId(tableId)
await config.executeMiddleware()
expect(config.throw).toHaveBeenNthCalledWith(
1,
400,
`"${tableId}" is not a valid view id`
)
})
})
})
})