2022-11-15 18:35:17 +01:00
|
|
|
import { getMultiIDParams, getGlobalIDFromUserMetadataID } from "../db/utils"
|
|
|
|
import {
|
|
|
|
roles,
|
|
|
|
db as dbCore,
|
|
|
|
cache,
|
|
|
|
tenancy,
|
|
|
|
context,
|
2023-07-18 17:57:48 +02:00
|
|
|
users,
|
2022-11-15 18:35:17 +01:00
|
|
|
} from "@budibase/backend-core"
|
|
|
|
import env from "../environment"
|
|
|
|
import { groups } from "@budibase/pro"
|
2023-03-21 14:55:28 +01:00
|
|
|
import { UserCtx, ContextUser, User, UserGroup } from "@budibase/types"
|
2023-07-27 21:36:32 +02:00
|
|
|
import cloneDeep from "lodash/cloneDeep"
|
2022-11-15 18:35:17 +01:00
|
|
|
|
|
|
|
export function updateAppRole(
|
|
|
|
user: ContextUser,
|
|
|
|
{ appId }: { appId?: string } = {}
|
|
|
|
) {
|
|
|
|
appId = appId || context.getAppId()
|
|
|
|
|
2023-04-12 20:59:05 +02:00
|
|
|
if (!user || (!user.roles && !user.userGroups)) {
|
2022-11-15 18:35:17 +01:00
|
|
|
return user
|
|
|
|
}
|
|
|
|
// if in an multi-tenancy environment make sure roles are never updated
|
|
|
|
if (env.MULTI_TENANCY && appId && !tenancy.isUserInAppTenant(appId, user)) {
|
2023-07-18 17:57:48 +02:00
|
|
|
user = users.removePortalUserPermissions(user)
|
2022-11-15 18:35:17 +01:00
|
|
|
user.roleId = roles.BUILTIN_ROLE_IDS.PUBLIC
|
|
|
|
return user
|
|
|
|
}
|
|
|
|
// always use the deployed app
|
2023-04-12 20:59:05 +02:00
|
|
|
if (appId && user.roles) {
|
2022-11-15 18:35:17 +01:00
|
|
|
user.roleId = user.roles[dbCore.getProdAppID(appId)]
|
|
|
|
}
|
|
|
|
// if a role wasn't found then either set as admin (builder) or public (everyone else)
|
2023-07-18 17:57:48 +02:00
|
|
|
if (!user.roleId && users.isBuilder(user, appId)) {
|
2022-11-15 18:35:17 +01:00
|
|
|
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,
|
2023-03-21 14:55:28 +01:00
|
|
|
opts: { appId?: string; groups?: UserGroup[] } = {}
|
2022-11-15 18:35:17 +01:00
|
|
|
) {
|
|
|
|
if (user.roleId && user.roleId !== roles.BUILTIN_ROLE_IDS.PUBLIC) {
|
|
|
|
return user
|
|
|
|
}
|
2023-03-21 14:55:28 +01:00
|
|
|
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
|
|
|
|
if (!user.roleId) {
|
|
|
|
user.roleId = roles.BUILTIN_ROLE_IDS.PUBLIC
|
2022-11-15 18:35:17 +01:00
|
|
|
}
|
|
|
|
return user
|
|
|
|
}
|
|
|
|
|
2023-04-12 20:59:05 +02:00
|
|
|
export async function processUser(
|
2022-11-15 18:35:17 +01:00
|
|
|
user: ContextUser,
|
2023-03-21 14:55:28 +01:00
|
|
|
opts: { appId?: string; groups?: UserGroup[] } = {}
|
2022-11-15 18:35:17 +01:00
|
|
|
) {
|
2023-06-20 18:07:34 +02:00
|
|
|
let clonedUser = cloneDeep(user)
|
|
|
|
if (clonedUser) {
|
|
|
|
delete clonedUser.password
|
2022-11-15 18:35:17 +01:00
|
|
|
}
|
2023-03-21 14:55:28 +01:00
|
|
|
const appId = opts.appId || context.getAppId()
|
2023-06-20 18:07:34 +02:00
|
|
|
clonedUser = updateAppRole(clonedUser, { appId })
|
|
|
|
if (!clonedUser.roleId && clonedUser?.userGroups?.length) {
|
|
|
|
clonedUser = await checkGroupRoles(clonedUser, {
|
|
|
|
appId,
|
|
|
|
groups: opts?.groups,
|
|
|
|
})
|
2022-11-15 18:35:17 +01:00
|
|
|
}
|
|
|
|
|
2023-06-20 18:07:34 +02:00
|
|
|
return clonedUser
|
2022-11-15 18:35:17 +01:00
|
|
|
}
|
|
|
|
|
2023-03-21 14:55:28 +01:00
|
|
|
export async function getCachedSelf(ctx: UserCtx, appId: string) {
|
2022-11-15 18:35:17 +01:00
|
|
|
// 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
|
|
|
|
const user = await cache.user.getUser(ctx.user?._id!)
|
|
|
|
return processUser(user, { appId })
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function getRawGlobalUser(userId: string) {
|
|
|
|
const db = tenancy.getGlobalDB()
|
2023-07-18 11:41:51 +02:00
|
|
|
return db.get<User>(getGlobalIDFromUserMetadataID(userId))
|
2022-11-15 18:35:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export async function getGlobalUser(userId: string) {
|
|
|
|
const appId = context.getAppId()
|
|
|
|
let user = await getRawGlobalUser(userId)
|
|
|
|
return processUser(user, { appId })
|
|
|
|
}
|
|
|
|
|
2023-04-12 20:59:05 +02:00
|
|
|
export async function getGlobalUsers(
|
|
|
|
userIds?: string[],
|
|
|
|
opts?: { noProcessing?: boolean }
|
|
|
|
) {
|
2022-11-15 18:35:17 +01:00
|
|
|
const appId = context.getAppId()
|
|
|
|
const db = tenancy.getGlobalDB()
|
|
|
|
let globalUsers
|
2023-04-04 19:03:56 +02:00
|
|
|
if (userIds) {
|
|
|
|
globalUsers = (await db.allDocs(getMultiIDParams(userIds))).rows.map(
|
2022-11-15 18:35:17 +01:00
|
|
|
row => row.doc
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
globalUsers = (
|
|
|
|
await db.allDocs(
|
|
|
|
dbCore.getGlobalUserParams(null, {
|
|
|
|
include_docs: true,
|
|
|
|
})
|
|
|
|
)
|
|
|
|
).rows.map(row => row.doc)
|
|
|
|
}
|
|
|
|
globalUsers = globalUsers
|
|
|
|
.filter(user => user != null)
|
|
|
|
.map(user => {
|
|
|
|
delete user.password
|
|
|
|
delete user.forceResetPassword
|
|
|
|
return user
|
|
|
|
})
|
|
|
|
|
2023-05-17 20:35:00 +02:00
|
|
|
if (opts?.noProcessing || !appId) {
|
2023-04-12 20:59:05 +02:00
|
|
|
return globalUsers
|
|
|
|
} else {
|
|
|
|
// pass in the groups, meaning we don't actually need to retrieve them for
|
|
|
|
// each user individually
|
|
|
|
const allGroups = await groups.fetch()
|
|
|
|
return Promise.all(
|
|
|
|
globalUsers.map(user => processUser(user, { groups: allGroups }))
|
|
|
|
)
|
|
|
|
}
|
2022-11-15 18:35:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export async function getGlobalUsersFromMetadata(users: ContextUser[]) {
|
2023-04-04 19:03:56 +02:00
|
|
|
const globalUsers = await getGlobalUsers(users.map(user => user._id!))
|
2022-11-15 18:35:17 +01:00
|
|
|
return users.map(user => {
|
|
|
|
const globalUser = globalUsers.find(
|
|
|
|
globalUser => globalUser && user._id?.includes(globalUser._id)
|
|
|
|
)
|
|
|
|
return {
|
|
|
|
...globalUser,
|
|
|
|
// doing user second overwrites the id and rev (always metadata)
|
|
|
|
...user,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|