Context binding for authenticated user in REST API querys. Includes fix for REST datasource UI

This commit is contained in:
Dean 2022-06-14 10:14:05 +01:00
parent b0b69222d1
commit 830f127343
7 changed files with 147 additions and 17 deletions

View File

@ -49,6 +49,42 @@ export const getBindableProperties = (asset, componentId) => {
] ]
} }
/**
* Gets all rest bindable data fields
*/
export const getRestBindings = () => {
const userBindings = getUserBindings()
return [...userBindings]
}
/**
* Utility - coverting a map of readable bindings to runtime
*/
export const readableToRuntimeMap = (bindings, ctx) => {
if (!bindings || !ctx) {
return {}
}
return Object.keys(ctx).reduce((acc, key) => {
let parsedQuery = readableToRuntimeBinding(bindings, ctx[key])
acc[key] = parsedQuery
return acc
}, {})
}
/**
* Utility - coverting a map of runtime bindings to readable
*/
export const runtimeToReadableMap = (bindings, ctx) => {
if (!bindings || !ctx) {
return {}
}
return Object.keys(ctx).reduce((acc, key) => {
let parsedQuery = runtimeToReadableBinding(bindings, ctx[key])
acc[key] = parsedQuery
return acc
}, {})
}
/** /**
* Gets the bindable properties exposed by a certain component. * Gets the bindable properties exposed by a certain component.
*/ */
@ -298,7 +334,6 @@ const getUserBindings = () => {
providerId: "user", providerId: "user",
}) })
}) })
return bindings return bindings
} }

View File

@ -10,11 +10,31 @@
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte" import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte"
import RestAuthenticationBuilder from "./auth/RestAuthenticationBuilder.svelte" import RestAuthenticationBuilder from "./auth/RestAuthenticationBuilder.svelte"
import ViewDynamicVariables from "./variables/ViewDynamicVariables.svelte" import ViewDynamicVariables from "./variables/ViewDynamicVariables.svelte"
import {
getRestBindings,
readableToRuntimeBinding,
runtimeToReadableMap,
} from "builderStore/dataBinding"
import { cloneDeep } from "lodash/fp"
export let datasource export let datasource
export let queries export let queries
let addHeader let addHeader
let parsedHeaders = runtimeToReadableMap(
getRestBindings(),
cloneDeep(datasource?.config?.defaultHeaders)
)
const onDefaultHeaderUpdate = headers => {
const flatHeaders = cloneDeep(headers).reduce((acc, entry) => {
acc[entry.name] = readableToRuntimeBinding(getRestBindings(), entry.value)
return acc
}, {})
datasource.config.defaultHeaders = flatHeaders
}
</script> </script>
<Divider size="S" /> <Divider size="S" />
@ -30,8 +50,8 @@
</Body> </Body>
<KeyValueBuilder <KeyValueBuilder
bind:this={addHeader} bind:this={addHeader}
bind:object={datasource.config.defaultHeaders} bind:object={parsedHeaders}
on:change on:change={evt => onDefaultHeaderUpdate(evt.detail)}
noAddButton noAddButton
/> />
<div> <div>

View File

