import { auth, context, permissions, roles, users, } from "@budibase/backend-core" import { PermissionLevel, PermissionType, Role, UserCtx } from "@budibase/types" import builderMiddleware from "./builder" import { isWebhookEndpoint } from "./utils" import { paramResource } from "./resourceId" import sdk from "../sdk" function hasResource(ctx: any) { return ctx.resourceId != null } const csrf = auth.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: UserCtx, resourceRoles: any, permType: PermissionType, permLevel: PermissionLevel ) => { const appId = context.getAppId() const isGlobalBuilderApi = permType === PermissionType.GLOBAL_BUILDER const isBuilderApi = permType === PermissionType.BUILDER const globalBuilder = users.isGlobalBuilder(ctx.user) let isBuilder = appId ? users.isBuilder(ctx.user, appId) : users.hasBuilderPermissions(ctx.user) // check if this is a builder api and the user is not a builder if ((isGlobalBuilderApi && !globalBuilder) || (isBuilderApi && !isBuilder)) { return ctx.throw(403, "Not Authorized") } // check for resource authorization if (!isBuilder) { await checkAuthorizedResource(ctx, resourceRoles, permType, permLevel) } } const checkAuthorizedResource = async ( ctx: UserCtx, resourceRoles: any, permType: PermissionType, permLevel: PermissionLevel ) => { // get the user's roles const roleId = ctx.roleId || roles.BUILTIN_ROLE_IDS.PUBLIC const userRoles = await roles.getUserRoleHierarchy(roleId) 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: any) => resourceRoles.indexOf(role._id) !== -1 ) if (!found) { ctx.throw(403, permError) } // fallback to the base permissions when no resource roles are found } else if ( !permissions.doesHaveBasePermission(permType, permLevel, userRoles) ) { ctx.throw(403, permError) } } const authorized = ( permType: PermissionType, permLevel?: PermissionLevel, opts = { schema: false }, resourcePath?: string ) => async (ctx: any, next: any) => { // webhooks don't need authentication, each webhook unique // also internal requests (between services) don't need authorized if (isWebhookEndpoint(ctx) || ctx.internal) { return next() } if (!ctx.user) { return ctx.throw(403, "No user info found") } // get the resource roles let resourceRoles: string[] = [] let otherLevelRoles: string[] = [] const otherLevel = permLevel === PermissionLevel.READ ? PermissionLevel.WRITE : PermissionLevel.READ if (resourcePath) { // Reusing the existing middleware to extract the value paramResource(resourcePath)(ctx, () => {}) } if (hasResource(ctx)) { const { resourceId, subResourceId } = ctx const permissions = await sdk.permissions.getResourcePerms(resourceId) const subPermissions = !!subResourceId && (await sdk.permissions.getResourcePerms(subResourceId)) function getPermLevel(permLevel: string) { let result: string[] = [] if (permissions[permLevel]) { result.push(permissions[permLevel].role) } if (subPermissions && subPermissions[permLevel]) { result.push(subPermissions[permLevel].role) } return result } resourceRoles = getPermLevel(permLevel!) if (opts && opts.schema) { otherLevelRoles = getPermLevel(otherLevel!) } } // if the resource is public, proceed if ( resourceRoles.includes(roles.BUILTIN_ROLE_IDS.PUBLIC) || (otherLevelRoles && otherLevelRoles.includes(roles.BUILTIN_ROLE_IDS.PUBLIC)) ) { return next() } // check authenticated if (!ctx.isAuthenticated) { return ctx.throw(403, "Session not authenticated") } // check general builder stuff, this middleware is a good way // to find API endpoints which are builder focused if ( permType === PermissionType.BUILDER || permType === PermissionType.GLOBAL_BUILDER ) { await builderMiddleware(ctx) } 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) } export default ( permType: PermissionType, permLevel?: PermissionLevel, opts = { schema: false } ) => authorized(permType, permLevel, opts) export const authorizedResource = ( permType: PermissionType, permLevel: PermissionLevel, resourcePath: string ) => { return authorized(permType, permLevel, undefined, resourcePath) }