Move permission updates into SDK
This commit is contained in:
parent
d4db493519
commit
55c7751dbb
|
@ -3,7 +3,6 @@ import {
|
||||||
UserCtx,
|
UserCtx,
|
||||||
Database,
|
Database,
|
||||||
Role,
|
Role,
|
||||||
PermissionLevel,
|
|
||||||
GetResourcePermsResponse,
|
GetResourcePermsResponse,
|
||||||
ResourcePermissionInfo,
|
ResourcePermissionInfo,
|
||||||
GetDependantResourcesResponse,
|
GetDependantResourcesResponse,
|
||||||
|
@ -12,107 +11,15 @@ import {
|
||||||
RemovePermissionRequest,
|
RemovePermissionRequest,
|
||||||
RemovePermissionResponse,
|
RemovePermissionResponse,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { getRoleParams } from "../../db/utils"
|
|
||||||
import {
|
import {
|
||||||
CURRENTLY_SUPPORTED_LEVELS,
|
CURRENTLY_SUPPORTED_LEVELS,
|
||||||
getBasePermissions,
|
getBasePermissions,
|
||||||
} from "../../utilities/security"
|
} from "../../utilities/security"
|
||||||
import { removeFromArray } from "../../utilities"
|
|
||||||
import sdk from "../../sdk"
|
import sdk from "../../sdk"
|
||||||
|
import { PermissionUpdateType } from "../../sdk/app/permissions"
|
||||||
export const enum PermissionUpdateType {
|
|
||||||
REMOVE = "remove",
|
|
||||||
ADD = "add",
|
|
||||||
}
|
|
||||||
|
|
||||||
const SUPPORTED_LEVELS = CURRENTLY_SUPPORTED_LEVELS
|
const SUPPORTED_LEVELS = CURRENTLY_SUPPORTED_LEVELS
|
||||||
|
|
||||||
// utility function to stop this repetition - permissions always stored under roles
|
|
||||||
async function getAllDBRoles(db: Database) {
|
|
||||||
const body = await db.allDocs<Role>(
|
|
||||||
getRoleParams(null, {
|
|
||||||
include_docs: true,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
return body.rows.map(row => row.doc!)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updatePermissionOnRole(
|
|
||||||
{
|
|
||||||
roleId,
|
|
||||||
resourceId,
|
|
||||||
level,
|
|
||||||
}: { roleId: string; resourceId: string; level: PermissionLevel },
|
|
||||||
updateType: PermissionUpdateType
|
|
||||||
) {
|
|
||||||
const db = context.getAppDB()
|
|
||||||
const remove = updateType === PermissionUpdateType.REMOVE
|
|
||||||
const isABuiltin = roles.isBuiltin(roleId)
|
|
||||||
const dbRoleId = roles.getDBRoleID(roleId)
|
|
||||||
const dbRoles = await getAllDBRoles(db)
|
|
||||||
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!)
|
|
||||||
dbRoles.push(builtin)
|
|
||||||
}
|
|
||||||
|
|
||||||
// now try to find any roles which need updated, e.g. removing the
|
|
||||||
// resource from another role and then adding to the new role
|
|
||||||
for (let role of dbRoles) {
|
|
||||||
let updated = false
|
|
||||||
const rolePermissions: Record<string, PermissionLevel[]> = role.permissions
|
|
||||||
? role.permissions
|
|
||||||
: {}
|
|
||||||
// make sure its an array, also handle migrating
|
|
||||||
if (
|
|
||||||
!rolePermissions[resourceId] ||
|
|
||||||
!Array.isArray(rolePermissions[resourceId])
|
|
||||||
) {
|
|
||||||
rolePermissions[resourceId] =
|
|
||||||
typeof rolePermissions[resourceId] === "string"
|
|
||||||
? [rolePermissions[resourceId] as unknown as PermissionLevel]
|
|
||||||
: []
|
|
||||||
}
|
|
||||||
// handle the removal/updating the role which has this permission first
|
|
||||||
// the updating (role._id !== dbRoleId) is required because a resource/level can
|
|
||||||
// only be permitted in a single role (this reduces hierarchy confusion and simplifies
|
|
||||||
// the general UI for this, rather than needing to show everywhere it is used)
|
|
||||||
if (
|
|
||||||
(role._id !== dbRoleId || remove) &&
|
|
||||||
rolePermissions[resourceId].indexOf(level) !== -1
|
|
||||||
) {
|
|
||||||
removeFromArray(rolePermissions[resourceId], level)
|
|
||||||
updated = true
|
|
||||||
}
|
|
||||||
// handle the adding, we're on the correct role, at it to this
|
|
||||||
if (!remove && role._id === dbRoleId) {
|
|
||||||
const set = new Set(rolePermissions[resourceId])
|
|
||||||
rolePermissions[resourceId] = [...set.add(level)]
|
|
||||||
updated = true
|
|
||||||
}
|
|
||||||
// handle the update, add it to bulk docs to perform at end
|
|
||||||
if (updated) {
|
|
||||||
role.permissions = rolePermissions
|
|
||||||
docUpdates.push(role)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await db.bulkDocs(docUpdates)
|
|
||||||
return response.map(resp => {
|
|
||||||
const version = docUpdates.find(role => role._id === resp.id)?.version
|
|
||||||
const _id = roles.getExternalRoleID(resp.id, version)
|
|
||||||
return {
|
|
||||||
_id,
|
|
||||||
rev: resp.rev,
|
|
||||||
error: resp.error,
|
|
||||||
reason: resp.reason,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchBuiltin(ctx: UserCtx) {
|
export function fetchBuiltin(ctx: UserCtx) {
|
||||||
ctx.body = Object.values(permissions.getBuiltinPermissions())
|
ctx.body = Object.values(permissions.getBuiltinPermissions())
|
||||||
}
|
}
|
||||||
|
@ -124,7 +31,7 @@ export function fetchLevels(ctx: UserCtx) {
|
||||||
|
|
||||||
export async function fetch(ctx: UserCtx) {
|
export async function fetch(ctx: UserCtx) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const dbRoles: Role[] = await getAllDBRoles(db)
|
const dbRoles: Role[] = await sdk.permissions.getAllDBRoles(db)
|
||||||
let permissions: any = {}
|
let permissions: any = {}
|
||||||
// create an object with structure role ID -> resource ID -> level
|
// create an object with structure role ID -> resource ID -> level
|
||||||
for (let role of dbRoles) {
|
for (let role of dbRoles) {
|
||||||
|
@ -186,12 +93,18 @@ export async function getDependantResources(
|
||||||
|
|
||||||
export async function addPermission(ctx: UserCtx<void, AddPermissionResponse>) {
|
export async function addPermission(ctx: UserCtx<void, AddPermissionResponse>) {
|
||||||
const params: AddPermissionRequest = ctx.params
|
const params: AddPermissionRequest = ctx.params
|
||||||
ctx.body = await updatePermissionOnRole(params, PermissionUpdateType.ADD)
|
ctx.body = await sdk.permissions.updatePermissionOnRole(
|
||||||
|
params,
|
||||||
|
PermissionUpdateType.ADD
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removePermission(
|
export async function removePermission(
|
||||||
ctx: UserCtx<void, RemovePermissionResponse>
|
ctx: UserCtx<void, RemovePermissionResponse>
|
||||||
) {
|
) {
|
||||||
const params: RemovePermissionRequest = ctx.params
|
const params: RemovePermissionRequest = ctx.params
|
||||||
ctx.body = await updatePermissionOnRole(params, PermissionUpdateType.REMOVE)
|
ctx.body = await sdk.permissions.updatePermissionOnRole(
|
||||||
|
params,
|
||||||
|
PermissionUpdateType.REMOVE
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,34 @@
|
||||||
import { db, roles } from "@budibase/backend-core"
|
import { db, roles, context } from "@budibase/backend-core"
|
||||||
import {
|
import {
|
||||||
PermissionLevel,
|
PermissionLevel,
|
||||||
PermissionSource,
|
PermissionSource,
|
||||||
VirtualDocumentType,
|
VirtualDocumentType,
|
||||||
|
Role,
|
||||||
|
Database,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { extractViewInfoFromID, isViewID } from "../../../db/utils"
|
import {
|
||||||
|
extractViewInfoFromID,
|
||||||
|
isViewID,
|
||||||
|
getRoleParams,
|
||||||
|
} from "../../../db/utils"
|
||||||
import {
|
import {
|
||||||
CURRENTLY_SUPPORTED_LEVELS,
|
CURRENTLY_SUPPORTED_LEVELS,
|
||||||
getBasePermissions,
|
getBasePermissions,
|
||||||
} from "../../../utilities/security"
|
} from "../../../utilities/security"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
import { isV2 } from "../views"
|
import { isV2 } from "../views"
|
||||||
|
import { removeFromArray } from "../../../utilities"
|
||||||
|
|
||||||
type ResourcePermissions = Record<
|
type ResourcePermissions = Record<
|
||||||
string,
|
string,
|
||||||
{ role: string; type: PermissionSource }
|
{ role: string; type: PermissionSource }
|
||||||
>
|
>
|
||||||
|
|
||||||
|
export const enum PermissionUpdateType {
|
||||||
|
REMOVE = "remove",
|
||||||
|
ADD = "add",
|
||||||
|
}
|
||||||
|
|
||||||
export async function getInheritablePermissions(
|
export async function getInheritablePermissions(
|
||||||
resourceId: string
|
resourceId: string
|
||||||
): Promise<ResourcePermissions | undefined> {
|
): Promise<ResourcePermissions | undefined> {
|
||||||
|
@ -100,3 +112,89 @@ export async function getDependantResources(
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function updatePermissionOnRole(
|
||||||
|
{
|
||||||
|
roleId,
|
||||||
|
resourceId,
|
||||||
|
level,
|
||||||
|
}: { roleId: string; resourceId: string; level: PermissionLevel },
|
||||||
|
updateType: PermissionUpdateType
|
||||||
|
) {
|
||||||
|
const db = context.getAppDB()
|
||||||
|
const remove = updateType === PermissionUpdateType.REMOVE
|
||||||
|
const isABuiltin = roles.isBuiltin(roleId)
|
||||||
|
const dbRoleId = roles.getDBRoleID(roleId)
|
||||||
|
const dbRoles = await getAllDBRoles(db)
|
||||||
|
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!)
|
||||||
|
dbRoles.push(builtin)
|
||||||
|
}
|
||||||
|
|
||||||
|
// now try to find any roles which need updated, e.g. removing the
|
||||||
|
// resource from another role and then adding to the new role
|
||||||
|
for (let role of dbRoles) {
|
||||||
|
let updated = false
|
||||||
|
const rolePermissions: Record<string, PermissionLevel[]> = role.permissions
|
||||||
|
? role.permissions
|
||||||
|
: {}
|
||||||
|
// make sure its an array, also handle migrating
|
||||||
|
if (
|
||||||
|
!rolePermissions[resourceId] ||
|
||||||
|
!Array.isArray(rolePermissions[resourceId])
|
||||||
|
) {
|
||||||
|
rolePermissions[resourceId] =
|
||||||
|
typeof rolePermissions[resourceId] === "string"
|
||||||
|
? [rolePermissions[resourceId] as unknown as PermissionLevel]
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
// handle the removal/updating the role which has this permission first
|
||||||
|
// the updating (role._id !== dbRoleId) is required because a resource/level can
|
||||||
|
// only be permitted in a single role (this reduces hierarchy confusion and simplifies
|
||||||
|
// the general UI for this, rather than needing to show everywhere it is used)
|
||||||
|
if (
|
||||||
|
(role._id !== dbRoleId || remove) &&
|
||||||
|
rolePermissions[resourceId].indexOf(level) !== -1
|
||||||
|
) {
|
||||||
|
removeFromArray(rolePermissions[resourceId], level)
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
// handle the adding, we're on the correct role, at it to this
|
||||||
|
if (!remove && role._id === dbRoleId) {
|
||||||
|
const set = new Set(rolePermissions[resourceId])
|
||||||
|
rolePermissions[resourceId] = [...set.add(level)]
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
// handle the update, add it to bulk docs to perform at end
|
||||||
|
if (updated) {
|
||||||
|
role.permissions = rolePermissions
|
||||||
|
docUpdates.push(role)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await db.bulkDocs(docUpdates)
|
||||||
|
return response.map(resp => {
|
||||||
|
const version = docUpdates.find(role => role._id === resp.id)?.version
|
||||||
|
const _id = roles.getExternalRoleID(resp.id, version)
|
||||||
|
return {
|
||||||
|
_id,
|
||||||
|
rev: resp.rev,
|
||||||
|
error: resp.error,
|
||||||
|
reason: resp.reason,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// utility function to stop this repetition - permissions always stored under roles
|
||||||
|
export async function getAllDBRoles(db: Database) {
|
||||||
|
const body = await db.allDocs<Role>(
|
||||||
|
getRoleParams(null, {
|
||||||
|
include_docs: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return body.rows.map(row => row.doc!)
|
||||||
|
}
|
||||||
|
|
|
@ -23,10 +23,7 @@ import { isExternalTableID } from "../../../integrations/utils"
|
||||||
import * as internal from "./internal"
|
import * as internal from "./internal"
|
||||||
import * as external from "./external"
|
import * as external from "./external"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
import {
|
import { updatePermissionOnRole, PermissionUpdateType } from "../permissions"
|
||||||
updatePermissionOnRole,
|
|
||||||
PermissionUpdateType,
|
|
||||||
} from "src/api/controllers/permission"
|
|
||||||
|
|
||||||
function pickApi(tableId: any) {
|
function pickApi(tableId: any) {
|
||||||
if (isExternalTableID(tableId)) {
|
if (isExternalTableID(tableId)) {
|
||||||
|
|
Loading…
Reference in New Issue