diff --git a/packages/server/src/api/controllers/permission.js b/packages/server/src/api/controllers/permission.js index 09ef11f55e..286ea5667c 100644 --- a/packages/server/src/api/controllers/permission.js +++ b/packages/server/src/api/controllers/permission.js @@ -4,11 +4,13 @@ const { PermissionTypes, higherPermission, getBuiltinPermissionByID, + isPermissionLevelHigherThanRead, } = require("../../utilities/security/permissions") const { isBuiltin, getDBRoleID, getExternalRoleID, + lowerBuiltinRoleID, BUILTIN_ROLES, } = require("../../utilities/security/roles") const { getRoleParams, DocumentTypes } = require("../../db/utils") @@ -20,33 +22,31 @@ const PermissionUpdateType = { ADD: "add", } -function getBasePermissions(resourceId) { +const SUPPORTED_LEVELS = [PermissionLevels.WRITE, PermissionLevels.READ] + +function getPermissionType(resourceId) { const docType = DocumentTypes.filter(docType => resourceId.startsWith(docType) )[0] - const levelsToFind = [PermissionLevels.WRITE, PermissionLevels.READ] - let type switch (docType) { case DocumentTypes.TABLE: case DocumentTypes.ROW: - type = PermissionTypes.TABLE - break + return PermissionTypes.TABLE case DocumentTypes.AUTOMATION: - type = PermissionTypes.AUTOMATION - break + return PermissionTypes.AUTOMATION case DocumentTypes.WEBHOOK: - type = PermissionTypes.WEBHOOK - break + return PermissionTypes.WEBHOOK case DocumentTypes.QUERY: case DocumentTypes.DATASOURCE: - type = PermissionTypes.QUERY - break + return PermissionTypes.QUERY default: // views don't have an ID, will end up here - type = PermissionTypes.VIEW - break + 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) { @@ -55,10 +55,17 @@ function getBasePermissions(resourceId) { const perms = getBuiltinPermissionByID(role.permissionId) const typedPermission = perms.permissions.find(perm => perm.type === type) if (typedPermission) { - // TODO: need to get the lowest role - // TODO: store the read/write with the lowest role + 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 @@ -132,7 +139,7 @@ exports.fetchBuiltin = function(ctx) { exports.fetchLevels = function(ctx) { // for now only provide the read/write perms externally - ctx.body = [PermissionLevels.WRITE, PermissionLevels.READ] + ctx.body = SUPPORTED_LEVELS } exports.fetch = async function(ctx) { @@ -167,11 +174,12 @@ exports.getResourcePerms = async function(ctx) { ) const roles = body.rows.map(row => row.doc) const resourcePerms = {} - for (let role of roles) { + 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[roleId] = higherPermission( + resourcePerms[level] = higherPermission( resourcePerms[roleId], role.permissions[resourceId] ) diff --git a/packages/server/src/utilities/security/permissions.js b/packages/server/src/utilities/security/permissions.js index dba4b99593..6752f790e6 100644 --- a/packages/server/src/utilities/security/permissions.js +++ b/packages/server/src/utilities/security/permissions.js @@ -23,6 +23,22 @@ function Permission(type, level) { this.type = type } +function levelToNumber(perm) { + switch (perm) { + // not everything has execute privileges + case PermissionLevels.EXECUTE: + return 0 + case PermissionLevels.READ: + return 1 + case PermissionLevels.WRITE: + return 2 + case PermissionLevels.ADMIN: + return 3 + default: + return -1 + } +} + /** * Given the specified permission level for the user return the levels they are allowed to carry out. * @param {string} userPermLevel The permission level of the user. @@ -149,22 +165,11 @@ exports.doesHaveBasePermission = (permType, permLevel, permissionIds) => { } exports.higherPermission = (perm1, perm2) => { - function toNum(perm) { - switch (perm) { - // not everything has execute privileges - case PermissionLevels.EXECUTE: - return 0 - case PermissionLevels.READ: - return 1 - case PermissionLevels.WRITE: - return 2 - case PermissionLevels.ADMIN: - return 3 - default: - return -1 - } - } - return toNum(perm1) > toNum(perm2) ? perm1 : perm2 + return levelToNumber(perm1) > levelToNumber(perm2) ? perm1 : perm2 +} + +exports.isPermissionLevelHigherThanRead = level => { + return levelToNumber(level) > 1 } // utility as a lot of things need simply the builder permission diff --git a/packages/server/src/utilities/security/roles.js b/packages/server/src/utilities/security/roles.js index 447c049e1d..0cd9d0621e 100644 --- a/packages/server/src/utilities/security/roles.js +++ b/packages/server/src/utilities/security/roles.js @@ -222,6 +222,31 @@ exports.getExternalRoleID = roleId => { return roleId } +/** + * Returns whichever roleID is lower. + */ +exports.lowerRoleID = async (appId, roleId1, roleId2) => { + // TODO: need to make this function work + const MAX = Object.values(BUILTIN_IDS).length + 1 + async function toNum(id) { + if (id === BUILTIN_IDS.ADMIN || id === BUILTIN_IDS.BUILDER) { + return MAX + } + let role = await exports.getRole(appId, id), + count = 0 + do { + if (!role) { + break + } + role = exports.BUILTIN_ROLES[role.inherits] + count++ + } while (role !== null) + return count + } + const [num1, num2] = Promise.all([toNum(roleId1), toNum(roleId2)]) + return num1 > num2 ? roleId2 : roleId1 +} + exports.AccessController = AccessController exports.BUILTIN_ROLE_IDS = BUILTIN_IDS exports.isBuiltin = isBuiltin