1
0
Fork 0
mirror of synced 2024-09-24 21:31:17 +12:00

Merge branch 'master' of github.com:Budibase/budibase into budi-8608-ai-platform-level-config-pt-2

This commit is contained in:
Martin McKeaveney 2024-09-16 11:54:12 +01:00
commit 7fc0f38296
32 changed files with 275 additions and 411 deletions

View file

@ -117,9 +117,9 @@ jobs:
- name: Test
run: |
if ${{ env.ONLY_AFFECTED_TASKS }}; then
yarn test --ignore=@budibase/worker --ignore=@budibase/server --ignore @budibase/account-portal-server --since=${{ env.NX_BASE_BRANCH }}
yarn test --ignore=@budibase/worker --ignore=@budibase/server --since=${{ env.NX_BASE_BRANCH }}
else
yarn test --ignore=@budibase/worker --ignore=@budibase/server --ignore @budibase/account-portal-server
yarn test --ignore=@budibase/worker --ignore=@budibase/server
fi
test-worker:

View file

@ -1,6 +1,6 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"version": "2.32.0",
"version": "2.32.4",
"npmClient": "yarn",
"packages": [
"packages/*",

View file

@ -117,7 +117,10 @@
"axios": "1.6.3",
"xml2js": "0.6.2",
"unset-value": "2.0.1",
"passport": "0.6.0"
"passport": "0.6.0",
"fast-xml-parser": "4.4.1",
"@azure/identity": "4.2.1",
"kind-of": "6.0.3"
},
"engines": {
"node": ">=20.0.0 <21.0.0"

@ -1 +1 @@
Subproject commit 7899d07904d89d48954dd500da7b5dec32b781dd
Subproject commit 905773d70854a43c6ef2461c7a49671bff56fedc

View file

@ -7,8 +7,9 @@ import {
doWithDB,
} from "../db"
import { getAppDB } from "../context"
import { Screen, Role as RoleDoc } from "@budibase/types"
import { Screen, Role as RoleDoc, RoleUIMetadata } from "@budibase/types"
import cloneDeep from "lodash/fp/cloneDeep"
import { RoleColor } from "@budibase/shared-core"
export const BUILTIN_ROLE_IDS = {
ADMIN: "ADMIN",
@ -44,11 +45,18 @@ export class Role implements RoleDoc {
permissionId: string
inherits?: string
version?: string
permissions = {}
permissions: Record<string, PermissionLevel[]> = {}
uiMetadata?: RoleUIMetadata
constructor(id: string, name: string, permissionId: string) {
constructor(
id: string,
name: string,
permissionId: string,
uiMetadata?: RoleUIMetadata
) {
this._id = id
this.name = name
this.uiMetadata = uiMetadata
this.permissionId = permissionId
// version for managing the ID - removing the role_ when responding
this.version = RoleIDVersion.NAME
@ -63,21 +71,54 @@ export class Role implements RoleDoc {
const BUILTIN_ROLES = {
ADMIN: new Role(
BUILTIN_IDS.ADMIN,
"Admin",
BuiltinPermissionID.ADMIN
BUILTIN_IDS.ADMIN,
BuiltinPermissionID.ADMIN,
{
displayName: "App admin",
description: "Can do everything",
color: RoleColor.ADMIN,
}
).addInheritance(BUILTIN_IDS.POWER),
POWER: new Role(
BUILTIN_IDS.POWER,
"Power",
BuiltinPermissionID.POWER
BUILTIN_IDS.POWER,
BuiltinPermissionID.POWER,
{
displayName: "App power user",
description: "An app user with more access",
color: RoleColor.POWER,
}
).addInheritance(BUILTIN_IDS.BASIC),
BASIC: new Role(
BUILTIN_IDS.BASIC,
"Basic",
BuiltinPermissionID.WRITE
BUILTIN_IDS.BASIC,
BuiltinPermissionID.WRITE,
{
displayName: "App user",
description: "Any logged in user",
color: RoleColor.BASIC,
}
).addInheritance(BUILTIN_IDS.PUBLIC),
PUBLIC: new Role(BUILTIN_IDS.PUBLIC, "Public", BuiltinPermissionID.PUBLIC),
BUILDER: new Role(BUILTIN_IDS.BUILDER, "Builder", BuiltinPermissionID.ADMIN),
PUBLIC: new Role(
BUILTIN_IDS.PUBLIC,
BUILTIN_IDS.PUBLIC,
BuiltinPermissionID.PUBLIC,
{
displayName: "Public user",
description: "Accessible to anyone",
color: RoleColor.PUBLIC,
}
),
BUILDER: new Role(
BUILTIN_IDS.BUILDER,
BUILTIN_IDS.BUILDER,
BuiltinPermissionID.ADMIN,
{
displayName: "Builder user",
description: "Users that can edit this app",
color: RoleColor.BUILDER,
}
),
}
export function getBuiltinRoles(): { [key: string]: RoleDoc } {
@ -244,9 +285,9 @@ export async function getUserRoleHierarchy(
// some templates/older apps will use a simple string instead of array for roles
// convert the string to an array using the theory that write is higher than read
export function checkForRoleResourceArray(
rolePerms: { [key: string]: string[] },
rolePerms: Record<string, PermissionLevel[]>,
resourceId: string
) {
): Record<string, PermissionLevel[]> {
if (rolePerms && !Array.isArray(rolePerms[resourceId])) {
const permLevel = rolePerms[resourceId] as any
rolePerms[resourceId] = [permLevel]

View file

@ -102,10 +102,6 @@ export const useAppBuilders = () => {
return useFeature(Feature.APP_BUILDERS)
}
export const useViewPermissions = () => {
return useFeature(Feature.VIEW_PERMISSIONS)
}
export const useViewReadonlyColumns = () => {
return useFeature(Feature.VIEW_READONLY_COLUMNS)
}

View file

@ -24,6 +24,7 @@
const dispatch = createEventDispatcher()
const RemoveID = "remove"
const subType = $licensing.license.plan.type ?? null
$: enrichLabel = label => (labelPrefix ? `${labelPrefix} ${label}` : label)
$: options = getOptions(
@ -68,13 +69,13 @@
}))
// Add creator if required
if (allowCreator) {
if (allowCreator || isEnterprisePlan(subType)) {
options.unshift({
_id: Constants.Roles.CREATOR,
name: "Can edit",
tag:
!$licensing.perAppBuildersEnabled &&
capitalise(Constants.PlanType.BUSINESS),
tag: isEnterprisePlan(subType)
? null
: capitalise(Constants.PlanType.ENTERPRISE),
})
}
@ -117,6 +118,14 @@
dispatch("change", e.detail)
}
}
function isEnterprisePlan(subType) {
return (
subType === Constants.PlanType.ENTERPRISE ||
subType === Constants.PlanType.ENTERPRISE_BASIC ||
subType === Constants.PlanType.ENTERPRISE_BASIC_TRIAL
)
}
</script>
{#if fancySelect}
@ -134,9 +143,12 @@
getOptionValue={role => role._id}
getOptionColour={getColor}
getOptionIcon={getIcon}
isOptionEnabled={option =>
option._id !== Constants.Roles.CREATOR ||
$licensing.perAppBuildersEnabled}
isOptionEnabled={option => {
if (option._id === Constants.Roles.CREATOR) {
return isEnterprisePlan(subType)
}
return true
}}
{placeholder}
{error}
/>
@ -154,10 +166,12 @@
getOptionValue={role => role._id}
getOptionColour={getColor}
getOptionIcon={getIcon}
isOptionEnabled={option =>
(option._id !== Constants.Roles.CREATOR ||
$licensing.perAppBuildersEnabled) &&
option.enabled !== false}
isOptionEnabled={option => {
if (option._id === Constants.Roles.CREATOR) {
return isEnterprisePlan(subType)
}
return option.enabled !== false
}}
{placeholder}
{error}
/>

View file

@ -85,6 +85,7 @@ export const PlanType = {
TEAM: "team",
PRO: "pro",
BUSINESS: "business",
PREMIUM: "premium",
ENTERPRISE: "enterprise",
ENTERPRISE_BASIC_TRIAL: "enterprise_basic_trial",
}

@ -1 +1 @@
Subproject commit ec1d2bda756f02c6b4efdee086e4c59b0c2a1b0c
Subproject commit 922431260e90d558a1ca55398475412e75088057

View file

@ -63,7 +63,7 @@
"@koa/router": "8.0.8",
"@socket.io/redis-adapter": "^8.2.1",
"@types/xml2js": "^0.4.14",
"airtable": "0.10.1",
"airtable": "0.12.2",
"arangojs": "7.2.0",
"archiver": "7.0.1",
"aws-sdk": "2.1030.0",

View file

@ -1,4 +1,4 @@
import { permissions, roles, context, HTTPError } from "@budibase/backend-core"
import { permissions, roles, context } from "@budibase/backend-core"
import {
UserCtx,
Database,
@ -45,18 +45,6 @@ async function updatePermissionOnRole(
}: { roleId: string; resourceId: string; level: PermissionLevel },
updateType: PermissionUpdateType
) {
const allowedAction = await sdk.permissions.resourceActionAllowed({
resourceId,
level,
})
if (!allowedAction.allowed) {
throw new HTTPError(
`You are not allowed to '${allowedAction.level}' the resource type '${allowedAction.resourceType}'`,
403
)
}
const db = context.getAppDB()
const remove = updateType === PermissionUpdateType.REMOVE
const isABuiltin = roles.isBuiltin(roleId)
@ -75,7 +63,9 @@ async function updatePermissionOnRole(
// resource from another role and then adding to the new role
for (let role of dbRoles) {
let updated = false
const rolePermissions = role.permissions ? role.permissions : {}
const rolePermissions: Record<string, PermissionLevel[]> = role.permissions
? role.permissions
: {}
// make sure its an array, also handle migrating
if (
!rolePermissions[resourceId] ||
@ -83,7 +73,7 @@ async function updatePermissionOnRole(
) {
rolePermissions[resourceId] =
typeof rolePermissions[resourceId] === "string"
? [rolePermissions[resourceId] as unknown as string]
? [rolePermissions[resourceId] as unknown as PermissionLevel]
: []
}
// handle the removal/updating the role which has this permission first
@ -182,9 +172,6 @@ export async function getResourcePerms(
},
{} as Record<string, ResourcePermissionInfo>
),
requiresPlanToModify: (
await sdk.permissions.allowsExplicitPermissions(resourceId)
).minPlan,
}
}

View file

@ -17,9 +17,9 @@ import {
SaveRoleResponse,
UserCtx,
UserMetadata,
UserRoles,
DocumentType,
} from "@budibase/types"
import { sdk as sharedSdk } from "@budibase/shared-core"
import { RoleColor, sdk as sharedSdk } from "@budibase/shared-core"
import sdk from "../../sdk"
const UpdateRolesOptions = {
@ -62,7 +62,8 @@ export async function find(ctx: UserCtx<void, FindRoleResponse>) {
export async function save(ctx: UserCtx<SaveRoleRequest, SaveRoleResponse>) {
const db = context.getAppDB()
let { _id, name, inherits, permissionId, version } = ctx.request.body
let { _id, name, inherits, permissionId, version, uiMetadata } =
ctx.request.body
let isCreate = false
const isNewVersion = version === roles.RoleIDVersion.NAME
@ -80,17 +81,25 @@ export async function save(ctx: UserCtx<SaveRoleRequest, SaveRoleResponse>) {
_id = dbCore.prefixRoleID(_id)
}
let dbRole
if (!isCreate) {
dbRole = await db.get<UserRoles>(_id)
let dbRole: Role | undefined
if (!isCreate && _id?.startsWith(DocumentType.ROLE)) {
dbRole = await db.get<Role>(_id)
}
if (dbRole && dbRole.name !== name && isNewVersion) {
ctx.throw(400, "Cannot change custom role name")
}
const role = new roles.Role(_id, name, permissionId).addInheritance(inherits)
if (ctx.request.body._rev) {
role._rev = ctx.request.body._rev
const role = new roles.Role(_id, name, permissionId, {
displayName: uiMetadata?.displayName || name,
description: uiMetadata?.description || "Custom role",
color: uiMetadata?.color || RoleColor.DEFAULT_CUSTOM,
}).addInheritance(inherits)
if (dbRole?.permissions && !role.permissions) {
role.permissions = dbRole.permissions
}
const foundRev = ctx.request.body._rev || dbRole?._rev
if (foundRev) {
role._rev = foundRev
}
const result = await db.put(role)
if (isCreate) {

View file

@ -1,20 +1,5 @@
const mockedSdk = sdk.permissions as jest.Mocked<typeof sdk.permissions>
jest.mock("../../../sdk/app/permissions", () => ({
...jest.requireActual("../../../sdk/app/permissions"),
resourceActionAllowed: jest.fn(),
}))
import sdk from "../../../sdk"
import { roles } from "@budibase/backend-core"
import {
Document,
DocumentType,
PermissionLevel,
Row,
Table,
ViewV2,
} from "@budibase/types"
import { Document, PermissionLevel, Row, Table, ViewV2 } from "@budibase/types"
import * as setup from "./utilities"
import { generator, mocks } from "@budibase/backend-core/tests"
@ -40,7 +25,6 @@ describe("/permission", () => {
beforeEach(async () => {
mocks.licenses.useCloudFree()
mockedSdk.resourceActionAllowed.mockResolvedValue({ allowed: true })
table = (await config.createTable()) as typeof table
row = await config.createRow()
@ -112,29 +96,6 @@ describe("/permission", () => {
expect(allRes.body[table._id]["read"]).toEqual(STD_ROLE_ID)
expect(allRes.body[table._id]["write"]).toEqual(HIGHER_ROLE_ID)
})
it("throw forbidden if the action is not allowed for the resource", async () => {
mockedSdk.resourceActionAllowed.mockResolvedValue({
allowed: false,
resourceType: DocumentType.DATASOURCE,
level: PermissionLevel.READ,
})
await config.api.permission.add(
{
roleId: STD_ROLE_ID,
resourceId: table._id,
level: PermissionLevel.EXECUTE,
},
{
status: 403,
body: {
message:
"You are not allowed to 'read' the resource type 'datasource'",
},
}
)
})
})
describe("remove", () => {
@ -148,29 +109,6 @@ describe("/permission", () => {
const permsRes = await config.api.permission.get(table._id)
expect(permsRes.permissions[STD_ROLE_ID]).toBeUndefined()
})
it("throw forbidden if the action is not allowed for the resource", async () => {
mockedSdk.resourceActionAllowed.mockResolvedValue({
allowed: false,
resourceType: DocumentType.DATASOURCE,
level: PermissionLevel.READ,
})
await config.api.permission.revoke(
{
roleId: STD_ROLE_ID,
resourceId: table._id,
level: PermissionLevel.EXECUTE,
},
{
status: 403,
body: {
message:
"You are not allowed to 'read' the resource type 'datasource'",
},
}
)
})
})
describe("check public user allowed", () => {
@ -206,27 +144,7 @@ describe("/permission", () => {
await config.api.viewV2.publicSearch(view.id, undefined, { status: 401 })
})
it("should ignore the view permissions if the flag is not on", async () => {
await config.api.permission.add({
roleId: STD_ROLE_ID,
resourceId: view.id,
level: PermissionLevel.READ,
})
await config.api.permission.revoke({
roleId: STD_ROLE_ID,
resourceId: table._id,
level: PermissionLevel.READ,
})
// replicate changes before checking permissions
await config.publish()
await config.api.viewV2.publicSearch(view.id, undefined, {
status: 401,
})
})
it("should use the view permissions if the flag is on", async () => {
mocks.licenses.useViewPermissions()
it("should use the view permissions", async () => {
await config.api.permission.add({
roleId: STD_ROLE_ID,
resourceId: view.id,

View file

@ -763,10 +763,6 @@ describe("/rowsActions", () => {
})
describe("role permission checks", () => {
beforeAll(() => {
mocks.licenses.useViewPermissions()
})
afterAll(() => {
mocks.licenses.useCloudFree()
})

View file

@ -2297,7 +2297,6 @@ describe.each([
describe("permissions", () => {
beforeEach(async () => {
mocks.licenses.useViewPermissions()
await Promise.all(
Array.from({ length: 10 }, () => config.api.row.save(table._id!, {}))
)

View file

@ -200,7 +200,7 @@ export function webhookValidator() {
export function roleValidator() {
const permLevelArray = Object.values(permissions.PermissionLevel)
const permissionString = Joi.string().valid(...permLevelArray)
return auth.joiValidator.body(
Joi.object({
_id: OPTIONAL_STRING,
@ -208,12 +208,23 @@ export function roleValidator() {
name: Joi.string()
.regex(/^[a-zA-Z0-9_]*$/)
.required(),
uiMetadata: Joi.object({
displayName: OPTIONAL_STRING,
color: OPTIONAL_STRING,
description: OPTIONAL_STRING,
}).optional(),
// this is the base permission ID (for now a built in)
permissionId: Joi.string()
.valid(...Object.values(permissions.BuiltinPermissionID))
.required(),
permissions: Joi.object()
.pattern(/.*/, [Joi.string().valid(...permLevelArray)])
.pattern(
/.*/,
Joi.alternatives().try(
Joi.array().items(permissionString),
permissionString
)
)
.optional(),
inherits: OPTIONAL_STRING,
}).unknown(true)

View file

@ -16,6 +16,8 @@ enum Model {
GPT_35_TURBO = "gpt-3.5-turbo",
// will only work with api keys that have access to the GPT4 API
GPT_4 = "gpt-4",
GPT_4O = "gpt-4o",
GPT_4O_MINI = "gpt-4o-mini",
}
export const definition: AutomationStepDefinition = {

View file

@ -18,6 +18,7 @@ import {
SearchFilters,
AutomationStoppedReason,
AutomationStatus,
AutomationRowEvent,
} from "@budibase/types"
import { executeInThread } from "../threads/automation"
import { dataFilters, sdk } from "@budibase/shared-core"
@ -28,6 +29,7 @@ const JOB_OPTS = {
removeOnFail: true,
}
import * as automationUtils from "../automations/automationUtils"
import { doesTableExist } from "../sdk/app/tables/getters"
async function getAllAutomations() {
const db = context.getAppDB()
@ -38,25 +40,35 @@ async function getAllAutomations() {
}
async function queueRelevantRowAutomations(
event: { appId: string; row: Row; oldRow: Row },
eventType: string
event: AutomationRowEvent,
eventType: AutomationEventType
) {
const tableId = event.row.tableId
if (event.appId == null) {
throw `No appId specified for ${eventType} - check event emitters.`
}
// make sure table exists and is valid before proceeding
if (!tableId || !(await doesTableExist(tableId))) {
return
}
await context.doInAppContext(event.appId, async () => {
let automations = await getAllAutomations()
// filter down to the correct event type and enabled automations
// make sure it is the correct table ID as well
automations = automations.filter(automation => {
const trigger = automation.definition.trigger
return trigger && trigger.event === eventType && !automation.disabled
return (
trigger &&
trigger.event === eventType &&
!automation.disabled &&
trigger?.inputs?.tableId === event.row.tableId
)
})
for (const automation of automations) {
const automationDef = automation.definition
const automationTrigger = automationDef?.trigger
// don't queue events which are for dev apps, only way to test automations is
// running tests on them, in production the test flag will never
// be checked due to lazy evaluation (first always false)
@ -72,11 +84,7 @@ async function queueRelevantRowAutomations(
row: event.row,
oldRow: event.oldRow,
})
if (
automationTrigger?.inputs &&
automationTrigger.inputs.tableId === event.row.tableId &&
shouldTrigger
) {
if (shouldTrigger) {
try {
await automationQueue.add({ automation, event }, JOB_OPTS)
} catch (e) {
@ -87,6 +95,17 @@ async function queueRelevantRowAutomations(
})
}
async function queueRowAutomations(
event: AutomationRowEvent,
type: AutomationEventType
) {
try {
await queueRelevantRowAutomations(event, type)
} catch (err: any) {
logging.logWarn("Unable to process row event", err)
}
}
emitter.on(
AutomationEventType.ROW_SAVE,
async function (event: UpdatedRowEventEmitter) {
@ -94,7 +113,7 @@ emitter.on(
if (!event || !event.row || !event.row.tableId) {
return
}
await queueRelevantRowAutomations(event, AutomationEventType.ROW_SAVE)
await queueRowAutomations(event, AutomationEventType.ROW_SAVE)
}
)
@ -103,7 +122,7 @@ emitter.on(AutomationEventType.ROW_UPDATE, async function (event) {
if (!event || !event.row || !event.row.tableId) {
return
}
await queueRelevantRowAutomations(event, AutomationEventType.ROW_UPDATE)
await queueRowAutomations(event, AutomationEventType.ROW_UPDATE)
})
emitter.on(AutomationEventType.ROW_DELETE, async function (event) {
@ -111,7 +130,7 @@ emitter.on(AutomationEventType.ROW_DELETE, async function (event) {
if (!event || !event.row || !event.row.tableId) {
return
}
await queueRelevantRowAutomations(event, AutomationEventType.ROW_DELETE)
await queueRowAutomations(event, AutomationEventType.ROW_DELETE)
})
function rowPassesFilters(row: Row, filters: SearchFilters) {

View file

@ -1,10 +1,7 @@
import { db, roles } from "@budibase/backend-core"
import { features } from "@budibase/pro"
import {
DocumentType,
PermissionLevel,
PermissionSource,
PlanType,
VirtualDocumentType,
} from "@budibase/types"
import { extractViewInfoFromID, isViewID } from "../../../db/utils"
@ -15,36 +12,6 @@ import {
import sdk from "../../../sdk"
import { isV2 } from "../views"
type ResourceActionAllowedResult =
| { allowed: true }
| {
allowed: false
level: PermissionLevel
resourceType: DocumentType | VirtualDocumentType
}
export async function resourceActionAllowed({
resourceId,
level,
}: {
resourceId: string
level: PermissionLevel
}): Promise<ResourceActionAllowedResult> {
if (!isViewID(resourceId)) {
return { allowed: true }
}
if (await features.isViewPermissionEnabled()) {
return { allowed: true }
}
return {
allowed: false,
level,
resourceType: VirtualDocumentType.VIEW,
}
}
type ResourcePermissions = Record<
string,
{ role: string; type: PermissionSource }
@ -58,20 +25,6 @@ export async function getInheritablePermissions(
}
}
export async function allowsExplicitPermissions(resourceId: string) {
if (isViewID(resourceId)) {
const allowed = await features.isViewPermissionEnabled()
const minPlan = !allowed ? PlanType.PREMIUM_PLUS : undefined
return {
allowed,
minPlan,
}
}
return { allowed: true }
}
export async function getResourcePerms(
resourceId: string
): Promise<ResourcePermissions> {
@ -81,16 +34,14 @@ export async function getResourcePerms(
const permsToInherit = await getInheritablePermissions(resourceId)
const allowsExplicitPerm = (await allowsExplicitPermissions(resourceId))
.allowed
for (let level of CURRENTLY_SUPPORTED_LEVELS) {
// update the various roleIds in the resource permissions
for (let role of rolesList) {
const rolePerms = allowsExplicitPerm
? roles.checkForRoleResourceArray(role.permissions || {}, resourceId)
: {}
if (rolePerms[resourceId]?.indexOf(level) > -1) {
const rolePerms = roles.checkForRoleResourceArray(
role.permissions || {},
resourceId
)
if (rolePerms[resourceId]?.indexOf(level as PermissionLevel) > -1) {
permissions[level] = {
role: roles.getExternalRoleID(role._id!, role.version),
type: PermissionSource.EXPLICIT,

View file

@ -1,53 +0,0 @@
import { PermissionLevel } from "@budibase/types"
import { mocks, structures } from "@budibase/backend-core/tests"
import { resourceActionAllowed } from ".."
import { generateViewID } from "../../../../db/utils"
import { initProMocks } from "../../../../tests/utilities/mocks/pro"
initProMocks()
describe("permissions sdk", () => {
beforeEach(() => {
mocks.licenses.useCloudFree()
})
describe("resourceActionAllowed", () => {
it("non view resources actions are always allowed", async () => {
const resourceId = structures.users.user()._id!
const result = await resourceActionAllowed({
resourceId,
level: PermissionLevel.READ,
})
expect(result).toEqual({ allowed: true })
})
it("view resources actions allowed if the feature flag is enabled", async () => {
mocks.licenses.useViewPermissions()
const resourceId = generateViewID(structures.generator.guid())
const result = await resourceActionAllowed({
resourceId,
level: PermissionLevel.READ,
})
expect(result).toEqual({ allowed: true })
})
it("view resources actions allowed if the feature flag is disabled", async () => {
const resourceId = generateViewID(structures.generator.guid())
const result = await resourceActionAllowed({
resourceId,
level: PermissionLevel.READ,
})
expect(result).toEqual({
allowed: false,
level: "read",
resourceType: "view",
})
})
})
})

View file

@ -101,6 +101,15 @@ export async function getTable(tableId: string): Promise<Table> {
return await processTable(output)
}
export async function doesTableExist(tableId: string): Promise<boolean> {
try {
const table = await getTable(tableId)
return !!table
} catch (err) {
return false
}
}
export async function getAllTables() {
const [internal, external] = await Promise.all([
getAllInternalTables(),

View file

@ -0,0 +1,8 @@
export enum RoleColor {
ADMIN = "var(--spectrum-global-color-static-red-400)",
POWER = "var(--spectrum-global-color-static-orange-400)",
BASIC = "var(--spectrum-global-color-static-green-400)",
PUBLIC = "var(--spectrum-global-color-static-blue-400)",
BUILDER = "var(--spectrum-global-color-static-magenta-600)",
DEFAULT_CUSTOM = "var(--spectrum-global-color-static-magenta-400)",
}

View file

@ -1,6 +1,7 @@
export * from "./api"
export * from "./fields"
export * from "./rows"
export * from "./colors"
export const OperatorOptions = {
Equals: {

View file

@ -1,4 +1,4 @@
import { PermissionLevel, PlanType } from "../../../sdk"
import { PermissionLevel } from "../../../sdk"
export interface ResourcePermissionInfo {
role: string
@ -8,7 +8,6 @@ export interface ResourcePermissionInfo {
export interface GetResourcePermsResponse {
permissions: Record<string, ResourcePermissionInfo>
requiresPlanToModify?: PlanType
}
export interface GetDependantResourcesResponse {

View file

@ -1,4 +1,4 @@
import { Role } from "../../documents"
import { Role, RoleUIMetadata } from "../../documents"
export interface SaveRoleRequest {
_id?: string
@ -7,6 +7,7 @@ export interface SaveRoleRequest {
inherits: string
permissionId: string
version: string
uiMetadata?: RoleUIMetadata
}
export interface SaveRoleResponse extends Role {}

View file

@ -140,6 +140,8 @@ enum Model {
GPT_35_TURBO = "gpt-3.5-turbo",
// will only work with api keys that have access to the GPT4 API
GPT_4 = "gpt-4",
GPT_4O = "gpt-4o",
GPT_4O_MINI = "gpt-4o-mini",
}
export type OpenAIStepOutputs = Omit<BaseAutomationOutputs, "response"> & {

View file

@ -1,9 +1,17 @@
import { Document } from "../document"
import { PermissionLevel } from "../../sdk"
export interface RoleUIMetadata {
displayName?: string
color?: string
description?: string
}
export interface Role extends Document {
permissionId: string
inherits?: string
permissions: { [key: string]: string[] }
permissions: Record<string, PermissionLevel[]>
version?: string
name: string
uiMetadata?: RoleUIMetadata
}

View file

@ -74,9 +74,8 @@ export enum UserStatus {
INACTIVE = "inactive",
}
export interface UserRoles {
[key: string]: string
}
// specifies a map of app ID to role ID
export type UserRoles = Record<string, string>
// UTILITY TYPES

View file

@ -15,4 +15,10 @@ export interface AutomationData {
automation: Automation
}
export interface AutomationRowEvent {
appId: string
row: Row
oldRow: Row
}
export type AutomationJob = Job<AutomationData>

View file

@ -13,6 +13,7 @@ export enum Feature {
APP_BUILDERS = "appBuilders",
OFFLINE = "offline",
EXPANDED_PUBLIC_API = "expandedPublicApi",
// deprecated - no longer licensed
VIEW_PERMISSIONS = "viewPermissions",
VIEW_READONLY_COLUMNS = "viewReadonlyColumns",
BUDIBASE_AI = "budibaseAI",

View file

@ -35,8 +35,9 @@ describe("/api/global/roles", () => {
const role = new roles.Role(
db.generateRoleID(ROLE_NAME),
roles.BUILTIN_ROLE_IDS.BASIC,
permissions.BuiltinPermissionID.READ_ONLY
ROLE_NAME,
permissions.BuiltinPermissionID.READ_ONLY,
{ displayName: roles.BUILTIN_ROLE_IDS.BASIC }
)
beforeAll(async () => {

193
yarn.lock
View file

@ -759,20 +759,20 @@
"@azure/abort-controller" "^1.0.0"
tslib "^2.2.0"
"@azure/identity@^3.4.1":
version "3.4.2"
resolved "https://registry.yarnpkg.com/@azure/identity/-/identity-3.4.2.tgz#6b01724c9caac7cadab6b63c76584345bda8e2de"
integrity sha512-0q5DL4uyR0EZ4RXQKD8MadGH6zTIcloUoS/RVbCpNpej4pwte0xpqYxk8K97Py2RiuUvI7F4GXpoT4046VfufA==
"@azure/identity@4.2.1", "@azure/identity@^3.4.1":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@azure/identity/-/identity-4.2.1.tgz#22b366201e989b7b41c0e1690e103bd579c31e4c"
integrity sha512-U8hsyC9YPcEIzoaObJlRDvp7KiF0MGS7xcWbyJSVvXRkC/HXo1f0oYeBYmEvVgRfacw7GHf6D6yAoh9JHz6A5Q==
dependencies:
"@azure/abort-controller" "^1.0.0"
"@azure/core-auth" "^1.5.0"
"@azure/core-client" "^1.4.0"
"@azure/core-rest-pipeline" "^1.1.0"
"@azure/core-tracing" "^1.0.0"
"@azure/core-util" "^1.6.1"
"@azure/core-util" "^1.3.0"
"@azure/logger" "^1.0.0"
"@azure/msal-browser" "^3.5.0"
"@azure/msal-node" "^2.5.1"
"@azure/msal-browser" "^3.11.1"
"@azure/msal-node" "^2.9.2"
events "^3.0.0"
jws "^4.0.0"
open "^8.0.0"
@ -803,24 +803,24 @@
dependencies:
tslib "^2.2.0"
"@azure/msal-browser@^3.5.0":
version "3.18.0"
resolved "https://registry.yarnpkg.com/@azure/msal-browser/-/msal-browser-3.18.0.tgz#dabbde2c53195a2e0ec8404f61f337c82c159b71"
integrity sha512-jvK5bDUWbpOaJt2Io/rjcaOVcUzkqkrCme/WntdV1SMUc67AiTcEdKuY6G/nMQ7N5Cfsk9SfpugflQwDku53yg==
"@azure/msal-browser@^3.11.1":
version "3.23.0"
resolved "https://registry.yarnpkg.com/@azure/msal-browser/-/msal-browser-3.23.0.tgz#446aaf268247e5943f464f007d3aa3a04abfe95b"
integrity sha512-+QgdMvaeEpdtgRTD7AHHq9aw8uga7mXVHV1KshO1RQ2uI5B55xJ4aEpGlg/ga3H+0arEVcRfT4ZVmX7QLXiCVw==
dependencies:
"@azure/msal-common" "14.13.0"
"@azure/msal-common" "14.14.2"
"@azure/msal-common@14.13.0":
version "14.13.0"
resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-14.13.0.tgz#7377b4909a46d19ea91dadd24af7705e6aa947af"
integrity sha512-b4M/tqRzJ4jGU91BiwCsLTqChveUEyFK3qY2wGfZ0zBswIBZjAxopx5CYt5wzZFKuN15HqRDYXQbztttuIC3nA==
"@azure/msal-common@14.14.2":
version "14.14.2"
resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-14.14.2.tgz#583b4ac9c089953718d7a5e2f3b8df2d4dbb17f4"
integrity sha512-XV0P5kSNwDwCA/SjIxTe9mEAsKB0NqGNSuaVrkCCE2lAyBr/D6YtD80Vkdp4tjWnPFwjzkwldjr1xU/facOJog==
"@azure/msal-node@^2.5.1":
version "2.10.0"
resolved "https://registry.yarnpkg.com/@azure/msal-node/-/msal-node-2.10.0.tgz#0b893ab05dbef5c963aba080c88a0330393c4973"
integrity sha512-JxsSE0464a8IA/+q5EHKmchwNyUFJHtCH00tSXsLaOddwLjG6yVvTH6lGgPcWMhO7YWUXj/XVgVgeE9kZtsPUQ==
"@azure/msal-node@^2.9.2":
version "2.13.1"
resolved "https://registry.yarnpkg.com/@azure/msal-node/-/msal-node-2.13.1.tgz#f144371275b7c3cbe564762b84772a9732457a47"
integrity sha512-sijfzPNorKt6+9g1/miHwhj6Iapff4mPQx1azmmZExgzUROqWTM1o3ACyxDja0g47VpowFy/sxTM/WsuCyXTiw==
dependencies:
"@azure/msal-common" "14.13.0"
"@azure/msal-common" "14.14.2"
jsonwebtoken "^9.0.0"
uuid "^8.3.0"
@ -2053,44 +2053,6 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/backend-core@2.31.8":
version "0.0.0"
dependencies:
"@budibase/nano" "10.1.5"
"@budibase/pouchdb-replication-stream" "1.2.11"
"@budibase/shared-core" "0.0.0"
"@budibase/types" "0.0.0"
aws-cloudfront-sign "3.0.2"
aws-sdk "2.1030.0"
bcrypt "5.1.0"
bcryptjs "2.4.3"
bull "4.10.1"
correlation-id "4.0.0"
dd-trace "5.2.0"
dotenv "16.0.1"
ioredis "5.3.2"
joi "17.6.0"
jsonwebtoken "9.0.2"
knex "2.4.2"
koa-passport "^6.0.0"
koa-pino-logger "4.0.0"
lodash "4.17.21"
node-fetch "2.6.7"
passport-google-oauth "2.0.0"
passport-local "1.0.0"
passport-oauth2-refresh "^2.1.0"
pino "8.11.0"
pino-http "8.3.3"
posthog-node "4.0.1"
pouchdb "7.3.0"
pouchdb-find "7.2.2"
redlock "4.2.0"
rotating-file-stream "3.1.0"
sanitize-s3-objectkey "0.0.1"
semver "^7.5.4"
tar-fs "2.1.1"
uuid "^8.3.2"
"@budibase/handlebars-helpers@^0.13.2":
version "0.13.2"
resolved "https://registry.yarnpkg.com/@budibase/handlebars-helpers/-/handlebars-helpers-0.13.2.tgz#73ab51c464e91fd955b429017648e0257060db77"
@ -2133,45 +2095,6 @@
pouchdb-promise "^6.0.4"
through2 "^2.0.0"
"@budibase/pro@npm:@budibase/pro@latest":
version "2.31.8"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.31.8.tgz#92b27f99f815f5d20bf58bfae916760b14a036da"
integrity sha512-nmNKVoMdUVqEIq6xqoBq0gVBCLkoPMszmn0Zu0SJ/Dc2SpsXhPz9S3n9xXfAA+FHUg9LgUAS+eKPCKPWZXtDHQ==
dependencies:
"@budibase/backend-core" "2.31.8"
"@budibase/shared-core" "2.31.8"
"@budibase/string-templates" "2.31.8"
"@budibase/types" "2.31.8"
"@koa/router" "8.0.8"
bull "4.10.1"
dd-trace "5.2.0"
joi "17.6.0"
jsonwebtoken "9.0.2"
lru-cache "^7.14.1"
memorystream "^0.3.1"
node-fetch "2.6.7"
scim-patch "^0.8.1"
scim2-parse-filter "^0.2.8"
"@budibase/shared-core@2.31.8":
version "0.0.0"
dependencies:
"@budibase/types" "0.0.0"
cron-validate "1.4.5"
"@budibase/string-templates@2.31.8":
version "0.0.0"
dependencies:
"@budibase/handlebars-helpers" "^0.13.2"
dayjs "^1.10.8"
handlebars "^4.7.8"
lodash.clonedeep "^4.5.0"
"@budibase/types@2.31.8":
version "0.0.0"
dependencies:
scim-patch "^0.8.1"
"@bull-board/api@5.10.2":
version "5.10.2"
resolved "https://registry.yarnpkg.com/@bull-board/api/-/api-5.10.2.tgz#ae8ff6918b23897bf879a6ead3683f964374c4b3"
@ -6967,16 +6890,16 @@ aggregate-error@^3.0.0:
clean-stack "^2.0.0"
indent-string "^4.0.0"
airtable@0.10.1:
version "0.10.1"
resolved "https://registry.yarnpkg.com/airtable/-/airtable-0.10.1.tgz#0b311002bb44b39f19bf7c4bd2d47d75c733bf87"
integrity sha512-obFW+R3ly2mKtCj0D/xto0ggUvYwdM0RJT3VJ9wvgqoxDkzqg2mNtkuTNfYjF6wWQA0GvoHG9guqzgBBqFjItw==
airtable@0.12.2:
version "0.12.2"
resolved "https://registry.yarnpkg.com/airtable/-/airtable-0.12.2.tgz#e53e66db86744f9bc684faa58881d6c9c12f0e6f"
integrity sha512-HS3VytUBTKj8A0vPl7DDr5p/w3IOGv6RXL0fv7eczOWAtj9Xe8ri4TAiZRXoOyo+Z/COADCj+oARFenbxhmkIg==
dependencies:
"@types/node" ">=8.0.0 <15"
abort-controller "^3.0.0"
abortcontroller-polyfill "^1.4.0"
lodash "^4.17.19"
node-fetch "^2.6.1"
lodash "^4.17.21"
node-fetch "^2.6.7"
ajv-formats@^2.0.2:
version "2.1.1"
@ -11367,27 +11290,13 @@ fast-url-parser@^1.1.3:
dependencies:
punycode "^1.3.2"
fast-xml-parser@4.2.5:
version "4.2.5"
resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz#a6747a09296a6cb34f2ae634019bf1738f3b421f"
integrity "sha1-pnR6CSlqbLNPKuY0AZvxc487Qh8= sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g=="
dependencies:
strnum "^1.0.5"
fast-xml-parser@^4.1.3:
fast-xml-parser@4.2.5, fast-xml-parser@4.4.1, fast-xml-parser@^4.1.3, fast-xml-parser@^4.2.2, fast-xml-parser@^4.2.5:
version "4.4.1"
resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz#86dbf3f18edf8739326447bcaac31b4ae7f6514f"
integrity sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==
dependencies:
strnum "^1.0.5"
fast-xml-parser@^4.2.2, fast-xml-parser@^4.2.5:
version "4.4.0"
resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.4.0.tgz#341cc98de71e9ba9e651a67f41f1752d1441a501"
integrity sha512-kLY3jFlwIYwBNDojclKsNAC12sfD6NwW74QB2CoNGPvtVxjliYehVunB3HYyNi+n4Tt1dAcgwYvmKF/Z18flqg==
dependencies:
strnum "^1.0.5"
fastq@^1.6.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c"
@ -13253,7 +13162,7 @@ is-boolean-object@^1.1.0:
call-bind "^1.0.2"
has-tostringtag "^1.0.0"
is-buffer@^1.1.5, is-buffer@~1.1.6:
is-buffer@~1.1.6:
version "1.1.6"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
@ -14653,14 +14562,7 @@ kill-port@^1.6.1:
get-them-args "1.3.2"
shell-exec "1.0.2"
kind-of@^3.0.2, kind-of@^3.1.0:
version "3.2.2"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
integrity sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==
dependencies:
is-buffer "^1.1.5"
kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3:
kind-of@6.0.3, kind-of@^3.0.2, kind-of@^3.1.0, kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
@ -15485,7 +15387,7 @@ lodash.xor@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.xor/-/lodash.xor-4.5.0.tgz#4d48ed7e98095b0632582ba714d3ff8ae8fb1db6"
integrity sha512-sVN2zimthq7aZ5sPGXnSz32rZPuqcparVW50chJQe+mzTYV+IsxSsl/2gnkWWE2Of7K3myBQBqtLKOUEHJKRsQ==
lodash@4.17.21, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.3, lodash@^4.7.0:
lodash@4.17.21, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.3, lodash@^4.7.0:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -17798,11 +17700,21 @@ periscopic@^3.1.0:
estree-walker "^3.0.0"
is-reference "^3.0.0"
pg-cloudflare@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98"
integrity sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==
pg-connection-string@2.5.0, pg-connection-string@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34"
integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==
pg-connection-string@^2.6.4:
version "2.6.4"
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.4.tgz#f543862adfa49fa4e14bc8a8892d2a84d754246d"
integrity sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==
pg-int8@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c"
@ -17813,11 +17725,21 @@ pg-pool@^3.6.0:
resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.0.tgz#3190df3e4747a0d23e5e9e8045bcd99bda0a712e"
integrity sha512-clFRf2ksqd+F497kWFyM21tMjeikn60oGDmqMT8UBrynEwVEX/5R5xd2sdvdo1cZCFlguORNpVuqxIj+aK4cfQ==
pg-pool@^3.6.2:
version "3.6.2"
resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.2.tgz#3a592370b8ae3f02a7c8130d245bc02fa2c5f3f2"
integrity sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==
pg-protocol@*, pg-protocol@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.6.0.tgz#4c91613c0315349363af2084608db843502f8833"
integrity sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==
pg-protocol@^1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.6.1.tgz#21333e6d83b01faaebfe7a33a7ad6bfd9ed38cb3"
integrity sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==
pg-types@^2.1.0, pg-types@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3"
@ -17842,6 +17764,19 @@ pg@8.10.0:
pg-types "^2.1.0"
pgpass "1.x"
pg@^8.12.0:
version "8.12.0"
resolved "https://registry.yarnpkg.com/pg/-/pg-8.12.0.tgz#9341724db571022490b657908f65aee8db91df79"
integrity sha512-A+LHUSnwnxrnL/tZ+OLfqR1SxLN3c/pgDztZ47Rpbsd4jUytsTtwQo/TLPRzPJMp/1pbhYVhH9cuSZLAajNfjQ==
dependencies:
pg-connection-string "^2.6.4"
pg-pool "^3.6.2"
pg-protocol "^1.6.1"
pg-types "^2.1.0"
pgpass "1.x"
optionalDependencies:
pg-cloudflare "^1.1.1"
pgpass@1.x:
version "1.0.5"
resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d"