From 88437e11d0973dab600ea625c53dd8ee455e219b Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 28 Mar 2022 16:34:50 +0100 Subject: [PATCH] Fix for #5103 - some templates are built on an older version that stored permissions differently, we can't migrate these as they will keep being added, easiest to just support the old method (apply the old rule and convert to the new format when retrieving roles). --- packages/backend-core/src/security/roles.js | 38 ++++++++++++++++--- .../server/src/api/controllers/permission.js | 6 +-- packages/server/src/api/routes/table.js | 2 +- packages/server/src/middleware/authorized.js | 32 +++++++++++++--- 4 files changed, 62 insertions(+), 16 deletions(-) diff --git a/packages/backend-core/src/security/roles.js b/packages/backend-core/src/security/roles.js index 11abc70bdd..8535cdc716 100644 --- a/packages/backend-core/src/security/roles.js +++ b/packages/backend-core/src/security/roles.js @@ -1,5 +1,5 @@ const { cloneDeep } = require("lodash/fp") -const { BUILTIN_PERMISSION_IDS } = require("./permissions") +const { BUILTIN_PERMISSION_IDS, PermissionLevels } = require("./permissions") const { generateRoleID, getRoleParams, @@ -180,6 +180,20 @@ exports.getUserRoleHierarchy = async (userRoleId, opts = { idOnly: true }) => { return opts.idOnly ? roles.map(role => role._id) : roles } +// this function checks that the provided permissions are in an array format +// some templates/older apps will use a simple string instead of array for roles +// convert the string to an array using the theory that write is higher than read +exports.checkForRoleResourceArray = (rolePerms, resourceId) => { + if (rolePerms && !Array.isArray(rolePerms[resourceId])) { + const permLevel = rolePerms[resourceId] + rolePerms[resourceId] = [permLevel] + if (permLevel === PermissionLevels.WRITE) { + rolePerms[resourceId].push(PermissionLevels.READ) + } + } + return rolePerms +} + /** * Given an app ID this will retrieve all of the roles that are currently within that app. * @return {Promise} An array of the role objects that were found. @@ -209,15 +223,27 @@ exports.getAllRoles = async appId => { roles.push(Object.assign(builtinRole, dbBuiltin)) } } + // check permissions + for (let role of roles) { + if (!role.permissions) { + continue + } + for (let resourceId of Object.keys(role.permissions)) { + role.permissions = exports.checkForRoleResourceArray( + role.permissions, + resourceId + ) + } + } return roles } /** - * This retrieves the required role - * @param permLevel - * @param resourceId - * @param subResourceId - * @return {Promise<{permissions}|Object>} + * This retrieves the required role for a resource + * @param permLevel The level of request + * @param resourceId The resource being requested + * @param subResourceId The sub resource being requested + * @return {Promise<{permissions}|Object>} returns the permissions required to access. */ exports.getRequiredResourceRole = async ( permLevel, diff --git a/packages/server/src/api/controllers/permission.js b/packages/server/src/api/controllers/permission.js index 0e37a3e7d3..e1547eb597 100644 --- a/packages/server/src/api/controllers/permission.js +++ b/packages/server/src/api/controllers/permission.js @@ -4,6 +4,7 @@ const { getDBRoleID, getExternalRoleID, getBuiltinRoles, + checkForRoleResourceArray, } = require("@budibase/backend-core/roles") const { getRoleParams } = require("../../db/utils") const { @@ -144,12 +145,11 @@ exports.getResourcePerms = async function (ctx) { for (let level of SUPPORTED_LEVELS) { // update the various roleIds in the resource permissions for (let role of roles) { - const rolePerms = role.permissions + const rolePerms = checkForRoleResourceArray(role.permissions, resourceId) if ( rolePerms && rolePerms[resourceId] && - (rolePerms[resourceId] === level || - rolePerms[resourceId].indexOf(level) !== -1) + rolePerms[resourceId].indexOf(level) !== -1 ) { permissions[level] = getExternalRoleID(role._id) } diff --git a/packages/server/src/api/routes/table.js b/packages/server/src/api/routes/table.js index 4d20b98962..5d2378710d 100644 --- a/packages/server/src/api/routes/table.js +++ b/packages/server/src/api/routes/table.js @@ -40,7 +40,7 @@ router .get( "/api/tables/:tableId", paramResource("tableId"), - authorized(PermissionTypes.TABLE, PermissionLevels.READ), + authorized(PermissionTypes.TABLE, PermissionLevels.READ, { schema: true }), tableController.find ) /** diff --git a/packages/server/src/middleware/authorized.js b/packages/server/src/middleware/authorized.js index c8d6497ca3..d6f904290a 100644 --- a/packages/server/src/middleware/authorized.js +++ b/packages/server/src/middleware/authorized.js @@ -5,6 +5,7 @@ const { } = require("@budibase/backend-core/roles") const { PermissionTypes, + PermissionLevels, doesHaveBasePermission, } = require("@budibase/backend-core/permissions") const builderMiddleware = require("./builder") @@ -64,7 +65,7 @@ const checkAuthorizedResource = async ( } module.exports = - (permType, permLevel = null) => + (permType, permLevel = null, opts = { schema: false }) => async (ctx, next) => { // webhooks don't need authentication, each webhook unique // also internal requests (between services) don't need authorized @@ -81,15 +82,25 @@ module.exports = await builderMiddleware(ctx, permType) // get the resource roles - let resourceRoles = [] + let resourceRoles = [], + otherLevelRoles + const otherLevel = + permLevel === PermissionLevels.READ + ? PermissionLevels.WRITE + : PermissionLevels.READ const appId = getAppId() if (appId && hasResource(ctx)) { resourceRoles = await getRequiredResourceRole(permLevel, ctx) + if (opts && opts.schema) { + otherLevelRoles = await getRequiredResourceRole(otherLevel, ctx) + } } // if the resource is public, proceed - const isPublicResource = resourceRoles.includes(BUILTIN_ROLE_IDS.PUBLIC) - if (isPublicResource) { + if ( + resourceRoles.includes(BUILTIN_ROLE_IDS.PUBLIC) || + (otherLevelRoles && otherLevelRoles.includes(BUILTIN_ROLE_IDS.PUBLIC)) + ) { return next() } @@ -98,8 +109,17 @@ module.exports = return ctx.throw(403, "Session not authenticated") } - // check authorized - await checkAuthorized(ctx, resourceRoles, permType, permLevel) + try { + // check authorized + await checkAuthorized(ctx, resourceRoles, permType, permLevel) + } catch (err) { + // this is a schema, check if + if (opts && opts.schema && permLevel) { + await checkAuthorized(ctx, otherLevelRoles, permType, otherLevel) + } else { + throw err + } + } // csrf protection return csrf(ctx, next)