Guard readonly fields
This commit is contained in:
parent
6acb3f6669
commit
65d2aa50c6
|
@ -34,7 +34,7 @@ async function parseSchema(view: CreateViewRequest) {
|
|||
return p
|
||||
}, {} as Record<string, RequiredKeys<ViewUIFieldMetadata>>)
|
||||
for (let [key, column] of Object.entries(finalViewSchema)) {
|
||||
if (!column.visible) {
|
||||
if (!column.visible && !column.readonly) {
|
||||
delete finalViewSchema[key]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,14 @@ import { DatabaseName, getDatasource } from "../../../integrations/tests/utils"
|
|||
import merge from "lodash/merge"
|
||||
import { quotas } from "@budibase/pro"
|
||||
import { roles } from "@budibase/backend-core"
|
||||
import * as schemaUtils from "../../../utilities/schema"
|
||||
|
||||
jest.mock("../../../utilities/schema", () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
...jest.requireActual("../../../utilities/schema"),
|
||||
}
|
||||
})
|
||||
|
||||
describe.each([
|
||||
["internal", undefined],
|
||||
|
@ -260,6 +268,45 @@ describe.each([
|
|||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("required fields cannot be marked as readonly", async () => {
|
||||
const isRequiredSpy = jest.spyOn(schemaUtils, "isRequired")
|
||||
|
||||
isRequiredSpy.mockReturnValue(true)
|
||||
|
||||
const table = await config.api.table.save(
|
||||
saveTableRequest({
|
||||
schema: {
|
||||
name: {
|
||||
name: "name",
|
||||
type: FieldType.STRING,
|
||||
},
|
||||
description: {
|
||||
name: "description",
|
||||
type: FieldType.STRING,
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
const newView: CreateViewRequest = {
|
||||
name: generator.name(),
|
||||
tableId: table._id!,
|
||||
schema: {
|
||||
name: {
|
||||
readonly: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
await config.api.viewV2.create(newView, {
|
||||
status: 400,
|
||||
body: {
|
||||
message: 'Field "name" cannot be readonly as it is a required field',
|
||||
status: 400,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("update", () => {
|
||||
|
|
|
@ -15,6 +15,7 @@ import { isExternalTableID } from "../../../integrations/utils"
|
|||
import * as internal from "./internal"
|
||||
import * as external from "./external"
|
||||
import sdk from "../../../sdk"
|
||||
import { isRequired } from "../../../utilities/schema"
|
||||
|
||||
function pickApi(tableId: any) {
|
||||
if (isExternalTableID(tableId)) {
|
||||
|
@ -35,20 +36,31 @@ export async function getEnriched(viewId: string): Promise<ViewV2Enriched> {
|
|||
|
||||
async function guardViewSchema(
|
||||
tableId: string,
|
||||
schema?: Record<string, ViewUIFieldMetadata>
|
||||
viewSchema?: Record<string, ViewUIFieldMetadata>
|
||||
) {
|
||||
if (!schema || !Object.keys(schema).length) {
|
||||
if (!viewSchema || !Object.keys(viewSchema).length) {
|
||||
return
|
||||
}
|
||||
const table = await sdk.tables.getTable(tableId)
|
||||
if (schema) {
|
||||
for (const field of Object.keys(schema)) {
|
||||
if (!table.schema[field]) {
|
||||
if (viewSchema) {
|
||||
for (const field of Object.keys(viewSchema)) {
|
||||
const tableSchemaField = table.schema[field]
|
||||
if (!tableSchemaField) {
|
||||
throw new HTTPError(
|
||||
`Field "${field}" is not valid for the requested table`,
|
||||
400
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
viewSchema[field].readonly &&
|
||||
isRequired(tableSchemaField.constraints)
|
||||
) {
|
||||
throw new HTTPError(
|
||||
`Field "${field}" cannot be readonly as it is a required field`,
|
||||
400
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
TableSchema,
|
||||
FieldSchema,
|
||||
Row,
|
||||
FieldConstraints,
|
||||
} from "@budibase/types"
|
||||
import { ValidColumnNameRegex, utils } from "@budibase/shared-core"
|
||||
import { db } from "@budibase/backend-core"
|
||||
|
@ -40,6 +41,15 @@ export function isRows(rows: any): rows is Rows {
|
|||
return Array.isArray(rows) && rows.every(row => typeof row === "object")
|
||||
}
|
||||
|
||||
export function isRequired(constraints: FieldConstraints | undefined) {
|
||||
const isRequired =
|
||||
!!constraints &&
|
||||
((typeof constraints.presence !== "boolean" &&
|
||||
!constraints.presence?.allowEmpty) ||
|
||||
constraints.presence === true)
|
||||
return isRequired
|
||||
}
|
||||
|
||||
export function validate(rows: Rows, schema: TableSchema): ValidationResults {
|
||||
const results: ValidationResults = {
|
||||
schemaValidation: {},
|
||||
|
@ -62,12 +72,6 @@ export function validate(rows: Rows, schema: TableSchema): ValidationResults {
|
|||
return
|
||||
}
|
||||
|
||||
const isRequired =
|
||||
!!constraints &&
|
||||
((typeof constraints.presence !== "boolean" &&
|
||||
!constraints.presence?.allowEmpty) ||
|
||||
constraints.presence === true)
|
||||
|
||||
// If the columnType is not a string, then it's not present in the schema, and should be added to the invalid columns array
|
||||
if (typeof columnType !== "string") {
|
||||
results.invalidColumns.push(columnName)
|
||||
|
@ -101,7 +105,12 @@ export function validate(rows: Rows, schema: TableSchema): ValidationResults {
|
|||
} else if (
|
||||
(columnType === FieldType.BB_REFERENCE ||
|
||||
columnType === FieldType.BB_REFERENCE_SINGLE) &&
|
||||
!isValidBBReference(columnData, columnType, columnSubtype, isRequired)
|
||||
!isValidBBReference(
|
||||
columnData,
|
||||
columnType,
|
||||
columnSubtype,
|
||||
isRequired(constraints)
|
||||
)
|
||||
) {
|
||||
results.schemaValidation[columnName] = false
|
||||
} else {
|
||||
|
|
Loading…
Reference in New Issue