From 9b4c22727f7b422dd4528ff4ddd58151bbb1bf06 Mon Sep 17 00:00:00 2001 From: Dean Date: Mon, 25 Mar 2024 12:36:56 +0000 Subject: [PATCH 1/8] Fix to ensure runtimeTime binding is used is automation js modal --- .../SetupPanel/AutomationBlockSetup.svelte | 3 ++- .../src/components/common/bindings/utils.js | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte index 7fa2401c88..0632993cf0 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte @@ -31,7 +31,7 @@ import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte" import CodeEditor from "components/common/CodeEditor/CodeEditor.svelte" import BindingSidePanel from "components/common/bindings/BindingSidePanel.svelte" - import { BindingHelpers } from "components/common/bindings/utils" + import { BindingHelpers, BindingType } from "components/common/bindings/utils" import { bindingsToCompletions, hbAutocomplete, @@ -576,6 +576,7 @@ { js: true, dontDecode: true, + type: BindingType.RUNTIME, } )} mode="javascript" diff --git a/packages/builder/src/components/common/bindings/utils.js b/packages/builder/src/components/common/bindings/utils.js index c60374f0f7..77e4a1dfb1 100644 --- a/packages/builder/src/components/common/bindings/utils.js +++ b/packages/builder/src/components/common/bindings/utils.js @@ -1,6 +1,11 @@ import { decodeJSBinding } from "@budibase/string-templates" import { hbInsert, jsInsert } from "components/common/CodeEditor" +export const BindingType = { + READABLE: "readableBinding", + RUNTIME: "runtimeBinding", +} + export class BindingHelpers { constructor(getCaretPosition, insertAtPos, { disableWrapping } = {}) { this.getCaretPosition = getCaretPosition @@ -25,16 +30,20 @@ export class BindingHelpers { } // Adds a data binding to the expression - onSelectBinding(value, binding, { js, dontDecode }) { + onSelectBinding( + value, + binding, + { js, dontDecode, type = BindingType.READABLE } + ) { const { start, end } = this.getCaretPosition() if (js) { const jsVal = dontDecode ? value : decodeJSBinding(value) - const insertVal = jsInsert(jsVal, start, end, binding.readableBinding, { + const insertVal = jsInsert(jsVal, start, end, binding[type], { disableWrapping: this.disableWrapping, }) this.insertAtPos({ start, end, value: insertVal }) } else { - const insertVal = hbInsert(value, start, end, binding.readableBinding) + const insertVal = hbInsert(value, start, end, binding[type]) this.insertAtPos({ start, end, value: insertVal }) } } From b77106480e2dba9dff5cf3b818c494a9ee8d2522 Mon Sep 17 00:00:00 2001 From: jvcalderon Date: Thu, 28 Mar 2024 11:46:58 +0100 Subject: [PATCH 2/8] Adds POST /api/global/users/sso endpoint --- packages/backend-core/src/platform/users.ts | 17 ++++++-- .../tests/core/utilities/structures/users.ts | 1 + packages/types/src/api/web/user.ts | 5 +++ .../src/api/controllers/global/users.ts | 19 +++++++++ packages/worker/src/api/index.ts | 9 +++++ .../src/api/routes/global/tests/users.spec.ts | 40 +++++++++++++++++++ .../worker/src/api/routes/global/users.ts | 6 +++ .../worker/src/api/routes/validation/users.ts | 9 +++++ packages/worker/src/tests/api/users.ts | 14 +++++++ 9 files changed, 116 insertions(+), 4 deletions(-) diff --git a/packages/backend-core/src/platform/users.ts b/packages/backend-core/src/platform/users.ts index 6f030afb7c..ccaad76b19 100644 --- a/packages/backend-core/src/platform/users.ts +++ b/packages/backend-core/src/platform/users.ts @@ -20,7 +20,7 @@ export async function lookupTenantId(userId: string) { return user.tenantId } -async function getUserDoc(emailOrId: string): Promise { +export async function getUserDoc(emailOrId: string): Promise { const db = getPlatformDB() return db.get(emailOrId) } @@ -79,6 +79,17 @@ async function addUserDoc(emailOrId: string, newDocFn: () => PlatformUser) { } } +export async function addSsoUser( + ssoId: string, + email: string, + userId: string, + tenantId: string +) { + return addUserDoc(ssoId, () => + newUserSsoIdDoc(ssoId, email, userId, tenantId) + ) +} + export async function addUser( tenantId: string, userId: string, @@ -91,9 +102,7 @@ export async function addUser( ] if (ssoId) { - promises.push( - addUserDoc(ssoId, () => newUserSsoIdDoc(ssoId, email, userId, tenantId)) - ) + promises.push(addSsoUser(ssoId, email, userId, tenantId)) } await Promise.all(promises) diff --git a/packages/backend-core/tests/core/utilities/structures/users.ts b/packages/backend-core/tests/core/utilities/structures/users.ts index db90887af2..88fb02617f 100644 --- a/packages/backend-core/tests/core/utilities/structures/users.ts +++ b/packages/backend-core/tests/core/utilities/structures/users.ts @@ -86,6 +86,7 @@ export function ssoUser( oauth2: opts.details?.oauth2, provider: opts.details?.provider!, providerType: opts.details?.providerType!, + ssoId: opts.details?.userId || uuid(), thirdPartyProfile: { email: base.email, picture: base.pictureUrl, diff --git a/packages/types/src/api/web/user.ts b/packages/types/src/api/web/user.ts index d68d687dcb..0ef7493016 100644 --- a/packages/types/src/api/web/user.ts +++ b/packages/types/src/api/web/user.ts @@ -68,6 +68,11 @@ export interface CreateAdminUserRequest { ssoId?: string } +export interface AddSSoUserRequest { + ssoId: string + email: string +} + export interface CreateAdminUserResponse { _id: string _rev: string diff --git a/packages/worker/src/api/controllers/global/users.ts b/packages/worker/src/api/controllers/global/users.ts index 6b9e533f78..4e59873e33 100644 --- a/packages/worker/src/api/controllers/global/users.ts +++ b/packages/worker/src/api/controllers/global/users.ts @@ -3,6 +3,7 @@ import env from "../../../environment" import { AcceptUserInviteRequest, AcceptUserInviteResponse, + AddSSoUserRequest, BulkUserRequest, BulkUserResponse, CloudAccount, @@ -15,6 +16,7 @@ import { LockName, LockType, MigrationType, + PlatformUserByEmail, SaveUserResponse, SearchUsersRequest, User, @@ -53,6 +55,23 @@ export const save = async (ctx: UserCtx) => { } } +export const addSsoSupport = async (ctx: Ctx) => { + const { email, ssoId } = ctx.request.body + try { + // Status is changed to 404 from getUserDoc if user is not found + let userByEmail = (await platform.users.getUserDoc(email)) as PlatformUserByEmail + await platform.users.addSsoUser( + ssoId, + email, + userByEmail.userId, + userByEmail.tenantId + ) + ctx.status = 200 + } catch (err: any) { + ctx.throw(err.status || 400, err) + } +} + const bulkDelete = async (userIds: string[], currentUserId: string) => { if (userIds?.indexOf(currentUserId) !== -1) { throw new Error("Unable to delete self.") diff --git a/packages/worker/src/api/index.ts b/packages/worker/src/api/index.ts index d7aef0b274..4936c104e1 100644 --- a/packages/worker/src/api/index.ts +++ b/packages/worker/src/api/index.ts @@ -41,6 +41,10 @@ const PUBLIC_ENDPOINTS = [ route: "/api/global/users/init", method: "POST", }, + { + route: "/api/global/users/sso", + method: "POST", + }, { route: "/api/global/users/invite/accept", method: "POST", @@ -81,6 +85,11 @@ const NO_TENANCY_ENDPOINTS = [ route: "/api/global/users/init", method: "POST", }, + // tenant is retrieved from the user found by the requested email + { + route: "/api/global/users/sso", + method: "POST", + }, // deprecated single tenant sso callback { route: "/api/admin/auth/google/callback", diff --git a/packages/worker/src/api/routes/global/tests/users.spec.ts b/packages/worker/src/api/routes/global/tests/users.spec.ts index 37f5721881..6923a7839b 100644 --- a/packages/worker/src/api/routes/global/tests/users.spec.ts +++ b/packages/worker/src/api/routes/global/tests/users.spec.ts @@ -520,10 +520,50 @@ describe("/api/global/users", () => { }) } + function createPasswordUser() { + return config.doInTenant(() => { + const user = structures.users.user() + return userSdk.db.save(user) + }) + } + it("should be able to update an sso user that has no password", async () => { const user = await createSSOUser() await config.api.users.saveUser(user) }) + + it("sso support couldn't be used by admin. It is cloud restricted and needs internal key", async () => { + const user = await config.createUser() + const ssoId = "fake-ssoId" + await config.api.users + .addSsoSupportDefaultAuth(ssoId, user.email) + .expect("Content-Type", /json/) + .expect(403) + }) + + it("if user email doesn't exist, SSO support couldn't be added. Not found error returned", async () => { + const ssoId = "fake-ssoId" + const email = "fake-email@budibase.com" + await config.api.users + .addSsoSupportInternalAPIAuth(ssoId, email) + .expect("Content-Type", /json/) + .expect(404) + }) + + it("if user email exist, SSO support is added", async () => { + const user = await createPasswordUser() + const ssoId = "fakessoId" + await config.api.users + .addSsoSupportInternalAPIAuth(ssoId, user.email) + .expect(200) + }) + + it("if user ssoId is already assigned, no change will be applied", async () => { + const user = await createSSOUser() + await config.api.users + .addSsoSupportInternalAPIAuth(user.ssoId, user.email) + .expect(200) + }) }) }) diff --git a/packages/worker/src/api/routes/global/users.ts b/packages/worker/src/api/routes/global/users.ts index 6b9291b88b..e7c77678fc 100644 --- a/packages/worker/src/api/routes/global/users.ts +++ b/packages/worker/src/api/routes/global/users.ts @@ -65,6 +65,12 @@ router users.buildUserSaveValidation(), controller.save ) + .post( + "/api/global/users/sso", + cloudRestricted, + users.buildAddSsoSupport(), + controller.addSsoSupport + ) .post( "/api/global/users/bulk", auth.adminOnly, diff --git a/packages/worker/src/api/routes/validation/users.ts b/packages/worker/src/api/routes/validation/users.ts index fbc85af2d3..cbd7567457 100644 --- a/packages/worker/src/api/routes/validation/users.ts +++ b/packages/worker/src/api/routes/validation/users.ts @@ -41,6 +41,15 @@ export const buildUserSaveValidation = () => { return auth.joiValidator.body(Joi.object(schema).required().unknown(true)) } +export const buildAddSsoSupport = () => { + return auth.joiValidator.body( + Joi.object({ + ssoId: Joi.string().required(), + email: Joi.string().required(), + }).required() + ) +} + export const buildUserBulkUserValidation = (isSelf = false) => { if (!isSelf) { schema = { diff --git a/packages/worker/src/tests/api/users.ts b/packages/worker/src/tests/api/users.ts index 45105c99da..d08a4ef8c7 100644 --- a/packages/worker/src/tests/api/users.ts +++ b/packages/worker/src/tests/api/users.ts @@ -127,6 +127,20 @@ export class UserAPI extends TestAPI { .expect(status ? status : 200) } + addSsoSupportInternalAPIAuth = (ssoId: string, email: string) => { + return this.request + .post(`/api/global/users/sso`) + .send({ ssoId, email }) + .set(this.config.internalAPIHeaders()) + } + + addSsoSupportDefaultAuth = (ssoId: string, email: string) => { + return this.request + .post(`/api/global/users/sso`) + .send({ ssoId, email }) + .set(this.config.defaultHeaders()) + } + deleteUser = (userId: string, status?: number) => { return this.request .delete(`/api/global/users/${userId}`) From af2407e8cdeaee721e14c6a64a0b1ae0f5b107a8 Mon Sep 17 00:00:00 2001 From: jvcalderon Date: Thu, 28 Mar 2024 12:04:27 +0100 Subject: [PATCH 3/8] Lint --- packages/worker/src/api/controllers/global/users.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/worker/src/api/controllers/global/users.ts b/packages/worker/src/api/controllers/global/users.ts index 4e59873e33..93f35b4c37 100644 --- a/packages/worker/src/api/controllers/global/users.ts +++ b/packages/worker/src/api/controllers/global/users.ts @@ -59,7 +59,9 @@ export const addSsoSupport = async (ctx: Ctx) => { const { email, ssoId } = ctx.request.body try { // Status is changed to 404 from getUserDoc if user is not found - let userByEmail = (await platform.users.getUserDoc(email)) as PlatformUserByEmail + let userByEmail = (await platform.users.getUserDoc( + email + )) as PlatformUserByEmail await platform.users.addSsoUser( ssoId, email, From d8de0a31541820a855427f7e794769abb2ac7f47 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 28 Mar 2024 12:14:56 +0000 Subject: [PATCH 4/8] An issue which comes up relatively often is that localhost is not a valid option for a datasource in our cloud and our self host environments. Fixing this so it only shows this in development. --- packages/server/src/integrations/couchdb.ts | 3 ++- packages/server/src/integrations/elasticsearch.ts | 3 ++- packages/server/src/integrations/microsoftSqlServer.ts | 4 ++-- packages/server/src/integrations/mongodb.ts | 3 ++- packages/server/src/integrations/mysql.ts | 3 ++- packages/server/src/integrations/oracle.ts | 3 ++- packages/server/src/integrations/postgres.ts | 3 ++- packages/server/src/integrations/redis.ts | 3 ++- packages/server/src/integrations/utils.ts | 9 +++++++++ 9 files changed, 25 insertions(+), 9 deletions(-) diff --git a/packages/server/src/integrations/couchdb.ts b/packages/server/src/integrations/couchdb.ts index 079f646b60..c271fb12b2 100644 --- a/packages/server/src/integrations/couchdb.ts +++ b/packages/server/src/integrations/couchdb.ts @@ -9,6 +9,7 @@ import { QueryType, } from "@budibase/types" import { db as dbCore } from "@budibase/backend-core" +import { HOST_ADDRESS } from "./utils" interface CouchDBConfig { url: string @@ -28,7 +29,7 @@ const SCHEMA: Integration = { url: { type: DatasourceFieldType.STRING, required: true, - default: "http://localhost:5984", + default: `http://${HOST_ADDRESS}:5984`, }, database: { type: DatasourceFieldType.STRING, diff --git a/packages/server/src/integrations/elasticsearch.ts b/packages/server/src/integrations/elasticsearch.ts index 7ae0295298..af03baaef1 100644 --- a/packages/server/src/integrations/elasticsearch.ts +++ b/packages/server/src/integrations/elasticsearch.ts @@ -8,6 +8,7 @@ import { } from "@budibase/types" import { Client, ClientOptions } from "@elastic/elasticsearch" +import { HOST_ADDRESS } from "./utils" interface ElasticsearchConfig { url: string @@ -29,7 +30,7 @@ const SCHEMA: Integration = { url: { type: DatasourceFieldType.STRING, required: true, - default: "http://localhost:9200", + default: `http://${HOST_ADDRESS}:9200`, }, ssl: { type: DatasourceFieldType.BOOLEAN, diff --git a/packages/server/src/integrations/microsoftSqlServer.ts b/packages/server/src/integrations/microsoftSqlServer.ts index 831fcd3d20..8e8e84f533 100644 --- a/packages/server/src/integrations/microsoftSqlServer.ts +++ b/packages/server/src/integrations/microsoftSqlServer.ts @@ -22,6 +22,7 @@ import { finaliseExternalTables, SqlClient, checkExternalTables, + HOST_ADDRESS, } from "./utils" import Sql from "./base/sql" import { MSSQLTablesResponse, MSSQLColumn } from "./base/types" @@ -88,7 +89,6 @@ const SCHEMA: Integration = { user: { type: DatasourceFieldType.STRING, required: true, - default: "localhost", }, password: { type: DatasourceFieldType.PASSWORD, @@ -96,7 +96,7 @@ const SCHEMA: Integration = { }, server: { type: DatasourceFieldType.STRING, - default: "localhost", + default: HOST_ADDRESS, }, port: { type: DatasourceFieldType.NUMBER, diff --git a/packages/server/src/integrations/mongodb.ts b/packages/server/src/integrations/mongodb.ts index c9852e4c7a..dea752502d 100644 --- a/packages/server/src/integrations/mongodb.ts +++ b/packages/server/src/integrations/mongodb.ts @@ -22,6 +22,7 @@ import { InsertManyResult, } from "mongodb" import environment from "../environment" +import { HOST_ADDRESS } from "./utils" export interface MongoDBConfig { connectionString: string @@ -51,7 +52,7 @@ const getSchema = () => { connectionString: { type: DatasourceFieldType.STRING, required: true, - default: "mongodb://localhost:27017", + default: `mongodb://${HOST_ADDRESS}:27017`, display: "Connection string", }, db: { diff --git a/packages/server/src/integrations/mysql.ts b/packages/server/src/integrations/mysql.ts index db3819f468..fd9d57d255 100644 --- a/packages/server/src/integrations/mysql.ts +++ b/packages/server/src/integrations/mysql.ts @@ -21,6 +21,7 @@ import { generateColumnDefinition, finaliseExternalTables, checkExternalTables, + HOST_ADDRESS, } from "./utils" import dayjs from "dayjs" import { NUMBER_REGEX } from "../utilities" @@ -49,7 +50,7 @@ const SCHEMA: Integration = { datasource: { host: { type: DatasourceFieldType.STRING, - default: "localhost", + default: HOST_ADDRESS, required: true, }, port: { diff --git a/packages/server/src/integrations/oracle.ts b/packages/server/src/integrations/oracle.ts index 83803906be..9104aadbcc 100644 --- a/packages/server/src/integrations/oracle.ts +++ b/packages/server/src/integrations/oracle.ts @@ -22,6 +22,7 @@ import { finaliseExternalTables, getSqlQuery, SqlClient, + HOST_ADDRESS, } from "./utils" import Sql from "./base/sql" import { @@ -63,7 +64,7 @@ const SCHEMA: Integration = { datasource: { host: { type: DatasourceFieldType.STRING, - default: "localhost", + default: HOST_ADDRESS, required: true, }, port: { diff --git a/packages/server/src/integrations/postgres.ts b/packages/server/src/integrations/postgres.ts index 635d834761..05a519da64 100644 --- a/packages/server/src/integrations/postgres.ts +++ b/packages/server/src/integrations/postgres.ts @@ -21,6 +21,7 @@ import { finaliseExternalTables, SqlClient, checkExternalTables, + HOST_ADDRESS, } from "./utils" import Sql from "./base/sql" import { PostgresColumn } from "./base/types" @@ -72,7 +73,7 @@ const SCHEMA: Integration = { datasource: { host: { type: DatasourceFieldType.STRING, - default: "localhost", + default: HOST_ADDRESS, required: true, }, port: { diff --git a/packages/server/src/integrations/redis.ts b/packages/server/src/integrations/redis.ts index 6a6331ccd4..e127cddd56 100644 --- a/packages/server/src/integrations/redis.ts +++ b/packages/server/src/integrations/redis.ts @@ -6,6 +6,7 @@ import { QueryType, } from "@budibase/types" import Redis from "ioredis" +import { HOST_ADDRESS } from "./utils" interface RedisConfig { host: string @@ -28,7 +29,7 @@ const SCHEMA: Integration = { host: { type: DatasourceFieldType.STRING, required: true, - default: "localhost", + default: HOST_ADDRESS, }, port: { type: DatasourceFieldType.NUMBER, diff --git a/packages/server/src/integrations/utils.ts b/packages/server/src/integrations/utils.ts index f73f522357..1ba379da9e 100644 --- a/packages/server/src/integrations/utils.ts +++ b/packages/server/src/integrations/utils.ts @@ -13,6 +13,7 @@ import { DEFAULT_BB_DATASOURCE_ID, } from "../constants" import { helpers } from "@budibase/shared-core" +import env from "../environment" const DOUBLE_SEPARATOR = `${SEPARATOR}${SEPARATOR}` const ROW_ID_REGEX = /^\[.*]$/g @@ -92,6 +93,14 @@ export enum SqlClient { ORACLE = "oracledb", } +const isCloud = env.isProd() && !env.SELF_HOSTED +const isSelfHost = env.isProd() && env.SELF_HOSTED +export const HOST_ADDRESS = isSelfHost + ? "host.docker.internal" + : isCloud + ? "" + : "localhost" + export function isExternalTableID(tableId: string) { return tableId.includes(DocumentType.DATASOURCE) } From 32a5ce24ec7f20003e671ac2c8d99bde322d59fa Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Thu, 28 Mar 2024 15:46:43 +0000 Subject: [PATCH 5/8] remove automatic ssoId generation --- packages/backend-core/tests/core/utilities/structures/users.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/backend-core/tests/core/utilities/structures/users.ts b/packages/backend-core/tests/core/utilities/structures/users.ts index 88fb02617f..db90887af2 100644 --- a/packages/backend-core/tests/core/utilities/structures/users.ts +++ b/packages/backend-core/tests/core/utilities/structures/users.ts @@ -86,7 +86,6 @@ export function ssoUser( oauth2: opts.details?.oauth2, provider: opts.details?.provider!, providerType: opts.details?.providerType!, - ssoId: opts.details?.userId || uuid(), thirdPartyProfile: { email: base.email, picture: base.pictureUrl, From a7722bccc094646de07607ca1a9edc3930237816 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Thu, 28 Mar 2024 16:18:51 +0000 Subject: [PATCH 6/8] fix worker test --- packages/worker/src/api/routes/global/tests/users.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/worker/src/api/routes/global/tests/users.spec.ts b/packages/worker/src/api/routes/global/tests/users.spec.ts index 6923a7839b..2198757be1 100644 --- a/packages/worker/src/api/routes/global/tests/users.spec.ts +++ b/packages/worker/src/api/routes/global/tests/users.spec.ts @@ -560,6 +560,7 @@ describe("/api/global/users", () => { it("if user ssoId is already assigned, no change will be applied", async () => { const user = await createSSOUser() + user.ssoId = "testssoId" await config.api.users .addSsoSupportInternalAPIAuth(user.ssoId, user.email) .expect(200) From 0fe6804290564756b95a5539a6ba20ac439a285a Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Thu, 28 Mar 2024 16:49:13 +0000 Subject: [PATCH 7/8] Bump version to 2.22.13 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 9cffdba08a..bacdcb782f 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.22.12", + "version": "2.22.13", "npmClient": "yarn", "packages": [ "packages/*", From 29d0b91cc2da8ab725111cbe22c60a1130c7beda Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Thu, 28 Mar 2024 17:28:41 +0000 Subject: [PATCH 8/8] update account-portal ref --- packages/account-portal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/account-portal b/packages/account-portal index 60658a052d..63ce32bca8 160000 --- a/packages/account-portal +++ b/packages/account-portal @@ -1 +1 @@ -Subproject commit 60658a052d2642e5f4a8038e253f771a24f34907 +Subproject commit 63ce32bca871f0a752323f5f7ebb5ec16bbbacc3