Some more fixes for RBAC as well as fixing the duplication of roles.

This commit is contained in:
mike12345567 2021-02-12 12:02:07 +00:00
parent 2c8696b8f0
commit fcc683cf99
4 changed files with 115 additions and 58 deletions

View File

@ -29,6 +29,7 @@
<div class="popover"> <div class="popover">
<h5>Who Can Access This Data?</h5> <h5>Who Can Access This Data?</h5>
<Label extraSmall grey>Specify the minimum access level role for this data.</Label>
<Spacer large /> <Spacer large />
<div class="row"> <div class="row">
<Label extraSmall grey>Level</Label> <Label extraSmall grey>Level</Label>

View File

@ -1,72 +1,42 @@
const { const {
BUILTIN_PERMISSIONS, BUILTIN_PERMISSIONS,
PermissionLevels, PermissionLevels,
PermissionTypes,
higherPermission,
getBuiltinPermissionByID,
isPermissionLevelHigherThanRead, isPermissionLevelHigherThanRead,
higherPermission,
} = require("../../utilities/security/permissions") } = require("../../utilities/security/permissions")
const { const {
isBuiltin, isBuiltin,
getDBRoleID, getDBRoleID,
getExternalRoleID, getExternalRoleID,
lowerBuiltinRoleID,
BUILTIN_ROLES, BUILTIN_ROLES,
} = require("../../utilities/security/roles") } = require("../../utilities/security/roles")
const { getRoleParams, DocumentTypes } = require("../../db/utils") const { getRoleParams } = require("../../db/utils")
const CouchDB = require("../../db") const CouchDB = require("../../db")
const { cloneDeep } = require("lodash/fp") const { cloneDeep } = require("lodash/fp")
const {
CURRENTLY_SUPPORTED_LEVELS,
getBasePermissions,
} = require("../../utilities/security/utilities")
const PermissionUpdateType = { const PermissionUpdateType = {
REMOVE: "remove", REMOVE: "remove",
ADD: "add", ADD: "add",
} }
const SUPPORTED_LEVELS = [PermissionLevels.WRITE, PermissionLevels.READ] const SUPPORTED_LEVELS = CURRENTLY_SUPPORTED_LEVELS
function getPermissionType(resourceId) { // quick function to perform a bit of weird logic, make sure fetch calls
const docType = Object.values(DocumentTypes).filter(docType => // always say a write role also has read permission
resourceId.startsWith(docType) function fetchLevelPerms(permissions, level, roleId) {
)[0] if (!permissions) {
switch (docType) { permissions = {}
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
} }
} permissions[level] = roleId
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 ( if (
typedPermission && isPermissionLevelHigherThanRead(level) &&
SUPPORTED_LEVELS.indexOf(typedPermission.level) !== -1 !permissions[PermissionLevels.READ]
) { ) {
const level = typedPermission.level permissions[PermissionLevels.READ] = roleId
permissions[level] = lowerBuiltinRoleID(permissions[level], roleId)
if (isPermissionLevelHigherThanRead(level)) {
permissions[PermissionLevels.READ] = lowerBuiltinRoleID(
permissions[PermissionLevels.READ],
roleId
)
}
}
} }
return permissions return permissions
} }
@ -118,7 +88,10 @@ async function updatePermissionOnRole(
} }
// handle the adding, we're on the correct role, at it to this // handle the adding, we're on the correct role, at it to this
if (!remove && role._id === dbRoleId) { if (!remove && role._id === dbRoleId) {
rolePermissions[resourceId] = level rolePermissions[resourceId] = higherPermission(
rolePermissions[resourceId],
level
)
updated = true updated = true
} }
// handle the update, add it to bulk docs to perform at end // handle the update, add it to bulk docs to perform at end
@ -156,13 +129,20 @@ exports.fetch = async function(ctx) {
} }
const roleId = getExternalRoleID(role._id) const roleId = getExternalRoleID(role._id)
for (let [resource, level] of Object.entries(role.permissions)) { for (let [resource, level] of Object.entries(role.permissions)) {
if (permissions[resource] == null) { permissions[resource] = fetchLevelPerms(
permissions[resource] = getBasePermissions(resource) permissions[resource],
} level,
permissions[resource][level] = roleId roleId
)
} }
} }
ctx.body = permissions // apply the base permissions
const finalPermissions = {}
for (let [resource, permission] of Object.entries(permissions)) {
const basePerms = getBasePermissions(resource)
finalPermissions[resource] = Object.assign(basePerms, permission)
}
ctx.body = finalPermissions
} }
exports.getResourcePerms = async function(ctx) { exports.getResourcePerms = async function(ctx) {
@ -174,16 +154,20 @@ exports.getResourcePerms = async function(ctx) {
}) })
) )
const roles = body.rows.map(row => row.doc) const roles = body.rows.map(row => row.doc)
const resourcePerms = getBasePermissions(resourceId) let permissions = {}
for (let level of SUPPORTED_LEVELS) { for (let level of SUPPORTED_LEVELS) {
// update the various roleIds in the resource permissions // update the various roleIds in the resource permissions
for (let role of roles) { for (let role of roles) {
if (role.permissions && role.permissions[resourceId] === level) { if (role.permissions && role.permissions[resourceId] === level) {
resourcePerms[level] = getExternalRoleID(role._id) permissions = fetchLevelPerms(
permissions,
level,
getExternalRoleID(role._id)
)
} }
} }
} }
ctx.body = resourcePerms ctx.body = Object.assign(getBasePermissions(resourceId), permissions)
} }
exports.addPermission = async function(ctx) { exports.addPermission = async function(ctx) {

View File

@ -57,7 +57,7 @@ exports.fetch = async function(ctx) {
include_docs: true, include_docs: true,
}) })
) )
const roles = body.rows.map(row => row.doc) let roles = body.rows.map(row => row.doc)
// need to combine builtin with any DB record of them (for sake of permissions) // need to combine builtin with any DB record of them (for sake of permissions)
for (let builtinRoleId of EXTERNAL_BUILTIN_ROLE_IDS) { for (let builtinRoleId of EXTERNAL_BUILTIN_ROLE_IDS) {
@ -68,6 +68,8 @@ exports.fetch = async function(ctx) {
if (dbBuiltin == null) { if (dbBuiltin == null) {
roles.push(builtinRole) roles.push(builtinRole)
} else { } 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)
roles.push(Object.assign(builtinRole, dbBuiltin)) roles.push(Object.assign(builtinRole, dbBuiltin))
} }

View File

@ -0,0 +1,70 @@
const {
PermissionLevels,
PermissionTypes,
getBuiltinPermissionByID,
isPermissionLevelHigherThanRead,
} = require("../../utilities/security/permissions")
const {
lowerBuiltinRoleID,
BUILTIN_ROLES,
} = require("../../utilities/security/roles")
const { DocumentTypes } = require("../../db/utils")
const CURRENTLY_SUPPORTED_LEVELS = [
PermissionLevels.WRITE,
PermissionLevels.READ,
]
exports.getPermissionType = resourceId => {
const docType = Object.values(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
}
}
/**
* works out the basic permissions based on builtin roles for a resource, using its ID
* @param resourceId
* @returns {{}}
*/
exports.getBasePermissions = resourceId => {
const type = exports.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 &&
CURRENTLY_SUPPORTED_LEVELS.indexOf(typedPermission.level) !== -1
) {
const level = typedPermission.level
permissions[level] = lowerBuiltinRoleID(permissions[level], roleId)
if (isPermissionLevelHigherThanRead(level)) {
permissions[PermissionLevels.READ] = lowerBuiltinRoleID(
permissions[PermissionLevels.READ],
roleId
)
}
}
}
return permissions
}
exports.CURRENTLY_SUPPORTED_LEVELS = CURRENTLY_SUPPORTED_LEVELS