Updates to remove app builder concept, denying access to app creation for app builders.

This commit is contained in:
mike12345567 2023-07-26 17:32:21 +01:00
parent c277b065db
commit 64a5426d36
11 changed files with 42 additions and 89 deletions

View File

@ -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

View File

@ -15,7 +15,7 @@ router
) )
.post( .post(
"/api/applications", "/api/applications",
authorized(permissions.BUILDER), authorized(permissions.GLOBAL_BUILDER),
applicationValidator(), applicationValidator(),
controller.create controller.create
) )

View File

@ -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)
} }

View File

@ -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

View File

@ -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?: {

View File

@ -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"

View File

@ -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",
}

View File

@ -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

View File

@ -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)

View File

@ -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
) )

View File

@ -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())