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.

This commit is contained in:
mike12345567 2023-08-22 18:14:08 +01:00
parent 466a2504e4
commit 656870db8b
6 changed files with 55 additions and 84 deletions

@ -1 +1 @@
Subproject commit a054a51726fa3f17879a469a04675778185b7ed7
Subproject commit 39d76da2124d96b1bcec0f2352ca5ef9b0c84318

View File

@ -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

View File

@ -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

View File

@ -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<User> {
export async function readGlobalUser(ctx: Ctx): Promise<User> {
const response = await fetch(
checkSlashesInUrl(
env.WORKER_URL + `/api/global/users/${ctx.params.userId}`

View File

@ -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)
}

View File

@ -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<InviteUsersRequest>) => {
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<InviteUsersRequest>) => {
}),
created: true,
}
ctx.body = createWithCredentials
} else {
ctx.throw(400, "User onboarding failed")
}