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 { InternalTables } from "../../db/utils"
import { getFullUser } from "../../utilities/users" import { getFullUser } from "../../utilities/users"
import { roles, context } from "@budibase/backend-core" import { roles, context } from "@budibase/backend-core"
import { groups } from "@budibase/pro" import { ContextUser, Row, UserCtx } from "@budibase/types"
import { ContextUser, User, Row, UserCtx } from "@budibase/types"
import sdk from "../../sdk" import sdk from "../../sdk"
import { processUser } from "src/utilities/global"
const PUBLIC_ROLE = roles.BUILTIN_ROLE_IDS.PUBLIC const PUBLIC_ROLE = roles.BUILTIN_ROLE_IDS.PUBLIC
@ -26,7 +26,7 @@ export async function fetchSelf(ctx: UserCtx) {
} }
const appId = context.getAppId() 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 // this shouldn't be returned by the app self
delete user.roles delete user.roles
// forward the csrf token from the session // forward the csrf token from the session
@ -36,8 +36,7 @@ export async function fetchSelf(ctx: UserCtx) {
const db = context.getAppDB() const db = context.getAppDB()
// check for group permissions // check for group permissions
if (!user.roleId || user.roleId === PUBLIC_ROLE) { if (!user.roleId || user.roleId === PUBLIC_ROLE) {
const groupRoleId = await groups.getGroupRoleId(user as User, appId) user = await processUser(user, { appId })
user.roleId = groupRoleId || user.roleId
} }
// remove the full roles structure // remove the full roles structure
delete user.roles delete user.roles

View File

@ -12,75 +12,64 @@ import { groups } from "@budibase/pro"
import { UserCtx, ContextUser, User, UserGroup } from "@budibase/types" import { UserCtx, ContextUser, User, UserGroup } from "@budibase/types"
import cloneDeep from "lodash/cloneDeep" import cloneDeep from "lodash/cloneDeep"
export function updateAppRole( export async function processUser(
user: ContextUser, user: ContextUser,
{ appId }: { appId?: string } = {} opts: { appId?: string; groups?: UserGroup[] } = {}
) { ) {
appId = appId || context.getAppId()
if (!user || (!user.roles && !user.userGroups)) { if (!user || (!user.roles && !user.userGroups)) {
return user 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)) { if (env.MULTI_TENANCY && appId && !tenancy.isUserInAppTenant(appId, user)) {
user = users.removePortalUserPermissions(user) user = users.removePortalUserPermissions(user)
user.roleId = roles.BUILTIN_ROLE_IDS.PUBLIC user.roleId = roles.BUILTIN_ROLE_IDS.PUBLIC
return user return user
} }
// always use the deployed app let groupList: UserGroup[] = []
if (appId && user.roles) { 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)] user.roleId = user.roles[dbCore.getProdAppID(appId)]
} }
// if a role wasn't found then either set as admin (builder) or public (everyone else) // try to get the role from the group list
if (!user.roleId && users.isBuilder(user, appId)) { if (!user.roleId && groupList) {
user.roleId = roles.BUILTIN_ROLE_IDS.ADMIN user.roleId = await groups.getGroupRoleId(user, appId, {
} else if (!user.roleId && !user?.userGroups?.length) { groups: groupList,
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,
}) })
} }
// final fallback, simply couldn't find a role - user must be public // final fallback, simply couldn't find a role - user must be public
if (!user.roleId) { if (!user.roleId) {
user.roleId = roles.BUILTIN_ROLE_IDS.PUBLIC user.roleId = roles.BUILTIN_ROLE_IDS.PUBLIC
} }
// remove the roles as it is now set
delete user.roles
return user 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) { export async function getCachedSelf(ctx: UserCtx, appId: string) {
// this has to be tenant aware, can't depend on the context to find it out // 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 // running some middlewares before the tenancy causes context to break

View File

@ -8,10 +8,9 @@ import {
logging, logging,
env as coreEnv, env as coreEnv,
} from "@budibase/backend-core" } from "@budibase/backend-core"
import { updateAppRole } from "./global" import { Ctx, User, EmailInvite } from "@budibase/types"
import { BBContext, User, EmailInvite } from "@budibase/types"
export function request(ctx?: BBContext, request?: any) { export function request(ctx?: Ctx, request?: any) {
if (!request.headers) { if (!request.headers) {
request.headers = {} request.headers = {}
} }
@ -43,7 +42,7 @@ export function request(ctx?: BBContext, request?: any) {
async function checkResponse( async function checkResponse(
response: any, response: any,
errorMsg: string, errorMsg: string,
{ ctx }: { ctx?: BBContext } = {} { ctx }: { ctx?: Ctx } = {}
) { ) {
if (response.status !== 200) { if (response.status !== 200) {
let error let error
@ -105,21 +104,7 @@ export async function sendSmtpEmail({
return checkResponse(response, "send email") return checkResponse(response, "send email")
} }
export async function getGlobalSelf(ctx: BBContext, appId?: string) { export async function removeAppFromUserRoles(ctx: Ctx, 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) {
const prodAppId = dbCore.getProdAppID(appId) const prodAppId = dbCore.getProdAppID(appId)
const response = await fetch( const response = await fetch(
checkSlashesInUrl(env.WORKER_URL + `/api/global/roles/${prodAppId}`), 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") return checkResponse(response, "remove app role")
} }
export async function allGlobalUsers(ctx: BBContext) { export async function allGlobalUsers(ctx: Ctx) {
const response = await fetch( const response = await fetch(
checkSlashesInUrl(env.WORKER_URL + "/api/global/users"), checkSlashesInUrl(env.WORKER_URL + "/api/global/users"),
// we don't want to use API key when getting self // 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 }) return checkResponse(response, "get users", { ctx })
} }
export async function saveGlobalUser(ctx: BBContext) { export async function saveGlobalUser(ctx: Ctx) {
const response = await fetch( const response = await fetch(
checkSlashesInUrl(env.WORKER_URL + "/api/global/users"), checkSlashesInUrl(env.WORKER_URL + "/api/global/users"),
// we don't want to use API key when getting self // 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 }) return checkResponse(response, "save user", { ctx })
} }
export async function deleteGlobalUser(ctx: BBContext) { export async function deleteGlobalUser(ctx: Ctx) {
const response = await fetch( const response = await fetch(
checkSlashesInUrl( checkSlashesInUrl(
env.WORKER_URL + `/api/global/users/${ctx.params.userId}` 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 }) 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( const response = await fetch(
checkSlashesInUrl( checkSlashesInUrl(
env.WORKER_URL + `/api/global/users/${ctx.params.userId}` env.WORKER_URL + `/api/global/users/${ctx.params.userId}`

View File

@ -48,7 +48,7 @@ export async function generateAPIKey(ctx: any) {
} catch (err) { } catch (err) {
devInfo = { _id: id, userId } devInfo = { _id: id, userId }
} }
devInfo.apiKey = await apiKey devInfo.apiKey = apiKey
await db.put(devInfo) await db.put(devInfo)
ctx.body = cleanupDevInfo(devInfo) ctx.body = cleanupDevInfo(devInfo)
} }
@ -63,7 +63,7 @@ export async function fetchAPIKey(ctx: any) {
devInfo = { devInfo = {
_id: id, _id: id,
userId: ctx.user._id, userId: ctx.user._id,
apiKey: await newApiKey(), apiKey: newApiKey(),
} }
await db.put(devInfo) await db.put(devInfo)
} }

View File

@ -25,11 +25,11 @@ import {
import { import {
accounts, accounts,
cache, cache,
ErrorCode,
events, events,
migrations, migrations,
tenancy,
platform, platform,
ErrorCode, tenancy,
} from "@budibase/backend-core" } from "@budibase/backend-core"
import { checkAnyUserExists } from "../../../utilities/users" import { checkAnyUserExists } from "../../../utilities/users"
import { isEmailConfigured } from "../../../utilities/email" import { isEmailConfigured } from "../../../utilities/email"
@ -280,7 +280,7 @@ export const onboardUsers = async (ctx: Ctx<InviteUsersRequest>) => {
let bulkCreateReponse = await userSdk.db.bulkCreate(users, []) let bulkCreateReponse = await userSdk.db.bulkCreate(users, [])
// Apply temporary credentials // Apply temporary credentials
let createWithCredentials = { ctx.body = {
...bulkCreateReponse, ...bulkCreateReponse,
successful: bulkCreateReponse?.successful.map(user => { successful: bulkCreateReponse?.successful.map(user => {
return { return {
@ -290,8 +290,6 @@ export const onboardUsers = async (ctx: Ctx<InviteUsersRequest>) => {
}), }),
created: true, created: true,
} }
ctx.body = createWithCredentials
} else { } else {
ctx.throw(400, "User onboarding failed") ctx.throw(400, "User onboarding failed")
} }