Merge branch 'develop' into fix/daysjs-export-err
This commit is contained in:
commit
b3e2c6334c
|
@ -1,4 +1,5 @@
|
||||||
{{- if .Values.globals.createSecrets -}}
|
{{- $existingSecret := lookup "v1" "Secret" .Release.Namespace (include "budibase.fullname" .) }}
|
||||||
|
{{- if .Values.globals.createSecrets }}
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Secret
|
kind: Secret
|
||||||
metadata:
|
metadata:
|
||||||
|
@ -10,8 +11,15 @@ metadata:
|
||||||
heritage: "{{ .Release.Service }}"
|
heritage: "{{ .Release.Service }}"
|
||||||
type: Opaque
|
type: Opaque
|
||||||
data:
|
data:
|
||||||
|
{{- if $existingSecret }}
|
||||||
|
internalApiKey: {{ index $existingSecret.data "internalApiKey" }}
|
||||||
|
jwtSecret: {{ index $existingSecret.data "jwtSecret" }}
|
||||||
|
objectStoreAccess: {{ index $existingSecret.data "objectStoreAccess" }}
|
||||||
|
objectStoreSecret: {{ index $existingSecret.data "objectStoreSecret" }}
|
||||||
|
{{- else }}
|
||||||
internalApiKey: {{ template "budibase.defaultsecret" .Values.globals.internalApiKey }}
|
internalApiKey: {{ template "budibase.defaultsecret" .Values.globals.internalApiKey }}
|
||||||
jwtSecret: {{ template "budibase.defaultsecret" .Values.globals.jwtSecret }}
|
jwtSecret: {{ template "budibase.defaultsecret" .Values.globals.jwtSecret }}
|
||||||
objectStoreAccess: {{ template "budibase.defaultsecret" .Values.services.objectStore.accessKey }}
|
objectStoreAccess: {{ template "budibase.defaultsecret" .Values.services.objectStore.accessKey }}
|
||||||
objectStoreSecret: {{ template "budibase.defaultsecret" .Values.services.objectStore.secretKey }}
|
objectStoreSecret: {{ template "budibase.defaultsecret" .Values.services.objectStore.secretKey }}
|
||||||
{{- end -}}
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.9.33-alpha.8",
|
"version": "2.9.39-alpha.1",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
"types": "dist/src/index.d.ts",
|
"types": "dist/src/index.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./dist/index.js",
|
".": "./dist/index.js",
|
||||||
"./tests": "./dist/tests.js",
|
"./tests": "./dist/tests/index.js",
|
||||||
"./*": "./dist/*.js"
|
"./*": "./dist/*.js"
|
||||||
},
|
},
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
|
|
|
@ -1,6 +1,21 @@
|
||||||
#!/usr/bin/node
|
#!/usr/bin/node
|
||||||
|
|
||||||
const coreBuild = require("../../../scripts/build")
|
const coreBuild = require("../../../scripts/build")
|
||||||
|
|
||||||
coreBuild("./src/plugin/index.ts", "./dist/plugins.js")
|
coreBuild("./src/plugin/index.ts", "./dist/plugins.js")
|
||||||
coreBuild("./src/index.ts", "./dist/index.js")
|
coreBuild("./src/index.ts", "./dist/index.js")
|
||||||
coreBuild("./tests/index.ts", "./dist/tests.js")
|
|
||||||
|
const glob = require("glob")
|
||||||
|
const inputFiles = [
|
||||||
|
...glob.sync("./src/**/*.[tj]s", { nodir: true }),
|
||||||
|
...glob.sync("./tests/**/*.[tj]s", { nodir: true }),
|
||||||
|
]
|
||||||
|
|
||||||
|
const path = require("path")
|
||||||
|
for (const file of inputFiles) {
|
||||||
|
coreBuild(file, `./${path.join("dist", file.replace(/\.ts$/, ".js"))}`, {
|
||||||
|
skipMeta: true,
|
||||||
|
bundle: false,
|
||||||
|
forcedFormat: "cjs",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import {
|
||||||
DatabasePutOpts,
|
DatabasePutOpts,
|
||||||
DatabaseCreateIndexOpts,
|
DatabaseCreateIndexOpts,
|
||||||
DatabaseDeleteIndexOpts,
|
DatabaseDeleteIndexOpts,
|
||||||
DocExistsResponse,
|
|
||||||
Document,
|
Document,
|
||||||
isDocument,
|
isDocument,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
@ -121,19 +120,6 @@ export class DatabaseImpl implements Database {
|
||||||
return this.updateOutput(() => db.get(id))
|
return this.updateOutput(() => db.get(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
async docExists(docId: string): Promise<DocExistsResponse> {
|
|
||||||
const db = await this.checkSetup()
|
|
||||||
let _rev, exists
|
|
||||||
try {
|
|
||||||
const { etag } = await db.head(docId)
|
|
||||||
_rev = etag
|
|
||||||
exists = true
|
|
||||||
} catch (err) {
|
|
||||||
exists = false
|
|
||||||
}
|
|
||||||
return { _rev, exists }
|
|
||||||
}
|
|
||||||
|
|
||||||
async remove(idOrDoc: string | Document, rev?: string) {
|
async remove(idOrDoc: string | Document, rev?: string) {
|
||||||
const db = await this.checkSetup()
|
const db = await this.checkSetup()
|
||||||
let _id: string
|
let _id: string
|
||||||
|
|
|
@ -11,7 +11,11 @@ export function getDB(dbName?: string, opts?: any): Database {
|
||||||
// we have to use a callback for this so that we can close
|
// we have to use a callback for this so that we can close
|
||||||
// the DB when we're done, without this manual requests would
|
// the DB when we're done, without this manual requests would
|
||||||
// need to close the database when done with it to avoid memory leaks
|
// need to close the database when done with it to avoid memory leaks
|
||||||
export async function doWithDB(dbName: string, cb: any, opts = {}) {
|
export async function doWithDB<T>(
|
||||||
|
dbName: string,
|
||||||
|
cb: (db: Database) => Promise<T>,
|
||||||
|
opts = {}
|
||||||
|
) {
|
||||||
const db = getDB(dbName, opts)
|
const db = getDB(dbName, opts)
|
||||||
// need this to be async so that we can correctly close DB after all
|
// need this to be async so that we can correctly close DB after all
|
||||||
// async operations have been completed
|
// async operations have been completed
|
||||||
|
|
|
@ -87,6 +87,7 @@ export const BUILTIN_PERMISSIONS = {
|
||||||
new Permission(PermissionType.QUERY, PermissionLevel.WRITE),
|
new Permission(PermissionType.QUERY, PermissionLevel.WRITE),
|
||||||
new Permission(PermissionType.TABLE, PermissionLevel.WRITE),
|
new Permission(PermissionType.TABLE, PermissionLevel.WRITE),
|
||||||
new Permission(PermissionType.AUTOMATION, PermissionLevel.EXECUTE),
|
new Permission(PermissionType.AUTOMATION, PermissionLevel.EXECUTE),
|
||||||
|
new Permission(PermissionType.LEGACY_VIEW, PermissionLevel.READ),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
POWER: {
|
POWER: {
|
||||||
|
@ -97,6 +98,7 @@ export const BUILTIN_PERMISSIONS = {
|
||||||
new Permission(PermissionType.USER, PermissionLevel.READ),
|
new Permission(PermissionType.USER, PermissionLevel.READ),
|
||||||
new Permission(PermissionType.AUTOMATION, PermissionLevel.EXECUTE),
|
new Permission(PermissionType.AUTOMATION, PermissionLevel.EXECUTE),
|
||||||
new Permission(PermissionType.WEBHOOK, PermissionLevel.READ),
|
new Permission(PermissionType.WEBHOOK, PermissionLevel.READ),
|
||||||
|
new Permission(PermissionType.LEGACY_VIEW, PermissionLevel.READ),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
ADMIN: {
|
ADMIN: {
|
||||||
|
@ -108,6 +110,7 @@ export const BUILTIN_PERMISSIONS = {
|
||||||
new Permission(PermissionType.AUTOMATION, PermissionLevel.ADMIN),
|
new Permission(PermissionType.AUTOMATION, PermissionLevel.ADMIN),
|
||||||
new Permission(PermissionType.WEBHOOK, PermissionLevel.READ),
|
new Permission(PermissionType.WEBHOOK, PermissionLevel.READ),
|
||||||
new Permission(PermissionType.QUERY, PermissionLevel.ADMIN),
|
new Permission(PermissionType.QUERY, PermissionLevel.ADMIN),
|
||||||
|
new Permission(PermissionType.LEGACY_VIEW, PermissionLevel.READ),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -253,7 +253,7 @@ export function checkForRoleResourceArray(
|
||||||
* Given an app ID this will retrieve all of the roles that are currently within that app.
|
* Given an app ID this will retrieve all of the roles that are currently within that app.
|
||||||
* @return {Promise<object[]>} An array of the role objects that were found.
|
* @return {Promise<object[]>} An array of the role objects that were found.
|
||||||
*/
|
*/
|
||||||
export async function getAllRoles(appId?: string) {
|
export async function getAllRoles(appId?: string): Promise<RoleDoc[]> {
|
||||||
if (appId) {
|
if (appId) {
|
||||||
return doWithDB(appId, internal)
|
return doWithDB(appId, internal)
|
||||||
} else {
|
} else {
|
||||||
|
@ -312,37 +312,6 @@ export async function getAllRoles(appId?: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This retrieves the required role for a resource
|
|
||||||
* @param permLevel The level of request
|
|
||||||
* @param resourceId The resource being requested
|
|
||||||
* @param subResourceId The sub resource being requested
|
|
||||||
* @return {Promise<{permissions}|Object>} returns the permissions required to access.
|
|
||||||
*/
|
|
||||||
export async function getRequiredResourceRole(
|
|
||||||
permLevel: string,
|
|
||||||
{ resourceId, subResourceId }: { resourceId?: string; subResourceId?: string }
|
|
||||||
) {
|
|
||||||
const roles = await getAllRoles()
|
|
||||||
let main = [],
|
|
||||||
sub = []
|
|
||||||
for (let role of roles) {
|
|
||||||
// no permissions, ignore it
|
|
||||||
if (!role.permissions) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const mainRes = resourceId ? role.permissions[resourceId] : undefined
|
|
||||||
const subRes = subResourceId ? role.permissions[subResourceId] : undefined
|
|
||||||
if (mainRes && mainRes.indexOf(permLevel) !== -1) {
|
|
||||||
main.push(role._id)
|
|
||||||
} else if (subRes && subRes.indexOf(permLevel) !== -1) {
|
|
||||||
sub.push(role._id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// for now just return the IDs
|
|
||||||
return main.concat(sub)
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AccessController {
|
export class AccessController {
|
||||||
userHierarchies: { [key: string]: string[] }
|
userHierarchies: { [key: string]: string[] }
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
export let text = null
|
export let text = null
|
||||||
export let condition = true
|
export let condition = true
|
||||||
export let duration = 3000
|
export let duration = 5000
|
||||||
export let position
|
export let position
|
||||||
export let type
|
export let type
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,9 @@ import { Screen } from "./utils/Screen"
|
||||||
import { Component } from "./utils/Component"
|
import { Component } from "./utils/Component"
|
||||||
|
|
||||||
export default function (datasources) {
|
export default function (datasources) {
|
||||||
|
if (!Array.isArray(datasources)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
return datasources.map(datasource => {
|
return datasources.map(datasource => {
|
||||||
return {
|
return {
|
||||||
name: `${datasource.name} - List`,
|
name: `${datasource.name} - List`,
|
||||||
|
|
|
@ -73,7 +73,7 @@
|
||||||
if (!perms["execute"]) {
|
if (!perms["execute"]) {
|
||||||
role = "BASIC"
|
role = "BASIC"
|
||||||
} else {
|
} else {
|
||||||
role = perms["execute"]
|
role = perms["execute"].role
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
import ManageAccessButton from "./buttons/ManageAccessButton.svelte"
|
import ManageAccessButton from "./buttons/ManageAccessButton.svelte"
|
||||||
import HideAutocolumnButton from "./buttons/HideAutocolumnButton.svelte"
|
import HideAutocolumnButton from "./buttons/HideAutocolumnButton.svelte"
|
||||||
import { notifications } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
|
import { ROW_EXPORT_FORMATS } from "constants/backend"
|
||||||
|
|
||||||
export let view = {}
|
export let view = {}
|
||||||
|
|
||||||
|
@ -19,6 +20,14 @@
|
||||||
let type = "internal"
|
let type = "internal"
|
||||||
|
|
||||||
$: name = view.name
|
$: name = view.name
|
||||||
|
$: calculation = view.calculation
|
||||||
|
|
||||||
|
$: supportedFormats = Object.values(ROW_EXPORT_FORMATS).filter(key => {
|
||||||
|
if (calculation && key === ROW_EXPORT_FORMATS.JSON_WITH_SCHEMA) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
// Fetch rows for specified view
|
// Fetch rows for specified view
|
||||||
$: fetchViewData(name, view.field, view.groupBy, view.calculation)
|
$: fetchViewData(name, view.field, view.groupBy, view.calculation)
|
||||||
|
@ -68,5 +77,5 @@
|
||||||
{/if}
|
{/if}
|
||||||
<ManageAccessButton resourceId={decodeURI(name)} />
|
<ManageAccessButton resourceId={decodeURI(name)} />
|
||||||
<HideAutocolumnButton bind:hideAutocolumns />
|
<HideAutocolumnButton bind:hideAutocolumns />
|
||||||
<ExportButton view={view.name} />
|
<ExportButton view={view.name} formats={supportedFormats} />
|
||||||
</Table>
|
</Table>
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
export let sorting
|
export let sorting
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let selectedRows
|
export let selectedRows
|
||||||
|
export let formats
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
</script>
|
</script>
|
||||||
|
@ -15,5 +16,5 @@
|
||||||
Export
|
Export
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
<ExportModal {view} {filters} {sorting} {selectedRows} />
|
<ExportModal {view} {filters} {sorting} {selectedRows} {formats} />
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -5,25 +5,19 @@
|
||||||
|
|
||||||
export let resourceId
|
export let resourceId
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let requiresLicence
|
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
let resourcePermissions
|
let resourcePermissions
|
||||||
|
|
||||||
async function openDropdown() {
|
async function openModal() {
|
||||||
resourcePermissions = await permissions.forResource(resourceId)
|
resourcePermissions = await permissions.forResourceDetailed(resourceId)
|
||||||
modal.show()
|
modal.show()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ActionButton icon="LockClosed" quiet on:click={openDropdown} {disabled}>
|
<ActionButton icon="LockClosed" quiet on:click={openModal} {disabled}>
|
||||||
Access
|
Access
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
<ManageAccessModal
|
<ManageAccessModal {resourceId} permissions={resourcePermissions} />
|
||||||
{resourceId}
|
|
||||||
{requiresLicence}
|
|
||||||
levels={$permissions}
|
|
||||||
permissions={resourcePermissions}
|
|
||||||
/>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -1,18 +1,30 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import { Modal, ActionButton } from "@budibase/bbui"
|
import { Modal, ActionButton, TooltipType, TempTooltip } from "@budibase/bbui"
|
||||||
import GridCreateViewModal from "../../modals/grid/GridCreateViewModal.svelte"
|
import GridCreateViewModal from "../../modals/grid/GridCreateViewModal.svelte"
|
||||||
|
|
||||||
const { rows, columns } = getContext("grid")
|
const { rows, columns, filter } = getContext("grid")
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
|
let firstFilterUsage = false
|
||||||
|
|
||||||
$: disabled = !$columns.length || !$rows.length
|
$: disabled = !$columns.length || !$rows.length
|
||||||
|
$: {
|
||||||
|
if ($filter?.length && !firstFilterUsage) {
|
||||||
|
firstFilterUsage = true
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ActionButton {disabled} icon="CollectionAdd" quiet on:click={modal.show}>
|
<TempTooltip
|
||||||
Add view
|
text="Create a view to save your filters"
|
||||||
</ActionButton>
|
type={TooltipType.Info}
|
||||||
|
condition={firstFilterUsage}
|
||||||
|
>
|
||||||
|
<ActionButton {disabled} icon="CollectionAdd" quiet on:click={modal.show}>
|
||||||
|
Create view
|
||||||
|
</ActionButton>
|
||||||
|
</TempTooltip>
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
<GridCreateViewModal />
|
<GridCreateViewModal />
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<script>
|
<script>
|
||||||
import { licensing, admin } from "stores/portal"
|
|
||||||
import ManageAccessButton from "../ManageAccessButton.svelte"
|
import ManageAccessButton from "../ManageAccessButton.svelte"
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
|
@ -13,17 +12,6 @@
|
||||||
}
|
}
|
||||||
return datasource.type === "table" ? datasource.tableId : datasource.id
|
return datasource.type === "table" ? datasource.tableId : datasource.id
|
||||||
}
|
}
|
||||||
|
|
||||||
var requiresLicence
|
|
||||||
$: {
|
|
||||||
if ($datasource.type === "viewV2" && !$licensing.isViewPermissionsEnabled) {
|
|
||||||
const requiredLicense = $admin?.cloud ? "Premium" : "Business"
|
|
||||||
requiresLicence = {
|
|
||||||
tier: requiredLicense,
|
|
||||||
message: `A ${requiredLicense} subscription is required to specify access level roles for this view.`,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ManageAccessButton {resourceId} {requiresLicence} />
|
<ManageAccessButton {resourceId} />
|
||||||
|
|
|
@ -9,30 +9,43 @@
|
||||||
import download from "downloadjs"
|
import download from "downloadjs"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { Constants, LuceneUtils } from "@budibase/frontend-core"
|
import { Constants, LuceneUtils } from "@budibase/frontend-core"
|
||||||
|
import { ROW_EXPORT_FORMATS } from "constants/backend"
|
||||||
const FORMATS = [
|
|
||||||
{
|
|
||||||
name: "CSV",
|
|
||||||
key: "csv",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "JSON",
|
|
||||||
key: "json",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "JSON with Schema",
|
|
||||||
key: "jsonWithSchema",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export let view
|
export let view
|
||||||
export let filters
|
export let filters
|
||||||
export let sorting
|
export let sorting
|
||||||
export let selectedRows = []
|
export let selectedRows = []
|
||||||
|
export let formats
|
||||||
|
|
||||||
let exportFormat = FORMATS[0].key
|
const FORMATS = [
|
||||||
|
{
|
||||||
|
name: "CSV",
|
||||||
|
key: ROW_EXPORT_FORMATS.CSV,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "JSON",
|
||||||
|
key: ROW_EXPORT_FORMATS.JSON,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "JSON with Schema",
|
||||||
|
key: ROW_EXPORT_FORMATS.JSON_WITH_SCHEMA,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
$: options = FORMATS.filter(format => {
|
||||||
|
if (formats && !formats.includes(format.key)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
let exportFormat
|
||||||
let filterLookup
|
let filterLookup
|
||||||
|
|
||||||
|
$: if (options && !exportFormat) {
|
||||||
|
exportFormat = Array.isArray(options) ? options[0]?.key : []
|
||||||
|
}
|
||||||
|
|
||||||
$: luceneFilter = LuceneUtils.buildLuceneQuery(filters)
|
$: luceneFilter = LuceneUtils.buildLuceneQuery(filters)
|
||||||
$: exportOpDisplay = buildExportOpDisplay(sorting, filterDisplay, filters)
|
$: exportOpDisplay = buildExportOpDisplay(sorting, filterDisplay, filters)
|
||||||
|
|
||||||
|
@ -190,7 +203,7 @@
|
||||||
<Select
|
<Select
|
||||||
label="Format"
|
label="Format"
|
||||||
bind:value={exportFormat}
|
bind:value={exportFormat}
|
||||||
options={FORMATS}
|
{options}
|
||||||
placeholder={null}
|
placeholder={null}
|
||||||
getOptionLabel={x => x.name}
|
getOptionLabel={x => x.name}
|
||||||
getOptionValue={x => x.key}
|
getOptionValue={x => x.key}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
|
import { PermissionSource } from "@budibase/types"
|
||||||
import { roles, permissions as permissionsStore } from "stores/backend"
|
import { roles, permissions as permissionsStore } from "stores/backend"
|
||||||
import {
|
import {
|
||||||
Label,
|
Label,
|
||||||
|
@ -9,58 +10,126 @@
|
||||||
ModalContent,
|
ModalContent,
|
||||||
Tags,
|
Tags,
|
||||||
Tag,
|
Tag,
|
||||||
|
Icon,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { capitalise } from "helpers"
|
import { capitalise } from "helpers"
|
||||||
|
import { get } from "svelte/store"
|
||||||
|
|
||||||
export let resourceId
|
export let resourceId
|
||||||
export let permissions
|
export let permissions
|
||||||
export let requiresLicence
|
|
||||||
|
const inheritedRoleId = "inherited"
|
||||||
|
|
||||||
async function changePermission(level, role) {
|
async function changePermission(level, role) {
|
||||||
try {
|
try {
|
||||||
await permissionsStore.save({
|
if (role === inheritedRoleId) {
|
||||||
level,
|
await permissionsStore.remove({
|
||||||
role,
|
level,
|
||||||
resource: resourceId,
|
role,
|
||||||
})
|
resource: resourceId,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await permissionsStore.save({
|
||||||
|
level,
|
||||||
|
role,
|
||||||
|
resource: resourceId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Show updated permissions in UI: REMOVE
|
// Show updated permissions in UI: REMOVE
|
||||||
permissions = await permissionsStore.forResource(resourceId)
|
permissions = await permissionsStore.forResourceDetailed(resourceId)
|
||||||
notifications.success("Updated permissions")
|
notifications.success("Updated permissions")
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error updating permissions")
|
notifications.error("Error updating permissions")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: computedPermissions = Object.entries(permissions.permissions).reduce(
|
||||||
|
(p, [level, roleInfo]) => {
|
||||||
|
p[level] = {
|
||||||
|
selectedValue:
|
||||||
|
roleInfo.permissionType === PermissionSource.INHERITED
|
||||||
|
? inheritedRoleId
|
||||||
|
: roleInfo.role,
|
||||||
|
options: [...get(roles)],
|
||||||
|
}
|
||||||
|
|
||||||
|
if (roleInfo.inheritablePermission) {
|
||||||
|
p[level].inheritOption = roleInfo.inheritablePermission
|
||||||
|
p[level].options.unshift({
|
||||||
|
_id: inheritedRoleId,
|
||||||
|
name: `Inherit (${
|
||||||
|
get(roles).find(x => x._id === roleInfo.inheritablePermission).name
|
||||||
|
})`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
|
||||||
|
$: requiresPlanToModify = permissions.requiresPlanToModify
|
||||||
|
|
||||||
|
let dependantsInfoMessage
|
||||||
|
async function loadDependantInfo() {
|
||||||
|
const dependantsInfo = await permissionsStore.getDependantsInfo(resourceId)
|
||||||
|
|
||||||
|
const resourceByType = dependantsInfo?.resourceByType
|
||||||
|
|
||||||
|
if (resourceByType) {
|
||||||
|
const total = Object.values(resourceByType).reduce((p, c) => p + c, 0)
|
||||||
|
let resourceDisplay =
|
||||||
|
Object.keys(resourceByType).length === 1 && resourceByType.view
|
||||||
|
? "view"
|
||||||
|
: "resource"
|
||||||
|
|
||||||
|
if (total === 1) {
|
||||||
|
dependantsInfoMessage = `1 ${resourceDisplay} is inheriting this access.`
|
||||||
|
} else if (total > 1) {
|
||||||
|
dependantsInfoMessage = `${total} ${resourceDisplay}s are inheriting this access.`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadDependantInfo()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ModalContent showCancelButton={false} confirmText="Done">
|
<ModalContent showCancelButton={false} confirmText="Done">
|
||||||
<span slot="header">
|
<span slot="header">
|
||||||
Manage Access
|
Manage Access
|
||||||
{#if requiresLicence}
|
{#if requiresPlanToModify}
|
||||||
<span class="lock-tag">
|
<span class="lock-tag">
|
||||||
<Tags>
|
<Tags>
|
||||||
<Tag icon="LockClosed">{requiresLicence.tier}</Tag>
|
<Tag icon="LockClosed">{capitalise(requiresPlanToModify)}</Tag>
|
||||||
</Tags>
|
</Tags>
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
{#if requiresLicence}
|
<Body size="S">Specify the minimum access level role for this data.</Body>
|
||||||
<Body size="S">{requiresLicence.message}</Body>
|
<div class="row">
|
||||||
{:else}
|
<Label extraSmall grey>Level</Label>
|
||||||
<Body size="S">Specify the minimum access level role for this data.</Body>
|
<Label extraSmall grey>Role</Label>
|
||||||
<div class="row">
|
{#each Object.keys(computedPermissions) as level}
|
||||||
<Label extraSmall grey>Level</Label>
|
<Input value={capitalise(level)} disabled />
|
||||||
<Label extraSmall grey>Role</Label>
|
<Select
|
||||||
{#each Object.keys(permissions) as level}
|
disabled={requiresPlanToModify}
|
||||||
<Input value={capitalise(level)} disabled />
|
placeholder={false}
|
||||||
<Select
|
value={computedPermissions[level].selectedValue}
|
||||||
value={permissions[level]}
|
on:change={e => changePermission(level, e.detail)}
|
||||||
on:change={e => changePermission(level, e.detail)}
|
options={computedPermissions[level].options}
|
||||||
options={$roles}
|
getOptionLabel={x => x.name}
|
||||||
getOptionLabel={x => x.name}
|
getOptionValue={x => x._id}
|
||||||
getOptionValue={x => x._id}
|
/>
|
||||||
/>
|
{/each}
|
||||||
{/each}
|
</div>
|
||||||
|
|
||||||
|
{#if dependantsInfoMessage}
|
||||||
|
<div class="inheriting-resources">
|
||||||
|
<Icon name="Alert" />
|
||||||
|
<Body size="S">
|
||||||
|
<i>
|
||||||
|
{dependantsInfoMessage}
|
||||||
|
</i>
|
||||||
|
</Body>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
@ -75,4 +144,9 @@
|
||||||
.lock-tag {
|
.lock-tag {
|
||||||
padding-left: var(--spacing-s);
|
padding-left: var(--spacing-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inheriting-resources {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -46,13 +46,13 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ModalContent
|
<ModalContent
|
||||||
title="Create View"
|
title="Create view"
|
||||||
confirmText="Create View"
|
confirmText="Create view"
|
||||||
onConfirm={saveView}
|
onConfirm={saveView}
|
||||||
disabled={nameExists}
|
disabled={nameExists}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
label="View Name"
|
label="View name"
|
||||||
thin
|
thin
|
||||||
bind:value={name}
|
bind:value={name}
|
||||||
error={nameExists ? "A view already exists with that name" : null}
|
error={nameExists ? "A view already exists with that name" : null}
|
||||||
|
|
|
@ -33,17 +33,19 @@
|
||||||
let anchors = {}
|
let anchors = {}
|
||||||
let draggableItems = []
|
let draggableItems = []
|
||||||
|
|
||||||
const buildDragable = items => {
|
const buildDraggable = items => {
|
||||||
return items.map(item => {
|
return items
|
||||||
return {
|
.map(item => {
|
||||||
id: listItemKey ? item[listItemKey] : generate(),
|
return {
|
||||||
item,
|
id: listItemKey ? item[listItemKey] : generate(),
|
||||||
}
|
item,
|
||||||
})
|
}
|
||||||
|
})
|
||||||
|
.filter(item => item.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (items) {
|
$: if (items) {
|
||||||
draggableItems = buildDragable(items)
|
draggableItems = buildDraggable(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateRowOrder = e => {
|
const updateRowOrder = e => {
|
||||||
|
|
|
@ -99,6 +99,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const type = getComponentForField(instance.field, schema)
|
const type = getComponentForField(instance.field, schema)
|
||||||
|
if (!type) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
instance._component = `@budibase/standard-components/${type}`
|
instance._component = `@budibase/standard-components/${type}`
|
||||||
|
|
||||||
const pseudoComponentInstance = store.actions.components.createInstance(
|
const pseudoComponentInstance = store.actions.components.createInstance(
|
||||||
|
@ -116,7 +119,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (sanitisedFields) {
|
$: if (sanitisedFields) {
|
||||||
fieldList = [...sanitisedFields, ...unconfigured].map(buildSudoInstance)
|
fieldList = [...sanitisedFields, ...unconfigured]
|
||||||
|
.map(buildSudoInstance)
|
||||||
|
.filter(x => x != null)
|
||||||
}
|
}
|
||||||
|
|
||||||
const processItemUpdate = e => {
|
const processItemUpdate = e => {
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
roleId = (await permissions.forResource(queryToFetch._id))["read"]
|
roleId = (await permissions.forResource(queryToFetch._id))["read"].role
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
roleId = Constants.Roles.BASIC
|
roleId = Constants.Roles.BASIC
|
||||||
}
|
}
|
||||||
|
|
|
@ -287,3 +287,9 @@ export const DatasourceTypes = {
|
||||||
GRAPH: "Graph",
|
GRAPH: "Graph",
|
||||||
API: "API",
|
API: "API",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ROW_EXPORT_FORMATS = {
|
||||||
|
CSV: "csv",
|
||||||
|
JSON: "json",
|
||||||
|
JSON_WITH_SCHEMA: "jsonWithSchema",
|
||||||
|
}
|
||||||
|
|
|
@ -120,7 +120,7 @@
|
||||||
await usersFetch.refresh()
|
await usersFetch.refresh()
|
||||||
|
|
||||||
filteredUsers = $usersFetch.rows
|
filteredUsers = $usersFetch.rows
|
||||||
.filter(user => !user?.admin?.global) // filter out global admins
|
.filter(user => user.email !== $auth.user.email)
|
||||||
.map(user => {
|
.map(user => {
|
||||||
const isAdminOrGlobalBuilder = sdk.users.isAdminOrGlobalBuilder(
|
const isAdminOrGlobalBuilder = sdk.users.isAdminOrGlobalBuilder(
|
||||||
user,
|
user,
|
||||||
|
@ -150,13 +150,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortInviteRoles = (a, b) => {
|
const sortInviteRoles = (a, b) => {
|
||||||
const aEmpty =
|
const aAppsEmpty = !a.info?.apps?.length && !a.info?.builder?.apps?.length
|
||||||
!a.info?.appBuilders?.length && Object.keys(a.info.apps).length === 0
|
const bAppsEmpty = !b.info?.apps?.length && !b.info?.builder?.apps?.length
|
||||||
const bEmpty =
|
|
||||||
!b.info?.appBuilders?.length && Object.keys(b.info.apps).length === 0
|
|
||||||
|
|
||||||
if (aEmpty && !bEmpty) return 1
|
return aAppsEmpty && !bAppsEmpty ? 1 : !aAppsEmpty && bAppsEmpty ? -1 : 0
|
||||||
if (!aEmpty && bEmpty) return -1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortRoles = (a, b) => {
|
const sortRoles = (a, b) => {
|
||||||
|
@ -366,18 +363,19 @@
|
||||||
const payload = [
|
const payload = [
|
||||||
{
|
{
|
||||||
email: newUserEmail,
|
email: newUserEmail,
|
||||||
builder: !!creationRoleType === Constants.BudibaseRoles.Admin,
|
builder: { global: creationRoleType === Constants.BudibaseRoles.Admin },
|
||||||
admin: !!creationRoleType === Constants.BudibaseRoles.Admin,
|
admin: { global: creationRoleType === Constants.BudibaseRoles.Admin },
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
if (creationAccessType === Constants.Roles.CREATOR) {
|
const notCreatingAdmin = creationRoleType !== Constants.BudibaseRoles.Admin
|
||||||
payload[0].appBuilders = [prodAppId]
|
const isCreator = creationAccessType === Constants.Roles.CREATOR
|
||||||
} else {
|
if (notCreatingAdmin && isCreator) {
|
||||||
payload[0].apps = {
|
payload[0].builder.apps = [prodAppId]
|
||||||
[prodAppId]: creationAccessType,
|
} else if (notCreatingAdmin && !isCreator) {
|
||||||
}
|
payload[0].apps = { [prodAppId]: creationAccessType }
|
||||||
}
|
}
|
||||||
|
|
||||||
let userInviteResponse
|
let userInviteResponse
|
||||||
try {
|
try {
|
||||||
userInviteResponse = await users.onboard(payload)
|
userInviteResponse = await users.onboard(payload)
|
||||||
|
@ -438,10 +436,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role === Constants.Roles.CREATOR) {
|
if (role === Constants.Roles.CREATOR) {
|
||||||
updateBody.appBuilders = [...(updateBody.appBuilders ?? []), prodAppId]
|
updateBody.builder = updateBody.builder || {}
|
||||||
|
updateBody.builder.apps = [...(updateBody.builder.apps ?? []), prodAppId]
|
||||||
delete updateBody?.apps?.[prodAppId]
|
delete updateBody?.apps?.[prodAppId]
|
||||||
} else if (role !== Constants.Roles.CREATOR && invite?.appBuilders) {
|
} else if (role !== Constants.Roles.CREATOR && invite?.builder?.apps) {
|
||||||
invite.appBuilders = []
|
invite.builder.apps = []
|
||||||
}
|
}
|
||||||
await users.updateInvite(updateBody)
|
await users.updateInvite(updateBody)
|
||||||
await filterInvites(query)
|
await filterInvites(query)
|
||||||
|
@ -494,6 +493,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getInviteRoleValue = invite => {
|
||||||
|
if (invite.info?.admin?.global && invite.info?.builder?.global) {
|
||||||
|
return Constants.Roles.ADMIN
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invite.info?.builder?.apps?.includes(prodAppId)) {
|
||||||
|
return Constants.Roles.CREATOR
|
||||||
|
}
|
||||||
|
|
||||||
|
return invite.info.apps?.[prodAppId]
|
||||||
|
}
|
||||||
|
|
||||||
const getRoleFooter = user => {
|
const getRoleFooter = user => {
|
||||||
if (user.group) {
|
if (user.group) {
|
||||||
const role = $roles.find(role => role._id === user.role)
|
const role = $roles.find(role => role._id === user.role)
|
||||||
|
@ -531,7 +542,9 @@
|
||||||
<Heading size="S">{invitingFlow ? "Invite new user" : "Users"}</Heading>
|
<Heading size="S">{invitingFlow ? "Invite new user" : "Users"}</Heading>
|
||||||
</div>
|
</div>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<Button on:click={openInviteFlow} size="S" cta>Invite user</Button>
|
{#if !invitingFlow}
|
||||||
|
<Button on:click={openInviteFlow} size="S" cta>Invite user</Button>
|
||||||
|
{/if}
|
||||||
<Icon
|
<Icon
|
||||||
color="var(--spectrum-global-color-gray-600)"
|
color="var(--spectrum-global-color-gray-600)"
|
||||||
name="RailRightClose"
|
name="RailRightClose"
|
||||||
|
@ -600,6 +613,11 @@
|
||||||
<div class="auth-entity-access-title">Access</div>
|
<div class="auth-entity-access-title">Access</div>
|
||||||
</div>
|
</div>
|
||||||
{#each filteredInvites as invite}
|
{#each filteredInvites as invite}
|
||||||
|
{@const user = {
|
||||||
|
isAdminOrGlobalBuilder:
|
||||||
|
invite.info?.admin?.global && invite.info?.builder?.global,
|
||||||
|
}}
|
||||||
|
|
||||||
<div class="auth-entity">
|
<div class="auth-entity">
|
||||||
<div class="details">
|
<div class="details">
|
||||||
<div class="user-email" title={invite.email}>
|
<div class="user-email" title={invite.email}>
|
||||||
|
@ -608,10 +626,9 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="auth-entity-access">
|
<div class="auth-entity-access">
|
||||||
<RoleSelect
|
<RoleSelect
|
||||||
|
footer={getRoleFooter(user)}
|
||||||
placeholder={false}
|
placeholder={false}
|
||||||
value={invite.info?.appBuilders?.includes(prodAppId)
|
value={getInviteRoleValue(invite)}
|
||||||
? Constants.Roles.CREATOR
|
|
||||||
: invite.info.apps?.[prodAppId]}
|
|
||||||
allowRemove={invite.info.apps?.[prodAppId]}
|
allowRemove={invite.info.apps?.[prodAppId]}
|
||||||
allowPublic={false}
|
allowPublic={false}
|
||||||
allowCreator={true}
|
allowCreator={true}
|
||||||
|
@ -624,6 +641,9 @@
|
||||||
}}
|
}}
|
||||||
autoWidth
|
autoWidth
|
||||||
align="right"
|
align="right"
|
||||||
|
allowedRoles={user.isAdminOrGlobalBuilder
|
||||||
|
? [Constants.Roles.ADMIN]
|
||||||
|
: null}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -110,7 +110,7 @@
|
||||||
if (mode === "table") {
|
if (mode === "table") {
|
||||||
datasourceModal.show()
|
datasourceModal.show()
|
||||||
} else if (mode === "blank") {
|
} else if (mode === "blank") {
|
||||||
let templates = getTemplates($store, $tables.list)
|
let templates = getTemplates($tables.list)
|
||||||
const blankScreenTemplate = templates.find(
|
const blankScreenTemplate = templates.find(
|
||||||
t => t.id === "createFromScratch"
|
t => t.id === "createFromScratch"
|
||||||
)
|
)
|
||||||
|
|
|
@ -46,7 +46,7 @@
|
||||||
|
|
||||||
let loaded = false
|
let loaded = false
|
||||||
let editModal, deleteModal
|
let editModal, deleteModal
|
||||||
$: console.log(group)
|
|
||||||
$: scimEnabled = $features.isScimEnabled
|
$: scimEnabled = $features.isScimEnabled
|
||||||
$: readonly = !sdk.users.isAdmin($auth.user) || scimEnabled
|
$: readonly = !sdk.users.isAdmin($auth.user) || scimEnabled
|
||||||
$: group = $groups.find(x => x._id === groupId)
|
$: group = $groups.find(x => x._id === groupId)
|
||||||
|
@ -62,7 +62,7 @@
|
||||||
? Constants.Roles.CREATOR
|
? Constants.Roles.CREATOR
|
||||||
: group?.roles?.[apps.getProdAppID(app.devId)],
|
: group?.roles?.[apps.getProdAppID(app.devId)],
|
||||||
}))
|
}))
|
||||||
$: console.log(groupApps)
|
|
||||||
$: {
|
$: {
|
||||||
if (loaded && !group?._id) {
|
if (loaded && !group?._id) {
|
||||||
$goto("./")
|
$goto("./")
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
export let row
|
export let row
|
||||||
$: console.log(row)
|
|
||||||
$: priviliged = sdk.users.isAdminOrBuilder(row)
|
$: priviliged = sdk.users.isAdminOrBuilder(row)
|
||||||
$: count = getCount(row)
|
$: count = getCount(row)
|
||||||
|
|
||||||
|
@ -14,10 +13,10 @@
|
||||||
return $apps.length
|
return $apps.length
|
||||||
} else {
|
} else {
|
||||||
return sdk.users.hasAppBuilderPermissions(row)
|
return sdk.users.hasAppBuilderPermissions(row)
|
||||||
? row.builder.apps.length +
|
? row?.builder?.apps?.length +
|
||||||
Object.keys(row.roles || {}).filter(appId =>
|
Object.keys(row.roles || {}).filter(appId => {
|
||||||
row.builder.apps.includes(appId)
|
row?.builder?.apps?.includes(appId)
|
||||||
).length
|
}).length
|
||||||
: value?.length || 0
|
: value?.length || 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
admin: "Full access",
|
admin: "Full access",
|
||||||
}
|
}
|
||||||
|
|
||||||
$: role = Constants.BudibaseRoleOptions.find(
|
$: role = Constants.BudibaseRoleOptionsOld.find(
|
||||||
x => x.value === users.getUserRole(row)
|
x => x.value === users.getUserRole(row)
|
||||||
)
|
)
|
||||||
$: value = role?.label || "Not available"
|
$: value = role?.label || "Not available"
|
||||||
|
|
|
@ -13,9 +13,22 @@ export function createPermissionStore() {
|
||||||
level,
|
level,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
remove: async ({ level, role, resource }) => {
|
||||||
|
return await API.removePermissionFromResource({
|
||||||
|
resourceId: resource,
|
||||||
|
roleId: role,
|
||||||
|
level,
|
||||||
|
})
|
||||||
|
},
|
||||||
forResource: async resourceId => {
|
forResource: async resourceId => {
|
||||||
|
return (await API.getPermissionForResource(resourceId)).permissions
|
||||||
|
},
|
||||||
|
forResourceDetailed: async resourceId => {
|
||||||
return await API.getPermissionForResource(resourceId)
|
return await API.getPermissionForResource(resourceId)
|
||||||
},
|
},
|
||||||
|
getDependantsInfo: async resourceId => {
|
||||||
|
return await API.getDependants(resourceId)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -121,8 +121,11 @@ export function createUsersStore() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const getUserRole = user =>
|
const getUserRole = user =>
|
||||||
sdk.users.isAdminOrGlobalBuilder(user) ? "admin" : "appUser"
|
sdk.users.isAdmin(user)
|
||||||
|
? "admin"
|
||||||
|
: sdk.users.isBuilder(user)
|
||||||
|
? "developer"
|
||||||
|
: "appUser"
|
||||||
const refreshUsage =
|
const refreshUsage =
|
||||||
fn =>
|
fn =>
|
||||||
async (...args) => {
|
async (...args) => {
|
||||||
|
|
|
@ -136,7 +136,7 @@
|
||||||
// Check arrays - remove any values not present in the field schema and
|
// Check arrays - remove any values not present in the field schema and
|
||||||
// convert any values supplied to strings
|
// convert any values supplied to strings
|
||||||
if (Array.isArray(value) && type === "array" && schema) {
|
if (Array.isArray(value) && type === "array" && schema) {
|
||||||
const options = schema?.constraints.inclusion || []
|
const options = schema?.constraints?.inclusion || []
|
||||||
return value.map(opt => String(opt)).filter(opt => options.includes(opt))
|
return value.map(opt => String(opt)).filter(opt => options.includes(opt))
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
|
|
|
@ -21,4 +21,27 @@ export const buildPermissionsEndpoints = API => ({
|
||||||
url: `/api/permission/${roleId}/${resourceId}/${level}`,
|
url: `/api/permission/${roleId}/${resourceId}/${level}`,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the the permissions for a certain resource
|
||||||
|
* @param resourceId the ID of the resource to update
|
||||||
|
* @param roleId the ID of the role to update the permissions of
|
||||||
|
* @param level the level to remove the role for this resource
|
||||||
|
* @return {Promise<*>}
|
||||||
|
*/
|
||||||
|
removePermissionFromResource: async ({ resourceId, roleId, level }) => {
|
||||||
|
return await API.delete({
|
||||||
|
url: `/api/permission/${roleId}/${resourceId}/${level}`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets info about the resources that depend on this resource permissions
|
||||||
|
* @param resourceId the resource ID to check
|
||||||
|
*/
|
||||||
|
getDependants: async resourceId => {
|
||||||
|
return await API.get({
|
||||||
|
url: `/api/permission/${resourceId}/dependants`,
|
||||||
|
})
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -144,8 +144,8 @@ export const buildUserEndpoints = API => ({
|
||||||
body: {
|
body: {
|
||||||
email,
|
email,
|
||||||
userInfo: {
|
userInfo: {
|
||||||
admin: admin ? { global: true } : undefined,
|
admin: admin?.global ? { global: true } : undefined,
|
||||||
builder: builder ? { global: true } : undefined,
|
builder: builder?.global ? { global: true } : undefined,
|
||||||
apps: apps ? apps : undefined,
|
apps: apps ? apps : undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -156,14 +156,13 @@ export const buildUserEndpoints = API => ({
|
||||||
return await API.post({
|
return await API.post({
|
||||||
url: "/api/global/users/onboard",
|
url: "/api/global/users/onboard",
|
||||||
body: payload.map(invite => {
|
body: payload.map(invite => {
|
||||||
const { email, admin, builder, apps, appBuilders } = invite
|
const { email, admin, builder, apps } = invite
|
||||||
return {
|
return {
|
||||||
email,
|
email,
|
||||||
userInfo: {
|
userInfo: {
|
||||||
admin: admin ? { global: true } : undefined,
|
admin,
|
||||||
builder: builder ? { global: true } : undefined,
|
builder,
|
||||||
apps: apps ? apps : undefined,
|
apps: apps ? apps : undefined,
|
||||||
appBuilders,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
@ -176,12 +175,11 @@ export const buildUserEndpoints = API => ({
|
||||||
* @param invite the invite code sent in the email
|
* @param invite the invite code sent in the email
|
||||||
*/
|
*/
|
||||||
updateUserInvite: async invite => {
|
updateUserInvite: async invite => {
|
||||||
console.log(invite)
|
|
||||||
await API.post({
|
await API.post({
|
||||||
url: `/api/global/users/invite/update/${invite.code}`,
|
url: `/api/global/users/invite/update/${invite.code}`,
|
||||||
body: {
|
body: {
|
||||||
apps: invite.apps,
|
apps: invite.apps,
|
||||||
appBuilders: invite.appBuilders,
|
builder: invite.builder,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
* Operator options for lucene queries
|
* Operator options for lucene queries
|
||||||
*/
|
*/
|
||||||
export { OperatorOptions, SqlNumberTypeRangeMap } from "@budibase/shared-core"
|
export { OperatorOptions, SqlNumberTypeRangeMap } from "@budibase/shared-core"
|
||||||
|
export { Feature as Features } from "@budibase/types"
|
||||||
|
|
||||||
// Cookie names
|
// Cookie names
|
||||||
export const Cookies = {
|
export const Cookies = {
|
||||||
|
@ -22,6 +23,11 @@ export const BudibaseRoles = {
|
||||||
Admin: "admin",
|
Admin: "admin",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const BudibaseRoleOptionsOld = [
|
||||||
|
{ label: "Developer", value: BudibaseRoles.Developer },
|
||||||
|
{ label: "Member", value: BudibaseRoles.AppUser },
|
||||||
|
{ label: "Admin", value: BudibaseRoles.Admin },
|
||||||
|
]
|
||||||
export const BudibaseRoleOptions = [
|
export const BudibaseRoleOptions = [
|
||||||
{ label: "Member", value: BudibaseRoles.AppUser },
|
{ label: "Member", value: BudibaseRoles.AppUser },
|
||||||
{ label: "Admin", value: BudibaseRoles.Admin },
|
{ label: "Admin", value: BudibaseRoles.Admin },
|
||||||
|
|
|
@ -39,8 +39,9 @@ import {
|
||||||
} from "../../db/defaultData/datasource_bb_default"
|
} from "../../db/defaultData/datasource_bb_default"
|
||||||
import { removeAppFromUserRoles } from "../../utilities/workerRequests"
|
import { removeAppFromUserRoles } from "../../utilities/workerRequests"
|
||||||
import { stringToReadStream } from "../../utilities"
|
import { stringToReadStream } from "../../utilities"
|
||||||
import { doesUserHaveLock } from "../../utilities/redis"
|
import { doesUserHaveLock, getLocksById } from "../../utilities/redis"
|
||||||
import { cleanupAutomations } from "../../automations/utils"
|
import { cleanupAutomations } from "../../automations/utils"
|
||||||
|
import { checkAppMetadata } from "../../automations/logging"
|
||||||
import { getUniqueRows } from "../../utilities/usageQuota/rows"
|
import { getUniqueRows } from "../../utilities/usageQuota/rows"
|
||||||
import { groups, licensing, quotas } from "@budibase/pro"
|
import { groups, licensing, quotas } from "@budibase/pro"
|
||||||
import {
|
import {
|
||||||
|
@ -50,6 +51,7 @@ import {
|
||||||
PlanType,
|
PlanType,
|
||||||
Screen,
|
Screen,
|
||||||
UserCtx,
|
UserCtx,
|
||||||
|
ContextUser,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { BASE_LAYOUT_PROP_IDS } from "../../constants/layouts"
|
import { BASE_LAYOUT_PROP_IDS } from "../../constants/layouts"
|
||||||
import sdk from "../../sdk"
|
import sdk from "../../sdk"
|
||||||
|
|
|
@ -20,7 +20,7 @@ import {
|
||||||
Automation,
|
Automation,
|
||||||
AutomationActionStepId,
|
AutomationActionStepId,
|
||||||
AutomationResults,
|
AutomationResults,
|
||||||
Ctx,
|
BBContext,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { getActionDefinitions as actionDefs } from "../../automations/actions"
|
import { getActionDefinitions as actionDefs } from "../../automations/actions"
|
||||||
import sdk from "../../sdk"
|
import sdk from "../../sdk"
|
||||||
|
@ -73,7 +73,7 @@ function cleanAutomationInputs(automation: Automation) {
|
||||||
return automation
|
return automation
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function create(ctx: Ctx) {
|
export async function create(ctx: BBContext) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
let automation = ctx.request.body
|
let automation = ctx.request.body
|
||||||
automation.appId = ctx.appId
|
automation.appId = ctx.appId
|
||||||
|
@ -142,7 +142,7 @@ export async function handleStepEvents(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function update(ctx: Ctx) {
|
export async function update(ctx: BBContext) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
let automation = ctx.request.body
|
let automation = ctx.request.body
|
||||||
automation.appId = ctx.appId
|
automation.appId = ctx.appId
|
||||||
|
@ -193,7 +193,7 @@ export async function update(ctx: Ctx) {
|
||||||
builderSocket?.emitAutomationUpdate(ctx, automation)
|
builderSocket?.emitAutomationUpdate(ctx, automation)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetch(ctx: Ctx) {
|
export async function fetch(ctx: BBContext) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const response = await db.allDocs(
|
const response = await db.allDocs(
|
||||||
getAutomationParams(null, {
|
getAutomationParams(null, {
|
||||||
|
@ -203,11 +203,12 @@ export async function fetch(ctx: Ctx) {
|
||||||
ctx.body = response.rows.map(row => row.doc)
|
ctx.body = response.rows.map(row => row.doc)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function find(ctx: Ctx) {
|
export async function find(ctx: BBContext) {
|
||||||
ctx.body = await sdk.automations.get(ctx.params.id)
|
const db = context.getAppDB()
|
||||||
|
ctx.body = await db.get(ctx.params.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function destroy(ctx: Ctx) {
|
export async function destroy(ctx: BBContext) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const automationId = ctx.params.id
|
const automationId = ctx.params.id
|
||||||
const oldAutomation = await db.get<Automation>(automationId)
|
const oldAutomation = await db.get<Automation>(automationId)
|
||||||
|
@ -221,11 +222,11 @@ export async function destroy(ctx: Ctx) {
|
||||||
builderSocket?.emitAutomationDeletion(ctx, automationId)
|
builderSocket?.emitAutomationDeletion(ctx, automationId)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function logSearch(ctx: Ctx) {
|
export async function logSearch(ctx: BBContext) {
|
||||||
ctx.body = await automations.logs.logSearch(ctx.request.body)
|
ctx.body = await automations.logs.logSearch(ctx.request.body)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function clearLogError(ctx: Ctx) {
|
export async function clearLogError(ctx: BBContext) {
|
||||||
const { automationId, appId } = ctx.request.body
|
const { automationId, appId } = ctx.request.body
|
||||||
await context.doInAppContext(appId, async () => {
|
await context.doInAppContext(appId, async () => {
|
||||||
const db = context.getProdAppDB()
|
const db = context.getProdAppDB()
|
||||||
|
@ -244,15 +245,15 @@ export async function clearLogError(ctx: Ctx) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getActionList(ctx: Ctx) {
|
export async function getActionList(ctx: BBContext) {
|
||||||
ctx.body = await getActionDefinitions()
|
ctx.body = await getActionDefinitions()
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getTriggerList(ctx: Ctx) {
|
export async function getTriggerList(ctx: BBContext) {
|
||||||
ctx.body = getTriggerDefinitions()
|
ctx.body = getTriggerDefinitions()
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getDefinitionList(ctx: Ctx) {
|
export async function getDefinitionList(ctx: BBContext) {
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
trigger: getTriggerDefinitions(),
|
trigger: getTriggerDefinitions(),
|
||||||
action: await getActionDefinitions(),
|
action: await getActionDefinitions(),
|
||||||
|
@ -265,7 +266,7 @@ export async function getDefinitionList(ctx: Ctx) {
|
||||||
* *
|
* *
|
||||||
*********************/
|
*********************/
|
||||||
|
|
||||||
export async function trigger(ctx: Ctx) {
|
export async function trigger(ctx: BBContext) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
let automation = await db.get<Automation>(ctx.params.id)
|
let automation = await db.get<Automation>(ctx.params.id)
|
||||||
|
|
||||||
|
@ -310,7 +311,7 @@ function prepareTestInput(input: any) {
|
||||||
return input
|
return input
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function test(ctx: Ctx) {
|
export async function test(ctx: BBContext) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
let automation = await db.get<Automation>(ctx.params.id)
|
let automation = await db.get<Automation>(ctx.params.id)
|
||||||
await setTestFlag(automation._id!)
|
await setTestFlag(automation._id!)
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
import { permissions, roles, context, HTTPError } from "@budibase/backend-core"
|
import { permissions, roles, context, HTTPError } from "@budibase/backend-core"
|
||||||
import { UserCtx, Database, Role, PermissionLevel } from "@budibase/types"
|
import {
|
||||||
|
UserCtx,
|
||||||
|
Database,
|
||||||
|
Role,
|
||||||
|
PermissionLevel,
|
||||||
|
GetResourcePermsResponse,
|
||||||
|
ResourcePermissionInfo,
|
||||||
|
GetDependantResourcesResponse,
|
||||||
|
} from "@budibase/types"
|
||||||
import { getRoleParams } from "../../db/utils"
|
import { getRoleParams } from "../../db/utils"
|
||||||
import {
|
import {
|
||||||
CURRENTLY_SUPPORTED_LEVELS,
|
CURRENTLY_SUPPORTED_LEVELS,
|
||||||
|
@ -145,33 +153,40 @@ export async function fetch(ctx: UserCtx) {
|
||||||
ctx.body = finalPermissions
|
ctx.body = finalPermissions
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getResourcePerms(ctx: UserCtx) {
|
export async function getResourcePerms(
|
||||||
|
ctx: UserCtx<void, GetResourcePermsResponse>
|
||||||
|
) {
|
||||||
const resourceId = ctx.params.resourceId
|
const resourceId = ctx.params.resourceId
|
||||||
const db = context.getAppDB()
|
const resourcePermissions = await sdk.permissions.getResourcePerms(resourceId)
|
||||||
const body = await db.allDocs(
|
const inheritablePermissions =
|
||||||
getRoleParams(null, {
|
await sdk.permissions.getInheritablePermissions(resourceId)
|
||||||
include_docs: true,
|
|
||||||
})
|
ctx.body = {
|
||||||
)
|
permissions: Object.entries(resourcePermissions).reduce(
|
||||||
const rolesList = body.rows.map(row => row.doc)
|
(p, [level, role]) => {
|
||||||
let permissions: Record<string, string> = {}
|
p[level] = {
|
||||||
for (let level of SUPPORTED_LEVELS) {
|
role: role.role,
|
||||||
// update the various roleIds in the resource permissions
|
permissionType: role.type,
|
||||||
for (let role of rolesList) {
|
inheritablePermission:
|
||||||
const rolePerms = roles.checkForRoleResourceArray(
|
inheritablePermissions && inheritablePermissions[level].role,
|
||||||
role.permissions,
|
}
|
||||||
resourceId
|
return p
|
||||||
)
|
},
|
||||||
if (
|
{} as Record<string, ResourcePermissionInfo>
|
||||||
rolePerms &&
|
),
|
||||||
rolePerms[resourceId] &&
|
requiresPlanToModify: (
|
||||||
rolePerms[resourceId].indexOf(level) !== -1
|
await sdk.permissions.allowsExplicitPermissions(resourceId)
|
||||||
) {
|
).minPlan,
|
||||||
permissions[level] = roles.getExternalRoleID(role._id, role.version)!
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
export async function getDependantResources(
|
||||||
|
ctx: UserCtx<void, GetDependantResourcesResponse>
|
||||||
|
) {
|
||||||
|
const resourceId = ctx.params.resourceId
|
||||||
|
ctx.body = {
|
||||||
|
resourceByType: await sdk.permissions.getDependantResources(resourceId),
|
||||||
}
|
}
|
||||||
ctx.body = Object.assign(getBasePermissions(resourceId), permissions)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function addPermission(ctx: UserCtx) {
|
export async function addPermission(ctx: UserCtx) {
|
||||||
|
|
|
@ -95,7 +95,7 @@ export async function fetchView(ctx: any) {
|
||||||
() =>
|
() =>
|
||||||
sdk.rows.fetchView(tableId, viewName, {
|
sdk.rows.fetchView(tableId, viewName, {
|
||||||
calculation,
|
calculation,
|
||||||
group,
|
group: calculation ? group : null,
|
||||||
field,
|
field,
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
|
|
|
@ -27,7 +27,7 @@ export function json(rows: Row[]) {
|
||||||
export function jsonWithSchema(schema: TableSchema, rows: Row[]) {
|
export function jsonWithSchema(schema: TableSchema, rows: Row[]) {
|
||||||
const newSchema: TableSchema = {}
|
const newSchema: TableSchema = {}
|
||||||
Object.values(schema).forEach(column => {
|
Object.values(schema).forEach(column => {
|
||||||
if (!column.autocolumn) {
|
if (!column.autocolumn && column.name) {
|
||||||
newSchema[column.name] = column
|
newSchema[column.name] = column
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -23,6 +23,11 @@ router
|
||||||
authorized(permissions.BUILDER),
|
authorized(permissions.BUILDER),
|
||||||
controller.getResourcePerms
|
controller.getResourcePerms
|
||||||
)
|
)
|
||||||
|
.get(
|
||||||
|
"/api/permission/:resourceId/dependants",
|
||||||
|
authorized(permissions.BUILDER),
|
||||||
|
controller.getDependantResources
|
||||||
|
)
|
||||||
// adding a specific role/level for the resource overrides the underlying access control
|
// adding a specific role/level for the resource overrides the underlying access control
|
||||||
.post(
|
.post(
|
||||||
"/api/permission/:roleId/:resourceId/:level",
|
"/api/permission/:roleId/:resourceId/:level",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
const mockedSdk = sdk.permissions as jest.Mocked<typeof sdk.permissions>
|
const mockedSdk = sdk.permissions as jest.Mocked<typeof sdk.permissions>
|
||||||
jest.mock("../../../sdk/app/permissions", () => ({
|
jest.mock("../../../sdk/app/permissions", () => ({
|
||||||
|
...jest.requireActual("../../../sdk/app/permissions"),
|
||||||
resourceActionAllowed: jest.fn(),
|
resourceActionAllowed: jest.fn(),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
@ -78,8 +79,12 @@ describe("/permission", () => {
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
expect(res.body["read"]).toEqual(STD_ROLE_ID)
|
expect(res.body).toEqual({
|
||||||
expect(res.body["write"]).toEqual(HIGHER_ROLE_ID)
|
permissions: {
|
||||||
|
read: { permissionType: "EXPLICIT", role: STD_ROLE_ID },
|
||||||
|
write: { permissionType: "BASE", role: HIGHER_ROLE_ID },
|
||||||
|
},
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should get resource permissions with multiple roles", async () => {
|
it("should get resource permissions with multiple roles", async () => {
|
||||||
|
@ -89,15 +94,20 @@ describe("/permission", () => {
|
||||||
level: PermissionLevel.WRITE,
|
level: PermissionLevel.WRITE,
|
||||||
})
|
})
|
||||||
const res = await config.api.permission.get(table._id)
|
const res = await config.api.permission.get(table._id)
|
||||||
expect(res.body["read"]).toEqual(STD_ROLE_ID)
|
expect(res.body).toEqual({
|
||||||
expect(res.body["write"]).toEqual(HIGHER_ROLE_ID)
|
permissions: {
|
||||||
|
read: { permissionType: "EXPLICIT", role: STD_ROLE_ID },
|
||||||
|
write: { permissionType: "EXPLICIT", role: HIGHER_ROLE_ID },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const allRes = await request
|
const allRes = await request
|
||||||
.get(`/api/permission`)
|
.get(`/api/permission`)
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
expect(allRes.body[table._id]["write"]).toEqual(HIGHER_ROLE_ID)
|
|
||||||
expect(allRes.body[table._id]["read"]).toEqual(STD_ROLE_ID)
|
expect(allRes.body[table._id]["read"]).toEqual(STD_ROLE_ID)
|
||||||
|
expect(allRes.body[table._id]["write"]).toEqual(HIGHER_ROLE_ID)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("throw forbidden if the action is not allowed for the resource", async () => {
|
it("throw forbidden if the action is not allowed for the resource", async () => {
|
||||||
|
@ -260,4 +270,21 @@ describe("/permission", () => {
|
||||||
expect(publicPerm.name).toBeDefined()
|
expect(publicPerm.name).toBeDefined()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("default permissions", () => {
|
||||||
|
it("legacy views", async () => {
|
||||||
|
const legacyView = await config.createLegacyView()
|
||||||
|
|
||||||
|
const res = await config.api.permission.get(legacyView.name)
|
||||||
|
|
||||||
|
expect(res.body).toEqual({
|
||||||
|
permissions: {
|
||||||
|
read: {
|
||||||
|
permissionType: "BASE",
|
||||||
|
role: "BASIC",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import tk from "timekeeper"
|
import tk from "timekeeper"
|
||||||
import { outputProcessing } from "../../../utilities/rowProcessor"
|
import { outputProcessing } from "../../../utilities/rowProcessor"
|
||||||
import * as setup from "./utilities"
|
import * as setup from "./utilities"
|
||||||
import { context, tenancy } from "@budibase/backend-core"
|
import { context, roles, tenancy } from "@budibase/backend-core"
|
||||||
import { quotas } from "@budibase/pro"
|
import { quotas } from "@budibase/pro"
|
||||||
import {
|
import {
|
||||||
FieldType,
|
FieldType,
|
||||||
MonthlyQuotaName,
|
MonthlyQuotaName,
|
||||||
|
PermissionLevel,
|
||||||
QuotaUsageType,
|
QuotaUsageType,
|
||||||
Row,
|
Row,
|
||||||
SortOrder,
|
SortOrder,
|
||||||
|
@ -16,6 +17,7 @@ import {
|
||||||
import {
|
import {
|
||||||
expectAnyInternalColsAttributes,
|
expectAnyInternalColsAttributes,
|
||||||
generator,
|
generator,
|
||||||
|
mocks,
|
||||||
structures,
|
structures,
|
||||||
} from "@budibase/backend-core/tests"
|
} from "@budibase/backend-core/tests"
|
||||||
|
|
||||||
|
@ -37,6 +39,7 @@ describe("/rows", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
mocks.licenses.useCloudFree()
|
||||||
table = await config.createTable()
|
table = await config.createTable()
|
||||||
row = basicRow(table._id!)
|
row = basicRow(table._id!)
|
||||||
})
|
})
|
||||||
|
@ -670,7 +673,7 @@ describe("/rows", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should be able to run on a view", async () => {
|
it("should be able to run on a view", async () => {
|
||||||
const view = await config.createView()
|
const view = await config.createLegacyView()
|
||||||
const row = await config.createRow()
|
const row = await config.createRow()
|
||||||
const rowUsage = await getRowUsage()
|
const rowUsage = await getRowUsage()
|
||||||
const queryUsage = await getQueryUsage()
|
const queryUsage = await getQueryUsage()
|
||||||
|
@ -1314,6 +1317,85 @@ describe("/rows", () => {
|
||||||
bookmark: expect.any(String),
|
bookmark: expect.any(String),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("permissions", () => {
|
||||||
|
let viewId: string
|
||||||
|
let tableId: string
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const table = await config.createTable(userTable())
|
||||||
|
const rows = []
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
rows.push(await config.createRow({ tableId: table._id }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const createViewResponse = await config.api.viewV2.create()
|
||||||
|
|
||||||
|
tableId = table._id!
|
||||||
|
viewId = createViewResponse.id
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mocks.licenses.useViewPermissions()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("does not allow public users to fetch by default", async () => {
|
||||||
|
await config.publish()
|
||||||
|
await config.api.viewV2.search(viewId, undefined, {
|
||||||
|
expectStatus: 403,
|
||||||
|
usePublicUser: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("allow public users to fetch when permissions are explicit", async () => {
|
||||||
|
await config.api.permission.set({
|
||||||
|
roleId: roles.BUILTIN_ROLE_IDS.PUBLIC,
|
||||||
|
level: PermissionLevel.READ,
|
||||||
|
resourceId: viewId,
|
||||||
|
})
|
||||||
|
await config.publish()
|
||||||
|
|
||||||
|
const response = await config.api.viewV2.search(viewId, undefined, {
|
||||||
|
usePublicUser: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response.body.rows).toHaveLength(10)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("allow public users to fetch when permissions are inherited", async () => {
|
||||||
|
await config.api.permission.set({
|
||||||
|
roleId: roles.BUILTIN_ROLE_IDS.PUBLIC,
|
||||||
|
level: PermissionLevel.READ,
|
||||||
|
resourceId: tableId,
|
||||||
|
})
|
||||||
|
await config.publish()
|
||||||
|
|
||||||
|
const response = await config.api.viewV2.search(viewId, undefined, {
|
||||||
|
usePublicUser: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response.body.rows).toHaveLength(10)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("respects inherited permissions, not allowing not public views from public tables", async () => {
|
||||||
|
await config.api.permission.set({
|
||||||
|
roleId: roles.BUILTIN_ROLE_IDS.PUBLIC,
|
||||||
|
level: PermissionLevel.READ,
|
||||||
|
resourceId: tableId,
|
||||||
|
})
|
||||||
|
await config.api.permission.set({
|
||||||
|
roleId: roles.BUILTIN_ROLE_IDS.POWER,
|
||||||
|
level: PermissionLevel.READ,
|
||||||
|
resourceId: viewId,
|
||||||
|
})
|
||||||
|
await config.publish()
|
||||||
|
|
||||||
|
await config.api.viewV2.search(viewId, undefined, {
|
||||||
|
usePublicUser: true,
|
||||||
|
expectStatus: 403,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -87,7 +87,7 @@ describe("/tables", () => {
|
||||||
|
|
||||||
it("updates all the row fields for a table when a schema key is renamed", async () => {
|
it("updates all the row fields for a table when a schema key is renamed", async () => {
|
||||||
const testTable = await config.createTable()
|
const testTable = await config.createTable()
|
||||||
await config.createView({
|
await config.createLegacyView({
|
||||||
name: "TestView",
|
name: "TestView",
|
||||||
field: "Price",
|
field: "Price",
|
||||||
calculation: "stats",
|
calculation: "stats",
|
||||||
|
@ -254,7 +254,7 @@ describe("/tables", () => {
|
||||||
}))
|
}))
|
||||||
|
|
||||||
await config.api.viewV2.create({ tableId })
|
await config.api.viewV2.create({ tableId })
|
||||||
await config.createView({ tableId, name: generator.guid() })
|
await config.createLegacyView({ tableId, name: generator.guid() })
|
||||||
|
|
||||||
const res = await config.api.table.fetch()
|
const res = await config.api.table.fetch()
|
||||||
|
|
||||||
|
|
|
@ -249,7 +249,7 @@ describe("/views", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("returns only custom views", async () => {
|
it("returns only custom views", async () => {
|
||||||
await config.createView({
|
await config.createLegacyView({
|
||||||
name: "TestView",
|
name: "TestView",
|
||||||
field: "Price",
|
field: "Price",
|
||||||
calculation: "stats",
|
calculation: "stats",
|
||||||
|
@ -267,7 +267,7 @@ describe("/views", () => {
|
||||||
|
|
||||||
describe("query", () => {
|
describe("query", () => {
|
||||||
it("returns data for the created view", async () => {
|
it("returns data for the created view", async () => {
|
||||||
await config.createView({
|
await config.createLegacyView({
|
||||||
name: "TestView",
|
name: "TestView",
|
||||||
field: "Price",
|
field: "Price",
|
||||||
calculation: "stats",
|
calculation: "stats",
|
||||||
|
@ -295,7 +295,7 @@ describe("/views", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("returns data for the created view using a group by", async () => {
|
it("returns data for the created view using a group by", async () => {
|
||||||
await config.createView({
|
await config.createLegacyView({
|
||||||
calculation: "stats",
|
calculation: "stats",
|
||||||
name: "TestView",
|
name: "TestView",
|
||||||
field: "Price",
|
field: "Price",
|
||||||
|
@ -331,7 +331,7 @@ describe("/views", () => {
|
||||||
describe("destroy", () => {
|
describe("destroy", () => {
|
||||||
it("should be able to delete a view", async () => {
|
it("should be able to delete a view", async () => {
|
||||||
const table = await config.createTable(priceTable())
|
const table = await config.createTable(priceTable())
|
||||||
const view = await config.createView()
|
const view = await config.createLegacyView()
|
||||||
const res = await request
|
const res = await request
|
||||||
.delete(`/api/views/${view.name}`)
|
.delete(`/api/views/${view.name}`)
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
|
@ -395,7 +395,7 @@ describe("/views", () => {
|
||||||
|
|
||||||
it("should be able to export a view as JSON", async () => {
|
it("should be able to export a view as JSON", async () => {
|
||||||
let table = await setupExport()
|
let table = await setupExport()
|
||||||
const view = await config.createView()
|
const view = await config.createLegacyView()
|
||||||
table = await config.getTable(table._id)
|
table = await config.getTable(table._id)
|
||||||
|
|
||||||
let res = await exportView(view.name, "json")
|
let res = await exportView(view.name, "json")
|
||||||
|
@ -407,7 +407,7 @@ describe("/views", () => {
|
||||||
|
|
||||||
it("should be able to export a view as CSV", async () => {
|
it("should be able to export a view as CSV", async () => {
|
||||||
let table = await setupExport()
|
let table = await setupExport()
|
||||||
const view = await config.createView()
|
const view = await config.createLegacyView()
|
||||||
table = await config.getTable(table._id)
|
table = await config.getTable(table._id)
|
||||||
|
|
||||||
let res = await exportView(view.name, "csv")
|
let res = await exportView(view.name, "csv")
|
||||||
|
|
|
@ -296,7 +296,7 @@ describe.each([
|
||||||
})
|
})
|
||||||
|
|
||||||
it("cannot update views v1", async () => {
|
it("cannot update views v1", async () => {
|
||||||
const viewV1 = await config.createView()
|
const viewV1 = await config.createLegacyView()
|
||||||
await config.api.viewV2.update(
|
await config.api.viewV2.update(
|
||||||
{
|
{
|
||||||
...viewV1,
|
...viewV1,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import Router from "@koa/router"
|
import Router from "@koa/router"
|
||||||
import * as viewController from "../controllers/view"
|
import * as viewController from "../controllers/view"
|
||||||
import * as rowController from "../controllers/row"
|
import * as rowController from "../controllers/row"
|
||||||
import authorized from "../../middleware/authorized"
|
import authorized, { authorizedResource } from "../../middleware/authorized"
|
||||||
import { paramResource } from "../../middleware/resourceId"
|
import { paramResource } from "../../middleware/resourceId"
|
||||||
import { permissions } from "@budibase/backend-core"
|
import { permissions } from "@budibase/backend-core"
|
||||||
|
|
||||||
|
@ -10,10 +10,10 @@ const router: Router = new Router()
|
||||||
router
|
router
|
||||||
.get(
|
.get(
|
||||||
"/api/v2/views/:viewId",
|
"/api/v2/views/:viewId",
|
||||||
paramResource("viewId"),
|
authorizedResource(
|
||||||
authorized(
|
permissions.PermissionType.VIEW,
|
||||||
permissions.PermissionType.TABLE,
|
permissions.PermissionLevel.READ,
|
||||||
permissions.PermissionLevel.READ
|
"viewId"
|
||||||
),
|
),
|
||||||
viewController.v2.get
|
viewController.v2.get
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,11 +6,11 @@ import { isDevAppID } from "../db/utils"
|
||||||
// need this to call directly, so we can get a response
|
// need this to call directly, so we can get a response
|
||||||
import { automationQueue } from "./bullboard"
|
import { automationQueue } from "./bullboard"
|
||||||
import { checkTestFlag } from "../utilities/redis"
|
import { checkTestFlag } from "../utilities/redis"
|
||||||
|
import * as utils from "./utils"
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import { context, db as dbCore } from "@budibase/backend-core"
|
import { context, db as dbCore } from "@budibase/backend-core"
|
||||||
import { Automation, Row, AutomationData, AutomationJob } from "@budibase/types"
|
import { Automation, Row, AutomationData, AutomationJob } from "@budibase/types"
|
||||||
import { executeSynchronously } from "../threads/automation"
|
import { executeSynchronously } from "../threads/automation"
|
||||||
import sdk from "../sdk"
|
|
||||||
|
|
||||||
export const TRIGGER_DEFINITIONS = definitions
|
export const TRIGGER_DEFINITIONS = definitions
|
||||||
const JOB_OPTS = {
|
const JOB_OPTS = {
|
||||||
|
@ -142,7 +142,7 @@ export async function rebootTrigger() {
|
||||||
let automations = await getAllAutomations()
|
let automations = await getAllAutomations()
|
||||||
let rebootEvents = []
|
let rebootEvents = []
|
||||||
for (let automation of automations) {
|
for (let automation of automations) {
|
||||||
if (sdk.automations.isReboot(automation)) {
|
if (utils.isRebootTrigger(automation)) {
|
||||||
const job = {
|
const job = {
|
||||||
automation,
|
automation,
|
||||||
event: {
|
event: {
|
||||||
|
|
|
@ -17,17 +17,16 @@ import {
|
||||||
import sdk from "../sdk"
|
import sdk from "../sdk"
|
||||||
import { automationsEnabled } from "../features"
|
import { automationsEnabled } from "../features"
|
||||||
|
|
||||||
|
const REBOOT_CRON = "@reboot"
|
||||||
const WH_STEP_ID = definitions.WEBHOOK.stepId
|
const WH_STEP_ID = definitions.WEBHOOK.stepId
|
||||||
|
const CRON_STEP_ID = definitions.CRON.stepId
|
||||||
let Runner: Thread
|
let Runner: Thread
|
||||||
if (automationsEnabled()) {
|
if (automationsEnabled()) {
|
||||||
Runner = new Thread(ThreadType.AUTOMATION)
|
Runner = new Thread(ThreadType.AUTOMATION)
|
||||||
}
|
}
|
||||||
|
|
||||||
function loggingArgs(
|
function loggingArgs(job: AutomationJob) {
|
||||||
job: AutomationJob,
|
return [
|
||||||
timing?: { start: number; complete?: boolean }
|
|
||||||
) {
|
|
||||||
const logs: any[] = [
|
|
||||||
{
|
{
|
||||||
_logKey: "automation",
|
_logKey: "automation",
|
||||||
trigger: job.data.automation.definition.trigger.event,
|
trigger: job.data.automation.definition.trigger.event,
|
||||||
|
@ -37,53 +36,24 @@ function loggingArgs(
|
||||||
jobId: job.id,
|
jobId: job.id,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
if (timing?.start) {
|
|
||||||
logs.push({
|
|
||||||
_logKey: "startTime",
|
|
||||||
start: timing.start,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (timing?.start && timing?.complete) {
|
|
||||||
const end = new Date().getTime()
|
|
||||||
const duration = end - timing.start
|
|
||||||
logs.push({
|
|
||||||
_logKey: "endTime",
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
logs.push({
|
|
||||||
_logKey: "duration",
|
|
||||||
duration,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return logs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function processEvent(job: AutomationJob) {
|
export async function processEvent(job: AutomationJob) {
|
||||||
const appId = job.data.event.appId!
|
const appId = job.data.event.appId!
|
||||||
const automationId = job.data.automation._id!
|
const automationId = job.data.automation._id!
|
||||||
const start = new Date().getTime()
|
|
||||||
const task = async () => {
|
const task = async () => {
|
||||||
try {
|
try {
|
||||||
// need to actually await these so that an error can be captured properly
|
// need to actually await these so that an error can be captured properly
|
||||||
console.log("automation running", ...loggingArgs(job, { start }))
|
console.log("automation running", ...loggingArgs(job))
|
||||||
|
|
||||||
const runFn = () => Runner.run(job)
|
const runFn = () => Runner.run(job)
|
||||||
const result = await quotas.addAutomation(runFn, {
|
const result = await quotas.addAutomation(runFn, {
|
||||||
automationId,
|
automationId,
|
||||||
})
|
})
|
||||||
const end = new Date().getTime()
|
console.log("automation completed", ...loggingArgs(job))
|
||||||
const duration = end - start
|
|
||||||
console.log(
|
|
||||||
"automation completed",
|
|
||||||
...loggingArgs(job, { start, complete: true })
|
|
||||||
)
|
|
||||||
return result
|
return result
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(
|
console.error(`automation was unable to run`, err, ...loggingArgs(job))
|
||||||
`automation was unable to run`,
|
|
||||||
err,
|
|
||||||
...loggingArgs(job, { start, complete: true })
|
|
||||||
)
|
|
||||||
return { err }
|
return { err }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -163,6 +133,19 @@ export async function clearMetadata() {
|
||||||
await db.bulkDocs(automationMetadata)
|
await db.bulkDocs(automationMetadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isCronTrigger(auto: Automation) {
|
||||||
|
return (
|
||||||
|
auto &&
|
||||||
|
auto.definition.trigger &&
|
||||||
|
auto.definition.trigger.stepId === CRON_STEP_ID
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isRebootTrigger(auto: Automation) {
|
||||||
|
const trigger = auto ? auto.definition.trigger : null
|
||||||
|
return isCronTrigger(auto) && trigger?.inputs.cron === REBOOT_CRON
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function handles checking of any cron jobs that need to be enabled/updated.
|
* This function handles checking of any cron jobs that need to be enabled/updated.
|
||||||
* @param {string} appId The ID of the app in which we are checking for webhooks
|
* @param {string} appId The ID of the app in which we are checking for webhooks
|
||||||
|
@ -170,14 +153,14 @@ export async function clearMetadata() {
|
||||||
*/
|
*/
|
||||||
export async function enableCronTrigger(appId: any, automation: Automation) {
|
export async function enableCronTrigger(appId: any, automation: Automation) {
|
||||||
const trigger = automation ? automation.definition.trigger : null
|
const trigger = automation ? automation.definition.trigger : null
|
||||||
const validCron = sdk.automations.isCron(automation) && trigger?.inputs.cron
|
|
||||||
const needsCreated =
|
|
||||||
!sdk.automations.isReboot(automation) &&
|
|
||||||
!sdk.automations.disabled(automation)
|
|
||||||
let enabled = false
|
let enabled = false
|
||||||
|
|
||||||
// need to create cron job
|
// need to create cron job
|
||||||
if (validCron && needsCreated) {
|
if (
|
||||||
|
isCronTrigger(automation) &&
|
||||||
|
!isRebootTrigger(automation) &&
|
||||||
|
trigger?.inputs.cron
|
||||||
|
) {
|
||||||
// make a job id rather than letting Bull decide, makes it easier to handle on way out
|
// make a job id rather than letting Bull decide, makes it easier to handle on way out
|
||||||
const jobId = `${appId}_cron_${newid()}`
|
const jobId = `${appId}_cron_${newid()}`
|
||||||
const job: any = await automationQueue.add(
|
const job: any = await automationQueue.add(
|
||||||
|
|
|
@ -6,11 +6,10 @@ import {
|
||||||
users,
|
users,
|
||||||
} from "@budibase/backend-core"
|
} from "@budibase/backend-core"
|
||||||
import { PermissionLevel, PermissionType, Role, UserCtx } from "@budibase/types"
|
import { PermissionLevel, PermissionType, Role, UserCtx } from "@budibase/types"
|
||||||
import { features } from "@budibase/pro"
|
|
||||||
import builderMiddleware from "./builder"
|
import builderMiddleware from "./builder"
|
||||||
import { isWebhookEndpoint } from "./utils"
|
import { isWebhookEndpoint } from "./utils"
|
||||||
import { paramResource } from "./resourceId"
|
import { paramResource } from "./resourceId"
|
||||||
import { extractViewInfoFromID, isViewID } from "../db/utils"
|
import sdk from "../sdk"
|
||||||
|
|
||||||
function hasResource(ctx: any) {
|
function hasResource(ctx: any) {
|
||||||
return ctx.resourceId != null
|
return ctx.resourceId != null
|
||||||
|
@ -77,31 +76,6 @@ const checkAuthorizedResource = async (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const resourceIdTranformers: Partial<
|
|
||||||
Record<PermissionType, (ctx: UserCtx) => Promise<void>>
|
|
||||||
> = {
|
|
||||||
[PermissionType.VIEW]: async ctx => {
|
|
||||||
const { resourceId } = ctx
|
|
||||||
if (!resourceId) {
|
|
||||||
ctx.throw(400, `Cannot obtain the view id`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isViewID(resourceId)) {
|
|
||||||
ctx.throw(400, `"${resourceId}" is not a valid view id`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (await features.isViewPermissionEnabled()) {
|
|
||||||
ctx.subResourceId = ctx.resourceId
|
|
||||||
ctx.resourceId = extractViewInfoFromID(resourceId).tableId
|
|
||||||
} else {
|
|
||||||
ctx.resourceId = extractViewInfoFromID(resourceId).tableId
|
|
||||||
delete ctx.subResourceId
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const authorized =
|
const authorized =
|
||||||
(
|
(
|
||||||
permType: PermissionType,
|
permType: PermissionType,
|
||||||
|
@ -121,8 +95,8 @@ const authorized =
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the resource roles
|
// get the resource roles
|
||||||
let resourceRoles: any = []
|
let resourceRoles: string[] = []
|
||||||
let otherLevelRoles: any = []
|
let otherLevelRoles: string[] = []
|
||||||
const otherLevel =
|
const otherLevel =
|
||||||
permLevel === PermissionLevel.READ
|
permLevel === PermissionLevel.READ
|
||||||
? PermissionLevel.WRITE
|
? PermissionLevel.WRITE
|
||||||
|
@ -133,21 +107,28 @@ const authorized =
|
||||||
paramResource(resourcePath)(ctx, () => {})
|
paramResource(resourcePath)(ctx, () => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resourceIdTranformers[permType]) {
|
|
||||||
await resourceIdTranformers[permType]!(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasResource(ctx)) {
|
if (hasResource(ctx)) {
|
||||||
const { resourceId, subResourceId } = ctx
|
const { resourceId, subResourceId } = ctx
|
||||||
resourceRoles = await roles.getRequiredResourceRole(permLevel!, {
|
|
||||||
resourceId,
|
const permissions = await sdk.permissions.getResourcePerms(resourceId)
|
||||||
subResourceId,
|
const subPermissions =
|
||||||
})
|
!!subResourceId &&
|
||||||
|
(await sdk.permissions.getResourcePerms(subResourceId))
|
||||||
|
|
||||||
|
function getPermLevel(permLevel: string) {
|
||||||
|
let result: string[] = []
|
||||||
|
if (permissions[permLevel]) {
|
||||||
|
result.push(permissions[permLevel].role)
|
||||||
|
}
|
||||||
|
if (subPermissions && subPermissions[permLevel]) {
|
||||||
|
result.push(subPermissions[permLevel].role)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceRoles = getPermLevel(permLevel!)
|
||||||
if (opts && opts.schema) {
|
if (opts && opts.schema) {
|
||||||
otherLevelRoles = await roles.getRequiredResourceRole(otherLevel, {
|
otherLevelRoles = getPermLevel(otherLevel!)
|
||||||
resourceId,
|
|
||||||
subResourceId,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,28 +1,20 @@
|
||||||
jest.mock("@budibase/backend-core", () => ({
|
jest.mock("../../sdk/app/permissions", () => ({
|
||||||
...jest.requireActual("@budibase/backend-core"),
|
...jest.requireActual("../../sdk/app/permissions"),
|
||||||
roles: {
|
getResourcePerms: jest.fn().mockResolvedValue([]),
|
||||||
...jest.requireActual("@budibase/backend-core").roles,
|
|
||||||
getRequiredResourceRole: jest.fn().mockResolvedValue([]),
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
jest.mock("../../environment", () => ({
|
|
||||||
prod: false,
|
|
||||||
isTest: () => true,
|
|
||||||
// @ts-ignore
|
|
||||||
isProd: () => this.prod,
|
|
||||||
_set: function (_key: string, value: string) {
|
|
||||||
this.prod = value === "production"
|
|
||||||
},
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
import { PermissionType, PermissionLevel } from "@budibase/types"
|
import {
|
||||||
|
PermissionType,
|
||||||
|
PermissionLevel,
|
||||||
|
PermissionSource,
|
||||||
|
} from "@budibase/types"
|
||||||
|
|
||||||
import authorizedMiddleware from "../authorized"
|
import authorizedMiddleware from "../authorized"
|
||||||
import env from "../../environment"
|
import env from "../../environment"
|
||||||
import { generateTableID, generateViewID } from "../../db/utils"
|
import { generateTableID, generateViewID } from "../../db/utils"
|
||||||
import { roles } from "@budibase/backend-core"
|
import { generator, mocks } from "@budibase/backend-core/tests"
|
||||||
import { mocks } from "@budibase/backend-core/tests"
|
|
||||||
import { initProMocks } from "../../tests/utilities/mocks/pro"
|
import { initProMocks } from "../../tests/utilities/mocks/pro"
|
||||||
|
import { getResourcePerms } from "../../sdk/app/permissions"
|
||||||
|
|
||||||
const APP_ID = ""
|
const APP_ID = ""
|
||||||
|
|
||||||
|
@ -189,23 +181,26 @@ describe("Authorization middleware", () => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("view type", () => {
|
describe("with resource", () => {
|
||||||
const tableId = generateTableID()
|
let resourceId: string
|
||||||
const viewId = generateViewID(tableId)
|
const mockedGetResourcePerms = getResourcePerms as jest.MockedFunction<
|
||||||
|
typeof getResourcePerms
|
||||||
const mockedGetRequiredResourceRole =
|
>
|
||||||
roles.getRequiredResourceRole as jest.MockedFunction<
|
|
||||||
typeof roles.getRequiredResourceRole
|
|
||||||
>
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
config.setMiddlewareRequiredPermission(
|
config.setMiddlewareRequiredPermission(
|
||||||
PermissionType.VIEW,
|
PermissionType.VIEW,
|
||||||
PermissionLevel.READ
|
PermissionLevel.READ
|
||||||
)
|
)
|
||||||
config.setResourceId(viewId)
|
resourceId = generator.guid()
|
||||||
|
config.setResourceId(resourceId)
|
||||||
|
|
||||||
mockedGetRequiredResourceRole.mockResolvedValue(["PUBLIC"])
|
mockedGetResourcePerms.mockResolvedValue({
|
||||||
|
[PermissionLevel.READ]: {
|
||||||
|
role: "PUBLIC",
|
||||||
|
type: PermissionSource.BASE,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
config.setUser({
|
config.setUser({
|
||||||
_id: "user",
|
_id: "user",
|
||||||
|
@ -215,57 +210,14 @@ describe("Authorization middleware", () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("will ignore view permissions if flag is off", async () => {
|
it("will fetch resource permissions when resource is set", async () => {
|
||||||
await config.executeMiddleware()
|
await config.executeMiddleware()
|
||||||
|
|
||||||
expect(config.throw).not.toBeCalled()
|
expect(config.throw).not.toBeCalled()
|
||||||
expect(config.next).toHaveBeenCalled()
|
expect(config.next).toHaveBeenCalled()
|
||||||
|
|
||||||
expect(mockedGetRequiredResourceRole).toBeCalledTimes(1)
|
expect(mockedGetResourcePerms).toBeCalledTimes(1)
|
||||||
expect(mockedGetRequiredResourceRole).toBeCalledWith(
|
expect(mockedGetResourcePerms).toBeCalledWith(resourceId)
|
||||||
PermissionLevel.READ,
|
|
||||||
expect.objectContaining({
|
|
||||||
resourceId: tableId,
|
|
||||||
subResourceId: undefined,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("will use view permissions if flag is on", async () => {
|
|
||||||
mocks.licenses.useViewPermissions()
|
|
||||||
await config.executeMiddleware()
|
|
||||||
|
|
||||||
expect(config.throw).not.toBeCalled()
|
|
||||||
expect(config.next).toHaveBeenCalled()
|
|
||||||
|
|
||||||
expect(mockedGetRequiredResourceRole).toBeCalledTimes(1)
|
|
||||||
expect(mockedGetRequiredResourceRole).toBeCalledWith(
|
|
||||||
PermissionLevel.READ,
|
|
||||||
expect.objectContaining({
|
|
||||||
resourceId: tableId,
|
|
||||||
subResourceId: viewId,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("throw an exception if the resource id is not provided", async () => {
|
|
||||||
config.setResourceId(undefined)
|
|
||||||
await config.executeMiddleware()
|
|
||||||
expect(config.throw).toHaveBeenNthCalledWith(
|
|
||||||
1,
|
|
||||||
400,
|
|
||||||
"Cannot obtain the view id"
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("throw an exception if the resource id is not a valid view id", async () => {
|
|
||||||
config.setResourceId(tableId)
|
|
||||||
await config.executeMiddleware()
|
|
||||||
expect(config.throw).toHaveBeenNthCalledWith(
|
|
||||||
1,
|
|
||||||
400,
|
|
||||||
`"${tableId}" is not a valid view id`
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -50,9 +50,9 @@ describe("migrations", () => {
|
||||||
await config.createRole()
|
await config.createRole()
|
||||||
await config.createRole()
|
await config.createRole()
|
||||||
await config.createTable()
|
await config.createTable()
|
||||||
await config.createView()
|
await config.createLegacyView()
|
||||||
await config.createTable()
|
await config.createTable()
|
||||||
await config.createView(structures.view(config.table!._id!))
|
await config.createLegacyView(structures.view(config.table!._id!))
|
||||||
await config.createScreen()
|
await config.createScreen()
|
||||||
await config.createScreen()
|
await config.createScreen()
|
||||||
|
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
import { context } from "@budibase/backend-core"
|
|
||||||
import { Automation, AutomationState, DocumentType } from "@budibase/types"
|
|
||||||
import { definitions } from "../../../automations/triggerInfo"
|
|
||||||
|
|
||||||
const REBOOT_CRON = "@reboot"
|
|
||||||
|
|
||||||
export async function exists(automationId: string) {
|
|
||||||
if (!automationId?.startsWith(DocumentType.AUTOMATION)) {
|
|
||||||
throw new Error("Invalid automation ID.")
|
|
||||||
}
|
|
||||||
const db = context.getAppDB()
|
|
||||||
return db.docExists(automationId)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function get(automationId: string) {
|
|
||||||
const db = context.getAppDB()
|
|
||||||
return (await db.get(automationId)) as Automation
|
|
||||||
}
|
|
||||||
|
|
||||||
export function disabled(automation: Automation) {
|
|
||||||
return automation.state === AutomationState.DISABLED || !hasSteps(automation)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCron(automation: Automation) {
|
|
||||||
return (
|
|
||||||
automation?.definition.trigger &&
|
|
||||||
automation?.definition.trigger.stepId === definitions.CRON.stepId
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isReboot(automation: Automation) {
|
|
||||||
const trigger = automation?.definition.trigger
|
|
||||||
return isCron(automation) && trigger?.inputs.cron === REBOOT_CRON
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hasSteps(automation: Automation) {
|
|
||||||
return automation?.definition?.steps?.length > 0
|
|
||||||
}
|
|
|
@ -1,9 +1,7 @@
|
||||||
import * as webhook from "./webhook"
|
import * as webhook from "./webhook"
|
||||||
import * as utils from "./utils"
|
import * as utils from "./utils"
|
||||||
import * as automations from "./automations"
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
webhook,
|
webhook,
|
||||||
utils,
|
utils,
|
||||||
...automations,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@ function tarFilesToTmp(tmpDir: string, files: string[]) {
|
||||||
export async function exportDB(
|
export async function exportDB(
|
||||||
dbName: string,
|
dbName: string,
|
||||||
opts: DBDumpOpts = {}
|
opts: DBDumpOpts = {}
|
||||||
): Promise<DBDumpOpts> {
|
): Promise<string> {
|
||||||
const exportOpts = {
|
const exportOpts = {
|
||||||
filter: opts?.filter,
|
filter: opts?.filter,
|
||||||
batch_size: 1000,
|
batch_size: 1000,
|
||||||
|
|
|
@ -1,10 +1,24 @@
|
||||||
|
import { context, db, env, roles } from "@budibase/backend-core"
|
||||||
|
import { features } from "@budibase/pro"
|
||||||
import {
|
import {
|
||||||
DocumentType,
|
DocumentType,
|
||||||
PermissionLevel,
|
PermissionLevel,
|
||||||
|
PermissionSource,
|
||||||
|
PlanType,
|
||||||
|
Role,
|
||||||
VirtualDocumentType,
|
VirtualDocumentType,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { isViewID } from "../../../db/utils"
|
import {
|
||||||
import { features } from "@budibase/pro"
|
extractViewInfoFromID,
|
||||||
|
getRoleParams,
|
||||||
|
isViewID,
|
||||||
|
} from "../../../db/utils"
|
||||||
|
import {
|
||||||
|
CURRENTLY_SUPPORTED_LEVELS,
|
||||||
|
getBasePermissions,
|
||||||
|
} from "../../../utilities/security"
|
||||||
|
import sdk from "../../../sdk"
|
||||||
|
import { isV2 } from "../views"
|
||||||
|
|
||||||
type ResourceActionAllowedResult =
|
type ResourceActionAllowedResult =
|
||||||
| { allowed: true }
|
| { allowed: true }
|
||||||
|
@ -35,3 +49,117 @@ export async function resourceActionAllowed({
|
||||||
resourceType: VirtualDocumentType.VIEW,
|
resourceType: VirtualDocumentType.VIEW,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ResourcePermissions = Record<
|
||||||
|
string,
|
||||||
|
{ role: string; type: PermissionSource }
|
||||||
|
>
|
||||||
|
|
||||||
|
export async function getInheritablePermissions(
|
||||||
|
resourceId: string
|
||||||
|
): Promise<ResourcePermissions | undefined> {
|
||||||
|
if (isViewID(resourceId)) {
|
||||||
|
return await getResourcePerms(extractViewInfoFromID(resourceId).tableId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function allowsExplicitPermissions(resourceId: string) {
|
||||||
|
if (isViewID(resourceId)) {
|
||||||
|
const allowed = await features.isViewPermissionEnabled()
|
||||||
|
const minPlan = !allowed
|
||||||
|
? env.SELF_HOSTED
|
||||||
|
? PlanType.BUSINESS
|
||||||
|
: PlanType.PREMIUM
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
return {
|
||||||
|
allowed,
|
||||||
|
minPlan,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { allowed: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getResourcePerms(
|
||||||
|
resourceId: string
|
||||||
|
): Promise<ResourcePermissions> {
|
||||||
|
const db = context.getAppDB()
|
||||||
|
const body = await db.allDocs(
|
||||||
|
getRoleParams(null, {
|
||||||
|
include_docs: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
const rolesList = body.rows.map<Role>(row => row.doc)
|
||||||
|
let permissions: ResourcePermissions = {}
|
||||||
|
|
||||||
|
const permsToInherit = await getInheritablePermissions(resourceId)
|
||||||
|
|
||||||
|
const allowsExplicitPerm = (await allowsExplicitPermissions(resourceId))
|
||||||
|
.allowed
|
||||||
|
|
||||||
|
for (let level of CURRENTLY_SUPPORTED_LEVELS) {
|
||||||
|
// update the various roleIds in the resource permissions
|
||||||
|
for (let role of rolesList) {
|
||||||
|
const rolePerms = allowsExplicitPerm
|
||||||
|
? roles.checkForRoleResourceArray(role.permissions, resourceId)
|
||||||
|
: {}
|
||||||
|
if (rolePerms[resourceId]?.indexOf(level) > -1) {
|
||||||
|
permissions[level] = {
|
||||||
|
role: roles.getExternalRoleID(role._id!, role.version),
|
||||||
|
type: PermissionSource.EXPLICIT,
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
!permissions[level] &&
|
||||||
|
permsToInherit &&
|
||||||
|
permsToInherit[level]
|
||||||
|
) {
|
||||||
|
permissions[level] = {
|
||||||
|
role: permsToInherit[level].role,
|
||||||
|
type: PermissionSource.INHERITED,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const basePermissions = Object.entries(
|
||||||
|
getBasePermissions(resourceId)
|
||||||
|
).reduce<ResourcePermissions>((p, [level, role]) => {
|
||||||
|
p[level] = { role, type: PermissionSource.BASE }
|
||||||
|
return p
|
||||||
|
}, {})
|
||||||
|
const result = Object.assign(basePermissions, permissions)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDependantResources(
|
||||||
|
resourceId: string
|
||||||
|
): Promise<Record<string, number> | undefined> {
|
||||||
|
if (db.isTableId(resourceId)) {
|
||||||
|
const dependants: Record<string, Set<string>> = {}
|
||||||
|
|
||||||
|
const table = await sdk.tables.getTable(resourceId)
|
||||||
|
const views = Object.values(table.views || {})
|
||||||
|
|
||||||
|
for (const view of views) {
|
||||||
|
if (!isV2(view)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const permissions = await getResourcePerms(view.id)
|
||||||
|
for (const [level, roleInfo] of Object.entries(permissions)) {
|
||||||
|
if (roleInfo.type === PermissionSource.INHERITED) {
|
||||||
|
dependants[VirtualDocumentType.VIEW] ??= new Set()
|
||||||
|
dependants[VirtualDocumentType.VIEW].add(view.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.entries(dependants).reduce((p, [type, resources]) => {
|
||||||
|
p[type] = resources.size
|
||||||
|
return p
|
||||||
|
}, {} as Record<string, number>)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
@ -622,7 +622,7 @@ class TestConfiguration {
|
||||||
|
|
||||||
// VIEW
|
// VIEW
|
||||||
|
|
||||||
async createView(config?: any) {
|
async createLegacyView(config?: any) {
|
||||||
if (!this.table) {
|
if (!this.table) {
|
||||||
throw "Test requires table to be configured."
|
throw "Test requires table to be configured."
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { default as threadUtils } from "./utils"
|
import { default as threadUtils } from "./utils"
|
||||||
import { Job } from "bull"
|
import { Job } from "bull"
|
||||||
threadUtils.threadSetup()
|
|
||||||
import {
|
import {
|
||||||
disableCronById,
|
disableCronById,
|
||||||
isErrorInOutput,
|
isErrorInOutput,
|
||||||
|
@ -35,8 +34,8 @@ import { cloneDeep } from "lodash/fp"
|
||||||
import { performance } from "perf_hooks"
|
import { performance } from "perf_hooks"
|
||||||
import * as sdkUtils from "../sdk/utils"
|
import * as sdkUtils from "../sdk/utils"
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import sdk from "../sdk"
|
|
||||||
|
|
||||||
|
threadUtils.threadSetup()
|
||||||
const FILTER_STEP_ID = actions.BUILTIN_ACTION_DEFINITIONS.FILTER.stepId
|
const FILTER_STEP_ID = actions.BUILTIN_ACTION_DEFINITIONS.FILTER.stepId
|
||||||
const LOOP_STEP_ID = actions.BUILTIN_ACTION_DEFINITIONS.LOOP.stepId
|
const LOOP_STEP_ID = actions.BUILTIN_ACTION_DEFINITIONS.LOOP.stepId
|
||||||
const CRON_STEP_ID = triggerDefs.CRON.stepId
|
const CRON_STEP_ID = triggerDefs.CRON.stepId
|
||||||
|
@ -520,8 +519,7 @@ class Orchestrator {
|
||||||
|
|
||||||
export function execute(job: Job<AutomationData>, callback: WorkerCallback) {
|
export function execute(job: Job<AutomationData>, callback: WorkerCallback) {
|
||||||
const appId = job.data.event.appId
|
const appId = job.data.event.appId
|
||||||
const automation = job.data.automation
|
const automationId = job.data.automation._id
|
||||||
const automationId = automation._id
|
|
||||||
if (!appId) {
|
if (!appId) {
|
||||||
throw new Error("Unable to execute, event doesn't contain app ID.")
|
throw new Error("Unable to execute, event doesn't contain app ID.")
|
||||||
}
|
}
|
||||||
|
@ -532,30 +530,10 @@ export function execute(job: Job<AutomationData>, callback: WorkerCallback) {
|
||||||
appId,
|
appId,
|
||||||
automationId,
|
automationId,
|
||||||
task: async () => {
|
task: async () => {
|
||||||
let automation = job.data.automation,
|
|
||||||
isCron = sdk.automations.isCron(job.data.automation),
|
|
||||||
notFound = false
|
|
||||||
try {
|
|
||||||
automation = await sdk.automations.get(automationId)
|
|
||||||
} catch (err: any) {
|
|
||||||
// automation no longer exists
|
|
||||||
notFound = err
|
|
||||||
}
|
|
||||||
const disabled = sdk.automations.disabled(automation)
|
|
||||||
const stopAutomation = disabled || notFound
|
|
||||||
const envVars = await sdkUtils.getEnvironmentVariables()
|
const envVars = await sdkUtils.getEnvironmentVariables()
|
||||||
// put into automation thread for whole context
|
// put into automation thread for whole context
|
||||||
await context.doInEnvironmentContext(envVars, async () => {
|
await context.doInEnvironmentContext(envVars, async () => {
|
||||||
const automationOrchestrator = new Orchestrator(job)
|
const automationOrchestrator = new Orchestrator(job)
|
||||||
// hard stop on automations
|
|
||||||
if (isCron && stopAutomation) {
|
|
||||||
await automationOrchestrator.stopCron(
|
|
||||||
disabled ? "disabled" : "not_found"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (stopAutomation) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const response = await automationOrchestrator.execute()
|
const response = await automationOrchestrator.execute()
|
||||||
callback(null, response)
|
callback(null, response)
|
||||||
|
|
|
@ -23,6 +23,9 @@ export function getPermissionType(resourceId: string) {
|
||||||
case DocumentType.QUERY:
|
case DocumentType.QUERY:
|
||||||
case DocumentType.DATASOURCE:
|
case DocumentType.DATASOURCE:
|
||||||
return permissions.PermissionType.QUERY
|
return permissions.PermissionType.QUERY
|
||||||
|
default:
|
||||||
|
// legacy views don't have an ID, will end up here
|
||||||
|
return permissions.PermissionType.LEGACY_VIEW
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,3 +4,4 @@ export * from "./row"
|
||||||
export * from "./view"
|
export * from "./view"
|
||||||
export * from "./rows"
|
export * from "./rows"
|
||||||
export * from "./table"
|
export * from "./table"
|
||||||
|
export * from "./permission"
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { PlanType } from "../../../sdk"
|
||||||
|
|
||||||
|
export interface ResourcePermissionInfo {
|
||||||
|
role: string
|
||||||
|
permissionType: string
|
||||||
|
inheritablePermission?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetResourcePermsResponse {
|
||||||
|
permissions: Record<string, ResourcePermissionInfo>
|
||||||
|
requiresPlanToModify?: PlanType
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetDependantResourcesResponse {
|
||||||
|
resourceByType?: Record<string, number>
|
||||||
|
}
|
|
@ -100,10 +100,6 @@ export const AutomationStepIdArray = [
|
||||||
...Object.values(AutomationTriggerStepId),
|
...Object.values(AutomationTriggerStepId),
|
||||||
]
|
]
|
||||||
|
|
||||||
export enum AutomationState {
|
|
||||||
DISABLED = "disabled",
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Automation extends Document {
|
export interface Automation extends Document {
|
||||||
definition: {
|
definition: {
|
||||||
steps: AutomationStep[]
|
steps: AutomationStep[]
|
||||||
|
@ -116,7 +112,6 @@ export interface Automation extends Document {
|
||||||
name: string
|
name: string
|
||||||
internal?: boolean
|
internal?: boolean
|
||||||
type?: string
|
type?: string
|
||||||
state?: AutomationState
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BaseIOStructure {
|
interface BaseIOStructure {
|
||||||
|
|
|
@ -40,11 +40,6 @@ export type DatabasePutOpts = {
|
||||||
force?: boolean
|
force?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DocExistsResponse = {
|
|
||||||
_rev?: string
|
|
||||||
exists: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DatabaseCreateIndexOpts = {
|
export type DatabaseCreateIndexOpts = {
|
||||||
index: {
|
index: {
|
||||||
fields: string[]
|
fields: string[]
|
||||||
|
@ -95,7 +90,6 @@ export interface Database {
|
||||||
exists(): Promise<boolean>
|
exists(): Promise<boolean>
|
||||||
checkSetup(): Promise<Nano.DocumentScope<any>>
|
checkSetup(): Promise<Nano.DocumentScope<any>>
|
||||||
get<T>(id?: string): Promise<T>
|
get<T>(id?: string): Promise<T>
|
||||||
docExists(id: string): Promise<DocExistsResponse>
|
|
||||||
remove(
|
remove(
|
||||||
id: string | Document,
|
id: string | Document,
|
||||||
rev?: string
|
rev?: string
|
||||||
|
|
|
@ -16,4 +16,11 @@ export enum PermissionType {
|
||||||
GLOBAL_BUILDER = "globalBuilder",
|
GLOBAL_BUILDER = "globalBuilder",
|
||||||
QUERY = "query",
|
QUERY = "query",
|
||||||
VIEW = "view",
|
VIEW = "view",
|
||||||
|
LEGACY_VIEW = "legacy_view",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum PermissionSource {
|
||||||
|
EXPLICIT = "EXPLICIT",
|
||||||
|
INHERITED = "INHERITED",
|
||||||
|
BASE = "BASE",
|
||||||
}
|
}
|
||||||
|
|
|
@ -266,17 +266,14 @@ export const onboardUsers = async (ctx: Ctx<InviteUsersRequest>) => {
|
||||||
|
|
||||||
// Temp password to be passed to the user.
|
// Temp password to be passed to the user.
|
||||||
createdPasswords[invite.email] = password
|
createdPasswords[invite.email] = password
|
||||||
let builder: { global: boolean; apps?: string[] } = { global: false }
|
|
||||||
if (invite.userInfo.appBuilders) {
|
|
||||||
builder.apps = invite.userInfo.appBuilders
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
email: invite.email,
|
email: invite.email,
|
||||||
password,
|
password,
|
||||||
forceResetPassword: true,
|
forceResetPassword: true,
|
||||||
roles: invite.userInfo.apps,
|
roles: invite.userInfo.apps,
|
||||||
admin: { global: false },
|
admin: invite.userInfo.admin,
|
||||||
builder,
|
builder: invite.userInfo.builder,
|
||||||
tenantId: tenancy.getTenantId(),
|
tenantId: tenancy.getTenantId(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -371,13 +368,10 @@ export const updateInvite = async (ctx: any) => {
|
||||||
...invite,
|
...invite,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!updateBody?.appBuilders || !updateBody.appBuilders?.length) {
|
if (!updateBody?.builder?.apps && updated.info?.builder?.apps) {
|
||||||
updated.info.appBuilders = []
|
updated.info.builder.apps = []
|
||||||
} else {
|
} else if (updateBody?.builder) {
|
||||||
updated.info.appBuilders = [
|
updated.info.builder = updateBody.builder
|
||||||
...(invite.info.appBuilders ?? []),
|
|
||||||
...updateBody.appBuilders,
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!updateBody?.apps || !Object.keys(updateBody?.apps).length) {
|
if (!updateBody?.apps || !Object.keys(updateBody?.apps).length) {
|
||||||
|
@ -409,15 +403,17 @@ export const inviteAccept = async (
|
||||||
lastName,
|
lastName,
|
||||||
password,
|
password,
|
||||||
email,
|
email,
|
||||||
|
admin: { global: info?.admin?.global || false },
|
||||||
roles: info.apps,
|
roles: info.apps,
|
||||||
tenantId: info.tenantId,
|
tenantId: info.tenantId,
|
||||||
}
|
}
|
||||||
let builder: { global: boolean; apps?: string[] } = { global: false }
|
let builder: { global: boolean; apps?: string[] } = {
|
||||||
|
global: info?.builder?.global || false,
|
||||||
|
}
|
||||||
|
|
||||||
if (info.appBuilders) {
|
if (info?.builder?.apps) {
|
||||||
builder.apps = info.appBuilders
|
builder.apps = info.builder.apps
|
||||||
request.builder = builder
|
request.builder = builder
|
||||||
delete info.appBuilders
|
|
||||||
}
|
}
|
||||||
delete info.apps
|
delete info.apps
|
||||||
request = {
|
request = {
|
||||||
|
|
|
@ -15,7 +15,7 @@ const { nodeExternalsPlugin } = require("esbuild-node-externals")
|
||||||
|
|
||||||
var argv = require("minimist")(process.argv.slice(2))
|
var argv = require("minimist")(process.argv.slice(2))
|
||||||
|
|
||||||
function runBuild(entry, outfile) {
|
function runBuild(entry, outfile, opts = { skipMeta: false, bundle: true }) {
|
||||||
const isDev = process.env.NODE_ENV !== "production"
|
const isDev = process.env.NODE_ENV !== "production"
|
||||||
const tsconfig = argv["p"] || `tsconfig.build.json`
|
const tsconfig = argv["p"] || `tsconfig.build.json`
|
||||||
const tsconfigPathPluginContent = JSON.parse(
|
const tsconfigPathPluginContent = JSON.parse(
|
||||||
|
@ -36,12 +36,16 @@ function runBuild(entry, outfile) {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const metafile = !opts.skipMeta
|
||||||
|
const { bundle } = opts
|
||||||
|
|
||||||
const sharedConfig = {
|
const sharedConfig = {
|
||||||
entryPoints: [entry],
|
entryPoints: [entry],
|
||||||
bundle: true,
|
bundle,
|
||||||
minify: !isDev,
|
minify: !isDev,
|
||||||
sourcemap: isDev,
|
sourcemap: isDev,
|
||||||
tsconfig,
|
tsconfig,
|
||||||
|
format: opts?.forcedFormat,
|
||||||
plugins: [
|
plugins: [
|
||||||
TsconfigPathsPlugin({ tsconfig: tsconfigPathPluginContent }),
|
TsconfigPathsPlugin({ tsconfig: tsconfigPathPluginContent }),
|
||||||
nodeExternalsPlugin(),
|
nodeExternalsPlugin(),
|
||||||
|
@ -50,15 +54,10 @@ function runBuild(entry, outfile) {
|
||||||
loader: {
|
loader: {
|
||||||
".svelte": "copy",
|
".svelte": "copy",
|
||||||
},
|
},
|
||||||
metafile: true,
|
metafile,
|
||||||
external: [
|
external: bundle
|
||||||
"deasync",
|
? ["deasync", "mock-aws-s3", "nock", "pino", "koa-pino-logger", "bull"]
|
||||||
"mock-aws-s3",
|
: undefined,
|
||||||
"nock",
|
|
||||||
"pino",
|
|
||||||
"koa-pino-logger",
|
|
||||||
"bull",
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
build({
|
build({
|
||||||
|
@ -77,10 +76,12 @@ function runBuild(entry, outfile) {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
fs.writeFileSync(
|
if (metafile) {
|
||||||
`dist/${path.basename(outfile)}.meta.json`,
|
fs.writeFileSync(
|
||||||
JSON.stringify(result.metafile)
|
`dist/${path.basename(outfile)}.meta.json`,
|
||||||
)
|
JSON.stringify(result.metafile)
|
||||||
|
)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue