Merge pull request #11001 from Budibase/feature/custom-role-readable-ids
Custom roles - readable IDs
This commit is contained in:
commit
f7cdf5f2bc
|
@ -81,8 +81,19 @@ export function generateAppUserID(prodAppId: string, userId: string) {
|
||||||
* Generates a new role ID.
|
* Generates a new role ID.
|
||||||
* @returns {string} The new role ID which the role doc can be stored under.
|
* @returns {string} The new role ID which the role doc can be stored under.
|
||||||
*/
|
*/
|
||||||
export function generateRoleID(id?: any) {
|
export function generateRoleID(name: string) {
|
||||||
return `${DocumentType.ROLE}${SEPARATOR}${id || newid()}`
|
const prefix = `${DocumentType.ROLE}${SEPARATOR}`
|
||||||
|
if (name.startsWith(prefix)) {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
return `${prefix}${name}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility function to be more verbose.
|
||||||
|
*/
|
||||||
|
export function prefixRoleID(name: string) {
|
||||||
|
return generateRoleID(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { BuiltinPermissionID, PermissionLevel } from "./permissions"
|
import { BuiltinPermissionID, PermissionLevel } from "./permissions"
|
||||||
import { generateRoleID, getRoleParams, DocumentType, SEPARATOR } from "../db"
|
import { prefixRoleID, getRoleParams, DocumentType, SEPARATOR } from "../db"
|
||||||
import { getAppDB } from "../context"
|
import { getAppDB } from "../context"
|
||||||
import { doWithDB } from "../db"
|
import { doWithDB } from "../db"
|
||||||
import { Screen, Role as RoleDoc } from "@budibase/types"
|
import { Screen, Role as RoleDoc } from "@budibase/types"
|
||||||
|
@ -25,18 +25,28 @@ const EXTERNAL_BUILTIN_ROLE_IDS = [
|
||||||
BUILTIN_IDS.PUBLIC,
|
BUILTIN_IDS.PUBLIC,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const RoleIDVersion = {
|
||||||
|
// original version, with a UUID based ID
|
||||||
|
UUID: undefined,
|
||||||
|
// new version - with name based ID
|
||||||
|
NAME: "name",
|
||||||
|
}
|
||||||
|
|
||||||
export class Role implements RoleDoc {
|
export class Role implements RoleDoc {
|
||||||
_id: string
|
_id: string
|
||||||
_rev?: string
|
_rev?: string
|
||||||
name: string
|
name: string
|
||||||
permissionId: string
|
permissionId: string
|
||||||
inherits?: string
|
inherits?: string
|
||||||
|
version?: string
|
||||||
permissions = {}
|
permissions = {}
|
||||||
|
|
||||||
constructor(id: string, name: string, permissionId: string) {
|
constructor(id: string, name: string, permissionId: string) {
|
||||||
this._id = id
|
this._id = id
|
||||||
this.name = name
|
this.name = name
|
||||||
this.permissionId = permissionId
|
this.permissionId = permissionId
|
||||||
|
// version for managing the ID - removing the role_ when responding
|
||||||
|
this.version = RoleIDVersion.NAME
|
||||||
}
|
}
|
||||||
|
|
||||||
addInheritance(inherits: string) {
|
addInheritance(inherits: string) {
|
||||||
|
@ -157,13 +167,16 @@ export async function getRole(
|
||||||
role = cloneDeep(
|
role = cloneDeep(
|
||||||
Object.values(BUILTIN_ROLES).find(role => role._id === roleId)
|
Object.values(BUILTIN_ROLES).find(role => role._id === roleId)
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
// make sure has the prefix (if it has it then it won't be added)
|
||||||
|
roleId = prefixRoleID(roleId)
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
const dbRole = await db.get(getDBRoleID(roleId))
|
const dbRole = await db.get(getDBRoleID(roleId))
|
||||||
role = Object.assign(role, dbRole)
|
role = Object.assign(role, dbRole)
|
||||||
// finalise the ID
|
// finalise the ID
|
||||||
role._id = getExternalRoleID(role._id)
|
role._id = getExternalRoleID(role._id, role.version)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!isBuiltin(roleId) && opts?.defaultPublic) {
|
if (!isBuiltin(roleId) && opts?.defaultPublic) {
|
||||||
return cloneDeep(BUILTIN_ROLES.PUBLIC)
|
return cloneDeep(BUILTIN_ROLES.PUBLIC)
|
||||||
|
@ -261,6 +274,9 @@ export async function getAllRoles(appId?: string) {
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
roles = body.rows.map((row: any) => row.doc)
|
roles = body.rows.map((row: any) => row.doc)
|
||||||
|
roles.forEach(
|
||||||
|
role => (role._id = getExternalRoleID(role._id!, role.version))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
const builtinRoles = getBuiltinRoles()
|
const builtinRoles = getBuiltinRoles()
|
||||||
|
|
||||||
|
@ -268,14 +284,15 @@ export async function getAllRoles(appId?: string) {
|
||||||
for (let builtinRoleId of EXTERNAL_BUILTIN_ROLE_IDS) {
|
for (let builtinRoleId of EXTERNAL_BUILTIN_ROLE_IDS) {
|
||||||
const builtinRole = builtinRoles[builtinRoleId]
|
const builtinRole = builtinRoles[builtinRoleId]
|
||||||
const dbBuiltin = roles.filter(
|
const dbBuiltin = roles.filter(
|
||||||
dbRole => getExternalRoleID(dbRole._id) === builtinRoleId
|
dbRole =>
|
||||||
|
getExternalRoleID(dbRole._id!, dbRole.version) === builtinRoleId
|
||||||
)[0]
|
)[0]
|
||||||
if (dbBuiltin == null) {
|
if (dbBuiltin == null) {
|
||||||
roles.push(builtinRole || builtinRoles.BASIC)
|
roles.push(builtinRole || builtinRoles.BASIC)
|
||||||
} else {
|
} else {
|
||||||
// remove role and all back after combining with the builtin
|
// remove role and all back after combining with the builtin
|
||||||
roles = roles.filter(role => role._id !== dbBuiltin._id)
|
roles = roles.filter(role => role._id !== dbBuiltin._id)
|
||||||
dbBuiltin._id = getExternalRoleID(dbBuiltin._id)
|
dbBuiltin._id = getExternalRoleID(dbBuiltin._id!, dbBuiltin.version)
|
||||||
roles.push(Object.assign(builtinRole, dbBuiltin))
|
roles.push(Object.assign(builtinRole, dbBuiltin))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -381,19 +398,22 @@ export class AccessController {
|
||||||
/**
|
/**
|
||||||
* Adds the "role_" for builtin role IDs which are to be written to the DB (for permissions).
|
* Adds the "role_" for builtin role IDs which are to be written to the DB (for permissions).
|
||||||
*/
|
*/
|
||||||
export function getDBRoleID(roleId?: string) {
|
export function getDBRoleID(roleName: string) {
|
||||||
if (roleId?.startsWith(DocumentType.ROLE)) {
|
if (roleName?.startsWith(DocumentType.ROLE)) {
|
||||||
return roleId
|
return roleName
|
||||||
}
|
}
|
||||||
return generateRoleID(roleId)
|
return prefixRoleID(roleName)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the "role_" from builtin role IDs that have been written to the DB (for permissions).
|
* Remove the "role_" from builtin role IDs that have been written to the DB (for permissions).
|
||||||
*/
|
*/
|
||||||
export function getExternalRoleID(roleId?: string) {
|
export function getExternalRoleID(roleId: string, version?: string) {
|
||||||
// for built-in roles we want to remove the DB role ID element (role_)
|
// for built-in roles we want to remove the DB role ID element (role_)
|
||||||
if (roleId?.startsWith(DocumentType.ROLE) && isBuiltin(roleId)) {
|
if (
|
||||||
|
(roleId.startsWith(DocumentType.ROLE) && isBuiltin(roleId)) ||
|
||||||
|
version === RoleIDVersion.NAME
|
||||||
|
) {
|
||||||
return roleId.split(`${DocumentType.ROLE}${SEPARATOR}`)[1]
|
return roleId.split(`${DocumentType.ROLE}${SEPARATOR}`)[1]
|
||||||
}
|
}
|
||||||
return roleId
|
return roleId
|
||||||
|
|
|
@ -12,15 +12,14 @@
|
||||||
let selectedRole = BASE_ROLE
|
let selectedRole = BASE_ROLE
|
||||||
let errors = []
|
let errors = []
|
||||||
let builtInRoles = ["Admin", "Power", "Basic", "Public"]
|
let builtInRoles = ["Admin", "Power", "Basic", "Public"]
|
||||||
|
let validRegex = /^[a-zA-Z0-9_]*$/
|
||||||
// Don't allow editing of public role
|
// Don't allow editing of public role
|
||||||
$: editableRoles = $roles.filter(role => role._id !== "PUBLIC")
|
$: editableRoles = $roles.filter(role => role._id !== "PUBLIC")
|
||||||
$: selectedRoleId = selectedRole._id
|
$: selectedRoleId = selectedRole._id
|
||||||
$: otherRoles = editableRoles.filter(role => role._id !== selectedRoleId)
|
$: otherRoles = editableRoles.filter(role => role._id !== selectedRoleId)
|
||||||
$: isCreating = selectedRoleId == null || selectedRoleId === ""
|
$: isCreating = selectedRoleId == null || selectedRoleId === ""
|
||||||
|
|
||||||
$: hasUniqueRoleName = !otherRoles
|
$: roleNameError = getRoleNameError(selectedRole.name)
|
||||||
?.map(role => role.name)
|
|
||||||
?.includes(selectedRole.name)
|
|
||||||
|
|
||||||
$: valid =
|
$: valid =
|
||||||
selectedRole.name &&
|
selectedRole.name &&
|
||||||
|
@ -101,6 +100,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getRoleNameError = name => {
|
||||||
|
const hasUniqueRoleName = !otherRoles
|
||||||
|
?.map(role => role.name)
|
||||||
|
?.includes(name)
|
||||||
|
const invalidRoleName = !validRegex.test(name)
|
||||||
|
if (!hasUniqueRoleName) {
|
||||||
|
return "Select a unique role name."
|
||||||
|
} else if (invalidRoleName) {
|
||||||
|
return "Please enter a role name consisting of only alphanumeric symbols and underscores"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMount(fetchBasePermissions)
|
onMount(fetchBasePermissions)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -108,7 +119,7 @@
|
||||||
title="Edit Roles"
|
title="Edit Roles"
|
||||||
confirmText={isCreating ? "Create" : "Save"}
|
confirmText={isCreating ? "Create" : "Save"}
|
||||||
onConfirm={saveRole}
|
onConfirm={saveRole}
|
||||||
disabled={!valid || !hasUniqueRoleName}
|
disabled={!valid || roleNameError}
|
||||||
>
|
>
|
||||||
{#if errors.length}
|
{#if errors.length}
|
||||||
<ErrorsBox {errors} />
|
<ErrorsBox {errors} />
|
||||||
|
@ -129,7 +140,7 @@
|
||||||
label="Name"
|
label="Name"
|
||||||
bind:value={selectedRole.name}
|
bind:value={selectedRole.name}
|
||||||
disabled={shouldDisableRoleInput}
|
disabled={shouldDisableRoleInput}
|
||||||
error={!hasUniqueRoleName ? "Select a unique role name." : null}
|
error={roleNameError}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
label="Inherits Role"
|
label="Inherits Role"
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {
|
||||||
getBasePermissions,
|
getBasePermissions,
|
||||||
} from "../../utilities/security"
|
} from "../../utilities/security"
|
||||||
import { removeFromArray } from "../../utilities"
|
import { removeFromArray } from "../../utilities"
|
||||||
import { BBContext, Database, Role } from "@budibase/types"
|
import { UserCtx, Database, Role } from "@budibase/types"
|
||||||
|
|
||||||
const PermissionUpdateType = {
|
const PermissionUpdateType = {
|
||||||
REMOVE: "remove",
|
REMOVE: "remove",
|
||||||
|
@ -38,12 +38,12 @@ async function updatePermissionOnRole(
|
||||||
const isABuiltin = roles.isBuiltin(roleId)
|
const isABuiltin = roles.isBuiltin(roleId)
|
||||||
const dbRoleId = roles.getDBRoleID(roleId)
|
const dbRoleId = roles.getDBRoleID(roleId)
|
||||||
const dbRoles = await getAllDBRoles(db)
|
const dbRoles = await getAllDBRoles(db)
|
||||||
const docUpdates = []
|
const docUpdates: Role[] = []
|
||||||
|
|
||||||
// the permission is for a built in, make sure it exists
|
// the permission is for a built in, make sure it exists
|
||||||
if (isABuiltin && !dbRoles.some(role => role._id === dbRoleId)) {
|
if (isABuiltin && !dbRoles.some(role => role._id === dbRoleId)) {
|
||||||
const builtin = roles.getBuiltinRoles()[roleId]
|
const builtin = roles.getBuiltinRoles()[roleId]
|
||||||
builtin._id = roles.getDBRoleID(builtin._id)
|
builtin._id = roles.getDBRoleID(builtin._id!)
|
||||||
dbRoles.push(builtin)
|
dbRoles.push(builtin)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,22 +88,23 @@ async function updatePermissionOnRole(
|
||||||
|
|
||||||
const response = await db.bulkDocs(docUpdates)
|
const response = await db.bulkDocs(docUpdates)
|
||||||
return response.map((resp: any) => {
|
return response.map((resp: any) => {
|
||||||
resp._id = roles.getExternalRoleID(resp.id)
|
const version = docUpdates.find(role => role._id === resp.id)?.version
|
||||||
|
resp._id = roles.getExternalRoleID(resp.id, version)
|
||||||
delete resp.id
|
delete resp.id
|
||||||
return resp
|
return resp
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchBuiltin(ctx: BBContext) {
|
export function fetchBuiltin(ctx: UserCtx) {
|
||||||
ctx.body = Object.values(permissions.getBuiltinPermissions())
|
ctx.body = Object.values(permissions.getBuiltinPermissions())
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchLevels(ctx: BBContext) {
|
export function fetchLevels(ctx: UserCtx) {
|
||||||
// for now only provide the read/write perms externally
|
// for now only provide the read/write perms externally
|
||||||
ctx.body = SUPPORTED_LEVELS
|
ctx.body = SUPPORTED_LEVELS
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetch(ctx: BBContext) {
|
export async function fetch(ctx: UserCtx) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const dbRoles: Role[] = await getAllDBRoles(db)
|
const dbRoles: Role[] = await getAllDBRoles(db)
|
||||||
let permissions: any = {}
|
let permissions: any = {}
|
||||||
|
@ -112,7 +113,7 @@ export async function fetch(ctx: BBContext) {
|
||||||
if (!role.permissions) {
|
if (!role.permissions) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const roleId = roles.getExternalRoleID(role._id)
|
const roleId = roles.getExternalRoleID(role._id!, role.version)
|
||||||
if (!roleId) {
|
if (!roleId) {
|
||||||
ctx.throw(400, "Unable to retrieve role")
|
ctx.throw(400, "Unable to retrieve role")
|
||||||
}
|
}
|
||||||
|
@ -132,7 +133,7 @@ export async function fetch(ctx: BBContext) {
|
||||||
ctx.body = finalPermissions
|
ctx.body = finalPermissions
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getResourcePerms(ctx: BBContext) {
|
export async function getResourcePerms(ctx: UserCtx) {
|
||||||
const resourceId = ctx.params.resourceId
|
const resourceId = ctx.params.resourceId
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const body = await db.allDocs(
|
const body = await db.allDocs(
|
||||||
|
@ -154,14 +155,14 @@ export async function getResourcePerms(ctx: BBContext) {
|
||||||
rolePerms[resourceId] &&
|
rolePerms[resourceId] &&
|
||||||
rolePerms[resourceId].indexOf(level) !== -1
|
rolePerms[resourceId].indexOf(level) !== -1
|
||||||
) {
|
) {
|
||||||
permissions[level] = roles.getExternalRoleID(role._id)!
|
permissions[level] = roles.getExternalRoleID(role._id, role.version)!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.body = Object.assign(getBasePermissions(resourceId), permissions)
|
ctx.body = Object.assign(getBasePermissions(resourceId), permissions)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function addPermission(ctx: BBContext) {
|
export async function addPermission(ctx: UserCtx) {
|
||||||
ctx.body = await updatePermissionOnRole(
|
ctx.body = await updatePermissionOnRole(
|
||||||
ctx.appId,
|
ctx.appId,
|
||||||
ctx.params,
|
ctx.params,
|
||||||
|
@ -169,7 +170,7 @@ export async function addPermission(ctx: BBContext) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removePermission(ctx: BBContext) {
|
export async function removePermission(ctx: UserCtx) {
|
||||||
ctx.body = await updatePermissionOnRole(
|
ctx.body = await updatePermissionOnRole(
|
||||||
ctx.appId,
|
ctx.appId,
|
||||||
ctx.params,
|
ctx.params,
|
||||||
|
|
|
@ -14,7 +14,8 @@ const UpdateRolesOptions = {
|
||||||
async function updateRolesOnUserTable(
|
async function updateRolesOnUserTable(
|
||||||
db: Database,
|
db: Database,
|
||||||
roleId: string,
|
roleId: string,
|
||||||
updateOption: string
|
updateOption: string,
|
||||||
|
roleVersion: string | undefined
|
||||||
) {
|
) {
|
||||||
const table = await db.get(InternalTables.USER_METADATA)
|
const table = await db.get(InternalTables.USER_METADATA)
|
||||||
const schema = table.schema
|
const schema = table.schema
|
||||||
|
@ -24,11 +25,15 @@ async function updateRolesOnUserTable(
|
||||||
if (prop === "roleId") {
|
if (prop === "roleId") {
|
||||||
updated = true
|
updated = true
|
||||||
const constraints = schema[prop].constraints
|
const constraints = schema[prop].constraints
|
||||||
const indexOf = constraints.inclusion.indexOf(roleId)
|
const updatedRoleId =
|
||||||
if (remove && indexOf !== -1) {
|
roleVersion === roles.RoleIDVersion.NAME
|
||||||
constraints.inclusion.splice(indexOf, 1)
|
? roles.getExternalRoleID(roleId, roleVersion)
|
||||||
} else if (!remove && indexOf === -1) {
|
: roleId
|
||||||
constraints.inclusion.push(roleId)
|
const indexOfRoleId = constraints.inclusion.indexOf(updatedRoleId)
|
||||||
|
if (remove && indexOfRoleId !== -1) {
|
||||||
|
constraints.inclusion.splice(indexOfRoleId, 1)
|
||||||
|
} else if (!remove && indexOfRoleId === -1) {
|
||||||
|
constraints.inclusion.push(updatedRoleId)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -48,14 +53,23 @@ export async function find(ctx: UserCtx) {
|
||||||
|
|
||||||
export async function save(ctx: UserCtx) {
|
export async function save(ctx: UserCtx) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
let { _id, name, inherits, permissionId } = ctx.request.body
|
let { _id, name, inherits, permissionId, version } = ctx.request.body
|
||||||
let isCreate = false
|
let isCreate = false
|
||||||
if (!_id) {
|
|
||||||
_id = generateRoleID()
|
if (_id && roles.isBuiltin(_id)) {
|
||||||
isCreate = true
|
|
||||||
} else if (roles.isBuiltin(_id)) {
|
|
||||||
ctx.throw(400, "Cannot update builtin roles.")
|
ctx.throw(400, "Cannot update builtin roles.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if not id found, then its creation
|
||||||
|
if (!_id) {
|
||||||
|
_id = generateRoleID(name)
|
||||||
|
isCreate = true
|
||||||
|
}
|
||||||
|
// version 2 roles need updated to add back role_
|
||||||
|
else if (version === roles.RoleIDVersion.NAME) {
|
||||||
|
_id = generateRoleID(name)
|
||||||
|
}
|
||||||
|
|
||||||
const role = new roles.Role(_id, name, permissionId).addInheritance(inherits)
|
const role = new roles.Role(_id, name, permissionId).addInheritance(inherits)
|
||||||
if (ctx.request.body._rev) {
|
if (ctx.request.body._rev) {
|
||||||
role._rev = ctx.request.body._rev
|
role._rev = ctx.request.body._rev
|
||||||
|
@ -66,7 +80,12 @@ export async function save(ctx: UserCtx) {
|
||||||
} else {
|
} else {
|
||||||
await events.role.updated(role)
|
await events.role.updated(role)
|
||||||
}
|
}
|
||||||
await updateRolesOnUserTable(db, _id, UpdateRolesOptions.CREATED)
|
await updateRolesOnUserTable(
|
||||||
|
db,
|
||||||
|
_id,
|
||||||
|
UpdateRolesOptions.CREATED,
|
||||||
|
role.version
|
||||||
|
)
|
||||||
role._rev = result.rev
|
role._rev = result.rev
|
||||||
ctx.body = role
|
ctx.body = role
|
||||||
ctx.message = `Role '${role.name}' created successfully.`
|
ctx.message = `Role '${role.name}' created successfully.`
|
||||||
|
@ -74,11 +93,14 @@ export async function save(ctx: UserCtx) {
|
||||||
|
|
||||||
export async function destroy(ctx: UserCtx) {
|
export async function destroy(ctx: UserCtx) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const roleId = ctx.params.roleId
|
let roleId = ctx.params.roleId
|
||||||
const role = await db.get(roleId)
|
|
||||||
if (roles.isBuiltin(roleId)) {
|
if (roles.isBuiltin(roleId)) {
|
||||||
ctx.throw(400, "Cannot delete builtin role.")
|
ctx.throw(400, "Cannot delete builtin role.")
|
||||||
|
} else {
|
||||||
|
// make sure has the prefix (if it has it then it won't be added)
|
||||||
|
roleId = generateRoleID(roleId)
|
||||||
}
|
}
|
||||||
|
const role = await db.get(roleId)
|
||||||
// first check no users actively attached to role
|
// first check no users actively attached to role
|
||||||
const users = (
|
const users = (
|
||||||
await db.allDocs(
|
await db.allDocs(
|
||||||
|
@ -97,7 +119,8 @@ export async function destroy(ctx: UserCtx) {
|
||||||
await updateRolesOnUserTable(
|
await updateRolesOnUserTable(
|
||||||
db,
|
db,
|
||||||
ctx.params.roleId,
|
ctx.params.roleId,
|
||||||
UpdateRolesOptions.REMOVED
|
UpdateRolesOptions.REMOVED,
|
||||||
|
role.version
|
||||||
)
|
)
|
||||||
ctx.message = `Role ${ctx.params.roleId} deleted successfully`
|
ctx.message = `Role ${ctx.params.roleId} deleted successfully`
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
|
|
|
@ -29,10 +29,11 @@ describe("/roles", () => {
|
||||||
|
|
||||||
describe("create", () => {
|
describe("create", () => {
|
||||||
it("returns a success message when role is successfully created", async () => {
|
it("returns a success message when role is successfully created", async () => {
|
||||||
const res = await createRole()
|
const role = basicRole()
|
||||||
|
const res = await createRole(role)
|
||||||
|
|
||||||
expect(res.res.statusMessage).toEqual(
|
expect(res.res.statusMessage).toEqual(
|
||||||
"Role 'NewRole' created successfully."
|
`Role '${role.name}' created successfully.`
|
||||||
)
|
)
|
||||||
expect(res.body._id).toBeDefined()
|
expect(res.body._id).toBeDefined()
|
||||||
expect(res.body._rev).toBeDefined()
|
expect(res.body._rev).toBeDefined()
|
||||||
|
@ -44,12 +45,13 @@ describe("/roles", () => {
|
||||||
|
|
||||||
describe("update", () => {
|
describe("update", () => {
|
||||||
it("updates a role", async () => {
|
it("updates a role", async () => {
|
||||||
let res = await createRole()
|
const role = basicRole()
|
||||||
|
let res = await createRole(role)
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
res = await createRole(res.body)
|
res = await createRole(res.body)
|
||||||
|
|
||||||
expect(res.res.statusMessage).toEqual(
|
expect(res.res.statusMessage).toEqual(
|
||||||
"Role 'NewRole' created successfully."
|
`Role '${role.name}' created successfully.`
|
||||||
)
|
)
|
||||||
expect(res.body._id).toBeDefined()
|
expect(res.body._id).toBeDefined()
|
||||||
expect(res.body._rev).toBeDefined()
|
expect(res.body._rev).toBeDefined()
|
||||||
|
@ -86,7 +88,7 @@ describe("/roles", () => {
|
||||||
expect(powerUserRole.inherits).toEqual(BUILTIN_ROLE_IDS.BASIC)
|
expect(powerUserRole.inherits).toEqual(BUILTIN_ROLE_IDS.BASIC)
|
||||||
expect(powerUserRole.permissionId).toEqual(BuiltinPermissionID.POWER)
|
expect(powerUserRole.permissionId).toEqual(BuiltinPermissionID.POWER)
|
||||||
|
|
||||||
const customRoleFetched = res.body.find(r => r._id === customRole._id)
|
const customRoleFetched = res.body.find(r => r._id === customRole.name)
|
||||||
expect(customRoleFetched).toBeDefined()
|
expect(customRoleFetched).toBeDefined()
|
||||||
expect(customRoleFetched.inherits).toEqual(BUILTIN_ROLE_IDS.BASIC)
|
expect(customRoleFetched.inherits).toEqual(BUILTIN_ROLE_IDS.BASIC)
|
||||||
expect(customRoleFetched.permissionId).toEqual(
|
expect(customRoleFetched.permissionId).toEqual(
|
||||||
|
|
|
@ -134,7 +134,7 @@ export function roleValidator() {
|
||||||
return auth.joiValidator.body(Joi.object({
|
return auth.joiValidator.body(Joi.object({
|
||||||
_id: OPTIONAL_STRING,
|
_id: OPTIONAL_STRING,
|
||||||
_rev: OPTIONAL_STRING,
|
_rev: OPTIONAL_STRING,
|
||||||
name: Joi.string().required(),
|
name: Joi.string().regex(/^[a-zA-Z0-9_]*$/).required(),
|
||||||
// this is the base permission ID (for now a built in)
|
// this is the base permission ID (for now a built in)
|
||||||
permissionId: Joi.string().valid(...Object.values(permissions.BuiltinPermissionID)).required(),
|
permissionId: Joi.string().valid(...Object.values(permissions.BuiltinPermissionID)).required(),
|
||||||
permissions: Joi.object()
|
permissions: Joi.object()
|
||||||
|
|
|
@ -22,12 +22,12 @@ import tar from "tar"
|
||||||
|
|
||||||
const MemoryStream = require("memorystream")
|
const MemoryStream = require("memorystream")
|
||||||
|
|
||||||
interface DBDumpOpts {
|
export interface DBDumpOpts {
|
||||||
filter?: any
|
filter?: any
|
||||||
exportPath?: string
|
exportPath?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExportOpts extends DBDumpOpts {
|
export interface ExportOpts extends DBDumpOpts {
|
||||||
tar?: boolean
|
tar?: boolean
|
||||||
excludeRows?: boolean
|
excludeRows?: boolean
|
||||||
excludeLogs?: boolean
|
excludeLogs?: boolean
|
||||||
|
@ -57,7 +57,10 @@ function tarFilesToTmp(tmpDir: string, files: string[]) {
|
||||||
* a filter function or the name of the export.
|
* a filter function or the name of the export.
|
||||||
* @return {*} either a readable stream or a string
|
* @return {*} either a readable stream or a string
|
||||||
*/
|
*/
|
||||||
export async function exportDB(dbName: string, opts: DBDumpOpts = {}) {
|
export async function exportDB(
|
||||||
|
dbName: string,
|
||||||
|
opts: DBDumpOpts = {}
|
||||||
|
): Promise<DBDumpOpts> {
|
||||||
const exportOpts = {
|
const exportOpts = {
|
||||||
filter: opts?.filter,
|
filter: opts?.filter,
|
||||||
batch_size: 1000,
|
batch_size: 1000,
|
||||||
|
@ -178,6 +181,7 @@ export async function exportApp(appId: string, config?: ExportOpts) {
|
||||||
* Streams a backup of the database state for an app
|
* Streams a backup of the database state for an app
|
||||||
* @param {string} appId The ID of the app which is to be backed up.
|
* @param {string} appId The ID of the app which is to be backed up.
|
||||||
* @param {boolean} excludeRows Flag to state whether the export should include data.
|
* @param {boolean} excludeRows Flag to state whether the export should include data.
|
||||||
|
* @param {string} encryptPassword password for encrypting the export.
|
||||||
* @returns {*} a readable stream of the backup which is written in real time
|
* @returns {*} a readable stream of the backup which is written in real time
|
||||||
*/
|
*/
|
||||||
export async function streamExportApp({
|
export async function streamExportApp({
|
||||||
|
|
|
@ -282,9 +282,10 @@ export function basicLinkedRow(
|
||||||
|
|
||||||
export function basicRole() {
|
export function basicRole() {
|
||||||
return {
|
return {
|
||||||
name: "NewRole",
|
name: `NewRole_${utils.newid()}`,
|
||||||
inherits: roles.BUILTIN_ROLE_IDS.BASIC,
|
inherits: roles.BUILTIN_ROLE_IDS.BASIC,
|
||||||
permissionId: permissions.BuiltinPermissionID.READ_ONLY,
|
permissionId: permissions.BuiltinPermissionID.READ_ONLY,
|
||||||
|
version: "name",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,4 +4,5 @@ export interface Role extends Document {
|
||||||
permissionId: string
|
permissionId: string
|
||||||
inherits?: string
|
inherits?: string
|
||||||
permissions: { [key: string]: string[] }
|
permissions: { [key: string]: string[] }
|
||||||
|
version?: string
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ jest.mock("@budibase/backend-core", () => {
|
||||||
|
|
||||||
let appId: string
|
let appId: string
|
||||||
let appDb: Database
|
let appDb: Database
|
||||||
|
const ROLE_NAME = "newRole"
|
||||||
|
|
||||||
async function addAppMetadata() {
|
async function addAppMetadata() {
|
||||||
await appDb.put({
|
await appDb.put({
|
||||||
|
@ -34,7 +35,7 @@ describe("/api/global/roles", () => {
|
||||||
const config = new TestConfiguration()
|
const config = new TestConfiguration()
|
||||||
|
|
||||||
const role = new roles.Role(
|
const role = new roles.Role(
|
||||||
db.generateRoleID("newRole"),
|
db.generateRoleID(ROLE_NAME),
|
||||||
roles.BUILTIN_ROLE_IDS.BASIC,
|
roles.BUILTIN_ROLE_IDS.BASIC,
|
||||||
permissions.BuiltinPermissionID.READ_ONLY
|
permissions.BuiltinPermissionID.READ_ONLY
|
||||||
)
|
)
|
||||||
|
@ -66,7 +67,7 @@ describe("/api/global/roles", () => {
|
||||||
const res = await config.api.roles.get()
|
const res = await config.api.roles.get()
|
||||||
expect(res.body).toBeDefined()
|
expect(res.body).toBeDefined()
|
||||||
expect(res.body[appId].roles.length).toEqual(5)
|
expect(res.body[appId].roles.length).toEqual(5)
|
||||||
expect(res.body[appId].roles.map((r: any) => r._id)).toContain(role._id)
|
expect(res.body[appId].roles.map((r: any) => r._id)).toContain(ROLE_NAME)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue