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)