@ -57,7 +57,8 @@
placeholder="Default" placeholder="Default"
thin thin
disabled={bindable} disabled={bindable}
bind:value={binding.default} on:change={evt => onBindingChange(binding.name, evt.detail)}
value={runtimeToReadableBinding(bindings, binding.default)}
/> />
{#if bindable} {#if bindable}
<DrawerBindableInput <DrawerBindableInput

View File

@ -12,4 +12,6 @@
} }
</script> </script>
<slot /> {#key $params.selectedDatasource}
<slot />
{/key}

View File

@ -40,13 +40,22 @@
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import { RawRestBodyTypes } from "constants/backend" import { RawRestBodyTypes } from "constants/backend"
import {
getRestBindings,
readableToRuntimeBinding,
runtimeToReadableBinding,
runtimeToReadableMap,
readableToRuntimeMap,
} from "builderStore/dataBinding"
let query, datasource let query, datasource
let breakQs = {}, let breakQs = {},
bindings = {} requestBindings = {}
let saveId, url let saveId, url
let response, schema, enabledHeaders let response, schema, enabledHeaders
let authConfigId let authConfigId
let dynamicVariables, addVariableModal, varBinding let dynamicVariables, addVariableModal, varBinding
let restBindings = getRestBindings()
$: datasourceType = datasource?.source $: datasourceType = datasource?.source
$: integrationInfo = $integrations[datasourceType] $: integrationInfo = $integrations[datasourceType]
@ -63,8 +72,10 @@
Object.keys(schema || {}).length !== 0 || Object.keys(schema || {}).length !== 0 ||
Object.keys(query?.schema || {}).length !== 0 Object.keys(query?.schema || {}).length !== 0
$: runtimeUrlQueries = readableToRuntimeMap(restBindings, breakQs)
function getSelectedQuery() { function getSelectedQuery() {
return cloneDeep( const cloneQuery = cloneDeep(
$queries.list.find(q => q._id === $queries.selected) || { $queries.list.find(q => q._id === $queries.selected) || {
datasourceId: $params.selectedDatasource, datasourceId: $params.selectedDatasource,
parameters: [], parameters: [],
@ -76,6 +87,30 @@
queryVerb: "read", queryVerb: "read",
} }
) )
if (cloneQuery?.fields?.headers) {
cloneQuery.fields.headers = runtimeToReadableMap(
restBindings,
cloneQuery.fields.headers
)
}
if (cloneQuery?.fields?.requestBody) {
cloneQuery.fields.requestBody = runtimeToReadableBinding(
restBindings,
cloneQuery.fields.requestBody
)
}
if (cloneQuery?.parameters) {
const flatParams = restUtils.queryParametersToKeyValue(
cloneQuery.parameters
)
const updatedParams = runtimeToReadableMap(restBindings, flatParams)
cloneQuery.parameters = restUtils.keyValueToQueryParameters(updatedParams)
}
return cloneQuery
} }
function checkQueryName(inputUrl = null) { function checkQueryName(inputUrl = null) {
@ -89,7 +124,9 @@
if (!base) { if (!base) {
return base return base
} }
const qs = restUtils.buildQueryString(qsObj) const qs = restUtils.buildQueryString(
runtimeToReadableMap(restBindings, qsObj)
)
let newUrl = base let newUrl = base
if (base.includes("?")) { if (base.includes("?")) {
newUrl = base.split("?")[0] newUrl = base.split("?")[0]
@ -98,14 +135,30 @@
} }
function buildQuery() { function buildQuery() {
const newQuery = { ...query } const newQuery = cloneDeep(query)
const queryString = restUtils.buildQueryString(breakQs) const queryString = restUtils.buildQueryString(runtimeUrlQueries)
newQuery.fields.headers = readableToRuntimeMap(
restBindings,
newQuery.fields.headers
)
newQuery.fields.requestBody = readableToRuntimeBinding(
restBindings,
newQuery.fields.requestBody
)
newQuery.fields.path = url.split("?")[0] newQuery.fields.path = url.split("?")[0]
newQuery.fields.queryString = queryString newQuery.fields.queryString = queryString
newQuery.fields.authConfigId = authConfigId newQuery.fields.authConfigId = authConfigId
newQuery.fields.disabledHeaders = restUtils.flipHeaderState(enabledHeaders) newQuery.fields.disabledHeaders = restUtils.flipHeaderState(enabledHeaders)
newQuery.schema = restUtils.fieldsToSchema(schema) newQuery.schema = restUtils.fieldsToSchema(schema)
newQuery.parameters = restUtils.keyValueToQueryParameters(bindings)
const parsedRequestBindings = readableToRuntimeMap(
restBindings,
requestBindings
)
newQuery.parameters = restUtils.keyValueToQueryParameters(
parsedRequestBindings
)
return newQuery return newQuery
} }
@ -127,7 +180,7 @@
async function runQuery() { async function runQuery() {
try { try {
response = await queries.preview(buildQuery(query)) response = await queries.preview(buildQuery())
if (response.rows.length === 0) { if (response.rows.length === 0) {
notifications.info("Request did not return any data") notifications.info("Request did not return any data")
} else { } else {
@ -250,6 +303,8 @@
const datasourceUrl = datasource?.config.url const datasourceUrl = datasource?.config.url
const qs = query?.fields.queryString const qs = query?.fields.queryString
breakQs = restUtils.breakQueryString(qs) breakQs = restUtils.breakQueryString(qs)
breakQs = runtimeToReadableMap(restBindings, breakQs)
const path = query.fields.path const path = query.fields.path
if ( if (
datasourceUrl && datasourceUrl &&
@ -260,7 +315,7 @@
} }
url = buildUrl(query.fields.path, breakQs) url = buildUrl(query.fields.path, breakQs)
schema = restUtils.schemaToFields(query.schema) schema = restUtils.schemaToFields(query.schema)
bindings = restUtils.queryParametersToKeyValue(query.parameters) requestBindings = restUtils.queryParametersToKeyValue(query.parameters)
authConfigId = getAuthConfigId() authConfigId = getAuthConfigId()
if (!query.fields.disabledHeaders) { if (!query.fields.disabledHeaders) {
query.fields.disabledHeaders = {} query.fields.disabledHeaders = {}
@ -344,7 +399,7 @@
<Tabs selected="Bindings" quiet noPadding noHorizPadding onTop> <Tabs selected="Bindings" quiet noPadding noHorizPadding onTop>
<Tab title="Bindings"> <Tab title="Bindings">
<KeyValueBuilder <KeyValueBuilder
bind:object={bindings} bind:object={requestBindings}
tooltip="Set the name of the binding which can be used in Handlebars statements throughout your query" tooltip="Set the name of the binding which can be used in Handlebars statements throughout your query"
name="binding" name="binding"
headings headings

View File

@ -121,6 +121,9 @@ export async function preview(ctx: any) {
parameters, parameters,
transformer, transformer,
queryId, queryId,
ctx: {
user: ctx.user,
},
}) })
const { rows, keys, info, extra } = await quotas.addQuery(runFn) const { rows, keys, info, extra } = await quotas.addQuery(runFn)

View File

@ -21,6 +21,8 @@ class QueryRunner {
this.queryId = input.queryId this.queryId = input.queryId
this.noRecursiveQuery = flags.noRecursiveQuery this.noRecursiveQuery = flags.noRecursiveQuery
this.cachedVariables = [] this.cachedVariables = []
// Additional context items for enrichment
this.ctx = input.ctx
// allows the response from a query to be stored throughout this // allows the response from a query to be stored throughout this
// execution so that if it needs to be re-used for another variable // execution so that if it needs to be re-used for another variable
// it can be // it can be
@ -38,12 +40,24 @@ class QueryRunner {
// pre-query, make sure datasource variables are added to parameters // pre-query, make sure datasource variables are added to parameters
const parameters = await this.addDatasourceVariables() const parameters = await this.addDatasourceVariables()
// Enrich the parameters with the addition context items.
// 'user' is now a reserved variable key in mapping parameters
const enrichedParameters = enrichQueryFields(parameters, this.ctx)
const enrichedContext = { ...enrichedParameters, ...this.ctx }
// Parse global headers
datasource.config.defaultHeaders = enrichQueryFields(
datasource.config.defaultHeaders,
enrichedContext
)
let query let query
// handle SQL injections by interpolating the variables // handle SQL injections by interpolating the variables
if (isSQL(datasource)) { if (isSQL(datasource)) {
query = interpolateSQL(fields, parameters, integration) query = interpolateSQL(fields, enrichedParameters, integration)
} else { } else {
query = enrichQueryFields(fields, parameters) query = enrichQueryFields(fields, enrichedContext)
} }
// Add pagination values for REST queries // Add pagination values for REST queries
@ -67,7 +81,7 @@ class QueryRunner {
if (transformer) { if (transformer) {
const runner = new ScriptRunner(transformer, { const runner = new ScriptRunner(transformer, {
data: rows, data: rows,
params: parameters, params: enrichedParameters,
}) })
rows = runner.execute() rows = runner.execute()
} }