1
0
Fork 0
mirror of synced 2024-07-11 01:06:04 +12:00

Handling the removal of the role_ prefix where applicable so that new role IDs present in the exact same way as built in roles.

This commit is contained in:
mike12345567 2023-06-22 18:02:35 +01:00
parent c4994b8369
commit baecab785d
5 changed files with 74 additions and 33 deletions

View file

@ -81,8 +81,12 @@ export function generateAppUserID(prodAppId: string, userId: string) {
* Generates a new role ID.
* @returns {string} The new role ID which the role doc can be stored under.
*/
export function generateRoleID(id?: any) {
return `${DocumentType.ROLE}${SEPARATOR}${id || newid()}`
export function generateRoleID(name: string) {
const prefix = `${DocumentType.ROLE}${SEPARATOR}`
if (name.startsWith(prefix)) {
return name
}
return `${prefix}${name}`
}
/**

View file

@ -25,18 +25,28 @@ const EXTERNAL_BUILTIN_ROLE_IDS = [
BUILTIN_IDS.PUBLIC,
]
export const RoleVersion = {
// original version, with a UUID based ID
VERSION_1: undefined,
// new version - with name based ID
VERSION_2: 2,
}
export class Role implements RoleDoc {
_id: string
_rev?: string
name: string
permissionId: string
inherits?: string
version?: number
permissions = {}
constructor(id: string, name: string, permissionId: string) {
this._id = id
this.name = name
this.permissionId = permissionId
// version for managing the ID - removing the role_ when responding
this.version = RoleVersion.VERSION_2
}
addInheritance(inherits: string) {
@ -157,13 +167,16 @@ export async function getRole(
role = cloneDeep(
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 = generateRoleID(roleId)
}
try {
const db = getAppDB()
const dbRole = await db.get(getDBRoleID(roleId))
role = Object.assign(role, dbRole)
// finalise the ID
role._id = getExternalRoleID(role._id)
role._id = getExternalRoleID(role._id, role.version)
} catch (err) {
if (!isBuiltin(roleId) && opts?.defaultPublic) {
return cloneDeep(BUILTIN_ROLES.PUBLIC)
@ -261,6 +274,9 @@ export async function getAllRoles(appId?: string) {
})
)
roles = body.rows.map((row: any) => row.doc)
roles.forEach(
role => (role._id = getExternalRoleID(role._id!, role.version))
)
}
const builtinRoles = getBuiltinRoles()
@ -268,14 +284,15 @@ export async function getAllRoles(appId?: string) {
for (let builtinRoleId of EXTERNAL_BUILTIN_ROLE_IDS) {
const builtinRole = builtinRoles[builtinRoleId]
const dbBuiltin = roles.filter(
dbRole => getExternalRoleID(dbRole._id) === builtinRoleId
dbRole =>
getExternalRoleID(dbRole._id!, dbRole.version) === builtinRoleId
)[0]
if (dbBuiltin == null) {
roles.push(builtinRole || builtinRoles.BASIC)
} else {
// remove role and all back after combining with the builtin
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))
}
}
@ -381,19 +398,22 @@ export class AccessController {
/**
* Adds the "role_" for builtin role IDs which are to be written to the DB (for permissions).
*/
export function getDBRoleID(roleId?: string) {
if (roleId?.startsWith(DocumentType.ROLE)) {
return roleId
export function getDBRoleID(roleName: string) {
if (roleName?.startsWith(DocumentType.ROLE)) {
return roleName
}
return generateRoleID(roleId)
return generateRoleID(roleName)
}
/**
* 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?: number) {
// 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 === RoleVersion.VERSION_2
) {
return roleId.split(`${DocumentType.ROLE}${SEPARATOR}`)[1]
}
return roleId

View file

@ -5,7 +5,7 @@ import {
getBasePermissions,
} from "../../utilities/security"
import { removeFromArray } from "../../utilities"
import { BBContext, Database, Role } from "@budibase/types"
import { UserCtx, Database, Role } from "@budibase/types"
const PermissionUpdateType = {
REMOVE: "remove",
@ -38,12 +38,12 @@ async function updatePermissionOnRole(
const isABuiltin = roles.isBuiltin(roleId)
const dbRoleId = roles.getDBRoleID(roleId)
const dbRoles = await getAllDBRoles(db)
const docUpdates = []
const docUpdates: Role[] = []
// the permission is for a built in, make sure it exists
if (isABuiltin && !dbRoles.some(role => role._id === dbRoleId)) {
const builtin = roles.getBuiltinRoles()[roleId]
builtin._id = roles.getDBRoleID(builtin._id)
builtin._id = roles.getDBRoleID(builtin._id!)
dbRoles.push(builtin)
}
@ -88,22 +88,23 @@ async function updatePermissionOnRole(
const response = await db.bulkDocs(docUpdates)
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
return resp
})
}
export function fetchBuiltin(ctx: BBContext) {
export function fetchBuiltin(ctx: UserCtx) {
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
ctx.body = SUPPORTED_LEVELS
}
export async function fetch(ctx: BBContext) {
export async function fetch(ctx: UserCtx) {
const db = context.getAppDB()
const dbRoles: Role[] = await getAllDBRoles(db)
let permissions: any = {}
@ -112,7 +113,7 @@ export async function fetch(ctx: BBContext) {
if (!role.permissions) {
continue
}
const roleId = roles.getExternalRoleID(role._id)
const roleId = roles.getExternalRoleID(role._id!, role.version)
if (!roleId) {
ctx.throw(400, "Unable to retrieve role")
}
@ -132,7 +133,7 @@ export async function fetch(ctx: BBContext) {
ctx.body = finalPermissions
}
export async function getResourcePerms(ctx: BBContext) {
export async function getResourcePerms(ctx: UserCtx) {
const resourceId = ctx.params.resourceId
const db = context.getAppDB()
const body = await db.allDocs(
@ -154,14 +155,14 @@ export async function getResourcePerms(ctx: BBContext) {
rolePerms[resourceId] &&
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)
}
export async function addPermission(ctx: BBContext) {
export async function addPermission(ctx: UserCtx) {
ctx.body = await updatePermissionOnRole(
ctx.appId,
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.appId,
ctx.params,

View file

@ -14,7 +14,8 @@ const UpdateRolesOptions = {
async function updateRolesOnUserTable(
db: Database,
roleId: string,
updateOption: string
updateOption: string,
roleVersion?: number
) {
const table = await db.get(InternalTables.USER_METADATA)
const schema = table.schema
@ -24,11 +25,15 @@ async function updateRolesOnUserTable(
if (prop === "roleId") {
updated = true
const constraints = schema[prop].constraints
const indexOf = constraints.inclusion.indexOf(roleId)
const updatedRoleId =
roleVersion === roles.RoleVersion.VERSION_2
? roles.getExternalRoleID(roleId, roleVersion)
: roleId
const indexOf = constraints.inclusion.indexOf(updatedRoleId)
if (remove && indexOf !== -1) {
constraints.inclusion.splice(indexOf, 1)
} else if (!remove && indexOf === -1) {
constraints.inclusion.push(roleId)
constraints.inclusion.push(updatedRoleId)
}
break
}
@ -48,14 +53,15 @@ export async function find(ctx: UserCtx) {
export async function save(ctx: UserCtx) {
const db = context.getAppDB()
let { _id, name, inherits, permissionId } = ctx.request.body
let { _id, name, inherits, permissionId, version } = ctx.request.body
let isCreate = false
if (!_id) {
_id = generateRoleID()
if (!_id || version === roles.RoleVersion.VERSION_2) {
_id = generateRoleID(name)
isCreate = true
} else if (roles.isBuiltin(_id)) {
ctx.throw(400, "Cannot update builtin roles.")
}
const role = new roles.Role(_id, name, permissionId).addInheritance(inherits)
if (ctx.request.body._rev) {
role._rev = ctx.request.body._rev
@ -66,7 +72,12 @@ export async function save(ctx: UserCtx) {
} else {
await events.role.updated(role)
}
await updateRolesOnUserTable(db, _id, UpdateRolesOptions.CREATED)
await updateRolesOnUserTable(
db,
_id,
UpdateRolesOptions.CREATED,
role.version
)
role._rev = result.rev
ctx.body = role
ctx.message = `Role '${role.name}' created successfully.`
@ -74,11 +85,14 @@ export async function save(ctx: UserCtx) {
export async function destroy(ctx: UserCtx) {
const db = context.getAppDB()
const roleId = ctx.params.roleId
const role = await db.get(roleId)
let roleId = ctx.params.roleId
if (roles.isBuiltin(roleId)) {
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
const users = (
await db.allDocs(
@ -97,7 +111,8 @@ export async function destroy(ctx: UserCtx) {
await updateRolesOnUserTable(
db,
ctx.params.roleId,
UpdateRolesOptions.REMOVED
UpdateRolesOptions.REMOVED,
role.version
)
ctx.message = `Role ${ctx.params.roleId} deleted successfully`
ctx.status = 200

View file

@ -4,4 +4,5 @@ export interface Role extends Document {
permissionId: string
inherits?: string
permissions: { [key: string]: string[] }
version?: number
}