1
0
Fork 0
mirror of synced 2024-09-08 13:41:09 +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. * 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}`
} }
/** /**

View file

@ -25,18 +25,28 @@ const EXTERNAL_BUILTIN_ROLE_IDS = [
BUILTIN_IDS.PUBLIC, 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 { 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?: number
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 = RoleVersion.VERSION_2
} }
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 = generateRoleID(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 generateRoleID(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?: number) {
// 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 === RoleVersion.VERSION_2
) {
return roleId.split(`${DocumentType.ROLE}${SEPARATOR}`)[1] return roleId.split(`${DocumentType.ROLE}${SEPARATOR}`)[1]
} }
return roleId return roleId

View file

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

View file

@ -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?: number
) { ) {
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 =
roleVersion === roles.RoleVersion.VERSION_2
? roles.getExternalRoleID(roleId, roleVersion)
: roleId
const indexOf = constraints.inclusion.indexOf(updatedRoleId)
if (remove && indexOf !== -1) { if (remove && indexOf !== -1) {
constraints.inclusion.splice(indexOf, 1) constraints.inclusion.splice(indexOf, 1)
} else if (!remove && indexOf === -1) { } else if (!remove && indexOf === -1) {
constraints.inclusion.push(roleId) constraints.inclusion.push(updatedRoleId)
} }
break break
} }
@ -48,14 +53,15 @@ 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) { if (!_id || version === roles.RoleVersion.VERSION_2) {
_id = generateRoleID() _id = generateRoleID(name)
isCreate = true isCreate = true
} else if (roles.isBuiltin(_id)) { } else if (roles.isBuiltin(_id)) {
ctx.throw(400, "Cannot update builtin roles.") ctx.throw(400, "Cannot update builtin roles.")
} }
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 +72,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 +85,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 +111,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

View file

@ -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?: number
} }