Merge remote-tracking branch 'origin/master' into feature/signature-field-and-component

This commit is contained in:
Dean 2024-04-02 09:28:05 +01:00
commit 3bb33e4c6d
21 changed files with 159 additions and 19 deletions

View File

@ -1,5 +1,5 @@
{
"version": "2.22.12",
"version": "2.22.13",
"npmClient": "yarn",
"packages": [
"packages/*",

@ -1 +1 @@
Subproject commit 60658a052d2642e5f4a8038e253f771a24f34907
Subproject commit 63ce32bca871f0a752323f5f7ebb5ec16bbbacc3

View File

@ -20,7 +20,7 @@ export async function lookupTenantId(userId: string) {
return user.tenantId
}
async function getUserDoc(emailOrId: string): Promise<PlatformUser> {
export async function getUserDoc(emailOrId: string): Promise<PlatformUser> {
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)

View File

@ -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"

View File

@ -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 })
}
}

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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: {

View File

@ -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: {

View File

@ -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: {

View File

@ -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: {

View File

@ -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,

View File

@ -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)
}

View File

@ -68,6 +68,11 @@ export interface CreateAdminUserRequest {
ssoId?: string
}
export interface AddSSoUserRequest {
ssoId: string
email: string
}
export interface CreateAdminUserResponse {
_id: string
_rev: string

View File

@ -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,25 @@ export const save = async (ctx: UserCtx<User, SaveUserResponse>) => {
}
}
export const addSsoSupport = async (ctx: Ctx<AddSSoUserRequest>) => {
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.")

View File

@ -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",

View File

@ -520,10 +520,51 @@ 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()
user.ssoId = "testssoId"
await config.api.users
.addSsoSupportInternalAPIAuth(user.ssoId, user.email)
.expect(200)
})
})
})

View File

@ -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,

View File

@ -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 = {

View File

@ -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}`)