From 656870db8b9ec5314f7e62329d71b6f225b2016c Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 22 Aug 2023 18:14:08 +0100 Subject: [PATCH] Adding last of support for per app group builder support, enriching the user on self return, as well as adding the functionality required to server middlewares. --- packages/pro | 2 +- packages/server/src/api/controllers/auth.ts | 9 +- packages/server/src/utilities/global.ts | 85 ++++++++----------- .../server/src/utilities/workerRequests.ts | 31 ++----- .../worker/src/api/controllers/global/self.ts | 4 +- .../src/api/controllers/global/users.ts | 8 +- 6 files changed, 55 insertions(+), 84 deletions(-) diff --git a/packages/pro b/packages/pro index a054a51726..39d76da212 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit a054a51726fa3f17879a469a04675778185b7ed7 +Subproject commit 39d76da2124d96b1bcec0f2352ca5ef9b0c84318 diff --git a/packages/server/src/api/controllers/auth.ts b/packages/server/src/api/controllers/auth.ts index bca6c4e64e..ae98d92332 100644 --- a/packages/server/src/api/controllers/auth.ts +++ b/packages/server/src/api/controllers/auth.ts @@ -2,9 +2,9 @@ import { outputProcessing } from "../../utilities/rowProcessor" import { InternalTables } from "../../db/utils" import { getFullUser } from "../../utilities/users" import { roles, context } from "@budibase/backend-core" -import { groups } from "@budibase/pro" -import { ContextUser, User, Row, UserCtx } from "@budibase/types" +import { ContextUser, Row, UserCtx } from "@budibase/types" import sdk from "../../sdk" +import { processUser } from "src/utilities/global" const PUBLIC_ROLE = roles.BUILTIN_ROLE_IDS.PUBLIC @@ -26,7 +26,7 @@ export async function fetchSelf(ctx: UserCtx) { } const appId = context.getAppId() - const user: ContextUser = await getFullUser(ctx, userId) + let user: ContextUser = await getFullUser(ctx, userId) // this shouldn't be returned by the app self delete user.roles // forward the csrf token from the session @@ -36,8 +36,7 @@ export async function fetchSelf(ctx: UserCtx) { const db = context.getAppDB() // check for group permissions if (!user.roleId || user.roleId === PUBLIC_ROLE) { - const groupRoleId = await groups.getGroupRoleId(user as User, appId) - user.roleId = groupRoleId || user.roleId + user = await processUser(user, { appId }) } // remove the full roles structure delete user.roles diff --git a/packages/server/src/utilities/global.ts b/packages/server/src/utilities/global.ts index 5561b59233..1cb7956d5e 100644 --- a/packages/server/src/utilities/global.ts +++ b/packages/server/src/utilities/global.ts @@ -12,75 +12,64 @@ import { groups } from "@budibase/pro" import { UserCtx, ContextUser, User, UserGroup } from "@budibase/types" import cloneDeep from "lodash/cloneDeep" -export function updateAppRole( +export async function processUser( user: ContextUser, - { appId }: { appId?: string } = {} + opts: { appId?: string; groups?: UserGroup[] } = {} ) { - appId = appId || context.getAppId() - if (!user || (!user.roles && !user.userGroups)) { return user } - // if in an multi-tenancy environment make sure roles are never updated + user = cloneDeep(user) + delete user.password + const appId = opts.appId || context.getAppId() + if (!appId) { + throw new Error("Unable to process user without app ID") + } + // if in a multi-tenancy environment and in wrong tenant make sure roles are never updated if (env.MULTI_TENANCY && appId && !tenancy.isUserInAppTenant(appId, user)) { user = users.removePortalUserPermissions(user) user.roleId = roles.BUILTIN_ROLE_IDS.PUBLIC return user } - // always use the deployed app - if (appId && user.roles) { + let groupList: UserGroup[] = [] + if (appId && user?.userGroups?.length) { + groupList = opts.groups + ? opts.groups + : await groups.getBulk(user.userGroups) + } + // check if a group provides builder access + const builderAppIds = await groups.getGroupBuilderAppIds(user, appId, { + groups: groupList, + }) + if (builderAppIds.length && !users.isBuilder(user, appId)) { + const existingApps = user.builder?.apps || [] + user.builder = { + apps: [...new Set(existingApps.concat(builderAppIds))], + } + } + // builders are always admins within the app + if (users.isBuilder(user, appId)) { + user.roleId = roles.BUILTIN_ROLE_IDS.ADMIN + } + // try to get the role from the user list + if (!user.roleId && appId && user.roles) { user.roleId = user.roles[dbCore.getProdAppID(appId)] } - // if a role wasn't found then either set as admin (builder) or public (everyone else) - if (!user.roleId && users.isBuilder(user, appId)) { - user.roleId = roles.BUILTIN_ROLE_IDS.ADMIN - } else if (!user.roleId && !user?.userGroups?.length) { - user.roleId = roles.BUILTIN_ROLE_IDS.PUBLIC - } - - delete user.roles - return user -} - -async function checkGroupRoles( - user: ContextUser, - opts: { appId?: string; groups?: UserGroup[] } = {} -) { - if (user.roleId && user.roleId !== roles.BUILTIN_ROLE_IDS.PUBLIC) { - return user - } - if (opts.appId) { - user.roleId = await groups.getGroupRoleId(user as User, opts.appId, { - groups: opts.groups, + // try to get the role from the group list + if (!user.roleId && groupList) { + user.roleId = await groups.getGroupRoleId(user, appId, { + groups: groupList, }) } // final fallback, simply couldn't find a role - user must be public if (!user.roleId) { user.roleId = roles.BUILTIN_ROLE_IDS.PUBLIC } + // remove the roles as it is now set + delete user.roles return user } -export async function processUser( - user: ContextUser, - opts: { appId?: string; groups?: UserGroup[] } = {} -) { - let clonedUser = cloneDeep(user) - if (clonedUser) { - delete clonedUser.password - } - const appId = opts.appId || context.getAppId() - clonedUser = updateAppRole(clonedUser, { appId }) - if (!clonedUser.roleId && clonedUser?.userGroups?.length) { - clonedUser = await checkGroupRoles(clonedUser, { - appId, - groups: opts?.groups, - }) - } - - return clonedUser -} - export async function getCachedSelf(ctx: UserCtx, appId: string) { // this has to be tenant aware, can't depend on the context to find it out // running some middlewares before the tenancy causes context to break diff --git a/packages/server/src/utilities/workerRequests.ts b/packages/server/src/utilities/workerRequests.ts index 5230e25bf7..fa9fde7297 100644 --- a/packages/server/src/utilities/workerRequests.ts +++ b/packages/server/src/utilities/workerRequests.ts @@ -8,10 +8,9 @@ import { logging, env as coreEnv, } from "@budibase/backend-core" -import { updateAppRole } from "./global" -import { BBContext, User, EmailInvite } from "@budibase/types" +import { Ctx, User, EmailInvite } from "@budibase/types" -export function request(ctx?: BBContext, request?: any) { +export function request(ctx?: Ctx, request?: any) { if (!request.headers) { request.headers = {} } @@ -43,7 +42,7 @@ export function request(ctx?: BBContext, request?: any) { async function checkResponse( response: any, errorMsg: string, - { ctx }: { ctx?: BBContext } = {} + { ctx }: { ctx?: Ctx } = {} ) { if (response.status !== 200) { let error @@ -105,21 +104,7 @@ export async function sendSmtpEmail({ return checkResponse(response, "send email") } -export async function getGlobalSelf(ctx: BBContext, appId?: string) { - const endpoint = `/api/global/self` - const response = await fetch( - checkSlashesInUrl(env.WORKER_URL + endpoint), - // we don't want to use API key when getting self - request(ctx, { method: "GET" }) - ) - let json = await checkResponse(response, "get self globally", { ctx }) - if (appId) { - json = updateAppRole(json) - } - return json -} - -export async function removeAppFromUserRoles(ctx: BBContext, appId: string) { +export async function removeAppFromUserRoles(ctx: Ctx, appId: string) { const prodAppId = dbCore.getProdAppID(appId) const response = await fetch( checkSlashesInUrl(env.WORKER_URL + `/api/global/roles/${prodAppId}`), @@ -130,7 +115,7 @@ export async function removeAppFromUserRoles(ctx: BBContext, appId: string) { return checkResponse(response, "remove app role") } -export async function allGlobalUsers(ctx: BBContext) { +export async function allGlobalUsers(ctx: Ctx) { const response = await fetch( checkSlashesInUrl(env.WORKER_URL + "/api/global/users"), // we don't want to use API key when getting self @@ -139,7 +124,7 @@ export async function allGlobalUsers(ctx: BBContext) { return checkResponse(response, "get users", { ctx }) } -export async function saveGlobalUser(ctx: BBContext) { +export async function saveGlobalUser(ctx: Ctx) { const response = await fetch( checkSlashesInUrl(env.WORKER_URL + "/api/global/users"), // we don't want to use API key when getting self @@ -148,7 +133,7 @@ export async function saveGlobalUser(ctx: BBContext) { return checkResponse(response, "save user", { ctx }) } -export async function deleteGlobalUser(ctx: BBContext) { +export async function deleteGlobalUser(ctx: Ctx) { const response = await fetch( checkSlashesInUrl( env.WORKER_URL + `/api/global/users/${ctx.params.userId}` @@ -159,7 +144,7 @@ export async function deleteGlobalUser(ctx: BBContext) { return checkResponse(response, "delete user", { ctx }) } -export async function readGlobalUser(ctx: BBContext): Promise { +export async function readGlobalUser(ctx: Ctx): Promise { const response = await fetch( checkSlashesInUrl( env.WORKER_URL + `/api/global/users/${ctx.params.userId}` diff --git a/packages/worker/src/api/controllers/global/self.ts b/packages/worker/src/api/controllers/global/self.ts index 7167d91b23..47dbef964e 100644 --- a/packages/worker/src/api/controllers/global/self.ts +++ b/packages/worker/src/api/controllers/global/self.ts @@ -48,7 +48,7 @@ export async function generateAPIKey(ctx: any) { } catch (err) { devInfo = { _id: id, userId } } - devInfo.apiKey = await apiKey + devInfo.apiKey = apiKey await db.put(devInfo) ctx.body = cleanupDevInfo(devInfo) } @@ -63,7 +63,7 @@ export async function fetchAPIKey(ctx: any) { devInfo = { _id: id, userId: ctx.user._id, - apiKey: await newApiKey(), + apiKey: newApiKey(), } await db.put(devInfo) } diff --git a/packages/worker/src/api/controllers/global/users.ts b/packages/worker/src/api/controllers/global/users.ts index a8207ea89e..ad906c8688 100644 --- a/packages/worker/src/api/controllers/global/users.ts +++ b/packages/worker/src/api/controllers/global/users.ts @@ -25,11 +25,11 @@ import { import { accounts, cache, + ErrorCode, events, migrations, - tenancy, platform, - ErrorCode, + tenancy, } from "@budibase/backend-core" import { checkAnyUserExists } from "../../../utilities/users" import { isEmailConfigured } from "../../../utilities/email" @@ -280,7 +280,7 @@ export const onboardUsers = async (ctx: Ctx) => { let bulkCreateReponse = await userSdk.db.bulkCreate(users, []) // Apply temporary credentials - let createWithCredentials = { + ctx.body = { ...bulkCreateReponse, successful: bulkCreateReponse?.successful.map(user => { return { @@ -290,8 +290,6 @@ export const onboardUsers = async (ctx: Ctx) => { }), created: true, } - - ctx.body = createWithCredentials } else { ctx.throw(400, "User onboarding failed") }