1
0
Fork 0
mirror of synced 2024-07-04 22:11:23 +12:00

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_APP = "by_app",
USER_BY_EMAIL = "by_email2", USER_BY_EMAIL = "by_email2",
BY_API_KEY = "by_api_key", BY_API_KEY = "by_api_key",
/** @deprecated - could be deleted */
USER_BY_BUILDERS = "by_builders",
LINK = "by_link", LINK = "by_link",
ROUTING = "screen_routes", ROUTING = "screen_routes",
AUTOMATION_LOGS = "automation_logs", AUTOMATION_LOGS = "automation_logs",

View file

@ -105,16 +105,6 @@ export const createApiKeyView = async () => {
await createView(db, viewJs, ViewName.BY_API_KEY) 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 { export interface QueryViewOptions {
arrayResponse?: boolean arrayResponse?: boolean
} }
@ -223,7 +213,6 @@ export const queryPlatformView = async <T>(
const CreateFuncByName: any = { const CreateFuncByName: any = {
[ViewName.USER_BY_EMAIL]: createNewUserEmailView, [ViewName.USER_BY_EMAIL]: createNewUserEmailView,
[ViewName.BY_API_KEY]: createApiKeyView, [ViewName.BY_API_KEY]: createApiKeyView,
[ViewName.USER_BY_BUILDERS]: createUserBuildersView,
[ViewName.USER_BY_APP]: createUserAppView, [ViewName.USER_BY_APP]: createUserAppView,
} }

View file

@ -21,6 +21,7 @@ import { processors } from "./processors"
import { newid } from "../utils" import { newid } from "../utils"
import * as installation from "../installation" import * as installation from "../installation"
import * as configs from "../configs" import * as configs from "../configs"
import * as users from "../users"
import { withCache, TTL, CacheKey } from "../cache/generic" import { withCache, TTL, CacheKey } from "../cache/generic"
/** /**
@ -164,8 +165,8 @@ const identifyUser = async (
const id = user._id as string const id = user._id as string
const tenantId = await getEventTenantId(user.tenantId) const tenantId = await getEventTenantId(user.tenantId)
const type = IdentityType.USER const type = IdentityType.USER
let builder = user.builder?.global || false let builder = users.hasBuilderPermissions(user)
let admin = user.admin?.global || false let admin = users.hasAdminPermissions(user)
let providerType let providerType
if (isSSOUser(user)) { if (isSSOUser(user)) {
providerType = user.providerType 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) => { export default async (ctx: UserCtx, next: any) => {
if ( const appId = getAppId()
!ctx.internal && if (!ctx.internal && !isBuilder(ctx.user, appId)) {
(!ctx.user || !ctx.user.builder || !ctx.user.builder.global)
) {
ctx.throw(403, "Builder user only endpoint.") ctx.throw(403, "Builder user only endpoint.")
} }
return next() 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) => { export default async (ctx: UserCtx, next: any) => {
if ( const appId = getAppId()
!ctx.internal && if (!ctx.internal && !isBuilder(ctx.user, appId) && !isAdmin(ctx.user)) {
(!ctx.user || !ctx.user.builder || !ctx.user.builder.global) && ctx.throw(403, "Admin/Builder user only endpoint.")
(!ctx.user || !ctx.user.admin || !ctx.user.admin.global)
) {
ctx.throw(403, "Builder user only endpoint.")
} }
return next() return next()
} }

View file

@ -12,7 +12,12 @@ import {
UNICODE_MAX, UNICODE_MAX,
ViewName, ViewName,
} from "./db" } from "./db"
import { BulkDocsResponse, SearchUsersRequest, User } from "@budibase/types" import {
BulkDocsResponse,
SearchUsersRequest,
User,
ContextUser,
} from "@budibase/types"
import { getGlobalDB } from "./context" import { getGlobalDB } from "./context"
import * as context 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. * Performs a starts with search on the global email view.
*/ */
export const searchGlobalUsersByEmail = async ( export const searchGlobalUsersByEmail = async (
email: string, email: string | unknown,
opts: any, opts: any,
getOpts?: GetOpts getOpts?: GetOpts
) => { ) => {
@ -248,3 +253,56 @@ export async function getUserCount() {
}) })
return response.total_rows 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, objectStore,
roles, roles,
tenancy, tenancy,
users,
} from "@budibase/backend-core" } from "@budibase/backend-core"
import { USERS_TABLE_SCHEMA } from "../../constants" import { USERS_TABLE_SCHEMA } from "../../constants"
import { import {
@ -222,6 +223,7 @@ export async function fetchAppDefinition(ctx: UserCtx) {
export async function fetchAppPackage(ctx: UserCtx) { export async function fetchAppPackage(ctx: UserCtx) {
const db = context.getAppDB() const db = context.getAppDB()
const appId = context.getAppId()
let application = await db.get<any>(DocumentType.APP_METADATA) let application = await db.get<any>(DocumentType.APP_METADATA)
const layouts = await getLayouts() const layouts = await getLayouts()
let screens = await getScreens() let screens = await getScreens()
@ -233,7 +235,7 @@ export async function fetchAppPackage(ctx: UserCtx) {
) )
// Only filter screens if the user is not a builder // 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 userRoleId = getUserRoleId(ctx)
const accessController = new roles.AccessController() const accessController = new roles.AccessController()
screens = await accessController.checkScreensAccess(screens, userRoleId) 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 { Role } from "@budibase/types"
import builderMiddleware from "./builder" import builderMiddleware from "./builder"
import { isWebhookEndpoint } from "./utils" import { isWebhookEndpoint } from "./utils"
@ -21,8 +27,9 @@ const checkAuthorized = async (
permType: any, permType: any,
permLevel: any permLevel: any
) => { ) => {
const appId = context.getAppId()
// check if this is a builder api and the user is not a builder // 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 const isBuilderApi = permType === permissions.PermissionType.BUILDER
if (isBuilderApi && !isBuilder) { if (isBuilderApi && !isBuilder) {
return ctx.throw(403, "Not Authorized") return ctx.throw(403, "Not Authorized")

View file

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

View file

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

View file

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

View file

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

View file

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