From a2667c6d72fdd6b2a6774d6cafc77926ea394a68 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 4 Aug 2023 15:43:02 +0100 Subject: [PATCH 01/45] Removing the ability to set roles, builder and admin structure through basic public API. --- .../api/controllers/public/applications.ts | 18 ++++---- .../src/api/controllers/public/queries.ts | 6 ++- .../server/src/api/controllers/public/rows.ts | 13 +++--- .../src/api/controllers/public/tables.ts | 13 +++--- .../src/api/controllers/public/users.ts | 44 ++++++++++++------- packages/server/src/utilities/users.ts | 17 ++----- 6 files changed, 60 insertions(+), 51 deletions(-) diff --git a/packages/server/src/api/controllers/public/applications.ts b/packages/server/src/api/controllers/public/applications.ts index c4041bedcf..fd72db95d3 100644 --- a/packages/server/src/api/controllers/public/applications.ts +++ b/packages/server/src/api/controllers/public/applications.ts @@ -3,6 +3,8 @@ import { search as stringSearch, addRev } from "./utils" import * as controller from "../application" import * as deployController from "../deploy" import { Application } from "../../../definitions/common" +import { UserCtx } from "@budibase/types" +import { Next } from "koa" function fixAppID(app: Application, params: any) { if (!params) { @@ -14,7 +16,7 @@ function fixAppID(app: Application, params: any) { return app } -async function setResponseApp(ctx: any) { +async function setResponseApp(ctx: UserCtx) { const appId = ctx.body?.appId if (appId && (!ctx.params || !ctx.params.appId)) { ctx.params = { appId } @@ -28,14 +30,14 @@ async function setResponseApp(ctx: any) { } } -export async function search(ctx: any, next: any) { +export async function search(ctx: UserCtx, next: Next) { const { name } = ctx.request.body const apps = await dbCore.getAllApps({ all: true }) ctx.body = stringSearch(apps, name) await next() } -export async function create(ctx: any, next: any) { +export async function create(ctx: UserCtx, next: Next) { if (!ctx.request.body || !ctx.request.body.useTemplate) { ctx.request.body = { useTemplate: false, @@ -47,14 +49,14 @@ export async function create(ctx: any, next: any) { await next() } -export async function read(ctx: any, next: any) { +export async function read(ctx: UserCtx, next: Next) { await context.doInAppContext(ctx.params.appId, async () => { await setResponseApp(ctx) await next() }) } -export async function update(ctx: any, next: any) { +export async function update(ctx: UserCtx, next: Next) { ctx.request.body = await addRev(fixAppID(ctx.request.body, ctx.params)) await context.doInAppContext(ctx.params.appId, async () => { await controller.update(ctx) @@ -63,7 +65,7 @@ export async function update(ctx: any, next: any) { }) } -export async function destroy(ctx: any, next: any) { +export async function destroy(ctx: UserCtx, next: Next) { await context.doInAppContext(ctx.params.appId, async () => { // get the app before deleting it await setResponseApp(ctx) @@ -75,14 +77,14 @@ export async function destroy(ctx: any, next: any) { }) } -export async function unpublish(ctx: any, next: any) { +export async function unpublish(ctx: UserCtx, next: Next) { await context.doInAppContext(ctx.params.appId, async () => { await controller.unpublish(ctx) await next() }) } -export async function publish(ctx: any, next: any) { +export async function publish(ctx: UserCtx, next: Next) { await context.doInAppContext(ctx.params.appId, async () => { await deployController.publishApp(ctx) await next() diff --git a/packages/server/src/api/controllers/public/queries.ts b/packages/server/src/api/controllers/public/queries.ts index 57ec608379..3cb1ab3812 100644 --- a/packages/server/src/api/controllers/public/queries.ts +++ b/packages/server/src/api/controllers/public/queries.ts @@ -1,14 +1,16 @@ import { search as stringSearch } from "./utils" import * as queryController from "../query" +import { UserCtx } from "@budibase/types" +import { Next } from "koa" -export async function search(ctx: any, next: any) { +export async function search(ctx: UserCtx, next: Next) { await queryController.fetch(ctx) const { name } = ctx.request.body ctx.body = stringSearch(ctx.body, name) await next() } -export async function execute(ctx: any, next: any) { +export async function execute(ctx: UserCtx, next: Next) { // don't wrap this, already returns "data" await queryController.executeV2(ctx) await next() diff --git a/packages/server/src/api/controllers/public/rows.ts b/packages/server/src/api/controllers/public/rows.ts index 39cf85a2a3..16403b06c9 100644 --- a/packages/server/src/api/controllers/public/rows.ts +++ b/packages/server/src/api/controllers/public/rows.ts @@ -1,7 +1,8 @@ import * as rowController from "../row" import { addRev } from "./utils" -import { Row } from "@budibase/types" +import { Row, UserCtx } from "@budibase/types" import { convertBookmark } from "../../../utilities" +import { Next } from "koa" // makes sure that the user doesn't need to pass in the type, tableId or _id params for // the call to be correct @@ -21,7 +22,7 @@ export function fixRow(row: Row, params: any) { return row } -export async function search(ctx: any, next: any) { +export async function search(ctx: UserCtx, next: Next) { let { sort, paginate, bookmark, limit, query } = ctx.request.body // update the body to the correct format of the internal search if (!sort) { @@ -40,25 +41,25 @@ export async function search(ctx: any, next: any) { await next() } -export async function create(ctx: any, next: any) { +export async function create(ctx: UserCtx, next: Next) { ctx.request.body = fixRow(ctx.request.body, ctx.params) await rowController.save(ctx) await next() } -export async function read(ctx: any, next: any) { +export async function read(ctx: UserCtx, next: Next) { await rowController.fetchEnrichedRow(ctx) await next() } -export async function update(ctx: any, next: any) { +export async function update(ctx: UserCtx, next: Next) { const { tableId } = ctx.params ctx.request.body = await addRev(fixRow(ctx.request.body, ctx.params), tableId) await rowController.save(ctx) await next() } -export async function destroy(ctx: any, next: any) { +export async function destroy(ctx: UserCtx, next: Next) { const { tableId } = ctx.params // set the body as expected, with the _id and _rev fields ctx.request.body = await addRev( diff --git a/packages/server/src/api/controllers/public/tables.ts b/packages/server/src/api/controllers/public/tables.ts index a346a750da..7486172fa3 100644 --- a/packages/server/src/api/controllers/public/tables.ts +++ b/packages/server/src/api/controllers/public/tables.ts @@ -1,6 +1,7 @@ import { search as stringSearch, addRev } from "./utils" import * as controller from "../table" -import { Table } from "@budibase/types" +import { Table, UserCtx } from "@budibase/types" +import { Next } from "koa" function fixTable(table: Table, params: any) { if (!params || !table) { @@ -15,24 +16,24 @@ function fixTable(table: Table, params: any) { return table } -export async function search(ctx: any, next: any) { +export async function search(ctx: UserCtx, next: Next) { const { name } = ctx.request.body await controller.fetch(ctx) ctx.body = stringSearch(ctx.body, name) await next() } -export async function create(ctx: any, next: any) { +export async function create(ctx: UserCtx, next: Next) { await controller.save(ctx) await next() } -export async function read(ctx: any, next: any) { +export async function read(ctx: UserCtx, next: Next) { await controller.find(ctx) await next() } -export async function update(ctx: any, next: any) { +export async function update(ctx: UserCtx, next: Next) { ctx.request.body = await addRev( fixTable(ctx.request.body, ctx.params), ctx.params.tableId @@ -41,7 +42,7 @@ export async function update(ctx: any, next: any) { await next() } -export async function destroy(ctx: any, next: any) { +export async function destroy(ctx: UserCtx, next: Next) { await controller.destroy(ctx) ctx.body = ctx.table await next() diff --git a/packages/server/src/api/controllers/public/users.ts b/packages/server/src/api/controllers/public/users.ts index 7192077d04..7aaf520dc4 100644 --- a/packages/server/src/api/controllers/public/users.ts +++ b/packages/server/src/api/controllers/public/users.ts @@ -7,16 +7,32 @@ import { import { publicApiUserFix } from "../../../utilities/users" import { db as dbCore } from "@budibase/backend-core" import { search as stringSearch } from "./utils" -import { BBContext, User } from "@budibase/types" +import { UserCtx, User } from "@budibase/types" +import { Next } from "koa" -function isLoggedInUser(ctx: BBContext, user: User) { +function removeRoles(ctx: UserCtx, oldUser?: User) { + const user = ctx.request.body + if (user.builder) { + user.builder = oldUser?.builder || undefined + } + if (user.admin) { + user.admin = oldUser?.admin || undefined + } + if (user.roles) { + user.roles = oldUser?.roles || {} + } + ctx.request.body = user + return ctx +} + +function isLoggedInUser(ctx: UserCtx, user: User) { const loggedInId = ctx.user?._id const globalUserId = dbCore.getGlobalIDFromUserMetadataID(loggedInId!) // check both just incase return globalUserId === user._id || loggedInId === user._id } -function getUser(ctx: BBContext, userId?: string) { +function getUser(ctx: UserCtx, userId?: string) { if (userId) { ctx.params = { userId } } else if (!ctx.params?.userId) { @@ -25,42 +41,38 @@ function getUser(ctx: BBContext, userId?: string) { return readGlobalUser(ctx) } -export async function search(ctx: BBContext, next: any) { +export async function search(ctx: UserCtx, next: Next) { const { name } = ctx.request.body const users = await allGlobalUsers(ctx) ctx.body = stringSearch(users, name, "email") await next() } -export async function create(ctx: BBContext, next: any) { - const response = await saveGlobalUser(publicApiUserFix(ctx)) +export async function create(ctx: UserCtx, next: Next) { + ctx = publicApiUserFix(removeRoles(ctx)) + const response = await saveGlobalUser(ctx) ctx.body = await getUser(ctx, response._id) await next() } -export async function read(ctx: BBContext, next: any) { +export async function read(ctx: UserCtx, next: Next) { ctx.body = await readGlobalUser(ctx) await next() } -export async function update(ctx: BBContext, next: any) { +export async function update(ctx: UserCtx, next: Next) { const user = await readGlobalUser(ctx) ctx.request.body = { ...ctx.request.body, _rev: user._rev, } - // disallow updating your own role - always overwrite with DB roles - if (isLoggedInUser(ctx, user)) { - ctx.request.body.builder = user.builder - ctx.request.body.admin = user.admin - ctx.request.body.roles = user.roles - } - const response = await saveGlobalUser(publicApiUserFix(ctx)) + ctx = publicApiUserFix(removeRoles(ctx, user)) + const response = await saveGlobalUser(ctx) ctx.body = await getUser(ctx, response._id) await next() } -export async function destroy(ctx: BBContext, next: any) { +export async function destroy(ctx: UserCtx, next: Next) { const user = await getUser(ctx) // disallow deleting yourself if (isLoggedInUser(ctx, user)) { diff --git a/packages/server/src/utilities/users.ts b/packages/server/src/utilities/users.ts index 1498a79719..f841ec3646 100644 --- a/packages/server/src/utilities/users.ts +++ b/packages/server/src/utilities/users.ts @@ -1,9 +1,9 @@ import { InternalTables } from "../db/utils" import { getGlobalUser } from "./global" -import { context, db as dbCore, roles } from "@budibase/backend-core" -import { BBContext } from "@budibase/types" +import { context, roles } from "@budibase/backend-core" +import { UserCtx } from "@budibase/types" -export async function getFullUser(ctx: BBContext, userId: string) { +export async function getFullUser(ctx: UserCtx, userId: string) { const global = await getGlobalUser(userId) let metadata: any = {} @@ -29,21 +29,12 @@ export async function getFullUser(ctx: BBContext, userId: string) { } } -export function publicApiUserFix(ctx: BBContext) { +export function publicApiUserFix(ctx: UserCtx) { if (!ctx.request.body) { return ctx } if (!ctx.request.body._id && ctx.params.userId) { ctx.request.body._id = ctx.params.userId } - if (!ctx.request.body.roles) { - ctx.request.body.roles = {} - } else { - const newRoles: { [key: string]: any } = {} - for (let [appId, role] of Object.entries(ctx.request.body.roles)) { - newRoles[dbCore.getProdAppID(appId)] = role - } - ctx.request.body.roles = newRoles - } return ctx } From ec761c238712d4ac04cebbc0763caad596a74ad9 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 4 Aug 2023 18:01:45 +0100 Subject: [PATCH 02/45] Building out public API for role assignment and un-assignment - need to flesh out pro component. --- packages/server/specs/openapi.json | 275 ++++++++++++------ packages/server/specs/openapi.yaml | 195 ++++++++----- packages/server/specs/resources/index.ts | 2 + packages/server/specs/resources/roles.ts | 65 +++++ packages/server/specs/resources/user.ts | 32 -- .../src/api/controllers/public/roles.ts | 15 + .../server/src/api/routes/public/roles.ts | 54 ++++ packages/server/src/definitions/openapi.ts | 133 ++++++--- 8 files changed, 545 insertions(+), 226 deletions(-) create mode 100644 packages/server/specs/resources/roles.ts create mode 100644 packages/server/src/api/controllers/public/roles.ts create mode 100644 packages/server/src/api/routes/public/roles.ts diff --git a/packages/server/specs/openapi.json b/packages/server/specs/openapi.json index d97b09568c..1071a39c29 100644 --- a/packages/server/specs/openapi.json +++ b/packages/server/specs/openapi.json @@ -1519,34 +1519,6 @@ "forceResetPassword": { "description": "If set to true forces the user to reset their password on first login.", "type": "boolean" - }, - "builder": { - "description": "Describes if the user is a builder user or not.", - "type": "object", - "properties": { - "global": { - "description": "If set to true the user will be able to build any app in the system.", - "type": "boolean" - } - } - }, - "admin": { - "description": "Describes if the user is an admin user or not.", - "type": "object", - "properties": { - "global": { - "description": "If set to true the user will be able to administrate the system.", - "type": "boolean" - } - } - }, - "roles": { - "description": "Contains the roles of the user per app (assuming they are not a builder user).", - "type": "object", - "additionalProperties": { - "type": "string", - "description": "A map of app ID (production app ID, minus the _dev component) to a role ID, e.g. ADMIN." - } } }, "required": [ @@ -1587,34 +1559,6 @@ "description": "If set to true forces the user to reset their password on first login.", "type": "boolean" }, - "builder": { - "description": "Describes if the user is a builder user or not.", - "type": "object", - "properties": { - "global": { - "description": "If set to true the user will be able to build any app in the system.", - "type": "boolean" - } - } - }, - "admin": { - "description": "Describes if the user is an admin user or not.", - "type": "object", - "properties": { - "global": { - "description": "If set to true the user will be able to administrate the system.", - "type": "boolean" - } - } - }, - "roles": { - "description": "Contains the roles of the user per app (assuming they are not a builder user).", - "type": "object", - "additionalProperties": { - "type": "string", - "description": "A map of app ID (production app ID, minus the _dev component) to a role ID, e.g. ADMIN." - } - }, "_id": { "description": "The ID of the user.", "type": "string" @@ -1666,34 +1610,6 @@ "description": "If set to true forces the user to reset their password on first login.", "type": "boolean" }, - "builder": { - "description": "Describes if the user is a builder user or not.", - "type": "object", - "properties": { - "global": { - "description": "If set to true the user will be able to build any app in the system.", - "type": "boolean" - } - } - }, - "admin": { - "description": "Describes if the user is an admin user or not.", - "type": "object", - "properties": { - "global": { - "description": "If set to true the user will be able to administrate the system.", - "type": "boolean" - } - } - }, - "roles": { - "description": "Contains the roles of the user per app (assuming they are not a builder user).", - "type": "object", - "additionalProperties": { - "type": "string", - "description": "A map of app ID (production app ID, minus the _dev component) to a role ID, e.g. ADMIN." - } - }, "_id": { "description": "The ID of the user.", "type": "string" @@ -1833,6 +1749,135 @@ "required": [ "name" ] + }, + "rolesAssign": { + "type": "object", + "properties": { + "builder": { + "type": "object", + "properties": { + "global": { + "type": "boolean" + } + }, + "description": "Add/remove global builder permissions from the list of users.", + "required": [ + "global" + ] + }, + "admin": { + "type": "object", + "properties": { + "global": { + "type": "boolean" + } + }, + "description": "Add/remove global admin permissions from the list of users.", + "required": [ + "global" + ] + }, + "role": { + "type": "object", + "properties": { + "roleId": { + "description": "The role ID, such as BASIC, ADMIN or a custom role ID.", + "type": "string" + }, + "appId": { + "description": "The app that the role relates to.", + "type": "string" + } + }, + "description": "Add/remove a per-app role, such as BASIC, ADMIN etc.", + "required": [ + "roleId", + "appId" + ] + }, + "userIds": { + "description": "The user IDs to be updated to add/remove the specified roles.", + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "userIds" + ] + }, + "rolesUnAssign": { + "type": "object", + "properties": { + "builder": { + "type": "object", + "properties": { + "global": { + "type": "boolean" + } + }, + "description": "Add/remove global builder permissions from the list of users.", + "required": [ + "global" + ] + }, + "admin": { + "type": "object", + "properties": { + "global": { + "type": "boolean" + } + }, + "description": "Add/remove global admin permissions from the list of users.", + "required": [ + "global" + ] + }, + "role": { + "type": "object", + "properties": { + "roleId": { + "description": "The role ID, such as BASIC, ADMIN or a custom role ID.", + "type": "string" + }, + "appId": { + "description": "The app that the role relates to.", + "type": "string" + } + }, + "description": "Add/remove a per-app role, such as BASIC, ADMIN etc.", + "required": [ + "roleId", + "appId" + ] + }, + "userIds": { + "description": "The user IDs to be updated to add/remove the specified roles.", + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "userIds" + ] + }, + "rolesOutput": { + "type": "object", + "properties": { + "userIds": { + "description": "The updated users' IDs", + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "userIds" + ] } } }, @@ -2186,6 +2231,68 @@ } } }, + "/roles/assign": { + "post": { + "operationId": "roleAssign", + "summary": "Assign a role to a list of users", + "tags": [ + "roles" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/rolesAssign" + } + } + } + }, + "responses": { + "200": { + "description": "Returns a list of updated user IDs", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/rolesOutput" + } + } + } + } + } + } + }, + "/roles/unassign": { + "post": { + "operationId": "roleUnAssign", + "summary": "Un-assign a role from a list of users", + "tags": [ + "roles" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/rolesUnAssign" + } + } + } + }, + "responses": { + "200": { + "description": "Returns a list of updated user IDs", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/rolesOutput" + } + } + } + } + } + } + }, "/tables/{tableId}/rows": { "post": { "operationId": "rowCreate", diff --git a/packages/server/specs/openapi.yaml b/packages/server/specs/openapi.yaml index 86807c9981..aa7b3ddb51 100644 --- a/packages/server/specs/openapi.yaml +++ b/packages/server/specs/openapi.yaml @@ -1296,29 +1296,6 @@ components: description: If set to true forces the user to reset their password on first login. type: boolean - builder: - description: Describes if the user is a builder user or not. - type: object - properties: - global: - description: If set to true the user will be able to build any app in the - system. - type: boolean - admin: - description: Describes if the user is an admin user or not. - type: object - properties: - global: - description: If set to true the user will be able to administrate the system. - type: boolean - roles: - description: Contains the roles of the user per app (assuming they are not a - builder user). - type: object - additionalProperties: - type: string - description: A map of app ID (production app ID, minus the _dev component) to a - role ID, e.g. ADMIN. required: - email - roles @@ -1351,29 +1328,6 @@ components: description: If set to true forces the user to reset their password on first login. type: boolean - builder: - description: Describes if the user is a builder user or not. - type: object - properties: - global: - description: If set to true the user will be able to build any app in the - system. - type: boolean - admin: - description: Describes if the user is an admin user or not. - type: object - properties: - global: - description: If set to true the user will be able to administrate the system. - type: boolean - roles: - description: Contains the roles of the user per app (assuming they are not a - builder user). - type: object - additionalProperties: - type: string - description: A map of app ID (production app ID, minus the _dev component) to a - role ID, e.g. ADMIN. _id: description: The ID of the user. type: string @@ -1414,29 +1368,6 @@ components: description: If set to true forces the user to reset their password on first login. type: boolean - builder: - description: Describes if the user is a builder user or not. - type: object - properties: - global: - description: If set to true the user will be able to build any app in the - system. - type: boolean - admin: - description: Describes if the user is an admin user or not. - type: object - properties: - global: - description: If set to true the user will be able to administrate the system. - type: boolean - roles: - description: Contains the roles of the user per app (assuming they are not a - builder user). - type: object - additionalProperties: - type: string - description: A map of app ID (production app ID, minus the _dev component) to a - role ID, e.g. ADMIN. _id: description: The ID of the user. type: string @@ -1547,6 +1478,94 @@ components: insensitive starts with match. required: - name + rolesAssign: + type: object + properties: + builder: + type: object + properties: + global: + type: boolean + description: Add/remove global builder permissions from the list of users. + required: + - global + admin: + type: object + properties: + global: + type: boolean + description: Add/remove global admin permissions from the list of users. + required: + - global + role: + type: object + properties: + roleId: + description: The role ID, such as BASIC, ADMIN or a custom role ID. + type: string + appId: + description: The app that the role relates to. + type: string + description: Add/remove a per-app role, such as BASIC, ADMIN etc. + required: + - roleId + - appId + userIds: + description: The user IDs to be updated to add/remove the specified roles. + type: array + items: + type: string + required: + - userIds + rolesUnAssign: + type: object + properties: + builder: + type: object + properties: + global: + type: boolean + description: Add/remove global builder permissions from the list of users. + required: + - global + admin: + type: object + properties: + global: + type: boolean + description: Add/remove global admin permissions from the list of users. + required: + - global + role: + type: object + properties: + roleId: + description: The role ID, such as BASIC, ADMIN or a custom role ID. + type: string + appId: + description: The app that the role relates to. + type: string + description: Add/remove a per-app role, such as BASIC, ADMIN etc. + required: + - roleId + - appId + userIds: + description: The user IDs to be updated to add/remove the specified roles. + type: array + items: + type: string + required: + - userIds + rolesOutput: + type: object + properties: + userIds: + description: The updated users' IDs + type: array + items: + type: string + required: + - userIds security: - ApiKeyAuth: [] paths: @@ -1757,6 +1776,44 @@ paths: examples: queries: $ref: "#/components/examples/queries" + /roles/assign: + post: + operationId: roleAssign + summary: Assign a role to a list of users + tags: + - roles + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/rolesAssign" + responses: + "200": + description: Returns a list of updated user IDs + content: + application/json: + schema: + $ref: "#/components/schemas/rolesOutput" + /roles/unassign: + post: + operationId: roleUnAssign + summary: Un-assign a role from a list of users + tags: + - roles + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/rolesUnAssign" + responses: + "200": + description: Returns a list of updated user IDs + content: + application/json: + schema: + $ref: "#/components/schemas/rolesOutput" "/tables/{tableId}/rows": post: operationId: rowCreate diff --git a/packages/server/specs/resources/index.ts b/packages/server/specs/resources/index.ts index 6b8a1aa437..c06148b7de 100644 --- a/packages/server/specs/resources/index.ts +++ b/packages/server/specs/resources/index.ts @@ -5,6 +5,7 @@ import query from "./query" import user from "./user" import metrics from "./metrics" import misc from "./misc" +import roles from "./roles" export const examples = { ...application.getExamples(), @@ -23,4 +24,5 @@ export const schemas = { ...query.getSchemas(), ...user.getSchemas(), ...misc.getSchemas(), + ...roles.getSchemas(), } diff --git a/packages/server/specs/resources/roles.ts b/packages/server/specs/resources/roles.ts new file mode 100644 index 0000000000..02254261be --- /dev/null +++ b/packages/server/specs/resources/roles.ts @@ -0,0 +1,65 @@ +import { object } from "./utils" +import Resource from "./utils/Resource" + +const roleSchema = object( + { + builder: object( + { + global: { + type: "boolean", + }, + }, + { + description: + "Add/remove global builder permissions from the list of users.", + } + ), + admin: object( + { + global: { + type: "boolean", + }, + }, + { + description: + "Add/remove global admin permissions from the list of users.", + } + ), + role: object( + { + roleId: { + description: "The role ID, such as BASIC, ADMIN or a custom role ID.", + type: "string", + }, + appId: { + description: "The app that the role relates to.", + type: "string", + }, + }, + { description: "Add/remove a per-app role, such as BASIC, ADMIN etc." } + ), + userIds: { + description: + "The user IDs to be updated to add/remove the specified roles.", + type: "array", + items: { + type: "string", + }, + }, + }, + { required: ["userIds"] } +) + +export default new Resource().setSchemas({ + rolesAssign: roleSchema, + rolesUnAssign: roleSchema, + rolesOutput: object({ + userIds: { + description: "The updated users' IDs", + type: "array", + items: { + type: "string", + }, + }, + }), +}) diff --git a/packages/server/specs/resources/user.ts b/packages/server/specs/resources/user.ts index a7b9f1ddb9..9ec5388672 100644 --- a/packages/server/specs/resources/user.ts +++ b/packages/server/specs/resources/user.ts @@ -57,38 +57,6 @@ const userSchema = object( "If set to true forces the user to reset their password on first login.", type: "boolean", }, - builder: { - description: "Describes if the user is a builder user or not.", - type: "object", - properties: { - global: { - description: - "If set to true the user will be able to build any app in the system.", - type: "boolean", - }, - }, - }, - admin: { - description: "Describes if the user is an admin user or not.", - type: "object", - properties: { - global: { - description: - "If set to true the user will be able to administrate the system.", - type: "boolean", - }, - }, - }, - roles: { - description: - "Contains the roles of the user per app (assuming they are not a builder user).", - type: "object", - additionalProperties: { - type: "string", - description: - "A map of app ID (production app ID, minus the _dev component) to a role ID, e.g. ADMIN.", - }, - }, }, { required: ["email", "roles"] } ) diff --git a/packages/server/src/api/controllers/public/roles.ts b/packages/server/src/api/controllers/public/roles.ts new file mode 100644 index 0000000000..3b70094ae1 --- /dev/null +++ b/packages/server/src/api/controllers/public/roles.ts @@ -0,0 +1,15 @@ +import { UserCtx } from "@budibase/types" +import { Next } from "koa" + +async function assign(ctx: UserCtx, next: Next) { + ctx.body = { message: "roles assigned" } +} + +async function unAssign(ctx: UserCtx, next: Next) { + ctx.body = { message: "roles un-assigned" } +} + +export default { + assign, + unAssign, +} diff --git a/packages/server/src/api/routes/public/roles.ts b/packages/server/src/api/routes/public/roles.ts new file mode 100644 index 0000000000..2332a0ffd0 --- /dev/null +++ b/packages/server/src/api/routes/public/roles.ts @@ -0,0 +1,54 @@ +import controller from "../../controllers/public/roles" +import Endpoint from "./utils/Endpoint" + +const write = [] + +/** + * @openapi + * /roles/assign: + * post: + * operationId: roleAssign + * summary: Assign a role to a list of users + * tags: + * - roles + * requestBody: + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/rolesAssign' + * responses: + * 200: + * description: Returns a list of updated user IDs + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/rolesOutput' + */ +write.push(new Endpoint("post", "/roles/assign", controller.assign)) + +/** + * @openapi + * /roles/unassign: + * post: + * operationId: roleUnAssign + * summary: Un-assign a role from a list of users + * tags: + * - roles + * requestBody: + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/rolesUnAssign' + * responses: + * 200: + * description: Returns a list of updated user IDs + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/rolesOutput' + */ +write.push(new Endpoint("post", "/roles/unassign", controller.unAssign)) + +export default { write, read: [] } diff --git a/packages/server/src/definitions/openapi.ts b/packages/server/src/definitions/openapi.ts index 5ca4990647..ee078d0821 100644 --- a/packages/server/src/definitions/openapi.ts +++ b/packages/server/src/definitions/openapi.ts @@ -34,6 +34,12 @@ export interface paths { /** Based on query properties (currently only name) search for queries. */ post: operations["querySearch"]; }; + "/roles/assign": { + post: operations["roleAssign"]; + }; + "/roles/unassign": { + post: operations["roleUnAssign"]; + }; "/tables/{tableId}/rows": { /** Creates a row within the specified table. */ post: operations["rowCreate"]; @@ -256,7 +262,8 @@ export interface components { | "auto" | "json" | "internal" - | "barcodeqr"; + | "barcodeqr" + | "bigint"; /** @description A constraint can be applied to the column which will be validated against when a row is saved. */ constraints?: { /** @enum {string} */ @@ -362,7 +369,8 @@ export interface components { | "auto" | "json" | "internal" - | "barcodeqr"; + | "barcodeqr" + | "bigint"; /** @description A constraint can be applied to the column which will be validated against when a row is saved. */ constraints?: { /** @enum {string} */ @@ -470,7 +478,8 @@ export interface components { | "auto" | "json" | "internal" - | "barcodeqr"; + | "barcodeqr" + | "bigint"; /** @description A constraint can be applied to the column which will be validated against when a row is saved. */ constraints?: { /** @enum {string} */ @@ -577,18 +586,8 @@ export interface components { lastName?: string; /** @description If set to true forces the user to reset their password on first login. */ forceResetPassword?: boolean; - /** @description Describes if the user is a builder user or not. */ - builder?: { - /** @description If set to true the user will be able to build any app in the system. */ - global?: boolean; - }; - /** @description Describes if the user is an admin user or not. */ - admin?: { - /** @description If set to true the user will be able to administrate the system. */ - global?: boolean; - }; - /** @description Contains the roles of the user per app (assuming they are not a builder user). */ - roles: { [key: string]: string }; + } & { + roles: unknown; }; userOutput: { data: { @@ -607,24 +606,14 @@ export interface components { lastName?: string; /** @description If set to true forces the user to reset their password on first login. */ forceResetPassword?: boolean; - /** @description Describes if the user is a builder user or not. */ - builder?: { - /** @description If set to true the user will be able to build any app in the system. */ - global?: boolean; - }; - /** @description Describes if the user is an admin user or not. */ - admin?: { - /** @description If set to true the user will be able to administrate the system. */ - global?: boolean; - }; - /** @description Contains the roles of the user per app (assuming they are not a builder user). */ - roles: { [key: string]: string }; /** @description The ID of the user. */ _id: string; + } & { + roles: unknown; }; }; userSearch: { - data: { + data: ({ /** @description The email address of the user, this must be unique. */ email: string; /** @description The password of the user if using password based login - this will never be returned. This can be left out of subsequent requests (updates) and will be enriched back into the user structure. */ @@ -640,21 +629,11 @@ export interface components { lastName?: string; /** @description If set to true forces the user to reset their password on first login. */ forceResetPassword?: boolean; - /** @description Describes if the user is a builder user or not. */ - builder?: { - /** @description If set to true the user will be able to build any app in the system. */ - global?: boolean; - }; - /** @description Describes if the user is an admin user or not. */ - admin?: { - /** @description If set to true the user will be able to administrate the system. */ - global?: boolean; - }; - /** @description Contains the roles of the user per app (assuming they are not a builder user). */ - roles: { [key: string]: string }; /** @description The ID of the user. */ _id: string; - }[]; + } & { + roles: unknown; + })[]; }; rowSearch: { query: { @@ -712,6 +691,48 @@ export interface components { /** @description The name to be used when searching - this will be used in a case insensitive starts with match. */ name: string; }; + rolesAssign: { + /** @description Add/remove global builder permissions from the list of users. */ + builder?: { + global: boolean; + }; + /** @description Add/remove global admin permissions from the list of users. */ + admin?: { + global: boolean; + }; + /** @description Add/remove a per-app role, such as BASIC, ADMIN etc. */ + role?: { + /** @description The role ID, such as BASIC, ADMIN or a custom role ID. */ + roleId: string; + /** @description The app that the role relates to. */ + appId: string; + }; + /** @description The user IDs to be updated to add/remove the specified roles. */ + userIds: string[]; + }; + rolesUnAssign: { + /** @description Add/remove global builder permissions from the list of users. */ + builder?: { + global: boolean; + }; + /** @description Add/remove global admin permissions from the list of users. */ + admin?: { + global: boolean; + }; + /** @description Add/remove a per-app role, such as BASIC, ADMIN etc. */ + role?: { + /** @description The role ID, such as BASIC, ADMIN or a custom role ID. */ + roleId: string; + /** @description The app that the role relates to. */ + appId: string; + }; + /** @description The user IDs to be updated to add/remove the specified roles. */ + userIds: string[]; + }; + rolesOutput: { + /** @description The updated users' IDs */ + userIds: string[]; + }; }; parameters: { /** @description The ID of the table which this request is targeting. */ @@ -907,6 +928,36 @@ export interface operations { }; }; }; + roleAssign: { + responses: { + /** Returns a list of updated user IDs */ + 200: { + content: { + "application/json": components["schemas"]["rolesOutput"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["rolesAssign"]; + }; + }; + }; + roleUnAssign: { + responses: { + /** Returns a list of updated user IDs */ + 200: { + content: { + "application/json": components["schemas"]["rolesOutput"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["rolesUnAssign"]; + }; + }; + }; /** Creates a row within the specified table. */ rowCreate: { parameters: { From d66a020b3cbae9c8f16d6e2b6080e8a1ddc8c0c5 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 10 Aug 2023 11:48:18 +0300 Subject: [PATCH 03/45] Fix build script with no pro locally --- scripts/build.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/build.js b/scripts/build.js index 2ca41b5f7d..50620d94f4 100755 --- a/scripts/build.js +++ b/scripts/build.js @@ -22,7 +22,10 @@ function runBuild(entry, outfile) { fs.readFileSync(tsconfig, "utf-8") ) - if (!fs.existsSync("../pro/src")) { + if ( + !fs.existsSync("../pro/src") && + tsconfigPathPluginContent.compilerOptions?.paths + ) { // If we don't have pro, we cannot bundle backend-core. // Otherwise, the main context will not be shared between libraries delete tsconfigPathPluginContent.compilerOptions.paths[ From 5b29e879a448ae4b993c9218d6e9d9ef84a6214d Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 10 Aug 2023 16:03:37 +0300 Subject: [PATCH 04/45] Fix dev when no pro loaded --- packages/server/package.json | 15 +++++++++++++++ packages/worker/package.json | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/packages/server/package.json b/packages/server/package.json index 7d0d8f5feb..29b8666746 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -179,5 +179,20 @@ }, "optionalDependencies": { "oracledb": "5.3.0" + }, + "nx": { + "targets": { + "dev:builder": { + "dependsOn": [ + { + "comment": "Required for pro usage when submodule not loaded", + "projects": [ + "@budibase/backend-core" + ], + "target": "build" + } + ] + } + } } } diff --git a/packages/worker/package.json b/packages/worker/package.json index a71e9519d9..3b66df264e 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -102,5 +102,20 @@ "tsconfig-paths": "4.0.0", "typescript": "4.7.3", "update-dotenv": "1.1.1" + }, + "nx": { + "targets": { + "dev:builder": { + "dependsOn": [ + { + "comment": "Required for pro usage when submodule not loaded", + "projects": [ + "@budibase/backend-core" + ], + "target": "build" + } + ] + } + } } } From 5669e82277b41ad2a30c7141f28d404919bf03ff Mon Sep 17 00:00:00 2001 From: Jonny McCullagh Date: Tue, 15 Aug 2023 10:59:46 +0100 Subject: [PATCH 05/45] adoptjdk repo has changed to adoptium (#11521) --- hosting/couchdb/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hosting/couchdb/Dockerfile b/hosting/couchdb/Dockerfile index 70b4413859..ce77002052 100644 --- a/hosting/couchdb/Dockerfile +++ b/hosting/couchdb/Dockerfile @@ -5,11 +5,11 @@ ENV COUCHDB_PASSWORD admin EXPOSE 5984 RUN apt-get update && apt-get install -y --no-install-recommends software-properties-common wget unzip curl && \ - wget -qO - https://adoptopenjdk.jfrog.io/adoptopenjdk/api/gpg/key/public | apt-key add - && \ + wget -O - https://packages.adoptium.net/artifactory/api/gpg/key/public | sudo apt-key add - && \ apt-add-repository 'deb http://security.debian.org/debian-security bullseye-security/updates main' && \ apt-add-repository 'deb http://archive.debian.org/debian stretch-backports main' && \ - apt-add-repository --yes https://adoptopenjdk.jfrog.io/adoptopenjdk/deb/ && \ - apt-get update && apt-get install -y --no-install-recommends adoptopenjdk-8-hotspot && \ + apt-add-repository 'deb https://packages.adoptium.net/artifactory/deb bullseye main' && \ + apt-get update && apt-get install -y --no-install-recommends temurin-8-jdk && \ rm -rf /var/lib/apt/lists/ # setup clouseau From b3e89890606f3ca4c37d16dc906f129eaa05f22f Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 15 Aug 2023 14:19:36 +0100 Subject: [PATCH 06/45] Adding pro integration. --- packages/pro | 2 +- packages/server/specs/resources/user.ts | 34 +++++++++++++++++++ .../src/api/controllers/public/users.ts | 20 ++--------- packages/types/src/sdk/licensing/feature.ts | 1 + 4 files changed, 39 insertions(+), 18 deletions(-) diff --git a/packages/pro b/packages/pro index 9b9c8cc08f..bf719cb968 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 9b9c8cc08f271bfc5dd401860f344f6eb336ab35 +Subproject commit bf719cb968a13183225696a74fb40a78668a54a6 diff --git a/packages/server/specs/resources/user.ts b/packages/server/specs/resources/user.ts index 9ec5388672..d00ed02f81 100644 --- a/packages/server/specs/resources/user.ts +++ b/packages/server/specs/resources/user.ts @@ -57,6 +57,40 @@ const userSchema = object( "If set to true forces the user to reset their password on first login.", type: "boolean", }, + builder: { + description: + "Describes if the user is a builder user or not. This field can only be set on a business or enterprise license.", + type: "object", + properties: { + global: { + description: + "If set to true the user will be able to build any app in the system.", + type: "boolean", + }, + }, + }, + admin: { + description: + "Describes if the user is an admin user or not. This field can only be set on a business or enterprise license.", + type: "object", + properties: { + global: { + description: + "If set to true the user will be able to administrate the system.", + type: "boolean", + }, + }, + }, + roles: { + description: + "Contains the roles of the user per app (assuming they are not a builder user). This field can only be set on a business or enterprise license.", + type: "object", + additionalProperties: { + type: "string", + description: + "A map of app ID (production app ID, minus the _dev component) to a role ID, e.g. ADMIN.", + }, + }, }, { required: ["email", "roles"] } ) diff --git a/packages/server/src/api/controllers/public/users.ts b/packages/server/src/api/controllers/public/users.ts index 7aaf520dc4..ddaa7a678e 100644 --- a/packages/server/src/api/controllers/public/users.ts +++ b/packages/server/src/api/controllers/public/users.ts @@ -9,21 +9,7 @@ import { db as dbCore } from "@budibase/backend-core" import { search as stringSearch } from "./utils" import { UserCtx, User } from "@budibase/types" import { Next } from "koa" - -function removeRoles(ctx: UserCtx, oldUser?: User) { - const user = ctx.request.body - if (user.builder) { - user.builder = oldUser?.builder || undefined - } - if (user.admin) { - user.admin = oldUser?.admin || undefined - } - if (user.roles) { - user.roles = oldUser?.roles || {} - } - ctx.request.body = user - return ctx -} +import { sdk } from "@budibase/pro" function isLoggedInUser(ctx: UserCtx, user: User) { const loggedInId = ctx.user?._id @@ -49,7 +35,7 @@ export async function search(ctx: UserCtx, next: Next) { } export async function create(ctx: UserCtx, next: Next) { - ctx = publicApiUserFix(removeRoles(ctx)) + ctx = publicApiUserFix(await sdk.publicApi.userSaveUpdate(ctx)) const response = await saveGlobalUser(ctx) ctx.body = await getUser(ctx, response._id) await next() @@ -66,7 +52,7 @@ export async function update(ctx: UserCtx, next: Next) { ...ctx.request.body, _rev: user._rev, } - ctx = publicApiUserFix(removeRoles(ctx, user)) + ctx = publicApiUserFix(await sdk.publicApi.userSaveUpdate(ctx, user)) const response = await saveGlobalUser(ctx) ctx.body = await getUser(ctx, response._id) await next() diff --git a/packages/types/src/sdk/licensing/feature.ts b/packages/types/src/sdk/licensing/feature.ts index 20f6813409..a1ace01e48 100644 --- a/packages/types/src/sdk/licensing/feature.ts +++ b/packages/types/src/sdk/licensing/feature.ts @@ -11,6 +11,7 @@ export enum Feature { SYNC_AUTOMATIONS = "syncAutomations", APP_BUILDERS = "appBuilders", OFFLINE = "offline", + USER_ROLE_PUBLIC_API = "userRolePublicApi", } export type PlanFeatures = { [key in PlanType]: Feature[] | undefined } From e97c042e954f8530396e2cf9f1be85d3ff7fd08b Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 15 Aug 2023 15:35:52 +0100 Subject: [PATCH 07/45] Adding test cases. --- .../tests/core/utilities/mocks/licenses.ts | 4 + packages/pro | 2 +- .../src/api/routes/public/tests/users.spec.js | 38 ------ .../src/api/routes/public/tests/users.spec.ts | 126 ++++++++++++++++++ yarn.lock | 30 +++-- 5 files changed, 151 insertions(+), 49 deletions(-) delete mode 100644 packages/server/src/api/routes/public/tests/users.spec.js create mode 100644 packages/server/src/api/routes/public/tests/users.spec.ts diff --git a/packages/backend-core/tests/core/utilities/mocks/licenses.ts b/packages/backend-core/tests/core/utilities/mocks/licenses.ts index 6747282040..14a1f1f4d3 100644 --- a/packages/backend-core/tests/core/utilities/mocks/licenses.ts +++ b/packages/backend-core/tests/core/utilities/mocks/licenses.ts @@ -86,6 +86,10 @@ export const useAuditLogs = () => { return useFeature(Feature.AUDIT_LOGS) } +export const usePublicApiUserRoles = () => { + return useFeature(Feature.USER_ROLE_PUBLIC_API) +} + export const useScimIntegration = () => { return useFeature(Feature.SCIM) } diff --git a/packages/pro b/packages/pro index bf719cb968..162b4efd14 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit bf719cb968a13183225696a74fb40a78668a54a6 +Subproject commit 162b4efd148fc31aa50b76318c6d4a6eee2a8074 diff --git a/packages/server/src/api/routes/public/tests/users.spec.js b/packages/server/src/api/routes/public/tests/users.spec.js deleted file mode 100644 index 1daa611df8..0000000000 --- a/packages/server/src/api/routes/public/tests/users.spec.js +++ /dev/null @@ -1,38 +0,0 @@ -const setup = require("../../tests/utilities") -const { generateMakeRequest } = require("./utils") - -const workerRequests = require("../../../../utilities/workerRequests") - -let config = setup.getConfig() -let apiKey, globalUser, makeRequest - -beforeAll(async () => { - await config.init() - globalUser = await config.globalUser() - apiKey = await config.generateApiKey(globalUser._id) - makeRequest = generateMakeRequest(apiKey) - workerRequests.readGlobalUser.mockReturnValue(globalUser) -}) - -afterAll(setup.afterAll) - -describe("check user endpoints", () => { - it("should not allow a user to update their own roles", async () => { - const res = await makeRequest("put", `/users/${globalUser._id}`, { - ...globalUser, - roles: { - "app_1": "ADMIN", - } - }) - expect(workerRequests.saveGlobalUser.mock.lastCall[0].body.data.roles["app_1"]).toBeUndefined() - expect(res.status).toBe(200) - expect(res.body.data.roles["app_1"]).toBeUndefined() - }) - - it("should not allow a user to delete themselves", async () => { - const res = await makeRequest("delete", `/users/${globalUser._id}`) - expect(res.status).toBe(405) - expect(workerRequests.deleteGlobalUser.mock.lastCall).toBeUndefined() - }) -}) - diff --git a/packages/server/src/api/routes/public/tests/users.spec.ts b/packages/server/src/api/routes/public/tests/users.spec.ts new file mode 100644 index 0000000000..c81acca1df --- /dev/null +++ b/packages/server/src/api/routes/public/tests/users.spec.ts @@ -0,0 +1,126 @@ +import * as setup from "../../tests/utilities" +import { generateMakeRequest, MakeRequestResponse } from "./utils" +import { User } from "@budibase/types" +import { mocks } from "@budibase/backend-core/tests" + +import * as workerRequests from "../../../../utilities/workerRequests" + +const mockedWorkerReq = jest.mocked(workerRequests) + +let config = setup.getConfig() +let apiKey: string, globalUser: User, makeRequest: MakeRequestResponse + +beforeAll(async () => { + await config.init() + globalUser = await config.globalUser() + apiKey = await config.generateApiKey(globalUser._id) + makeRequest = generateMakeRequest(apiKey) + mockedWorkerReq.readGlobalUser.mockImplementation(() => + Promise.resolve(globalUser) + ) +}) + +afterAll(setup.afterAll) + +function base() { + return { + tenantId: config.getTenantId(), + firstName: "Test", + lastName: "Test", + } +} + +function updateMock() { + mockedWorkerReq.readGlobalUser.mockImplementation(ctx => ctx.request.body) +} + +describe("check user endpoints", () => { + it("should not allow a user to update their own roles", async () => { + const res = await makeRequest("put", `/users/${globalUser._id}`, { + ...globalUser, + roles: { + app_1: "ADMIN", + }, + }) + expect( + mockedWorkerReq.saveGlobalUser.mock.lastCall?.[0].body.data.roles["app_1"] + ).toBeUndefined() + expect(res.status).toBe(200) + expect(res.body.data.roles["app_1"]).toBeUndefined() + }) + + it("should not allow a user to delete themselves", async () => { + const res = await makeRequest("delete", `/users/${globalUser._id}`) + expect(res.status).toBe(405) + expect(mockedWorkerReq.deleteGlobalUser.mock.lastCall).toBeUndefined() + }) +}) + +describe("no user role update in free", () => { + beforeAll(() => { + updateMock() + }) + + it("should not allow 'roles' to be updated", async () => { + const res = await makeRequest("post", "/users", { + ...base(), + roles: { app_a: "BASIC" }, + }) + expect(res.status).toBe(200) + expect(res.body.data.roles["app_a"]).toBeUndefined() + }) + + it("should not allow 'admin' to be updated", async () => { + const res = await makeRequest("post", "/users", { + ...base(), + admin: { global: true }, + }) + expect(res.status).toBe(200) + expect(res.body.data.admin).toBeUndefined() + }) + + it("should not allow 'builder' to be updated", async () => { + const res = await makeRequest("post", "/users", { + ...base(), + builder: { global: true }, + }) + expect(res.status).toBe(200) + expect(res.body.data.builder).toBeUndefined() + }) +}) + +describe("no user role update in business", () => { + beforeAll(() => { + updateMock() + mocks.licenses.usePublicApiUserRoles() + }) + + it("should allow 'roles' to be updated", async () => { + const res = await makeRequest("post", "/users", { + ...base(), + roles: { app_a: "BASIC" }, + }) + expect(res.status).toBe(200) + expect(res.body.data.roles["app_a"]).toBe("BASIC") + }) + + it("should allow 'admin' to be updated", async () => { + mocks.licenses.usePublicApiUserRoles() + const res = await makeRequest("post", "/users", { + ...base(), + admin: { global: true }, + }) + expect(res.status).toBe(200) + expect(res.body.data.admin.global).toBe(true) + }) + + it("should allow 'builder' to be updated", async () => { + mocks.licenses.usePublicApiUserRoles() + const res = await makeRequest("post", "/users", { + ...base(), + builder: { global: true }, + }) + expect(res.status).toBe(200) + expect(res.body.data.builder.global).toBe(true) + }) +}) diff --git a/yarn.lock b/yarn.lock index 827c94a176..4f5e0db2e7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10283,7 +10283,7 @@ denque@^1.1.0: resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf" integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw== -denque@^2.0.1, denque@^2.1.0: +denque@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== @@ -16986,6 +16986,11 @@ long@^5.0.0: resolved "https://registry.yarnpkg.com/long/-/long-5.2.1.tgz#e27595d0083d103d2fa2c20c7699f8e0c92b897f" integrity sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A== +long@^5.2.1: + version "5.2.3" + resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" + integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== + lookpath@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/lookpath/-/lookpath-1.1.0.tgz#932d68371a2f0b4a5644f03d6a2b4728edba96d2" @@ -17052,6 +17057,11 @@ lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== +lru-cache@^8.0.0: + version "8.0.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-8.0.5.tgz#983fe337f3e176667f8e567cfcce7cb064ea214e" + integrity sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA== + lru-cache@^9.0.0: version "9.0.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-9.0.1.tgz#ac061ed291f8b9adaca2b085534bb1d3b61bef83" @@ -17952,17 +17962,17 @@ mute-stream@~1.0.0: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-1.0.0.tgz#e31bd9fe62f0aed23520aa4324ea6671531e013e" integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA== -mysql2@2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-2.3.3.tgz#944f3deca4b16629052ff8614fbf89d5552545a0" - integrity sha512-wxJUev6LgMSgACDkb/InIFxDprRa6T95+VEoR+xPvtngtccNH2dGjEB/fVZ8yg1gWv1510c9CvXuJHi5zUm0ZA== +mysql2@3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.5.2.tgz#a06050e1514e9ac15711a8b883ffd51cb44b2dc8" + integrity sha512-cptobmhYkYeTBIFp2c0piw2+gElpioga1rUw5UidHvo8yaHijMZoo8A3zyBVoo/K71f7ZFvrShA9iMIy9dCzCA== dependencies: - denque "^2.0.1" + denque "^2.1.0" generate-function "^2.3.1" iconv-lite "^0.6.3" - long "^4.0.0" - lru-cache "^6.0.0" - named-placeholders "^1.1.2" + long "^5.2.1" + lru-cache "^8.0.0" + named-placeholders "^1.1.3" seq-queue "^0.0.5" sqlstring "^2.3.2" @@ -17975,7 +17985,7 @@ mz@^2.4.0, mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" -named-placeholders@^1.1.2: +named-placeholders@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/named-placeholders/-/named-placeholders-1.1.3.tgz#df595799a36654da55dda6152ba7a137ad1d9351" integrity sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w== From cb49906d36a2bcee33e2b4beb5af10c70ad29709 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 15 Aug 2023 15:55:56 +0100 Subject: [PATCH 08/45] Updating pro reference. --- packages/pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pro b/packages/pro index 162b4efd14..7394675d71 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 162b4efd148fc31aa50b76318c6d4a6eee2a8074 +Subproject commit 7394675d71f1a0f8c5471644ed03a079683297a2 From a6a70c2d0918a4efb23a9a51415c23a689e728fd Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 15 Aug 2023 16:46:21 +0100 Subject: [PATCH 09/45] Building out the role assignment/unassignment APIs as new components of the public API. --- packages/backend-core/src/users/db.ts | 20 +++++++++----- packages/pro | 2 +- packages/server/specs/resources/roles.ts | 7 +++++ .../src/api/controllers/public/roles.ts | 27 +++++++++++++++---- .../src/api/controllers/public/users.ts | 4 +-- packages/types/src/api/index.ts | 1 + packages/types/src/api/public/.keep | 0 packages/types/src/api/public/index.ts | 1 + packages/types/src/api/public/roles.ts | 16 +++++++++++ 9 files changed, 64 insertions(+), 14 deletions(-) delete mode 100644 packages/types/src/api/public/.keep create mode 100644 packages/types/src/api/public/index.ts create mode 100644 packages/types/src/api/public/roles.ts diff --git a/packages/backend-core/src/users/db.ts b/packages/backend-core/src/users/db.ts index 55cc97bb1c..14140cba81 100644 --- a/packages/backend-core/src/users/db.ts +++ b/packages/backend-core/src/users/db.ts @@ -1,30 +1,30 @@ import env from "../environment" import * as eventHelpers from "./events" import * as accounts from "../accounts" +import * as accountSdk from "../accounts" import * as cache from "../cache" -import { getIdentity, getTenantId, getGlobalDB } from "../context" +import { getGlobalDB, getIdentity, getTenantId } from "../context" import * as dbUtils from "../db" import { EmailUnavailableError, HTTPError } from "../errors" import * as platform from "../platform" import * as sessions from "../security/sessions" import * as usersCore from "./users" import { + Account, AllDocsResponse, BulkUserCreated, BulkUserDeleted, + isSSOAccount, + isSSOUser, RowResponse, SaveUserOpts, User, - Account, - isSSOUser, - isSSOAccount, UserStatus, } from "@budibase/types" -import * as accountSdk from "../accounts" import { - validateUniqueUser, getAccountHolderFromUserIds, isAdmin, + validateUniqueUser, } from "./utils" import { searchExistingEmails } from "./lookup" import { hash } from "../utils" @@ -179,6 +179,14 @@ export class UserDB { return user } + static async bulkGet(userIds: string[]) { + return await usersCore.bulkGetGlobalUsersById(userIds) + } + + static async bulkUpdate(users: User[]) { + return await usersCore.bulkUpdateGlobalUsers(users) + } + static async save(user: User, opts: SaveUserOpts = {}): Promise { // default booleans to true if (opts.hashPassword == null) { diff --git a/packages/pro b/packages/pro index 7394675d71..1fc0fe26ec 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 7394675d71f1a0f8c5471644ed03a079683297a2 +Subproject commit 1fc0fe26ec843794c4b80d25e593e3f6edd34840 diff --git a/packages/server/specs/resources/roles.ts b/packages/server/specs/resources/roles.ts index 02254261be..f4fd870b7b 100644 --- a/packages/server/specs/resources/roles.ts +++ b/packages/server/specs/resources/roles.ts @@ -3,6 +3,13 @@ import Resource from "./utils/Resource" const roleSchema = object( { + appBuilder: object({ + appId: { + description: + "The app that the users should have app builder privileges granted for.", + type: "string", + }, + }), builder: object( { global: { diff --git a/packages/server/src/api/controllers/public/roles.ts b/packages/server/src/api/controllers/public/roles.ts index 3b70094ae1..1ff11c48c2 100644 --- a/packages/server/src/api/controllers/public/roles.ts +++ b/packages/server/src/api/controllers/public/roles.ts @@ -1,12 +1,29 @@ -import { UserCtx } from "@budibase/types" +import { + UserCtx, + RoleAssignmentResponse, + RoleAssignmentRequest, +} from "@budibase/types" import { Next } from "koa" +import { sdk } from "@budibase/pro" -async function assign(ctx: UserCtx, next: Next) { - ctx.body = { message: "roles assigned" } +async function assign( + ctx: UserCtx, + next: Next +) { + const { userIds, ...assignmentProps } = ctx.request.body + await sdk.publicApi.roles.assign(userIds, assignmentProps) + ctx.body = { userIds } + await next() } -async function unAssign(ctx: UserCtx, next: Next) { - ctx.body = { message: "roles un-assigned" } +async function unAssign( + ctx: UserCtx, + next: Next +) { + const { userIds, ...unAssignmentProps } = ctx.request.body + await sdk.publicApi.roles.unAssign(userIds, unAssignmentProps) + ctx.body = { userIds } + await next() } export default { diff --git a/packages/server/src/api/controllers/public/users.ts b/packages/server/src/api/controllers/public/users.ts index ddaa7a678e..bb6fc3a6e7 100644 --- a/packages/server/src/api/controllers/public/users.ts +++ b/packages/server/src/api/controllers/public/users.ts @@ -35,7 +35,7 @@ export async function search(ctx: UserCtx, next: Next) { } export async function create(ctx: UserCtx, next: Next) { - ctx = publicApiUserFix(await sdk.publicApi.userSaveUpdate(ctx)) + ctx = publicApiUserFix(await sdk.publicApi.users.roleCheck(ctx)) const response = await saveGlobalUser(ctx) ctx.body = await getUser(ctx, response._id) await next() @@ -52,7 +52,7 @@ export async function update(ctx: UserCtx, next: Next) { ...ctx.request.body, _rev: user._rev, } - ctx = publicApiUserFix(await sdk.publicApi.userSaveUpdate(ctx, user)) + ctx = publicApiUserFix(await sdk.publicApi.users.roleCheck(ctx, user)) const response = await saveGlobalUser(ctx) ctx.body = await getUser(ctx, response._id) await next() diff --git a/packages/types/src/api/index.ts b/packages/types/src/api/index.ts index 5fa77b18ea..9339ae7147 100644 --- a/packages/types/src/api/index.ts +++ b/packages/types/src/api/index.ts @@ -1,2 +1,3 @@ export * from "./account" export * from "./web" +export * from "./public" diff --git a/packages/types/src/api/public/.keep b/packages/types/src/api/public/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/types/src/api/public/index.ts b/packages/types/src/api/public/index.ts new file mode 100644 index 0000000000..2a3a9dddba --- /dev/null +++ b/packages/types/src/api/public/index.ts @@ -0,0 +1 @@ +export * from "./roles" diff --git a/packages/types/src/api/public/roles.ts b/packages/types/src/api/public/roles.ts new file mode 100644 index 0000000000..fbef8af8b1 --- /dev/null +++ b/packages/types/src/api/public/roles.ts @@ -0,0 +1,16 @@ +export interface RoleAssignmentRequest { + role?: { + appId: string + roleId: string + } + appBuilder?: { + appId: string + } + builder?: boolean + admin?: boolean + userIds: string[] +} + +export interface RoleAssignmentResponse { + userIds: string[] +} From 8f81a16340ad6eae770fb165a6100c25cb4b02ab Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 15 Aug 2023 17:33:22 +0100 Subject: [PATCH 10/45] Updating types to be based on the open API definition rather than types. --- packages/server/specs/openapi.json | 162 ++++++++++++++---- packages/server/specs/openapi.yaml | 145 ++++++++++++---- packages/server/specs/resources/roles.ts | 55 +++--- .../api/controllers/public/mapping/types.ts | 4 + .../src/api/controllers/public/roles.ts | 19 +- .../server/src/api/routes/public/roles.ts | 2 + packages/server/src/definitions/openapi.ts | 82 ++++++--- packages/types/src/api/index.ts | 1 - packages/types/src/api/public/.keep | 0 packages/types/src/api/public/index.ts | 1 - packages/types/src/api/public/roles.ts | 16 -- 11 files changed, 340 insertions(+), 147 deletions(-) create mode 100644 packages/types/src/api/public/.keep delete mode 100644 packages/types/src/api/public/index.ts delete mode 100644 packages/types/src/api/public/roles.ts diff --git a/packages/server/specs/openapi.json b/packages/server/specs/openapi.json index 1071a39c29..1e5718c5b5 100644 --- a/packages/server/specs/openapi.json +++ b/packages/server/specs/openapi.json @@ -1519,6 +1519,34 @@ "forceResetPassword": { "description": "If set to true forces the user to reset their password on first login.", "type": "boolean" + }, + "builder": { + "description": "Describes if the user is a builder user or not. This field can only be set on a business or enterprise license.", + "type": "object", + "properties": { + "global": { + "description": "If set to true the user will be able to build any app in the system.", + "type": "boolean" + } + } + }, + "admin": { + "description": "Describes if the user is an admin user or not. This field can only be set on a business or enterprise license.", + "type": "object", + "properties": { + "global": { + "description": "If set to true the user will be able to administrate the system.", + "type": "boolean" + } + } + }, + "roles": { + "description": "Contains the roles of the user per app (assuming they are not a builder user). This field can only be set on a business or enterprise license.", + "type": "object", + "additionalProperties": { + "type": "string", + "description": "A map of app ID (production app ID, minus the _dev component) to a role ID, e.g. ADMIN." + } } }, "required": [ @@ -1559,6 +1587,34 @@ "description": "If set to true forces the user to reset their password on first login.", "type": "boolean" }, + "builder": { + "description": "Describes if the user is a builder user or not. This field can only be set on a business or enterprise license.", + "type": "object", + "properties": { + "global": { + "description": "If set to true the user will be able to build any app in the system.", + "type": "boolean" + } + } + }, + "admin": { + "description": "Describes if the user is an admin user or not. This field can only be set on a business or enterprise license.", + "type": "object", + "properties": { + "global": { + "description": "If set to true the user will be able to administrate the system.", + "type": "boolean" + } + } + }, + "roles": { + "description": "Contains the roles of the user per app (assuming they are not a builder user). This field can only be set on a business or enterprise license.", + "type": "object", + "additionalProperties": { + "type": "string", + "description": "A map of app ID (production app ID, minus the _dev component) to a role ID, e.g. ADMIN." + } + }, "_id": { "description": "The ID of the user.", "type": "string" @@ -1610,6 +1666,34 @@ "description": "If set to true forces the user to reset their password on first login.", "type": "boolean" }, + "builder": { + "description": "Describes if the user is a builder user or not. This field can only be set on a business or enterprise license.", + "type": "object", + "properties": { + "global": { + "description": "If set to true the user will be able to build any app in the system.", + "type": "boolean" + } + } + }, + "admin": { + "description": "Describes if the user is an admin user or not. This field can only be set on a business or enterprise license.", + "type": "object", + "properties": { + "global": { + "description": "If set to true the user will be able to administrate the system.", + "type": "boolean" + } + } + }, + "roles": { + "description": "Contains the roles of the user per app (assuming they are not a builder user). This field can only be set on a business or enterprise license.", + "type": "object", + "additionalProperties": { + "type": "string", + "description": "A map of app ID (production app ID, minus the _dev component) to a role ID, e.g. ADMIN." + } + }, "_id": { "description": "The ID of the user.", "type": "string" @@ -1753,29 +1837,26 @@ "rolesAssign": { "type": "object", "properties": { - "builder": { + "appBuilder": { "type": "object", "properties": { - "global": { - "type": "boolean" + "appId": { + "description": "The app that the users should have app builder privileges granted for.", + "type": "string" } }, - "description": "Add/remove global builder permissions from the list of users.", + "description": "Allow setting users to builders per app.", "required": [ - "global" + "appId" ] }, + "builder": { + "type": "boolean", + "description": "Add/remove global builder permissions from the list of users." + }, "admin": { - "type": "object", - "properties": { - "global": { - "type": "boolean" - } - }, - "description": "Add/remove global admin permissions from the list of users.", - "required": [ - "global" - ] + "type": "boolean", + "description": "Add/remove global admin permissions from the list of users." }, "role": { "type": "object", @@ -1810,29 +1891,26 @@ "rolesUnAssign": { "type": "object", "properties": { - "builder": { + "appBuilder": { "type": "object", "properties": { - "global": { - "type": "boolean" + "appId": { + "description": "The app that the users should have app builder privileges granted for.", + "type": "string" } }, - "description": "Add/remove global builder permissions from the list of users.", + "description": "Allow setting users to builders per app.", "required": [ - "global" + "appId" ] }, + "builder": { + "type": "boolean", + "description": "Add/remove global builder permissions from the list of users." + }, "admin": { - "type": "object", - "properties": { - "global": { - "type": "boolean" - } - }, - "description": "Add/remove global admin permissions from the list of users.", - "required": [ - "global" - ] + "type": "boolean", + "description": "Add/remove global admin permissions from the list of users." }, "role": { "type": "object", @@ -1867,16 +1945,24 @@ "rolesOutput": { "type": "object", "properties": { - "userIds": { - "description": "The updated users' IDs", - "type": "array", - "items": { - "type": "string" - } + "data": { + "type": "object", + "properties": { + "userIds": { + "description": "The updated users' IDs", + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "userIds" + ] } }, "required": [ - "userIds" + "data" ] } } @@ -2235,6 +2321,7 @@ "post": { "operationId": "roleAssign", "summary": "Assign a role to a list of users", + "description": "This is a business/enterprise only endpoint", "tags": [ "roles" ], @@ -2266,6 +2353,7 @@ "post": { "operationId": "roleUnAssign", "summary": "Un-assign a role from a list of users", + "description": "This is a business/enterprise only endpoint", "tags": [ "roles" ], diff --git a/packages/server/specs/openapi.yaml b/packages/server/specs/openapi.yaml index aa7b3ddb51..07320917b8 100644 --- a/packages/server/specs/openapi.yaml +++ b/packages/server/specs/openapi.yaml @@ -1296,6 +1296,32 @@ components: description: If set to true forces the user to reset their password on first login. type: boolean + builder: + description: Describes if the user is a builder user or not. This field can only + be set on a business or enterprise license. + type: object + properties: + global: + description: If set to true the user will be able to build any app in the + system. + type: boolean + admin: + description: Describes if the user is an admin user or not. This field can only + be set on a business or enterprise license. + type: object + properties: + global: + description: If set to true the user will be able to administrate the system. + type: boolean + roles: + description: Contains the roles of the user per app (assuming they are not a + builder user). This field can only be set on a business or + enterprise license. + type: object + additionalProperties: + type: string + description: A map of app ID (production app ID, minus the _dev component) to a + role ID, e.g. ADMIN. required: - email - roles @@ -1328,6 +1354,32 @@ components: description: If set to true forces the user to reset their password on first login. type: boolean + builder: + description: Describes if the user is a builder user or not. This field can only + be set on a business or enterprise license. + type: object + properties: + global: + description: If set to true the user will be able to build any app in the + system. + type: boolean + admin: + description: Describes if the user is an admin user or not. This field can only + be set on a business or enterprise license. + type: object + properties: + global: + description: If set to true the user will be able to administrate the system. + type: boolean + roles: + description: Contains the roles of the user per app (assuming they are not a + builder user). This field can only be set on a business or + enterprise license. + type: object + additionalProperties: + type: string + description: A map of app ID (production app ID, minus the _dev component) to a + role ID, e.g. ADMIN. _id: description: The ID of the user. type: string @@ -1368,6 +1420,32 @@ components: description: If set to true forces the user to reset their password on first login. type: boolean + builder: + description: Describes if the user is a builder user or not. This field can only + be set on a business or enterprise license. + type: object + properties: + global: + description: If set to true the user will be able to build any app in the + system. + type: boolean + admin: + description: Describes if the user is an admin user or not. This field can only + be set on a business or enterprise license. + type: object + properties: + global: + description: If set to true the user will be able to administrate the system. + type: boolean + roles: + description: Contains the roles of the user per app (assuming they are not a + builder user). This field can only be set on a business or + enterprise license. + type: object + additionalProperties: + type: string + description: A map of app ID (production app ID, minus the _dev component) to a + role ID, e.g. ADMIN. _id: description: The ID of the user. type: string @@ -1481,22 +1559,22 @@ components: rolesAssign: type: object properties: + appBuilder: + type: object + properties: + appId: + description: The app that the users should have app builder privileges granted + for. + type: string + description: Allow setting users to builders per app. + required: + - appId builder: - type: object - properties: - global: - type: boolean + type: boolean description: Add/remove global builder permissions from the list of users. - required: - - global admin: - type: object - properties: - global: - type: boolean + type: boolean description: Add/remove global admin permissions from the list of users. - required: - - global role: type: object properties: @@ -1520,22 +1598,22 @@ components: rolesUnAssign: type: object properties: + appBuilder: + type: object + properties: + appId: + description: The app that the users should have app builder privileges granted + for. + type: string + description: Allow setting users to builders per app. + required: + - appId builder: - type: object - properties: - global: - type: boolean + type: boolean description: Add/remove global builder permissions from the list of users. - required: - - global admin: - type: object - properties: - global: - type: boolean + type: boolean description: Add/remove global admin permissions from the list of users. - required: - - global role: type: object properties: @@ -1559,13 +1637,18 @@ components: rolesOutput: type: object properties: - userIds: - description: The updated users' IDs - type: array - items: - type: string + data: + type: object + properties: + userIds: + description: The updated users' IDs + type: array + items: + type: string + required: + - userIds required: - - userIds + - data security: - ApiKeyAuth: [] paths: @@ -1780,6 +1863,7 @@ paths: post: operationId: roleAssign summary: Assign a role to a list of users + description: This is a business/enterprise only endpoint tags: - roles requestBody: @@ -1799,6 +1883,7 @@ paths: post: operationId: roleUnAssign summary: Un-assign a role from a list of users + description: This is a business/enterprise only endpoint tags: - roles requestBody: diff --git a/packages/server/specs/resources/roles.ts b/packages/server/specs/resources/roles.ts index f4fd870b7b..1033d640ce 100644 --- a/packages/server/specs/resources/roles.ts +++ b/packages/server/specs/resources/roles.ts @@ -3,35 +3,26 @@ import Resource from "./utils/Resource" const roleSchema = object( { - appBuilder: object({ - appId: { - description: - "The app that the users should have app builder privileges granted for.", - type: "string", - }, - }), - builder: object( + appBuilder: object( { - global: { - type: "boolean", + appId: { + description: + "The app that the users should have app builder privileges granted for.", + type: "string", }, }, - { - description: - "Add/remove global builder permissions from the list of users.", - } - ), - admin: object( - { - global: { - type: "boolean", - }, - }, - { - description: - "Add/remove global admin permissions from the list of users.", - } + { description: "Allow setting users to builders per app." } ), + builder: { + type: "boolean", + description: + "Add/remove global builder permissions from the list of users.", + }, + admin: { + type: "boolean", + description: + "Add/remove global admin permissions from the list of users.", + }, role: object( { roleId: { @@ -61,12 +52,14 @@ export default new Resource().setSchemas({ rolesAssign: roleSchema, rolesUnAssign: roleSchema, rolesOutput: object({ - userIds: { - description: "The updated users' IDs", - type: "array", - items: { - type: "string", + data: object({ + userIds: { + description: "The updated users' IDs", + type: "array", + items: { + type: "string", + }, }, - }, + }), }), }) diff --git a/packages/server/src/api/controllers/public/mapping/types.ts b/packages/server/src/api/controllers/public/mapping/types.ts index e3c8719d87..9fea9b7213 100644 --- a/packages/server/src/api/controllers/public/mapping/types.ts +++ b/packages/server/src/api/controllers/public/mapping/types.ts @@ -16,6 +16,10 @@ export type CreateRowParams = components["schemas"]["row"] export type User = components["schemas"]["userOutput"]["data"] export type CreateUserParams = components["schemas"]["user"] +export type RoleAssignRequest = components["schemas"]["rolesAssign"] +export type RoleUnAssignRequest = components["schemas"]["rolesUnAssign"] +export type RoleAssignmentResponse = components["schemas"]["rolesOutput"] + export type SearchInputParams = | components["schemas"]["nameSearch"] | components["schemas"]["rowSearch"] diff --git a/packages/server/src/api/controllers/public/roles.ts b/packages/server/src/api/controllers/public/roles.ts index 1ff11c48c2..362f25da58 100644 --- a/packages/server/src/api/controllers/public/roles.ts +++ b/packages/server/src/api/controllers/public/roles.ts @@ -1,28 +1,29 @@ -import { - UserCtx, - RoleAssignmentResponse, - RoleAssignmentRequest, -} from "@budibase/types" +import { UserCtx } from "@budibase/types" import { Next } from "koa" import { sdk } from "@budibase/pro" +import { + RoleAssignmentResponse, + RoleUnAssignRequest, + RoleAssignRequest, +} from "./mapping/types" async function assign( - ctx: UserCtx, + ctx: UserCtx, next: Next ) { const { userIds, ...assignmentProps } = ctx.request.body await sdk.publicApi.roles.assign(userIds, assignmentProps) - ctx.body = { userIds } + ctx.body = { data: { userIds } } await next() } async function unAssign( - ctx: UserCtx, + ctx: UserCtx, next: Next ) { const { userIds, ...unAssignmentProps } = ctx.request.body await sdk.publicApi.roles.unAssign(userIds, unAssignmentProps) - ctx.body = { userIds } + ctx.body = { data: { userIds } } await next() } diff --git a/packages/server/src/api/routes/public/roles.ts b/packages/server/src/api/routes/public/roles.ts index 2332a0ffd0..905f364cbe 100644 --- a/packages/server/src/api/routes/public/roles.ts +++ b/packages/server/src/api/routes/public/roles.ts @@ -9,6 +9,7 @@ const write = [] * post: * operationId: roleAssign * summary: Assign a role to a list of users + * description: This is a business/enterprise only endpoint * tags: * - roles * requestBody: @@ -33,6 +34,7 @@ write.push(new Endpoint("post", "/roles/assign", controller.assign)) * post: * operationId: roleUnAssign * summary: Un-assign a role from a list of users + * description: This is a business/enterprise only endpoint * tags: * - roles * requestBody: diff --git a/packages/server/src/definitions/openapi.ts b/packages/server/src/definitions/openapi.ts index ee078d0821..fe5c17b218 100644 --- a/packages/server/src/definitions/openapi.ts +++ b/packages/server/src/definitions/openapi.ts @@ -35,9 +35,11 @@ export interface paths { post: operations["querySearch"]; }; "/roles/assign": { + /** This is a business/enterprise only endpoint */ post: operations["roleAssign"]; }; "/roles/unassign": { + /** This is a business/enterprise only endpoint */ post: operations["roleUnAssign"]; }; "/tables/{tableId}/rows": { @@ -586,8 +588,18 @@ export interface components { lastName?: string; /** @description If set to true forces the user to reset their password on first login. */ forceResetPassword?: boolean; - } & { - roles: unknown; + /** @description Describes if the user is a builder user or not. This field can only be set on a business or enterprise license. */ + builder?: { + /** @description If set to true the user will be able to build any app in the system. */ + global?: boolean; + }; + /** @description Describes if the user is an admin user or not. This field can only be set on a business or enterprise license. */ + admin?: { + /** @description If set to true the user will be able to administrate the system. */ + global?: boolean; + }; + /** @description Contains the roles of the user per app (assuming they are not a builder user). This field can only be set on a business or enterprise license. */ + roles: { [key: string]: string }; }; userOutput: { data: { @@ -606,14 +618,24 @@ export interface components { lastName?: string; /** @description If set to true forces the user to reset their password on first login. */ forceResetPassword?: boolean; + /** @description Describes if the user is a builder user or not. This field can only be set on a business or enterprise license. */ + builder?: { + /** @description If set to true the user will be able to build any app in the system. */ + global?: boolean; + }; + /** @description Describes if the user is an admin user or not. This field can only be set on a business or enterprise license. */ + admin?: { + /** @description If set to true the user will be able to administrate the system. */ + global?: boolean; + }; + /** @description Contains the roles of the user per app (assuming they are not a builder user). This field can only be set on a business or enterprise license. */ + roles: { [key: string]: string }; /** @description The ID of the user. */ _id: string; - } & { - roles: unknown; }; }; userSearch: { - data: ({ + data: { /** @description The email address of the user, this must be unique. */ email: string; /** @description The password of the user if using password based login - this will never be returned. This can be left out of subsequent requests (updates) and will be enriched back into the user structure. */ @@ -629,11 +651,21 @@ export interface components { lastName?: string; /** @description If set to true forces the user to reset their password on first login. */ forceResetPassword?: boolean; + /** @description Describes if the user is a builder user or not. This field can only be set on a business or enterprise license. */ + builder?: { + /** @description If set to true the user will be able to build any app in the system. */ + global?: boolean; + }; + /** @description Describes if the user is an admin user or not. This field can only be set on a business or enterprise license. */ + admin?: { + /** @description If set to true the user will be able to administrate the system. */ + global?: boolean; + }; + /** @description Contains the roles of the user per app (assuming they are not a builder user). This field can only be set on a business or enterprise license. */ + roles: { [key: string]: string }; /** @description The ID of the user. */ _id: string; - } & { - roles: unknown; - })[]; + }[]; }; rowSearch: { query: { @@ -692,14 +724,15 @@ export interface components { name: string; }; rolesAssign: { + /** @description Allow setting users to builders per app. */ + appBuilder?: { + /** @description The app that the users should have app builder privileges granted for. */ + appId: string; + }; /** @description Add/remove global builder permissions from the list of users. */ - builder?: { - global: boolean; - }; + builder?: boolean; /** @description Add/remove global admin permissions from the list of users. */ - admin?: { - global: boolean; - }; + admin?: boolean; /** @description Add/remove a per-app role, such as BASIC, ADMIN etc. */ role?: { /** @description The role ID, such as BASIC, ADMIN or a custom role ID. */ @@ -711,14 +744,15 @@ export interface components { userIds: string[]; }; rolesUnAssign: { + /** @description Allow setting users to builders per app. */ + appBuilder?: { + /** @description The app that the users should have app builder privileges granted for. */ + appId: string; + }; /** @description Add/remove global builder permissions from the list of users. */ - builder?: { - global: boolean; - }; + builder?: boolean; /** @description Add/remove global admin permissions from the list of users. */ - admin?: { - global: boolean; - }; + admin?: boolean; /** @description Add/remove a per-app role, such as BASIC, ADMIN etc. */ role?: { /** @description The role ID, such as BASIC, ADMIN or a custom role ID. */ @@ -730,8 +764,10 @@ export interface components { userIds: string[]; }; rolesOutput: { - /** @description The updated users' IDs */ - userIds: string[]; + data: { + /** @description The updated users' IDs */ + userIds: string[]; + }; }; }; parameters: { @@ -928,6 +964,7 @@ export interface operations { }; }; }; + /** This is a business/enterprise only endpoint */ roleAssign: { responses: { /** Returns a list of updated user IDs */ @@ -943,6 +980,7 @@ export interface operations { }; }; }; + /** This is a business/enterprise only endpoint */ roleUnAssign: { responses: { /** Returns a list of updated user IDs */ diff --git a/packages/types/src/api/index.ts b/packages/types/src/api/index.ts index 9339ae7147..5fa77b18ea 100644 --- a/packages/types/src/api/index.ts +++ b/packages/types/src/api/index.ts @@ -1,3 +1,2 @@ export * from "./account" export * from "./web" -export * from "./public" diff --git a/packages/types/src/api/public/.keep b/packages/types/src/api/public/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/types/src/api/public/index.ts b/packages/types/src/api/public/index.ts deleted file mode 100644 index 2a3a9dddba..0000000000 --- a/packages/types/src/api/public/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./roles" diff --git a/packages/types/src/api/public/roles.ts b/packages/types/src/api/public/roles.ts deleted file mode 100644 index fbef8af8b1..0000000000 --- a/packages/types/src/api/public/roles.ts +++ /dev/null @@ -1,16 +0,0 @@ -export interface RoleAssignmentRequest { - role?: { - appId: string - roleId: string - } - appBuilder?: { - appId: string - } - builder?: boolean - admin?: boolean - userIds: string[] -} - -export interface RoleAssignmentResponse { - userIds: string[] -} From f0ee662d32e9283f5bf842ff0c3f94e58f8dd42e Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Wed, 16 Aug 2023 17:17:05 +0100 Subject: [PATCH 11/45] adding offline mode to .env --- hosting/docker-compose.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hosting/docker-compose.yaml b/hosting/docker-compose.yaml index bad34a20ea..b3887c15fa 100644 --- a/hosting/docker-compose.yaml +++ b/hosting/docker-compose.yaml @@ -27,6 +27,7 @@ services: BB_ADMIN_USER_EMAIL: ${BB_ADMIN_USER_EMAIL} BB_ADMIN_USER_PASSWORD: ${BB_ADMIN_USER_PASSWORD} PLUGINS_DIR: ${PLUGINS_DIR} + OFFLINE_MODE: ${OFFLINE_MODE} depends_on: - worker-service - redis-service @@ -54,6 +55,7 @@ services: INTERNAL_API_KEY: ${INTERNAL_API_KEY} REDIS_URL: redis-service:6379 REDIS_PASSWORD: ${REDIS_PASSWORD} + OFFLINE_MODE: ${OFFLINE_MODE} depends_on: - redis-service - minio-service From 9489e309b52390fdd9bb28d3dd0c7622e23c806e Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 16 Aug 2023 18:12:21 +0100 Subject: [PATCH 12/45] Improving logging of publishing around CRON jobs, try to understand what is being disabled/created. --- .../server/src/api/controllers/deploy/index.ts | 17 +++++++++-------- packages/server/src/automations/utils.ts | 7 +++++-- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/server/src/api/controllers/deploy/index.ts b/packages/server/src/api/controllers/deploy/index.ts index a2b164ffc6..66439d3411 100644 --- a/packages/server/src/api/controllers/deploy/index.ts +++ b/packages/server/src/api/controllers/deploy/index.ts @@ -77,18 +77,19 @@ async function initDeployedApp(prodAppId: any) { ) ).rows.map((row: any) => row.doc) await clearMetadata() - console.log("You have " + automations.length + " automations") + const { count } = await disableAllCrons(prodAppId) const promises = [] - console.log("Disabling prod crons..") - await disableAllCrons(prodAppId) - console.log("Prod Cron triggers disabled..") - console.log("Enabling cron triggers for deployed app..") for (let automation of automations) { promises.push(enableCronTrigger(prodAppId, automation)) } - await Promise.all(promises) - console.log("Enabled cron triggers for deployed app..") - // sync the automations back to the dev DB - since there is now cron + const results = await Promise.all(promises) + const enabledCount = results + .map(result => result.enabled) + .filter(result => result).length + console.log( + `Cleared ${count} old CRON, enabled ${enabledCount} new CRON triggers for app deployment` + ) + // sync the automations back to the dev DB - since there is now CRON // information attached await sdk.applications.syncApp(dbCore.getDevAppID(prodAppId), { automationOnly: true, diff --git a/packages/server/src/automations/utils.ts b/packages/server/src/automations/utils.ts index 7b0ab4f2fd..14835820d9 100644 --- a/packages/server/src/automations/utils.ts +++ b/packages/server/src/automations/utils.ts @@ -130,7 +130,8 @@ export async function disableAllCrons(appId: any) { } } } - return Promise.all(promises) + const results = await Promise.all(promises) + return { count: results.length / 2 } } export async function disableCronById(jobId: number | string) { @@ -169,6 +170,7 @@ export async function enableCronTrigger(appId: any, automation: Automation) { const needsCreated = !sdk.automations.isReboot(automation) && !sdk.automations.disabled(automation) + let enabled = false // need to create cron job if (validCron && needsCreated) { @@ -191,8 +193,9 @@ export async function enableCronTrigger(appId: any, automation: Automation) { automation._id = response.id automation._rev = response.rev }) + enabled = true } - return automation + return { enabled, automation } } /** From e068e301ff230ca3d3290b0ec999759e4cc85c66 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 16 Aug 2023 18:21:53 +0100 Subject: [PATCH 13/45] Updating automation history tab to handle the stopped error status which can occur for CRONs. --- .../_components/StatusRenderer.svelte | 10 ++++++---- .../settings/automation-history/index.svelte | 4 +++- packages/server/src/threads/automation.ts | 6 +++++- packages/types/src/documents/app/automation.ts | 5 ++++- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/packages/builder/src/pages/builder/app/[application]/settings/automation-history/_components/StatusRenderer.svelte b/packages/builder/src/pages/builder/app/[application]/settings/automation-history/_components/StatusRenderer.svelte index f041faa349..fd289163b4 100644 --- a/packages/builder/src/pages/builder/app/[application]/settings/automation-history/_components/StatusRenderer.svelte +++ b/packages/builder/src/pages/builder/app/[application]/settings/automation-history/_components/StatusRenderer.svelte @@ -4,11 +4,13 @@ $: isError = !value || value.toLowerCase() === "error" $: isStoppedError = value?.toLowerCase() === "stopped_error" - $: isStopped = value?.toLowerCase() === "stopped" || isStoppedError - $: info = getInfo(isError, isStopped) + $: isStopped = value?.toLowerCase() === "stopped" + $: info = getInfo(isError, isStopped, isStoppedError) - const getInfo = (error, stopped) => { - if (error) { + function getInfo(error, stopped, stoppedError) { + if (stoppedError) { + return { color: "red", message: "Stopped - Error" } + } else if (error) { return { color: "red", message: "Error" } } else if (stopped) { return { color: "yellow", message: "Stopped" } diff --git a/packages/builder/src/pages/builder/app/[application]/settings/automation-history/index.svelte b/packages/builder/src/pages/builder/app/[application]/settings/automation-history/index.svelte index 190ced4b36..373a47aa2e 100644 --- a/packages/builder/src/pages/builder/app/[application]/settings/automation-history/index.svelte +++ b/packages/builder/src/pages/builder/app/[application]/settings/automation-history/index.svelte @@ -22,7 +22,8 @@ const ERROR = "error", SUCCESS = "success", - STOPPED = "stopped" + STOPPED = "stopped", + STOPPED_ERROR = "stopped_error" const sidePanel = getContext("side-panel") let pageInfo = createPaginationStore() @@ -52,6 +53,7 @@ { value: SUCCESS, label: "Success" }, { value: ERROR, label: "Error" }, { value: STOPPED, label: "Stopped" }, + { value: STOPPED_ERROR, label: "Stopped - Error" }, ] const runHistorySchema = { diff --git a/packages/server/src/threads/automation.ts b/packages/server/src/threads/automation.ts index 8bf0b506fe..fc64e206d6 100644 --- a/packages/server/src/threads/automation.ts +++ b/packages/server/src/threads/automation.ts @@ -20,6 +20,7 @@ import { AutomationMetadata, AutomationStatus, AutomationStep, + AutomationStepStatus, } from "@budibase/types" import { AutomationContext, @@ -452,7 +453,10 @@ class Orchestrator { this.executionOutput.steps.splice(loopStepNumber + 1, 0, { id: step.id, stepId: step.stepId, - outputs: { status: AutomationStatus.NO_ITERATIONS, success: true }, + outputs: { + status: AutomationStepStatus.NO_ITERATIONS, + success: true, + }, inputs: {}, }) diff --git a/packages/types/src/documents/app/automation.ts b/packages/types/src/documents/app/automation.ts index 05ac76d5e9..f4a0dd33f2 100644 --- a/packages/types/src/documents/app/automation.ts +++ b/packages/types/src/documents/app/automation.ts @@ -179,12 +179,15 @@ export interface AutomationTrigger extends AutomationTriggerSchema { id: string } +export enum AutomationStepStatus { + NO_ITERATIONS = "no_iterations", +} + export enum AutomationStatus { SUCCESS = "success", ERROR = "error", STOPPED = "stopped", STOPPED_ERROR = "stopped_error", - NO_ITERATIONS = "no_iterations", } export interface AutomationResults { From 8576f165836d6b38320bf6a86b85c25485acbd31 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Wed, 16 Aug 2023 21:07:29 +0000 Subject: [PATCH 14/45] Bump version to 2.9.26 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index d00419f904..708cc9da5f 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.9.26-alpha.2", + "version": "2.9.26", "npmClient": "yarn", "packages": [ "packages/*" From 6d40a54fd533d6516f5de93b3fb535719526171a Mon Sep 17 00:00:00 2001 From: melohagan <101575380+melohagan@users.noreply.github.com> Date: Thu, 17 Aug 2023 10:10:52 +0100 Subject: [PATCH 15/45] Only get definition for given schema (#11532) --- .vscode/launch.json | 2 -- .../server/src/integrations/microsoftSqlServer.ts | 12 +++++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 6c0089bb6b..15fc7dcf6e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,7 +8,6 @@ "name": "Budibase Server", "type": "node", "request": "launch", - "runtimeVersion": "14.20.1", "runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"], "args": ["${workspaceFolder}/packages/server/src/index.ts"], "cwd": "${workspaceFolder}/packages/server" @@ -17,7 +16,6 @@ "name": "Budibase Worker", "type": "node", "request": "launch", - "runtimeVersion": "14.20.1", "runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"], "args": ["${workspaceFolder}/packages/worker/src/index.ts"], "cwd": "${workspaceFolder}/packages/worker" diff --git a/packages/server/src/integrations/microsoftSqlServer.ts b/packages/server/src/integrations/microsoftSqlServer.ts index 2669277512..cd62e590d8 100644 --- a/packages/server/src/integrations/microsoftSqlServer.ts +++ b/packages/server/src/integrations/microsoftSqlServer.ts @@ -341,10 +341,10 @@ class SqlServerIntegration extends Sql implements DatasourcePlus { } } - getDefinitionSQL(tableName: string) { + getDefinitionSQL(tableName: string, schemaName: string) { return `select * from INFORMATION_SCHEMA.COLUMNS - where TABLE_NAME='${tableName}'` + where TABLE_NAME='${tableName}' AND TABLE_SCHEMA='${schemaName}'` } getConstraintsSQL(tableName: string) { @@ -388,16 +388,18 @@ class SqlServerIntegration extends Sql implements DatasourcePlus { throw "Unable to get list of tables in database" } - const schema = this.config.schema || DEFAULT_SCHEMA + const schemaName = this.config.schema || DEFAULT_SCHEMA const tableNames = tableInfo - .filter((record: any) => record.TABLE_SCHEMA === schema) + .filter((record: any) => record.TABLE_SCHEMA === schemaName) .map((record: any) => record.TABLE_NAME) .filter((name: string) => this.MASTER_TABLES.indexOf(name) === -1) const tables: Record = {} for (let tableName of tableNames) { // get the column definition (type) - const definition = await this.runSQL(this.getDefinitionSQL(tableName)) + const definition = await this.runSQL( + this.getDefinitionSQL(tableName, schemaName) + ) // find primary key constraints const constraints = await this.runSQL(this.getConstraintsSQL(tableName)) // find the computed and identity columns (auto columns) From 6ec948907e90b2189bd8fe02124363c4c8db51c0 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Thu, 17 Aug 2023 09:11:10 +0000 Subject: [PATCH 16/45] Bump version to 2.9.26-alpha.3 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index d00419f904..c23b30da74 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.9.26-alpha.2", + "version": "2.9.26-alpha.3", "npmClient": "yarn", "packages": [ "packages/*" From 07da8a5e0f69bc998b2626fc68f80006ee01d8fc Mon Sep 17 00:00:00 2001 From: Jonny McCullagh Date: Thu, 17 Aug 2023 10:14:09 +0100 Subject: [PATCH 17/45] remove enabled from probe values.yaml (#11546) --- charts/budibase/values.yaml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/charts/budibase/values.yaml b/charts/budibase/values.yaml index e5ce4f53fd..e5f1eabb53 100644 --- a/charts/budibase/values.yaml +++ b/charts/budibase/values.yaml @@ -137,7 +137,6 @@ services: path: /health port: 10000 scheme: HTTP - enabled: true periodSeconds: 3 failureThreshold: 1 livenessProbe: @@ -170,7 +169,6 @@ services: path: /health port: 4002 scheme: HTTP - enabled: true periodSeconds: 3 failureThreshold: 1 livenessProbe: @@ -204,7 +202,6 @@ services: path: /health port: 4003 scheme: HTTP - enabled: true periodSeconds: 3 failureThreshold: 1 livenessProbe: @@ -411,14 +408,12 @@ couchdb: ## Ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes # FOR COUCHDB livenessProbe: - enabled: true failureThreshold: 3 initialDelaySeconds: 0 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 1 readinessProbe: - enabled: true failureThreshold: 3 initialDelaySeconds: 0 periodSeconds: 10 From 2fab9a3ded83ae0a308de338c0d013023c95c200 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Thu, 17 Aug 2023 09:14:28 +0000 Subject: [PATCH 18/45] Bump version to 2.9.27 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 708cc9da5f..d64c4c2b1b 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.9.26", + "version": "2.9.27", "npmClient": "yarn", "packages": [ "packages/*" From fd099658a069e17f5247586940ae648c9172dbae Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Thu, 17 Aug 2023 10:28:15 +0100 Subject: [PATCH 19/45] revert develop into master --- .github/workflows/budibase_ci.yml | 113 +-- .../workflows/check_unreleased_changes.yml | 29 - .github/workflows/release-develop.yml | 2 +- .nvmrc | 2 +- .tool-versions | 4 +- .vscode/launch.json | 72 +- docs/CONTRIBUTING.md | 2 +- hosting/single/Dockerfile | 4 +- package.json | 4 +- .../backend-core/src/security/permissions.ts | 4 + .../core/utilities/testContainerUtils.ts | 2 +- .../actions/ChangeFormStep.svelte | 10 +- .../controls/FilterEditor/FilterDrawer.svelte | 11 +- packages/client/manifest.json | 9 - .../src/components/app/forms/Form.svelte | 11 +- .../src/components/app/forms/InnerForm.svelte | 2 +- packages/server/Dockerfile | 4 +- packages/server/package.json | 2 +- packages/server/scripts/test.sh | 5 +- .../server/src/api/controllers/query/index.ts | 4 +- .../src/api/controllers/row/external.ts | 13 +- .../server/src/api/controllers/row/index.ts | 7 +- .../src/api/controllers/row/internal.ts | 23 +- .../server/src/api/controllers/row/utils.ts | 17 +- .../server/src/api/controllers/row/views.ts | 78 ++- .../src/api/controllers/table/external.ts | 20 +- .../server/src/api/controllers/table/index.ts | 4 +- .../src/api/controllers/table/internal.ts | 35 +- .../server/src/api/controllers/table/utils.ts | 2 +- packages/server/src/api/routes/row.ts | 170 +++-- .../server/src/api/routes/tests/row.spec.ts | 656 +++++++++--------- .../src/api/routes/tests/viewV2.spec.ts | 18 +- packages/server/src/api/routes/view.ts | 2 +- .../tests/{filter.spec.ts => filter.spec.js} | 23 +- packages/server/src/db/utils.ts | 17 +- packages/server/src/integrations/mysql.ts | 66 +- packages/server/src/middleware/noViewData.ts | 9 + .../src/middleware/tests/noViewData.spec.ts | 83 +++ .../middleware/tests/trimViewRowInfo.spec.ts | 27 +- .../server/src/middleware/trimViewRowInfo.ts | 33 +- packages/server/src/sdk/app/rows/search.ts | 16 +- .../src/sdk/app/rows/search/external.ts | 3 +- .../src/sdk/app/rows/search/internal.ts | 4 +- .../app/rows/search/tests/external.spec.ts | 11 +- .../app/rows/search/tests/internal.spec.ts | 3 +- packages/server/src/sdk/app/views/index.ts | 49 +- .../src/sdk/app/views/tests/views.spec.ts | 383 ++-------- .../server/src/tests/utilities/api/row.ts | 37 +- .../server/src/tests/utilities/api/viewV2.ts | 74 +- packages/server/src/threads/definitions.ts | 6 - packages/server/src/threads/query.ts | 8 +- packages/server/src/utilities/security.ts | 8 +- packages/shared-core/package.json | 11 - packages/shared-core/src/filters.ts | 86 ++- packages/shared-core/tsconfig.build.json | 2 +- packages/types/src/api/web/app/rows.ts | 13 +- packages/types/src/api/web/app/table.ts | 12 +- packages/types/src/api/web/index.ts | 1 - packages/types/src/api/web/searchFilter.ts | 51 -- packages/types/src/documents/app/view.ts | 7 +- packages/types/src/documents/document.ts | 6 - packages/types/src/sdk/datasources.ts | 6 - packages/types/src/sdk/index.ts | 1 - packages/types/src/sdk/permissions.ts | 1 + packages/types/src/sdk/row.ts | 16 - packages/worker/Dockerfile | 2 +- .../validators/dynamodb.integration.spec.ts | 4 +- scripts/build.js | 1 + yarn.lock | 30 +- 69 files changed, 1035 insertions(+), 1416 deletions(-) delete mode 100644 .github/workflows/check_unreleased_changes.yml rename packages/server/src/automations/tests/{filter.spec.ts => filter.spec.js} (76%) create mode 100644 packages/server/src/middleware/noViewData.ts create mode 100644 packages/server/src/middleware/tests/noViewData.spec.ts delete mode 100644 packages/types/src/api/web/searchFilter.ts delete mode 100644 packages/types/src/sdk/row.ts diff --git a/.github/workflows/budibase_ci.yml b/.github/workflows/budibase_ci.yml index 6cdfba068b..9da52f8bc0 100644 --- a/.github/workflows/budibase_ci.yml +++ b/.github/workflows/budibase_ci.yml @@ -18,8 +18,6 @@ env: BRANCH: ${{ github.event.pull_request.head.ref }} BASE_BRANCH: ${{ github.event.pull_request.base.ref}} PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} - NX_BASE_BRANCH: origin/${{ github.base_ref }} - USE_NX_AFFECTED: ${{ github.event_name == 'pull_request' && github.base_ref != 'master'}} jobs: lint: @@ -27,20 +25,20 @@ jobs: steps: - name: Checkout repo and submodules uses: actions/checkout@v3 - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase' + if: github.repository == 'Budibase/budibase' with: submodules: true token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }} - name: Checkout repo only uses: actions/checkout@v3 - if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != 'Budibase/budibase' + if: github.repository != 'Budibase/budibase' - - name: Use Node.js 18.x + - name: Use Node.js 14.x uses: actions/setup-node@v3 with: - node-version: 18.x + node-version: 14.x cache: "yarn" - - run: yarn --frozen-lockfile + - run: yarn - run: yarn lint build: @@ -48,66 +46,45 @@ jobs: steps: - name: Checkout repo and submodules uses: actions/checkout@v3 - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase' + if: github.repository == 'Budibase/budibase' with: submodules: true token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }} - fetch-depth: 0 - name: Checkout repo only uses: actions/checkout@v3 - if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != 'Budibase/budibase' - with: - fetch-depth: 0 + if: github.repository != 'Budibase/budibase' - - name: Use Node.js 18.x + - name: Use Node.js 14.x uses: actions/setup-node@v3 with: - node-version: 18.x + node-version: 14.x cache: "yarn" - - run: yarn --frozen-lockfile - + - run: yarn # Run build all the projects - - name: Build - run: | - yarn build + - run: yarn build # Check the types of the projects built via esbuild - - name: Check types - run: | - if ${{ env.USE_NX_AFFECTED }}; then - yarn check:types --since=${{ env.NX_BASE_BRANCH }} - else - yarn check:types - fi + - run: yarn check:types test-libraries: runs-on: ubuntu-latest steps: - name: Checkout repo and submodules uses: actions/checkout@v3 - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase' + if: github.repository == 'Budibase/budibase' with: submodules: true token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }} - fetch-depth: 0 - name: Checkout repo only uses: actions/checkout@v3 - if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != 'Budibase/budibase' - with: - fetch-depth: 0 + if: github.repository != 'Budibase/budibase' - - name: Use Node.js 18.x + - name: Use Node.js 14.x uses: actions/setup-node@v3 with: - node-version: 18.x + node-version: 14.x cache: "yarn" - - run: yarn --frozen-lockfile - - name: Test - run: | - if ${{ env.USE_NX_AFFECTED }}; then - yarn test --ignore=@budibase/worker --ignore=@budibase/server --ignore=@budibase/pro --since=${{ env.NX_BASE_BRANCH }} - else - yarn test --ignore=@budibase/worker --ignore=@budibase/server --ignore=@budibase/pro - fi + - run: yarn + - run: yarn test --ignore=@budibase/worker --ignore=@budibase/server --ignore=@budibase/pro - uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos @@ -119,31 +96,21 @@ jobs: steps: - name: Checkout repo and submodules uses: actions/checkout@v3 - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase' + if: github.repository == 'Budibase/budibase' with: submodules: true token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }} - fetch-depth: 0 - name: Checkout repo only uses: actions/checkout@v3 - if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != 'Budibase/budibase' - with: - fetch-depth: 0 + if: github.repository != 'Budibase/budibase' - - name: Use Node.js 18.x + - name: Use Node.js 14.x uses: actions/setup-node@v3 with: - node-version: 18.x + node-version: 14.x cache: "yarn" - - run: yarn --frozen-lockfile - - name: Test worker and server - run: | - if ${{ env.USE_NX_AFFECTED }}; then - yarn test --scope=@budibase/worker --scope=@budibase/server --since=${{ env.NX_BASE_BRANCH }} - else - yarn test --scope=@budibase/worker --scope=@budibase/server - fi - + - run: yarn + - run: yarn test --scope=@budibase/worker --scope=@budibase/server - uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_TOKEN || github.token }} # not required for public repos @@ -152,49 +119,42 @@ jobs: test-pro: runs-on: ubuntu-latest - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase' + if: github.repository == 'Budibase/budibase' steps: - name: Checkout repo and submodules uses: actions/checkout@v3 with: submodules: true token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }} - fetch-depth: 0 - - name: Use Node.js 18.x + - name: Use Node.js 14.x uses: actions/setup-node@v3 with: - node-version: 18.x + node-version: 14.x cache: "yarn" - - run: yarn --frozen-lockfile - - name: Test - run: | - if ${{ env.USE_NX_AFFECTED }}; then - yarn test --scope=@budibase/pro --since=${{ env.NX_BASE_BRANCH }} - else - yarn test --scope=@budibase/pro - fi + - run: yarn + - run: yarn test --scope=@budibase/pro integration-test: runs-on: ubuntu-latest steps: - name: Checkout repo and submodules uses: actions/checkout@v3 - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase' + if: github.repository == 'Budibase/budibase' with: submodules: true token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }} - name: Checkout repo only uses: actions/checkout@v3 - if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != 'Budibase/budibase' + if: github.repository != 'Budibase/budibase' - - name: Use Node.js 18.x + - name: Use Node.js 14.x uses: actions/setup-node@v3 with: - node-version: 18.x + node-version: 14.x cache: "yarn" - - run: yarn --frozen-lockfile - - run: yarn build --scope @budibase/server --scope @budibase/worker --scope @budibase/client + - run: yarn + - run: yarn build --projects=@budibase/server,@budibase/worker,@budibase/client - name: Run tests run: | cd qa-core @@ -206,12 +166,13 @@ jobs: check-pro-submodule: runs-on: ubuntu-latest - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase' + if: github.repository == 'Budibase/budibase' steps: - name: Checkout repo and submodules uses: actions/checkout@v3 with: submodules: true + fetch-depth: 0 token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }} - name: Check pro commit diff --git a/.github/workflows/check_unreleased_changes.yml b/.github/workflows/check_unreleased_changes.yml deleted file mode 100644 index d558330545..0000000000 --- a/.github/workflows/check_unreleased_changes.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: check_unreleased_changes - -on: - pull_request: - branches: - - master - -jobs: - check_unreleased: - runs-on: ubuntu-latest - steps: - - name: Check for unreleased changes - env: - REPO: "Budibase/budibase" - TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - RELEASE_TIMESTAMP=$(curl -s -H "Authorization: token $TOKEN" \ - "https://api.github.com/repos/$REPO/releases/latest" | \ - jq -r .published_at) - COMMIT_TIMESTAMP=$(curl -s -H "Authorization: token $TOKEN" \ - "https://api.github.com/repos/$REPO/commits/master" | \ - jq -r .commit.committer.date) - RELEASE_SECONDS=$(date --date="$RELEASE_TIMESTAMP" "+%s") - COMMIT_SECONDS=$(date --date="$COMMIT_TIMESTAMP" "+%s") - if (( COMMIT_SECONDS > RELEASE_SECONDS )); then - echo "There are unreleased changes. Please release these changes before merging." - exit 1 - fi - echo "No unreleased changes detected." diff --git a/.github/workflows/release-develop.yml b/.github/workflows/release-develop.yml index bd727b7865..61cb283e28 100644 --- a/.github/workflows/release-develop.yml +++ b/.github/workflows/release-develop.yml @@ -44,7 +44,7 @@ jobs: - uses: actions/setup-node@v1 with: - node-version: 18.x + node-version: 14.x - run: yarn install --frozen-lockfile - name: Update versions diff --git a/.nvmrc b/.nvmrc index 7950a44576..835d07c442 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v18.17.0 +v14.20.1 diff --git a/.tool-versions b/.tool-versions index a909d60941..9f2ea77b14 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,3 +1,3 @@ -nodejs 18.17.0 +nodejs 14.21.3 python 3.10.0 -yarn 1.22.19 +yarn 1.22.19 \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 6c0089bb6b..8cb49d5825 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,32 +1,42 @@ { - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Budibase Server", - "type": "node", - "request": "launch", - "runtimeVersion": "14.20.1", - "runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"], - "args": ["${workspaceFolder}/packages/server/src/index.ts"], - "cwd": "${workspaceFolder}/packages/server" - }, - { - "name": "Budibase Worker", - "type": "node", - "request": "launch", - "runtimeVersion": "14.20.1", - "runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"], - "args": ["${workspaceFolder}/packages/worker/src/index.ts"], - "cwd": "${workspaceFolder}/packages/worker" - } - ], - "compounds": [ - { - "name": "Start Budibase", - "configurations": ["Budibase Server", "Budibase Worker"] - } - ] -} + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Budibase Server", + "type": "node", + "request": "launch", + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register/transpile-only" + ], + "args": [ + "${workspaceFolder}/packages/server/src/index.ts" + ], + "cwd": "${workspaceFolder}/packages/server" + }, + { + "name": "Budibase Worker", + "type": "node", + "request": "launch", + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register/transpile-only" + ], + "args": [ + "${workspaceFolder}/packages/worker/src/index.ts" + ], + "cwd": "${workspaceFolder}/packages/worker" + }, + ], + "compounds": [ + { + "name": "Start Budibase", + "configurations": ["Budibase Server", "Budibase Worker"] + } + ] +} \ No newline at end of file diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 70f198a84c..2fb4c36fa8 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -90,7 +90,7 @@ Component libraries are collections of components as well as the definition of t #### 1. Prerequisites -- NodeJS version `18.x.x` +- NodeJS version `14.x.x` - Python version `3.x` ### Using asdf (recommended) diff --git a/hosting/single/Dockerfile b/hosting/single/Dockerfile index 9fdf2449d1..e43e5ad10c 100644 --- a/hosting/single/Dockerfile +++ b/hosting/single/Dockerfile @@ -1,7 +1,7 @@ -FROM node:18-slim as build +FROM node:14-slim as build # install node-gyp dependencies -RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends apt-utils cron g++ make python3 +RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends apt-utils cron g++ make python # add pin script WORKDIR / diff --git a/package.json b/package.json index 4e4befb5f2..d27af2e27d 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "preinstall": "node scripts/syncProPackage.js", "setup": "git config submodule.recurse true && git submodule update && node ./hosting/scripts/setup.js && yarn && yarn build && yarn dev", "bootstrap": "./scripts/link-dependencies.sh && echo '***BOOTSTRAP ONLY REQUIRED FOR USE WITH ACCOUNT PORTAL***'", - "build": "lerna run build --stream", + "build": "yarn nx run-many -t=build", "build:dev": "lerna run --stream prebuild && yarn nx run-many --target=build --output-style=dynamic --watch --preserveWatchOutput", "check:types": "lerna run check:types", "backend:bootstrap": "./scripts/scopeBackend.sh && yarn run bootstrap", @@ -109,7 +109,7 @@ "@budibase/types": "0.0.0" }, "engines": { - "node": ">=18.0.0 <19.0.0" + "node": ">=14.0.0 <15.0.0" }, "dependencies": {} } diff --git a/packages/backend-core/src/security/permissions.ts b/packages/backend-core/src/security/permissions.ts index aa0b20a30c..70dae57ae6 100644 --- a/packages/backend-core/src/security/permissions.ts +++ b/packages/backend-core/src/security/permissions.ts @@ -78,6 +78,7 @@ export const BUILTIN_PERMISSIONS = { permissions: [ new Permission(PermissionType.QUERY, PermissionLevel.READ), new Permission(PermissionType.TABLE, PermissionLevel.READ), + new Permission(PermissionType.VIEW, PermissionLevel.READ), ], }, WRITE: { @@ -86,6 +87,7 @@ export const BUILTIN_PERMISSIONS = { permissions: [ new Permission(PermissionType.QUERY, PermissionLevel.WRITE), new Permission(PermissionType.TABLE, PermissionLevel.WRITE), + new Permission(PermissionType.VIEW, PermissionLevel.READ), new Permission(PermissionType.AUTOMATION, PermissionLevel.EXECUTE), ], }, @@ -96,6 +98,7 @@ export const BUILTIN_PERMISSIONS = { new Permission(PermissionType.TABLE, PermissionLevel.WRITE), new Permission(PermissionType.USER, PermissionLevel.READ), new Permission(PermissionType.AUTOMATION, PermissionLevel.EXECUTE), + new Permission(PermissionType.VIEW, PermissionLevel.READ), new Permission(PermissionType.WEBHOOK, PermissionLevel.READ), ], }, @@ -106,6 +109,7 @@ export const BUILTIN_PERMISSIONS = { new Permission(PermissionType.TABLE, PermissionLevel.ADMIN), new Permission(PermissionType.USER, PermissionLevel.ADMIN), new Permission(PermissionType.AUTOMATION, PermissionLevel.ADMIN), + new Permission(PermissionType.VIEW, PermissionLevel.ADMIN), new Permission(PermissionType.WEBHOOK, PermissionLevel.READ), new Permission(PermissionType.QUERY, PermissionLevel.ADMIN), ], diff --git a/packages/backend-core/tests/core/utilities/testContainerUtils.ts b/packages/backend-core/tests/core/utilities/testContainerUtils.ts index 06bd91392e..f6c702f7ef 100644 --- a/packages/backend-core/tests/core/utilities/testContainerUtils.ts +++ b/packages/backend-core/tests/core/utilities/testContainerUtils.ts @@ -80,7 +80,7 @@ function getRedisConfig() { export function setupEnv(...envs: any[]) { const couch = getCouchConfig(), - minio = getMinioConfig(), + minio = getCouchConfig(), redis = getRedisConfig() const configs = [ { key: "COUCH_DB_PORT", value: couch.port }, diff --git a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/ChangeFormStep.svelte b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/ChangeFormStep.svelte index 81a2119474..ca2df71c6d 100644 --- a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/ChangeFormStep.svelte +++ b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/ChangeFormStep.svelte @@ -1,12 +1,10 @@