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).

This commit is contained in:
mike12345567 2022-03-28 16:34:50 +01:00
parent 7cb2cd2139
commit e073bdb5a4
4 changed files with 62 additions and 16 deletions

View File

@ -1,5 +1,5 @@
const { cloneDeep } = require("lodash/fp") const { cloneDeep } = require("lodash/fp")
const { BUILTIN_PERMISSION_IDS } = require("./permissions") const { BUILTIN_PERMISSION_IDS, PermissionLevels } = require("./permissions")
const { const {
generateRoleID, generateRoleID,
getRoleParams, getRoleParams,
@ -180,6 +180,20 @@ exports.getUserRoleHierarchy = async (userRoleId, opts = { idOnly: true }) => {
return opts.idOnly ? roles.map(role => role._id) : roles 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. * Given an app ID this will retrieve all of the roles that are currently within that app.
* @return {Promise<object[]>} An array of the role objects that were found. * @return {Promise<object[]>} An array of the role objects that were found.
@ -209,15 +223,27 @@ exports.getAllRoles = async appId => {
roles.push(Object.assign(builtinRole, dbBuiltin)) 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 return roles
} }
/** /**
* This retrieves the required role * This retrieves the required role for a resource
* @param permLevel * @param permLevel The level of request
* @param resourceId * @param resourceId The resource being requested
* @param subResourceId * @param subResourceId The sub resource being requested
* @return {Promise<{permissions}|Object>} * @return {Promise<{permissions}|Object>} returns the permissions required to access.
*/ */
exports.getRequiredResourceRole = async ( exports.getRequiredResourceRole = async (
permLevel, permLevel,

View File

@ -4,6 +4,7 @@ const {
getDBRoleID, getDBRoleID,
getExternalRoleID, getExternalRoleID,
getBuiltinRoles, getBuiltinRoles,
checkForRoleResourceArray,
} = require("@budibase/backend-core/roles") } = require("@budibase/backend-core/roles")
const { getRoleParams } = require("../../db/utils") const { getRoleParams } = require("../../db/utils")
const { const {
@ -144,12 +145,11 @@ exports.getResourcePerms = async function (ctx) {
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) {
const rolePerms = role.permissions const rolePerms = checkForRoleResourceArray(role.permissions, resourceId)
if ( if (
rolePerms && rolePerms &&
rolePerms[resourceId] && rolePerms[resourceId] &&
(rolePerms[resourceId] === level || rolePerms[resourceId].indexOf(level) !== -1
rolePerms[resourceId].indexOf(level) !== -1)
) { ) {
permissions[level] = getExternalRoleID(role._id) permissions[level] = getExternalRoleID(role._id)
} }

View File

@ -40,7 +40,7 @@ router
.get( .get(
"/api/tables/:tableId", "/api/tables/:tableId",
paramResource("tableId"), paramResource("tableId"),
authorized(PermissionTypes.TABLE, PermissionLevels.READ), authorized(PermissionTypes.TABLE, PermissionLevels.READ, { schema: true }),
tableController.find tableController.find
) )
/** /**

View File

@ -5,6 +5,7 @@ const {
} = require("@budibase/backend-core/roles") } = require("@budibase/backend-core/roles")
const { const {
PermissionTypes, PermissionTypes,
PermissionLevels,
doesHaveBasePermission, doesHaveBasePermission,
} = require("@budibase/backend-core/permissions") } = require("@budibase/backend-core/permissions")
const builderMiddleware = require("./builder") const builderMiddleware = require("./builder")
@ -64,7 +65,7 @@ const checkAuthorizedResource = async (
} }
module.exports = module.exports =
(permType, permLevel = null) => (permType, permLevel = null, opts = { schema: false }) =>
async (ctx, next) => { async (ctx, next) => {
// webhooks don't need authentication, each webhook unique // webhooks don't need authentication, each webhook unique
// also internal requests (between services) don't need authorized // also internal requests (between services) don't need authorized
@ -81,15 +82,25 @@ module.exports =
await builderMiddleware(ctx, permType) await builderMiddleware(ctx, permType)
// get the resource roles // get the resource roles
let resourceRoles = [] let resourceRoles = [],
otherLevelRoles
const otherLevel =
permLevel === PermissionLevels.READ
? PermissionLevels.WRITE
: PermissionLevels.READ
const appId = getAppId() const appId = getAppId()
if (appId && hasResource(ctx)) { if (appId && hasResource(ctx)) {
resourceRoles = await getRequiredResourceRole(permLevel, ctx) resourceRoles = await getRequiredResourceRole(permLevel, ctx)
if (opts && opts.schema) {
otherLevelRoles = await getRequiredResourceRole(otherLevel, ctx)
}
} }
// if the resource is public, proceed // if the resource is public, proceed
const isPublicResource = resourceRoles.includes(BUILTIN_ROLE_IDS.PUBLIC) if (
if (isPublicResource) { resourceRoles.includes(BUILTIN_ROLE_IDS.PUBLIC) ||
(otherLevelRoles && otherLevelRoles.includes(BUILTIN_ROLE_IDS.PUBLIC))
) {
return next() return next()
} }
@ -98,8 +109,17 @@ module.exports =
return ctx.throw(403, "Session not authenticated") return ctx.throw(403, "Session not authenticated")
} }
// check authorized try {
await checkAuthorized(ctx, resourceRoles, permType, permLevel) // 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 // csrf protection
return csrf(ctx, next) return csrf(ctx, next)