Updates to remove app builder concept, denying access to app creation for app builders.
This commit is contained in:
parent
c277b065db
commit
64a5426d36
|
@ -1,29 +1,12 @@
|
||||||
const { flatten } = require("lodash")
|
import { flatten } from "lodash"
|
||||||
const { cloneDeep } = require("lodash/fp")
|
import { cloneDeep } from "lodash/fp"
|
||||||
|
import { PermissionType, PermissionLevel } from "@budibase/types"
|
||||||
|
export { PermissionType, PermissionLevel } from "@budibase/types"
|
||||||
|
|
||||||
export type RoleHierarchy = {
|
export type RoleHierarchy = {
|
||||||
permissionId: string
|
permissionId: string
|
||||||
}[]
|
}[]
|
||||||
|
|
||||||
export enum PermissionLevel {
|
|
||||||
READ = "read",
|
|
||||||
WRITE = "write",
|
|
||||||
EXECUTE = "execute",
|
|
||||||
ADMIN = "admin",
|
|
||||||
}
|
|
||||||
|
|
||||||
// these are the global types, that govern the underlying default behaviour
|
|
||||||
export enum PermissionType {
|
|
||||||
APP = "app",
|
|
||||||
TABLE = "table",
|
|
||||||
USER = "user",
|
|
||||||
AUTOMATION = "automation",
|
|
||||||
WEBHOOK = "webhook",
|
|
||||||
BUILDER = "builder",
|
|
||||||
VIEW = "view",
|
|
||||||
QUERY = "query",
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Permission {
|
export class Permission {
|
||||||
type: PermissionType
|
type: PermissionType
|
||||||
level: PermissionLevel
|
level: PermissionLevel
|
||||||
|
@ -173,3 +156,4 @@ export function isPermissionLevelHigherThanRead(level: PermissionLevel) {
|
||||||
|
|
||||||
// utility as a lot of things need simply the builder permission
|
// utility as a lot of things need simply the builder permission
|
||||||
export const BUILDER = PermissionType.BUILDER
|
export const BUILDER = PermissionType.BUILDER
|
||||||
|
export const GLOBAL_BUILDER = PermissionType.GLOBAL_BUILDER
|
||||||
|
|
|
@ -15,7 +15,7 @@ router
|
||||||
)
|
)
|
||||||
.post(
|
.post(
|
||||||
"/api/applications",
|
"/api/applications",
|
||||||
authorized(permissions.BUILDER),
|
authorized(permissions.GLOBAL_BUILDER),
|
||||||
applicationValidator(),
|
applicationValidator(),
|
||||||
controller.create
|
controller.create
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {
|
||||||
context,
|
context,
|
||||||
users,
|
users,
|
||||||
} from "@budibase/backend-core"
|
} from "@budibase/backend-core"
|
||||||
import { Role, UserCtx } from "@budibase/types"
|
import { Role, UserCtx, PermissionType, PermissionLevel } from "@budibase/types"
|
||||||
import builderMiddleware from "./builder"
|
import builderMiddleware from "./builder"
|
||||||
import { isWebhookEndpoint } from "./utils"
|
import { isWebhookEndpoint } from "./utils"
|
||||||
|
|
||||||
|
@ -24,8 +24,8 @@ const csrf = auth.buildCsrfMiddleware()
|
||||||
const checkAuthorized = async (
|
const checkAuthorized = async (
|
||||||
ctx: UserCtx,
|
ctx: UserCtx,
|
||||||
resourceRoles: any,
|
resourceRoles: any,
|
||||||
permType: any,
|
permType: PermissionType,
|
||||||
permLevel: any
|
permLevel: PermissionLevel
|
||||||
) => {
|
) => {
|
||||||
const appId = context.getAppId()
|
const appId = context.getAppId()
|
||||||
// check if this is a builder api and the user is not a builder
|
// check if this is a builder api and the user is not a builder
|
||||||
|
@ -47,10 +47,10 @@ const checkAuthorized = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkAuthorizedResource = async (
|
const checkAuthorizedResource = async (
|
||||||
ctx: any,
|
ctx: UserCtx,
|
||||||
resourceRoles: any,
|
resourceRoles: any,
|
||||||
permType: any,
|
permType: PermissionType,
|
||||||
permLevel: any
|
permLevel: PermissionLevel
|
||||||
) => {
|
) => {
|
||||||
// get the user's roles
|
// get the user's roles
|
||||||
const roleId = ctx.roleId || roles.BUILTIN_ROLE_IDS.PUBLIC
|
const roleId = ctx.roleId || roles.BUILTIN_ROLE_IDS.PUBLIC
|
||||||
|
@ -122,7 +122,10 @@ export default (
|
||||||
|
|
||||||
// check general builder stuff, this middleware is a good way
|
// check general builder stuff, this middleware is a good way
|
||||||
// to find API endpoints which are builder focused
|
// to find API endpoints which are builder focused
|
||||||
if (permType === permissions.PermissionType.BUILDER) {
|
if (
|
||||||
|
permType === permissions.PermissionType.BUILDER ||
|
||||||
|
permType === permissions.PermissionType.GLOBAL_BUILDER
|
||||||
|
) {
|
||||||
await builderMiddleware(ctx)
|
await builderMiddleware(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
setDebounce,
|
setDebounce,
|
||||||
} from "../utilities/redis"
|
} from "../utilities/redis"
|
||||||
import { db as dbCore, cache } from "@budibase/backend-core"
|
import { db as dbCore, cache } from "@budibase/backend-core"
|
||||||
import { UserCtx, Database, App } from "@budibase/types"
|
import { UserCtx, Database } from "@budibase/types"
|
||||||
|
|
||||||
const DEBOUNCE_TIME_SEC = 30
|
const DEBOUNCE_TIME_SEC = 30
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,6 @@ export interface User extends Document {
|
||||||
roles: UserRoles
|
roles: UserRoles
|
||||||
builder?: {
|
builder?: {
|
||||||
global?: boolean
|
global?: boolean
|
||||||
appBuilder?: boolean
|
|
||||||
apps?: string[]
|
apps?: string[]
|
||||||
}
|
}
|
||||||
admin?: {
|
admin?: {
|
||||||
|
|
|
@ -18,3 +18,4 @@ export * from "./sso"
|
||||||
export * from "./user"
|
export * from "./user"
|
||||||
export * from "./cli"
|
export * from "./cli"
|
||||||
export * from "./websocket"
|
export * from "./websocket"
|
||||||
|
export * from "./permissions"
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
export enum PermissionLevel {
|
||||||
|
READ = "read",
|
||||||
|
WRITE = "write",
|
||||||
|
EXECUTE = "execute",
|
||||||
|
ADMIN = "admin",
|
||||||
|
}
|
||||||
|
|
||||||
|
// these are the global types, that govern the underlying default behaviour
|
||||||
|
export enum PermissionType {
|
||||||
|
APP = "app",
|
||||||
|
TABLE = "table",
|
||||||
|
USER = "user",
|
||||||
|
AUTOMATION = "automation",
|
||||||
|
WEBHOOK = "webhook",
|
||||||
|
BUILDER = "builder",
|
||||||
|
GLOBAL_BUILDER = "globalBuilder",
|
||||||
|
VIEW = "view",
|
||||||
|
QUERY = "query",
|
||||||
|
}
|
|
@ -433,26 +433,9 @@ export const inviteAccept = async (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const grantAppBuilder = async (ctx: Ctx) => {
|
|
||||||
const { userId } = ctx.params
|
|
||||||
const user = await userSdk.db.getUser(userId)
|
|
||||||
if (!user.builder) {
|
|
||||||
user.builder = {}
|
|
||||||
}
|
|
||||||
user.builder.appBuilder = true
|
|
||||||
await userSdk.db.save(user, { hashPassword: false })
|
|
||||||
ctx.body = { message: `User "${user.email}" granted app builder permissions` }
|
|
||||||
}
|
|
||||||
|
|
||||||
export const addAppBuilder = async (ctx: Ctx) => {
|
export const addAppBuilder = async (ctx: Ctx) => {
|
||||||
const { userId, appId } = ctx.params
|
const { userId, appId } = ctx.params
|
||||||
const user = await userSdk.db.getUser(userId)
|
const user = await userSdk.db.getUser(userId)
|
||||||
if (!user.builder?.appBuilder && !userSdk.core.isGlobalBuilder(user)) {
|
|
||||||
ctx.throw(
|
|
||||||
400,
|
|
||||||
"Unable to update access, user must be granted app builder permissions."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (userSdk.core.isGlobalBuilder(user)) {
|
if (userSdk.core.isGlobalBuilder(user)) {
|
||||||
ctx.body = { message: "User already admin - no permissions updated." }
|
ctx.body = { message: "User already admin - no permissions updated." }
|
||||||
return
|
return
|
||||||
|
@ -472,12 +455,6 @@ export const addAppBuilder = async (ctx: Ctx) => {
|
||||||
export const removeAppBuilder = async (ctx: Ctx) => {
|
export const removeAppBuilder = async (ctx: Ctx) => {
|
||||||
const { userId, appId } = ctx.params
|
const { userId, appId } = ctx.params
|
||||||
const user = await userSdk.db.getUser(userId)
|
const user = await userSdk.db.getUser(userId)
|
||||||
if (!user.builder?.appBuilder && !userSdk.core.isGlobalBuilder(user)) {
|
|
||||||
ctx.throw(
|
|
||||||
400,
|
|
||||||
"Unable to update access, user must be granted app builder permissions."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (userSdk.core.isGlobalBuilder(user)) {
|
if (userSdk.core.isGlobalBuilder(user)) {
|
||||||
ctx.body = { message: "User already admin - no permissions removed." }
|
ctx.body = { message: "User already admin - no permissions removed." }
|
||||||
return
|
return
|
||||||
|
|
|
@ -24,27 +24,9 @@ describe("/api/global/users/:userId/app/builder", () => {
|
||||||
return response.body as User
|
return response.body as User
|
||||||
}
|
}
|
||||||
|
|
||||||
async function grantAppBuilder(): Promise<User> {
|
|
||||||
const user = await newUser()
|
|
||||||
await config.api.users.grantAppBuilder(user._id!)
|
|
||||||
return await getUser(user._id!)
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("POST /api/global/users/:userId/app/builder", () => {
|
|
||||||
it("should be able to grant a user builder permissions", async () => {
|
|
||||||
const user = await grantAppBuilder()
|
|
||||||
expect(user.builder?.appBuilder).toBe(true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("PATCH /api/global/users/:userId/app/:appId/builder", () => {
|
describe("PATCH /api/global/users/:userId/app/:appId/builder", () => {
|
||||||
it("shouldn't allow granting access to an app to a non-app builder", async () => {
|
|
||||||
const user = await newUser()
|
|
||||||
await config.api.users.grantBuilderToApp(user._id!, MOCK_APP_ID, 400)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should be able to grant a user access to a particular app", async () => {
|
it("should be able to grant a user access to a particular app", async () => {
|
||||||
const user = await grantAppBuilder()
|
const user = await newUser()
|
||||||
await config.api.users.grantBuilderToApp(user._id!, MOCK_APP_ID)
|
await config.api.users.grantBuilderToApp(user._id!, MOCK_APP_ID)
|
||||||
const updated = await getUser(user._id!)
|
const updated = await getUser(user._id!)
|
||||||
expect(updated.builder?.appBuilder).toBe(true)
|
expect(updated.builder?.appBuilder).toBe(true)
|
||||||
|
@ -54,7 +36,7 @@ describe("/api/global/users/:userId/app/builder", () => {
|
||||||
|
|
||||||
describe("DELETE /api/global/users/:userId/app/:appId/builder", () => {
|
describe("DELETE /api/global/users/:userId/app/:appId/builder", () => {
|
||||||
it("should allow revoking access", async () => {
|
it("should allow revoking access", async () => {
|
||||||
const user = await grantAppBuilder()
|
const user = await newUser()
|
||||||
await config.api.users.grantBuilderToApp(user._id!, MOCK_APP_ID)
|
await config.api.users.grantBuilderToApp(user._id!, MOCK_APP_ID)
|
||||||
let updated = await getUser(user._id!)
|
let updated = await getUser(user._id!)
|
||||||
expect(updated.builder?.apps![0]).toBe(MOCK_APP_ID)
|
expect(updated.builder?.apps![0]).toBe(MOCK_APP_ID)
|
||||||
|
|
|
@ -122,8 +122,7 @@ router
|
||||||
buildAdminInitValidation(),
|
buildAdminInitValidation(),
|
||||||
controller.adminUser
|
controller.adminUser
|
||||||
)
|
)
|
||||||
.post("/api/global/users/:userId/app/builder", controller.grantAppBuilder)
|
.post(
|
||||||
.patch(
|
|
||||||
"/api/global/users/:userId/app/:appId/builder",
|
"/api/global/users/:userId/app/:appId/builder",
|
||||||
controller.addAppBuilder
|
controller.addAppBuilder
|
||||||
)
|
)
|
||||||
|
|
|
@ -141,30 +141,19 @@ export class UserAPI extends TestAPI {
|
||||||
.expect(opts?.status ? opts.status : 200)
|
.expect(opts?.status ? opts.status : 200)
|
||||||
}
|
}
|
||||||
|
|
||||||
grantAppBuilder = (userId: string) => {
|
|
||||||
return this.request
|
|
||||||
.post(`/api/global/users/${userId}/app/builder`)
|
|
||||||
.set(this.config.defaultHeaders())
|
|
||||||
.expect("Content-Type", /json/)
|
|
||||||
.expect(200)
|
|
||||||
}
|
|
||||||
|
|
||||||
grantBuilderToApp = (
|
grantBuilderToApp = (
|
||||||
userId: string,
|
userId: string,
|
||||||
appId: string,
|
appId: string,
|
||||||
statusCode: number = 200
|
statusCode: number = 200
|
||||||
) => {
|
) => {
|
||||||
return this.request
|
return this.request
|
||||||
.patch(`/api/global/users/${userId}/app/${appId}/builder`)
|
.post(`/api/global/users/${userId}/app/${appId}/builder`)
|
||||||
.set(this.config.defaultHeaders())
|
.set(this.config.defaultHeaders())
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(statusCode)
|
.expect(statusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
revokeBuilderToApp = (
|
revokeBuilderToApp = (userId: string, appId: string) => {
|
||||||
userId: string,
|
|
||||||
appId: string
|
|
||||||
) => {
|
|
||||||
return this.request
|
return this.request
|
||||||
.delete(`/api/global/users/${userId}/app/${appId}/builder`)
|
.delete(`/api/global/users/${userId}/app/${appId}/builder`)
|
||||||
.set(this.config.defaultHeaders())
|
.set(this.config.defaultHeaders())
|
||||||
|
|
Loading…
Reference in New Issue