2021-11-15 14:48:26 +01:00
|
|
|
const {
|
|
|
|
getUserRoleHierarchy,
|
|
|
|
getRequiredResourceRole,
|
|
|
|
BUILTIN_ROLE_IDS,
|
2022-01-10 20:33:00 +01:00
|
|
|
} = require("@budibase/backend-core/roles")
|
2020-11-12 18:06:55 +01:00
|
|
|
const {
|
|
|
|
PermissionTypes,
|
2022-03-28 17:34:50 +02:00
|
|
|
PermissionLevels,
|
2021-02-08 18:52:22 +01:00
|
|
|
doesHaveBasePermission,
|
2022-01-10 20:33:00 +01:00
|
|
|
} = require("@budibase/backend-core/permissions")
|
2021-05-21 14:07:10 +02:00
|
|
|
const builderMiddleware = require("./builder")
|
2021-11-03 15:08:47 +01:00
|
|
|
const { isWebhookEndpoint } = require("./utils")
|
2022-01-25 23:54:50 +01:00
|
|
|
const { buildCsrfMiddleware } = require("@budibase/backend-core/auth")
|
2022-01-31 15:09:07 +01:00
|
|
|
const { getAppId } = require("@budibase/backend-core/context")
|
2020-11-11 18:34:15 +01:00
|
|
|
|
2021-02-08 18:52:22 +01:00
|
|
|
function hasResource(ctx) {
|
|
|
|
return ctx.resourceId != null
|
|
|
|
}
|
|
|
|
|
2022-01-25 23:54:50 +01:00
|
|
|
const csrf = buildCsrfMiddleware()
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Apply authorization to the requested resource:
|
|
|
|
* - If this is a builder resource the user must be a builder.
|
|
|
|
* - Builders can access all resources.
|
|
|
|
* - Otherwise the user must have the required role.
|
|
|
|
*/
|
|
|
|
const checkAuthorized = async (ctx, resourceRoles, permType, permLevel) => {
|
|
|
|
// check if this is a builder api and the user is not a builder
|
|
|
|
const isBuilder = ctx.user && ctx.user.builder && ctx.user.builder.global
|
|
|
|
const isBuilderApi = permType === PermissionTypes.BUILDER
|
|
|
|
if (isBuilderApi && !isBuilder) {
|
|
|
|
return ctx.throw(403, "Not Authorized")
|
|
|
|
}
|
|
|
|
|
|
|
|
// check for resource authorization
|
|
|
|
if (!isBuilder) {
|
|
|
|
await checkAuthorizedResource(ctx, resourceRoles, permType, permLevel)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const checkAuthorizedResource = async (
|
|
|
|
ctx,
|
|
|
|
resourceRoles,
|
|
|
|
permType,
|
|
|
|
permLevel
|
|
|
|
) => {
|
|
|
|
// get the user's roles
|
|
|
|
const roleId = ctx.roleId || BUILTIN_ROLE_IDS.PUBLIC
|
2022-01-31 15:09:07 +01:00
|
|
|
const userRoles = await getUserRoleHierarchy(roleId, {
|
2022-01-25 23:54:50 +01:00
|
|
|
idOnly: false,
|
|
|
|
})
|
|
|
|
const permError = "User does not have permission"
|
|
|
|
// check if the user has the required role
|
|
|
|
if (resourceRoles.length > 0) {
|
|
|
|
// deny access if the user doesn't have the required resource role
|
|
|
|
const found = userRoles.find(role => resourceRoles.indexOf(role._id) !== -1)
|
|
|
|
if (!found) {
|
|
|
|
ctx.throw(403, permError)
|
|
|
|
}
|
|
|
|
// fallback to the base permissions when no resource roles are found
|
|
|
|
} else if (!doesHaveBasePermission(permType, permLevel, userRoles)) {
|
|
|
|
ctx.throw(403, permError)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-15 20:39:40 +02:00
|
|
|
module.exports =
|
2022-03-28 17:34:50 +02:00
|
|
|
(permType, permLevel = null, opts = { schema: false }) =>
|
2021-06-15 20:39:40 +02:00
|
|
|
async (ctx, next) => {
|
|
|
|
// webhooks don't need authentication, each webhook unique
|
2021-11-04 15:53:03 +01:00
|
|
|
// also internal requests (between services) don't need authorized
|
2021-11-08 18:28:32 +01:00
|
|
|
if (isWebhookEndpoint(ctx) || ctx.internal) {
|
2021-06-15 20:39:40 +02:00
|
|
|
return next()
|
|
|
|
}
|
2020-10-12 12:57:37 +02:00
|
|
|
|
2021-06-15 20:39:40 +02:00
|
|
|
if (!ctx.user) {
|
|
|
|
return ctx.throw(403, "No user info found")
|
|
|
|
}
|
2020-06-18 17:59:31 +02:00
|
|
|
|
2021-06-15 20:39:40 +02:00
|
|
|
// check general builder stuff, this middleware is a good way
|
|
|
|
// to find API endpoints which are builder focused
|
|
|
|
await builderMiddleware(ctx, permType)
|
2020-05-27 18:23:01 +02:00
|
|
|
|
2022-01-25 23:54:50 +01:00
|
|
|
// get the resource roles
|
2022-03-28 17:34:50 +02:00
|
|
|
let resourceRoles = [],
|
|
|
|
otherLevelRoles
|
|
|
|
const otherLevel =
|
|
|
|
permLevel === PermissionLevels.READ
|
|
|
|
? PermissionLevels.WRITE
|
|
|
|
: PermissionLevels.READ
|
2022-01-31 15:09:07 +01:00
|
|
|
const appId = getAppId()
|
|
|
|
if (appId && hasResource(ctx)) {
|
|
|
|
resourceRoles = await getRequiredResourceRole(permLevel, ctx)
|
2022-03-28 17:34:50 +02:00
|
|
|
if (opts && opts.schema) {
|
|
|
|
otherLevelRoles = await getRequiredResourceRole(otherLevel, ctx)
|
|
|
|
}
|
2021-06-15 20:39:40 +02:00
|
|
|
}
|
2021-02-09 18:24:36 +01:00
|
|
|
|
2022-01-25 23:54:50 +01:00
|
|
|
// if the resource is public, proceed
|
2022-03-28 17:34:50 +02:00
|
|
|
if (
|
|
|
|
resourceRoles.includes(BUILTIN_ROLE_IDS.PUBLIC) ||
|
|
|
|
(otherLevelRoles && otherLevelRoles.includes(BUILTIN_ROLE_IDS.PUBLIC))
|
|
|
|
) {
|
2022-01-25 23:54:50 +01:00
|
|
|
return next()
|
2021-06-15 20:39:40 +02:00
|
|
|
}
|
2020-05-27 18:23:01 +02:00
|
|
|
|
2022-01-25 23:54:50 +01:00
|
|
|
// check authenticated
|
|
|
|
if (!ctx.isAuthenticated) {
|
|
|
|
return ctx.throw(403, "Session not authenticated")
|
2021-06-15 20:39:40 +02:00
|
|
|
}
|
|
|
|
|
2022-03-28 17:34:50 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2022-01-25 23:54:50 +01:00
|
|
|
|
|
|
|
// csrf protection
|
|
|
|
return csrf(ctx, next)
|
2021-06-15 20:39:40 +02:00
|
|
|
}
|