Skip to content

Commit 13da1e4

Browse files
authored
Merge pull request #16782 from Budibase/fix/role-publish
Fix role publishing
2 parents 40fef1c + b304384 commit 13da1e4

File tree

4 files changed

+72
-14
lines changed

4 files changed

+72
-14
lines changed

packages/backend-core/src/constants/db.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { prefixed, DocumentType } from "@budibase/types"
1+
import {
2+
prefixed,
3+
DocumentType,
4+
SEPARATOR,
5+
InternalTable,
6+
} from "@budibase/types"
27

38
export {
49
SEPARATOR,
@@ -78,6 +83,7 @@ export const DEFAULT_INVENTORY_TABLE_ID = "ta_bb_inventory"
7883
export const DEFAULT_EXPENSES_TABLE_ID = "ta_bb_expenses"
7984
export const DEFAULT_EMPLOYEE_TABLE_ID = "ta_bb_employee"
8085
export { DEFAULT_BB_DATASOURCE_ID } from "@budibase/shared-core"
86+
export const USER_METADATA_PREFIX = `${DocumentType.ROW}${SEPARATOR}${InternalTable.USER_METADATA}${SEPARATOR}`
8187

8288
export const enum DesignDocuments {
8389
SQLITE = SQLITE_DESIGN_DOC_ID,

packages/backend-core/src/db/Replication.ts

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import PouchDB from "pouchdb"
2-
import { getPouchDB, closePouchDB } from "./couch"
3-
import { DocumentType, Document } from "@budibase/types"
4-
import { DesignDocuments } from "../constants"
2+
import { closePouchDB, getPouchDB } from "./couch"
3+
import { Document, DocumentType } from "@budibase/types"
4+
import { DesignDocuments, SEPARATOR, USER_METADATA_PREFIX } from "../constants"
55

66
enum ReplicationDirection {
77
TO_PRODUCTION = "toProduction",
88
TO_DEV = "toDev",
99
}
1010

11+
type DocumentWithID = Omit<Document, "_id"> & { _id: string }
12+
1113
class Replication {
1214
source: PouchDB.Database
1315
target: PouchDB.Database
@@ -78,33 +80,38 @@ class Replication {
7880
tableSyncList = tablesToSync
7981
}
8082

81-
const isData = (_id?: string) =>
82-
_id?.startsWith(DocumentType.ROW) || _id?.startsWith(DocumentType.LINK)
83+
const startsWithID = (_id: string, documentType: string) => {
84+
return _id?.startsWith(documentType + SEPARATOR)
85+
}
86+
87+
const isData = (_id: string) =>
88+
startsWithID(_id, DocumentType.ROW) ||
89+
startsWithID(_id, DocumentType.LINK)
8390

8491
return {
8592
...opts,
86-
filter: (doc: Document, params: any) => {
93+
filter: (doc: DocumentWithID, params: any) => {
8794
if (!isCreation && doc._id === DesignDocuments.MIGRATIONS) {
8895
return false
8996
}
9097
// don't sync design documents
91-
if (toDev && doc._id?.startsWith("_design")) {
98+
if (toDev && doc._id.startsWith("_design")) {
9299
return false
93100
}
94101
// always replicate deleted documents
95102
if (doc._deleted) {
96103
return true
97104
}
98-
if (
99-
isData(doc._id) &&
100-
(tableSyncList?.find(id => doc._id?.includes(id)) || syncAllTables)
101-
) {
105+
// always sync users from dev
106+
if (startsWithID(doc._id, USER_METADATA_PREFIX)) {
102107
return true
103108
}
104109
if (isData(doc._id)) {
105-
return false
110+
return (
111+
!!tableSyncList?.find(id => doc._id.includes(id)) || syncAllTables
112+
)
106113
}
107-
if (doc._id?.startsWith(DocumentType.AUTOMATION_LOG)) {
114+
if (startsWithID(doc._id, DocumentType.AUTOMATION_LOG)) {
108115
return false
109116
}
110117
if (doc._id === DocumentType.APP_METADATA) {

packages/server/src/api/controllers/deploy/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,9 @@ export const publishApp = async function (
251251
replication.appReplicateOpts({
252252
isCreation: !isPublished,
253253
tablesToSync,
254+
// don't use checkpoints, this can stop data that was previous ignored
255+
// getting written - if not seeding tables we don't need to worry about it
256+
checkpoint: !seedProductionTables,
254257
})
255258
)
256259
// app metadata is excluded as it is likely to be in conflict

packages/server/src/api/routes/tests/application.spec.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import env from "../../../environment"
1717
import {
1818
type App,
1919
BuiltinPermissionID,
20+
PermissionLevel,
2021
Screen,
2122
WorkspaceApp,
2223
} from "@budibase/types"
@@ -1101,6 +1102,47 @@ describe.each([false, true])(
11011102
).toBeUndefined()
11021103
})
11031104
})
1105+
1106+
it("should publish table permissions for custom roles correctly", async () => {
1107+
// Create a table for testing permissions
1108+
const table = await config.api.table.save(basicTable())
1109+
expect(table._id).toBeDefined()
1110+
1111+
// Create a custom role
1112+
const customRole = await config.api.roles.save({
1113+
name: "TestRole",
1114+
inherits: "PUBLIC",
1115+
permissionId: BuiltinPermissionID.READ_ONLY,
1116+
version: "name",
1117+
})
1118+
expect(customRole._id).toBeDefined()
1119+
1120+
// Add READ permission for the custom role on the table
1121+
await config.api.permission.add({
1122+
roleId: customRole._id!,
1123+
resourceId: table._id!,
1124+
level: PermissionLevel.READ,
1125+
})
1126+
1127+
// Verify permissions exist in development
1128+
const devPermissions = await config.api.permission.get(table._id!)
1129+
expect(devPermissions.permissions.read.role).toBe(customRole.name)
1130+
1131+
// Publish the application
1132+
await config.publish()
1133+
1134+
// Verify permissions are correctly published to production
1135+
await config.withProdApp(async () => {
1136+
const prodPermissions = await config.api.permission.get(table._id!)
1137+
expect(prodPermissions.permissions.read.role).toBe(customRole.name)
1138+
1139+
// Also verify the role itself exists in production
1140+
const roles = await config.api.roles.fetch()
1141+
const prodRole = roles.find(r => r.name === customRole.name)
1142+
expect(prodRole).toBeDefined()
1143+
expect(prodRole!.name).toBe("TestRole")
1144+
})
1145+
})
11041146
})
11051147

11061148
describe("manage client library version", () => {

0 commit comments

Comments
 (0)