diff --git a/packages/server/src/middleware/authorized.js b/packages/server/src/middleware/authorized.js index ad2c4344fa..b838ef4e29 100644 --- a/packages/server/src/middleware/authorized.js +++ b/packages/server/src/middleware/authorized.js @@ -1,10 +1,11 @@ const { BUILTIN_ROLE_IDS, - getUserPermissionIds, + getUserPermissions, } = require("../utilities/security/roles") const { PermissionTypes, - doesHavePermission, + doesHaveResourcePermission, + doesHaveBasePermission, } = require("../utilities/security/permissions") const env = require("../environment") const { isAPIKeyValid } = require("../utilities/security/apikey") @@ -14,6 +15,10 @@ const ADMIN_ROLES = [BUILTIN_ROLE_IDS.ADMIN, BUILTIN_ROLE_IDS.BUILDER] const LOCAL_PASS = new RegExp(["webhooks/trigger", "webhooks/schema"].join("|")) +function hasResource(ctx) { + return ctx.resourceId != null +} + module.exports = (permType, permLevel = null) => async (ctx, next) => { // webhooks can pass locally if (!env.CLOUD && LOCAL_PASS.test(ctx.request.url)) { @@ -47,7 +52,10 @@ module.exports = (permType, permLevel = null) => async (ctx, next) => { } const role = ctx.user.role - const permissions = await getUserPermissionIds(ctx.appId, role._id) + const { basePermissions, permissions } = await getUserPermissions( + ctx.appId, + role._id + ) if (ADMIN_ROLES.indexOf(role._id) !== -1) { return next() } @@ -56,7 +64,14 @@ module.exports = (permType, permLevel = null) => async (ctx, next) => { ctx.throw(403, "Not Authorized") } - if (!doesHavePermission(permType, permLevel, permissions)) { + if ( + hasResource(ctx) && + doesHaveResourcePermission(permissions, permLevel, ctx) + ) { + return next() + } + + if (!doesHaveBasePermission(permType, permLevel, basePermissions)) { ctx.throw(403, "User does not have permission") } diff --git a/packages/server/src/utilities/security/permissions.js b/packages/server/src/utilities/security/permissions.js index 8e3dc4e831..c0bc26cb8f 100644 --- a/packages/server/src/utilities/security/permissions.js +++ b/packages/server/src/utilities/security/permissions.js @@ -97,7 +97,35 @@ exports.BUILTIN_PERMISSIONS = { }, } -exports.doesHavePermission = (permType, permLevel, permissionIds) => { +exports.doesHaveResourcePermission = ( + permissions, + permLevel, + { resourceId, subResourceId } +) => { + // set foundSub to not subResourceId, incase there is no subResource + let foundMain = false, + foundSub = !subResourceId + for (let [resource, level] of Object.entries(permissions)) { + const levels = getAllowedLevels(level) + if (resource === resourceId && levels.indexOf(permLevel) !== -1) { + foundMain = true + } + if ( + subResourceId && + resource === subResourceId && + levels.indexOf(permLevel) !== -1 + ) { + foundSub = true + } + // this will escape if foundMain only when no sub resource + if (foundMain && foundSub) { + break + } + } + return foundMain && foundSub +} + +exports.doesHaveBasePermission = (permType, permLevel, permissionIds) => { const builtins = Object.values(exports.BUILTIN_PERMISSIONS) let permissions = flatten( builtins diff --git a/packages/server/src/utilities/security/roles.js b/packages/server/src/utilities/security/roles.js index 2379e090fd..c5cf28783d 100644 --- a/packages/server/src/utilities/security/roles.js +++ b/packages/server/src/utilities/security/roles.js @@ -1,6 +1,10 @@ const CouchDB = require("../../db") const { cloneDeep } = require("lodash/fp") -const { BUILTIN_PERMISSION_IDS } = require("./permissions") +const { + BUILTIN_PERMISSION_IDS, + higherPermission, + doesHaveBasePermission, +} = require("./permissions") const BUILTIN_IDS = { ADMIN: "ADMIN", @@ -127,14 +131,26 @@ exports.getUserRoleHierarchy = async (appId, userRoleId) => { * Get all of the user permissions which could be found across the role hierarchy * @param appId The ID of the application from which roles should be obtained. * @param userRoleId The user's role ID, this can be found in their access token. - * @returns {Promise} A list of permission IDs these should all be unique. + * @returns {Promise<{basePermissions: string[], permissions: Object}>} the base + * permission IDs as well as any custom resource permissions. */ -exports.getUserPermissionIds = async (appId, userRoleId) => { - return [ - ...new Set( - (await getAllUserRoles(appId, userRoleId)).map(role => role.permissionId) - ), +exports.getUserPermissions = async (appId, userRoleId) => { + const rolesHierarchy = await getAllUserRoles(appId, userRoleId) + const basePermissions = [ + ...new Set(rolesHierarchy.map(role => role.permissionId)), ] + const permissions = {} + for (let role of rolesHierarchy) { + if (role.permissions) { + for (let [resource, level] of Object.entries(role.permissions)) { + permissions[resource] = higherPermission(permissions[resource], level) + } + } + } + return { + basePermissions, + permissions, + } } class AccessController {