1
0
Fork 0
mirror of synced 2024-06-28 11:00:55 +12:00
budibase/packages/server/src/api/controllers/permission.js

206 lines
5.8 KiB
JavaScript
Raw Normal View History

2021-02-06 04:58:25 +13:00
const {
BUILTIN_PERMISSIONS,
PermissionLevels,
PermissionTypes,
higherPermission,
getBuiltinPermissionByID,
isPermissionLevelHigherThanRead,
2021-02-06 04:58:25 +13:00
} = require("../../utilities/security/permissions")
const {
isBuiltin,
getDBRoleID,
getExternalRoleID,
lowerBuiltinRoleID,
BUILTIN_ROLES,
} = require("../../utilities/security/roles")
const { getRoleParams, DocumentTypes } = require("../../db/utils")
const CouchDB = require("../../db")
const { cloneDeep } = require("lodash/fp")
const PermissionUpdateType = {
REMOVE: "remove",
ADD: "add",
}
const SUPPORTED_LEVELS = [PermissionLevels.WRITE, PermissionLevels.READ]
function getPermissionType(resourceId) {
const docType = DocumentTypes.filter(docType =>
resourceId.startsWith(docType)
)[0]
switch (docType) {
case DocumentTypes.TABLE:
case DocumentTypes.ROW:
return PermissionTypes.TABLE
case DocumentTypes.AUTOMATION:
return PermissionTypes.AUTOMATION
case DocumentTypes.WEBHOOK:
return PermissionTypes.WEBHOOK
case DocumentTypes.QUERY:
case DocumentTypes.DATASOURCE:
return PermissionTypes.QUERY
default:
// views don't have an ID, will end up here
return PermissionTypes.VIEW
}
}
async function getBasePermissions(resourceId) {
const type = getPermissionType(resourceId)
const permissions = {}
for (let [roleId, role] of Object.entries(BUILTIN_ROLES)) {
if (!role.permissionId) {
continue
}
const perms = getBuiltinPermissionByID(role.permissionId)
const typedPermission = perms.permissions.find(perm => perm.type === type)
if (typedPermission) {
const level = typedPermission.level
permissions[level] = lowerBuiltinRoleID(permissions[level], roleId)
if (isPermissionLevelHigherThanRead(level)) {
permissions[PermissionLevels.READ] = lowerBuiltinRoleID(
permissions[PermissionLevels.READ],
roleId
)
}
}
}
return permissions
}
// utility function to stop this repetition - permissions always stored under roles
async function getAllDBRoles(db) {
const body = await db.allDocs(
getRoleParams(null, {
include_docs: true,
})
)
return body.rows.map(row => row.doc)
}
async function updatePermissionOnRole(
appId,
{ roleId, resourceId, level },
updateType
) {
const db = new CouchDB(appId)
const remove = updateType === PermissionUpdateType.REMOVE
const isABuiltin = isBuiltin(roleId)
const dbRoleId = getDBRoleID(roleId)
const dbRoles = await getAllDBRoles(db)
const docUpdates = []
2021-02-06 04:58:25 +13:00
// the permission is for a built in, make sure it exists
if (isABuiltin && !dbRoles.some(role => role._id === dbRoleId)) {
const builtin = cloneDeep(BUILTIN_ROLES[roleId])
builtin._id = 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 = role.permissions ? role.permissions : {}
// 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] === level
) {
delete rolePermissions[resourceId]
updated = true
}
// handle the adding, we're on the correct role, at it to this
if (!remove && role._id === dbRoleId) {
rolePermissions[resourceId] = 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)
2021-02-10 02:14:23 +13:00
return response.map(resp => {
resp._id = getExternalRoleID(resp.id)
delete resp.id
return resp
})
2021-02-06 04:58:25 +13:00
}
exports.fetchBuiltin = function(ctx) {
ctx.body = Object.values(BUILTIN_PERMISSIONS)
}
2021-02-06 04:58:25 +13:00
exports.fetchLevels = function(ctx) {
// for now only provide the read/write perms externally
ctx.body = SUPPORTED_LEVELS
}
exports.fetch = async function(ctx) {
const db = new CouchDB(ctx.appId)
const roles = await getAllDBRoles(db)
let permissions = {}
// create an object with structure role ID -> resource ID -> level
for (let role of roles) {
if (role.permissions) {
const roleId = getExternalRoleID(role._id)
if (permissions[roleId] == null) {
permissions[roleId] = {}
}
for (let [resource, level] of Object.entries(role.permissions)) {
permissions[roleId][resource] = higherPermission(
permissions[roleId][resource],
level
)
}
}
}
ctx.body = permissions
}
exports.getResourcePerms = async function(ctx) {
const resourceId = ctx.params.resourceId
const db = new CouchDB(ctx.appId)
const body = await db.allDocs(
getRoleParams(null, {
include_docs: true,
})
)
const roles = body.rows.map(row => row.doc)
const resourcePerms = {}
for (let level of SUPPORTED_LEVELS) {
for (let role of roles)
// update the various roleIds in the resource permissions
if (role.permissions && role.permissions[resourceId]) {
const roleId = getExternalRoleID(role._id)
resourcePerms[level] = higherPermission(
resourcePerms[roleId],
role.permissions[resourceId]
)
}
}
ctx.body = resourcePerms
2021-02-06 04:58:25 +13:00
}
exports.addPermission = async function(ctx) {
ctx.body = await updatePermissionOnRole(
ctx.appId,
ctx.params,
PermissionUpdateType.ADD
)
2021-02-06 04:58:25 +13:00
}
exports.removePermission = async function(ctx) {
ctx.body = await updatePermissionOnRole(
ctx.appId,
ctx.params,
PermissionUpdateType.REMOVE
)
2021-02-06 04:58:25 +13:00
}