Merge pull request #9481 from Budibase/feature/9480

Password replacement for REST authentication basic type
This commit is contained in:
Michael Drury 2023-01-31 12:48:38 +00:00 committed by GitHub
commit 099179f848
7 changed files with 105 additions and 67 deletions

View File

@ -134,7 +134,7 @@
on:blur={onBlur}
on:focus={onFocus}
on:input={onInput}
{type}
type={hbsValue.length ? "text" : type}
style={align ? `text-align: ${align};` : ""}
class="spectrum-Textfield-input"
inputmode={type === "number" ? "decimal" : "text"}

View File

@ -248,6 +248,7 @@
/>
<EnvDropdown
label="Password"
type="password"
bind:value={form.basic.password}
on:change={onFieldChange}
on:blur={() => (blurred.basic.password = true)}

View File

@ -7,44 +7,3 @@
export interface QueryOptions {
disableReturning?: boolean
}
export enum AuthType {
BASIC = "basic",
BEARER = "bearer",
}
interface AuthConfig {
_id: string
name: string
type: AuthType
config: BasicAuthConfig | BearerAuthConfig
}
export interface BasicAuthConfig {
username: string
password: string
}
export interface BearerAuthConfig {
token: string
}
export interface RestConfig {
url: string
rejectUnauthorized: boolean
defaultHeaders: {
[key: string]: any
}
legacyHttpParser: boolean
authConfigs: AuthConfig[]
staticVariables: {
[key: string]: string
}
dynamicVariables: [
{
name: string
queryId: string
value: string
}
]
}

View File

@ -6,13 +6,11 @@ import {
IntegrationBase,
PaginationValues,
RestQueryFields as RestQuery,
} from "@budibase/types"
import {
RestConfig,
AuthType,
BasicAuthConfig,
BearerAuthConfig,
} from "../definitions/datasource"
RestAuthType,
RestBasicAuthConfig,
RestBearerAuthConfig,
} from "@budibase/types"
import { get } from "lodash"
import * as https from "https"
import qs from "querystring"
@ -331,14 +329,14 @@ class RestIntegration implements IntegrationBase {
if (authConfig) {
let config
switch (authConfig.type) {
case AuthType.BASIC:
config = authConfig.config as BasicAuthConfig
case RestAuthType.BASIC:
config = authConfig.config as RestBasicAuthConfig
headers.Authorization = `Basic ${Buffer.from(
`${config.username}:${config.password}`
).toString("base64")}`
break
case AuthType.BEARER:
config = authConfig.config as BearerAuthConfig
case RestAuthType.BEARER:
config = authConfig.config as RestBearerAuthConfig
headers.Authorization = `Bearer ${config.token}`
break
}
@ -428,5 +426,4 @@ class RestIntegration implements IntegrationBase {
export default {
schema: SCHEMA,
integration: RestIntegration,
AuthType,
}

View File

@ -15,6 +15,7 @@ jest.mock("node-fetch", () => {
import fetch from "node-fetch"
import { default as RestIntegration } from "../rest"
import { RestAuthType } from "@budibase/types"
const FormData = require("form-data")
const { URLSearchParams } = require("url")
@ -229,7 +230,7 @@ describe("REST Integration", () => {
const basicAuth = {
_id: "c59c14bd1898a43baa08da68959b24686",
name: "basic-1",
type: RestIntegration.AuthType.BASIC,
type: RestAuthType.BASIC,
config: {
username: "user",
password: "password",
@ -239,7 +240,7 @@ describe("REST Integration", () => {
const bearerAuth = {
_id: "0d91d732f34e4befabeff50b392a8ff3",
name: "bearer-1",
type: RestIntegration.AuthType.BEARER,
type: RestAuthType.BEARER,
config: {
token: "mytoken",
},
@ -581,6 +582,7 @@ describe("REST Integration", () => {
})
await config.integration.read({})
// @ts-ignore
const calls: any = fetch.mock.calls[0]
const url = calls[0]
expect(url).toBe(`${BASE_URL}/`)

View File

@ -1,17 +1,19 @@
import { context } from "@budibase/backend-core"
import { processObjectSync, findHBSBlocks } from "@budibase/string-templates"
import { findHBSBlocks, processObjectSync } from "@budibase/string-templates"
import {
Datasource,
DatasourceFieldType,
Integration,
PASSWORD_REPLACEMENT,
RestAuthConfig,
RestAuthType,
RestBasicAuthConfig,
SourceName,
} from "@budibase/types"
import { cloneDeep } from "lodash/fp"
import { getEnvironmentVariables } from "../../utils"
import { getDefinitions } from "../../../integrations"
const ENV_VAR_PREFIX = "env."
const USER_PREFIX = "user"
async function enrichDatasourceWithValues(datasource: Datasource) {
const cloned = cloneDeep(datasource)
@ -47,6 +49,18 @@ export async function getWithEnvVars(datasourceId: string) {
return enrichDatasourceWithValues(datasource)
}
function hasAuthConfigs(datasource: Datasource) {
return datasource.source === SourceName.REST && datasource.config?.authConfigs
}
function useEnvVars(str: any) {
if (typeof str !== "string") {
return false
}
const blocks = findHBSBlocks(str)
return blocks.find(block => block.includes(ENV_VAR_PREFIX)) != null
}
export async function removeSecrets(datasources: Datasource[]) {
const definitions = await getDefinitions()
for (let datasource of datasources) {
@ -56,17 +70,24 @@ export async function removeSecrets(datasources: Datasource[]) {
if (datasource.config.auth) {
delete datasource.config.auth
}
// remove passwords
for (let key of Object.keys(datasource.config)) {
if (typeof datasource.config[key] !== "string") {
continue
// specific to REST datasources, contains passwords
if (hasAuthConfigs(datasource)) {
const configs = datasource.config.authConfigs as RestAuthConfig[]
for (let config of configs) {
if (config.type !== RestAuthType.BASIC) {
continue
}
const basic = config.config as RestBasicAuthConfig
if (!useEnvVars(basic.password)) {
basic.password = PASSWORD_REPLACEMENT
}
}
const blocks = findHBSBlocks(datasource.config[key] as string)
const usesEnvVars =
blocks.find(block => block.includes(ENV_VAR_PREFIX)) != null
}
// remove general passwords
for (let key of Object.keys(datasource.config)) {
if (
!usesEnvVars &&
schema.datasource?.[key]?.type === DatasourceFieldType.PASSWORD
schema.datasource?.[key]?.type === DatasourceFieldType.PASSWORD &&
!useEnvVars(datasource.config[key])
) {
datasource.config[key] = PASSWORD_REPLACEMENT
}
@ -84,6 +105,23 @@ export function mergeConfigs(update: Datasource, old: Datasource) {
if (!update.config) {
return update
}
// specific to REST datasources, fix the auth configs again if required
if (hasAuthConfigs(update)) {
const configs = update.config.authConfigs as RestAuthConfig[]
const oldConfigs = old.config?.authConfigs as RestAuthConfig[]
for (let config of configs) {
if (config.type !== RestAuthType.BASIC) {
continue
}
const basic = config.config as RestBasicAuthConfig
const oldBasic = oldConfigs.find(old => old.name === config.name)
?.config as RestBasicAuthConfig
if (basic.password === PASSWORD_REPLACEMENT) {
basic.password = oldBasic.password
}
}
}
// update back to actual passwords for everything else
for (let [key, value] of Object.entries(update.config)) {
if (value !== PASSWORD_REPLACEMENT) {
continue

View File

@ -15,3 +15,44 @@ export interface Datasource extends Document {
[key: string]: Table
}
}
export enum RestAuthType {
BASIC = "basic",
BEARER = "bearer",
}
export interface RestBasicAuthConfig {
username: string
password: string
}
export interface RestBearerAuthConfig {
token: string
}
export interface RestAuthConfig {
_id: string
name: string
type: RestAuthType
config: RestBasicAuthConfig | RestBearerAuthConfig
}
export interface RestConfig {
url: string
rejectUnauthorized: boolean
defaultHeaders: {
[key: string]: any
}
legacyHttpParser: boolean
authConfigs: RestAuthConfig[]
staticVariables: {
[key: string]: string
}
dynamicVariables: [
{
name: string
queryId: string
value: string
}
]
}