Main body of work to handle the new approach of per app builders support.

This commit is contained in:
mike12345567 2023-07-18 16:57:48 +01:00
parent af5d024d89
commit 39746e0bf0
14 changed files with 115 additions and 62 deletions

View File

@ -14,8 +14,6 @@ export enum ViewName {
USER_BY_APP = "by_app",
USER_BY_EMAIL = "by_email2",
BY_API_KEY = "by_api_key",
/** @deprecated - could be deleted */
USER_BY_BUILDERS = "by_builders",
LINK = "by_link",
ROUTING = "screen_routes",
AUTOMATION_LOGS = "automation_logs",

View File

@ -105,16 +105,6 @@ export const createApiKeyView = async () => {
await createView(db, viewJs, ViewName.BY_API_KEY)
}
export const createUserBuildersView = async () => {
const db = getGlobalDB()
const viewJs = `function(doc) {
if (doc.builder && doc.builder.global === true) {
emit(doc._id, doc._id)
}
}`
await createView(db, viewJs, ViewName.USER_BY_BUILDERS)
}
export interface QueryViewOptions {
arrayResponse?: boolean
}
@ -223,7 +213,6 @@ export const queryPlatformView = async <T>(
const CreateFuncByName: any = {
[ViewName.USER_BY_EMAIL]: createNewUserEmailView,
[ViewName.BY_API_KEY]: createApiKeyView,
[ViewName.USER_BY_BUILDERS]: createUserBuildersView,
[ViewName.USER_BY_APP]: createUserAppView,
}

View File

@ -21,6 +21,7 @@ import { processors } from "./processors"
import { newid } from "../utils"
import * as installation from "../installation"
import * as configs from "../configs"
import * as users from "../users"
import { withCache, TTL, CacheKey } from "../cache/generic"
/**
@ -164,8 +165,8 @@ const identifyUser = async (
const id = user._id as string
const tenantId = await getEventTenantId(user.tenantId)
const type = IdentityType.USER
let builder = user.builder?.global || false
let admin = user.admin?.global || false
let builder = users.hasBuilderPermissions(user)
let admin = users.hasAdminPermissions(user)
let providerType
if (isSSOUser(user)) {
providerType = user.providerType

View File

@ -1,10 +1,10 @@
import { BBContext } from "@budibase/types"
import { UserCtx } from "@budibase/types"
import { isBuilder } from "../users"
import { getAppId } from "../context"
export default async (ctx: BBContext, next: any) => {
if (
!ctx.internal &&
(!ctx.user || !ctx.user.builder || !ctx.user.builder.global)
) {
export default async (ctx: UserCtx, next: any) => {
const appId = getAppId()
if (!ctx.internal && !isBuilder(ctx.user, appId)) {
ctx.throw(403, "Builder user only endpoint.")
}
return next()

View File

@ -1,12 +1,11 @@
import { BBContext } from "@budibase/types"
import { UserCtx } from "@budibase/types"
import { isBuilder, isAdmin } from "../users"
import { getAppId } from "../context"
export default async (ctx: BBContext, next: any) => {
if (
!ctx.internal &&
(!ctx.user || !ctx.user.builder || !ctx.user.builder.global) &&
(!ctx.user || !ctx.user.admin || !ctx.user.admin.global)
) {
ctx.throw(403, "Builder user only endpoint.")
export default async (ctx: UserCtx, next: any) => {
const appId = getAppId()
if (!ctx.internal && !isBuilder(ctx.user, appId) && !isAdmin(ctx.user)) {
ctx.throw(403, "Admin/Builder user only endpoint.")
}
return next()
}

View File

@ -12,7 +12,12 @@ import {
UNICODE_MAX,
ViewName,
} from "./db"
import { BulkDocsResponse, SearchUsersRequest, User } from "@budibase/types"
import {
BulkDocsResponse,
SearchUsersRequest,
User,
ContextUser,
} from "@budibase/types"
import { getGlobalDB } from "./context"
import * as context from "./context"
@ -178,7 +183,7 @@ export const getGlobalUserByAppPage = (appId: string, user: User) => {
* Performs a starts with search on the global email view.
*/
export const searchGlobalUsersByEmail = async (
email: string,
email: string | unknown,
opts: any,
getOpts?: GetOpts
) => {
@ -248,3 +253,56 @@ export async function getUserCount() {
})
return response.total_rows
}
// checks if a user is specifically a builder, given an app ID
export function isBuilder(user: User | ContextUser, appId?: string) {
if (user.builder?.global) {
return true
} else if (appId && user.builder?.apps?.includes(appId)) {
return true
}
return false
}
// alias for hasAdminPermission, currently do the same thing
// in future whether someone has admin permissions and whether they are
// an admin for a specific resource could be separated
export function isAdmin(user: User | ContextUser) {
return hasAdminPermissions(user)
}
// checks if a user is capable of building any app
export function hasBuilderPermissions(user?: User | ContextUser) {
if (!user) {
return false
}
return user.builder?.global || user.builder?.apps?.length !== 0
}
// checks if a user is capable of being an admin
export function hasAdminPermissions(user?: User | ContextUser) {
if (!user) {
return false
}
return user.admin?.global
}
// used to remove the builder/admin permissions, for processing the
// user as an app user (they may have some specific role/group
export function removePortalUserPermissions(user: User | ContextUser) {
delete user.admin
delete user.builder
return user
}
export function cleanseUserObject(user: User | ContextUser, base?: User) {
delete user.admin
delete user.builder
delete user.roles
if (base) {
user.admin = base.admin
user.builder = base.builder
user.roles = base.roles
}
return user
}

View File

@ -30,6 +30,7 @@ import {
objectStore,
roles,
tenancy,
users,
} from "@budibase/backend-core"
import { USERS_TABLE_SCHEMA } from "../../constants"
import {
@ -222,6 +223,7 @@ export async function fetchAppDefinition(ctx: UserCtx) {
export async function fetchAppPackage(ctx: UserCtx) {
const db = context.getAppDB()
const appId = context.getAppId()
let application = await db.get<any>(DocumentType.APP_METADATA)
const layouts = await getLayouts()
let screens = await getScreens()
@ -233,7 +235,7 @@ export async function fetchAppPackage(ctx: UserCtx) {
)
// Only filter screens if the user is not a builder
if (!(ctx.user.builder && ctx.user.builder.global)) {
if (!users.isBuilder(ctx.user, appId)) {
const userRoleId = getUserRoleId(ctx)
const accessController = new roles.AccessController()
screens = await accessController.checkScreensAccess(screens, userRoleId)

View File

@ -1,4 +1,10 @@
import { roles, permissions, auth, context } from "@budibase/backend-core"
import {
roles,
permissions,
auth,
context,
users,
} from "@budibase/backend-core"
import { Role } from "@budibase/types"
import builderMiddleware from "./builder"
import { isWebhookEndpoint } from "./utils"
@ -21,8 +27,9 @@ const checkAuthorized = async (
permType: any,
permLevel: any
) => {
const appId = context.getAppId()
// check if this is a builder api and the user is not a builder
const isBuilder = ctx.user && ctx.user.builder && ctx.user.builder.global
const isBuilder = users.isBuilder(ctx.user, appId)
const isBuilderApi = permType === permissions.PermissionType.BUILDER
if (isBuilderApi && !isBuilder) {
return ctx.throw(403, "Not Authorized")

View File

@ -4,12 +4,14 @@ import {
roles,
tenancy,
context,
users,
} from "@budibase/backend-core"
import { generateUserMetadataID, isDevAppID } from "../db/utils"
import { getCachedSelf } from "../utilities/global"
import env from "../environment"
import { isWebhookEndpoint } from "./utils"
import { UserCtx } from "@budibase/types"
import { UserCtx, ContextUser } from "@budibase/types"
import { removePortalUserPermissions } from "@budibase/backend-core/src/users"
export default async (ctx: UserCtx, next: any) => {
// try to get the appID from the request
@ -42,8 +44,7 @@ export default async (ctx: UserCtx, next: any) => {
roleId = globalUser.roleId || roleId
// Allow builders to specify their role via a header
const isBuilder =
globalUser && globalUser.builder && globalUser.builder.global
const isBuilder = users.isBuilder(globalUser, appId)
const isDevApp = appId && isDevAppID(appId)
const roleHeader =
ctx.request &&
@ -56,8 +57,7 @@ export default async (ctx: UserCtx, next: any) => {
roleId = roleHeader
// Delete admin and builder flags so that the specified role is honoured
delete ctx.user.builder
delete ctx.user.admin
ctx.user = users.removePortalUserPermissions(ctx.user) as ContextUser
}
} catch (error) {
// Swallow error and do nothing
@ -81,9 +81,7 @@ export default async (ctx: UserCtx, next: any) => {
!tenancy.isUserInAppTenant(requestAppId, ctx.user)
) {
// don't error, simply remove the users rights (they are a public user)
delete ctx.user.builder
delete ctx.user.admin
delete ctx.user.roles
ctx.user = users.cleanseUserObject(ctx.user) as ContextUser
ctx.isAuthenticated = false
roleId = roles.BUILTIN_ROLE_IDS.PUBLIC
skipCookie = true

View File

@ -1,4 +1,8 @@
import { events, db as dbUtils } from "@budibase/backend-core"
import {
events,
db as dbUtils,
users as usersCore,
} from "@budibase/backend-core"
import { User, CloudAccount } from "@budibase/types"
import { DEFAULT_TIMESTAMP } from ".."
@ -30,11 +34,11 @@ export const backfill = async (
await events.identification.identifyUser(user, account, timestamp)
await events.user.created(user, timestamp)
if (user.admin?.global) {
if (usersCore.hasAdminPermissions(user)) {
await events.user.permissionAdminAssigned(user, timestamp)
}
if (user.builder?.global) {
if (usersCore.hasBuilderPermissions(user)) {
await events.user.permissionBuilderAssigned(user, timestamp)
}

View File

@ -5,6 +5,7 @@ import {
cache,
tenancy,
context,
users,
} from "@budibase/backend-core"
import env from "../environment"
import { groups } from "@budibase/pro"
@ -22,8 +23,7 @@ export function updateAppRole(
}
// if in an multi-tenancy environment make sure roles are never updated
if (env.MULTI_TENANCY && appId && !tenancy.isUserInAppTenant(appId, user)) {
delete user.builder
delete user.admin
user = users.removePortalUserPermissions(user)
user.roleId = roles.BUILTIN_ROLE_IDS.PUBLIC
return user
}
@ -32,7 +32,7 @@ export function updateAppRole(
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 && user.builder && user.builder.global) {
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

View File

@ -47,7 +47,6 @@ export interface User extends Document {
}
admin?: {
global: boolean
apps?: string[]
}
password?: string
status?: UserStatus

View File

@ -1,15 +1,18 @@
import env from "../../environment"
import { events, accounts, tenancy } from "@budibase/backend-core"
import { events, accounts, tenancy, users } from "@budibase/backend-core"
import { User, UserRoles, CloudAccount } from "@budibase/types"
const hasBuilderPerm = users.hasBuilderPermissions
const hasAdminPerm = users.hasAdminPermissions
export const handleDeleteEvents = async (user: any) => {
await events.user.deleted(user)
if (isBuilder(user)) {
if (hasBuilderPerm(user)) {
await events.user.permissionBuilderRemoved(user)
}
if (isAdmin(user)) {
if (hasAdminPerm(user)) {
await events.user.permissionAdminRemoved(user)
}
}
@ -103,23 +106,20 @@ export const handleSaveEvents = async (
await handleAppRoleEvents(user, existingUser)
}
const isBuilder = (user: any) => user.builder && user.builder.global
const isAdmin = (user: any) => user.admin && user.admin.global
export const isAddingBuilder = (user: any, existingUser: any) => {
return isAddingPermission(user, existingUser, isBuilder)
return isAddingPermission(user, existingUser, hasBuilderPerm)
}
export const isRemovingBuilder = (user: any, existingUser: any) => {
return isRemovingPermission(user, existingUser, isBuilder)
return isRemovingPermission(user, existingUser, hasBuilderPerm)
}
const isAddingAdmin = (user: any, existingUser: any) => {
return isAddingPermission(user, existingUser, isAdmin)
return isAddingPermission(user, existingUser, hasAdminPerm)
}
const isRemovingAdmin = (user: any, existingUser: any) => {
return isRemovingPermission(user, existingUser, isAdmin)
return isRemovingPermission(user, existingUser, hasAdminPerm)
}
const isOnboardingComplete = (user: any, existingUser: any) => {

View File

@ -252,9 +252,7 @@ export const save = async (
let builtUser = await buildUser(user, opts, tenantId, dbUser)
// don't allow a user to update its own roles/perms
if (opts.currentUserId && opts.currentUserId === dbUser?._id) {
builtUser.builder = dbUser.builder
builtUser.admin = dbUser.admin
builtUser.roles = dbUser.roles
builtUser = usersCore.cleanseUserObject(builtUser, dbUser) as User
}
if (!dbUser && roles?.length) {