Merge branch 'master' into view-calculation-no-deletes
This commit is contained in:
commit
cc07b1407b
|
@ -65,7 +65,13 @@ export enum BuiltinPermissionID {
|
||||||
POWER = "power",
|
POWER = "power",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BUILTIN_PERMISSIONS = {
|
export const BUILTIN_PERMISSIONS: {
|
||||||
|
[key in keyof typeof BuiltinPermissionID]: {
|
||||||
|
_id: (typeof BuiltinPermissionID)[key]
|
||||||
|
name: string
|
||||||
|
permissions: Permission[]
|
||||||
|
}
|
||||||
|
} = {
|
||||||
PUBLIC: {
|
PUBLIC: {
|
||||||
_id: BuiltinPermissionID.PUBLIC,
|
_id: BuiltinPermissionID.PUBLIC,
|
||||||
name: "Public",
|
name: "Public",
|
||||||
|
|
|
@ -94,7 +94,7 @@
|
||||||
loadDependantInfo()
|
loadDependantInfo()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ModalContent showCancelButton={false} confirmText="Done">
|
<ModalContent showCancelButton={false} showConfirmButton={false}>
|
||||||
<span slot="header">
|
<span slot="header">
|
||||||
Manage Access
|
Manage Access
|
||||||
{#if requiresPlanToModify}
|
{#if requiresPlanToModify}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { permissions, roles, context } from "@budibase/backend-core"
|
import { permissions, roles, context } from "@budibase/backend-core"
|
||||||
import {
|
import {
|
||||||
UserCtx,
|
UserCtx,
|
||||||
Role,
|
|
||||||
GetResourcePermsResponse,
|
GetResourcePermsResponse,
|
||||||
ResourcePermissionInfo,
|
ResourcePermissionInfo,
|
||||||
GetDependantResourcesResponse,
|
GetDependantResourcesResponse,
|
||||||
|
@ -9,6 +8,7 @@ import {
|
||||||
AddPermissionRequest,
|
AddPermissionRequest,
|
||||||
RemovePermissionRequest,
|
RemovePermissionRequest,
|
||||||
RemovePermissionResponse,
|
RemovePermissionResponse,
|
||||||
|
FetchResourcePermissionInfoResponse,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import {
|
import {
|
||||||
CURRENTLY_SUPPORTED_LEVELS,
|
CURRENTLY_SUPPORTED_LEVELS,
|
||||||
|
@ -28,10 +28,12 @@ export function fetchLevels(ctx: UserCtx) {
|
||||||
ctx.body = SUPPORTED_LEVELS
|
ctx.body = SUPPORTED_LEVELS
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetch(ctx: UserCtx) {
|
export async function fetch(
|
||||||
|
ctx: UserCtx<void, FetchResourcePermissionInfoResponse>
|
||||||
|
) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const dbRoles: Role[] = await sdk.permissions.getAllDBRoles(db)
|
const dbRoles = await sdk.permissions.getAllDBRoles(db)
|
||||||
let permissions: any = {}
|
let permissions: Record<string, Record<string, string>> = {}
|
||||||
// create an object with structure role ID -> resource ID -> level
|
// create an object with structure role ID -> resource ID -> level
|
||||||
for (let role of dbRoles) {
|
for (let role of dbRoles) {
|
||||||
if (!role.permissions) {
|
if (!role.permissions) {
|
||||||
|
@ -43,13 +45,13 @@ export async function fetch(ctx: UserCtx) {
|
||||||
}
|
}
|
||||||
for (let [resource, levelArr] of Object.entries(role.permissions)) {
|
for (let [resource, levelArr] of Object.entries(role.permissions)) {
|
||||||
const levels: string[] = Array.isArray(levelArr) ? levelArr : [levelArr]
|
const levels: string[] = Array.isArray(levelArr) ? levelArr : [levelArr]
|
||||||
const perms: Record<string, string> = {}
|
const perms: Record<string, string> = permissions[resource] || {}
|
||||||
levels.forEach(level => (perms[level] = roleId!))
|
levels.forEach(level => (perms[level] = roleId!))
|
||||||
permissions[resource] = perms
|
permissions[resource] = perms
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// apply the base permissions
|
// apply the base permissions
|
||||||
const finalPermissions: Record<string, Record<string, string>> = {}
|
const finalPermissions: FetchResourcePermissionInfoResponse = {}
|
||||||
for (let [resource, permission] of Object.entries(permissions)) {
|
for (let [resource, permission] of Object.entries(permissions)) {
|
||||||
const basePerms = getBasePermissions(resource)
|
const basePerms = getBasePermissions(resource)
|
||||||
finalPermissions[resource] = Object.assign(basePerms, permission)
|
finalPermissions[resource] = Object.assign(basePerms, permission)
|
||||||
|
@ -92,18 +94,17 @@ export async function getDependantResources(
|
||||||
|
|
||||||
export async function addPermission(ctx: UserCtx<void, AddPermissionResponse>) {
|
export async function addPermission(ctx: UserCtx<void, AddPermissionResponse>) {
|
||||||
const params: AddPermissionRequest = ctx.params
|
const params: AddPermissionRequest = ctx.params
|
||||||
ctx.body = await sdk.permissions.updatePermissionOnRole(
|
await sdk.permissions.updatePermissionOnRole(params, PermissionUpdateType.ADD)
|
||||||
params,
|
ctx.status = 200
|
||||||
PermissionUpdateType.ADD
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removePermission(
|
export async function removePermission(
|
||||||
ctx: UserCtx<void, RemovePermissionResponse>
|
ctx: UserCtx<void, RemovePermissionResponse>
|
||||||
) {
|
) {
|
||||||
const params: RemovePermissionRequest = ctx.params
|
const params: RemovePermissionRequest = ctx.params
|
||||||
ctx.body = await sdk.permissions.updatePermissionOnRole(
|
await sdk.permissions.updatePermissionOnRole(
|
||||||
params,
|
params,
|
||||||
PermissionUpdateType.REMOVE
|
PermissionUpdateType.REMOVE
|
||||||
)
|
)
|
||||||
|
ctx.status = 200
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { roles } from "@budibase/backend-core"
|
import { roles } from "@budibase/backend-core"
|
||||||
import { Document, PermissionLevel, Row, Table, ViewV2 } from "@budibase/types"
|
import { Document, PermissionLevel, Row } from "@budibase/types"
|
||||||
import * as setup from "./utilities"
|
import * as setup from "./utilities"
|
||||||
import { generator, mocks } from "@budibase/backend-core/tests"
|
import { generator, mocks } from "@budibase/backend-core/tests"
|
||||||
|
|
||||||
|
@ -9,13 +9,11 @@ const { BUILTIN_ROLE_IDS } = roles
|
||||||
const HIGHER_ROLE_ID = BUILTIN_ROLE_IDS.BASIC
|
const HIGHER_ROLE_ID = BUILTIN_ROLE_IDS.BASIC
|
||||||
const STD_ROLE_ID = BUILTIN_ROLE_IDS.PUBLIC
|
const STD_ROLE_ID = BUILTIN_ROLE_IDS.PUBLIC
|
||||||
|
|
||||||
|
const DEFAULT_TABLE_ROLE_ID = BUILTIN_ROLE_IDS.ADMIN
|
||||||
|
|
||||||
describe("/permission", () => {
|
describe("/permission", () => {
|
||||||
let request = setup.getRequest()
|
let request = setup.getRequest()
|
||||||
let config = setup.getConfig()
|
let config = setup.getConfig()
|
||||||
let table: Table & { _id: string }
|
|
||||||
let perms: Document[]
|
|
||||||
let row: Row
|
|
||||||
let view: ViewV2
|
|
||||||
|
|
||||||
afterAll(setup.afterAll)
|
afterAll(setup.afterAll)
|
||||||
|
|
||||||
|
@ -25,18 +23,6 @@ describe("/permission", () => {
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
mocks.licenses.useCloudFree()
|
mocks.licenses.useCloudFree()
|
||||||
|
|
||||||
table = (await config.createTable()) as typeof table
|
|
||||||
row = await config.createRow()
|
|
||||||
view = await config.api.viewV2.create({
|
|
||||||
tableId: table._id!,
|
|
||||||
name: generator.guid(),
|
|
||||||
})
|
|
||||||
perms = await config.api.permission.add({
|
|
||||||
roleId: STD_ROLE_ID,
|
|
||||||
resourceId: table._id,
|
|
||||||
level: PermissionLevel.READ,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("levels", () => {
|
describe("levels", () => {
|
||||||
|
@ -54,137 +40,251 @@ describe("/permission", () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("add", () => {
|
describe("table permissions", () => {
|
||||||
it("should be able to add permission to a role for the table", async () => {
|
let tableId: string
|
||||||
expect(perms.length).toEqual(1)
|
|
||||||
expect(perms[0]._id).toEqual(`${STD_ROLE_ID}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should get the resource permissions", async () => {
|
beforeEach(async () => {
|
||||||
const res = await request
|
const table = await config.createTable()
|
||||||
.get(`/api/permission/${table._id}`)
|
tableId = table._id!
|
||||||
.set(config.defaultHeaders())
|
|
||||||
.expect("Content-Type", /json/)
|
|
||||||
.expect(200)
|
|
||||||
expect(res.body).toEqual({
|
|
||||||
permissions: {
|
|
||||||
read: { permissionType: "EXPLICIT", role: STD_ROLE_ID },
|
|
||||||
write: { permissionType: "BASE", role: HIGHER_ROLE_ID },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should get resource permissions with multiple roles", async () => {
|
|
||||||
perms = await config.api.permission.add({
|
|
||||||
roleId: HIGHER_ROLE_ID,
|
|
||||||
resourceId: table._id,
|
|
||||||
level: PermissionLevel.WRITE,
|
|
||||||
})
|
|
||||||
const res = await config.api.permission.get(table._id)
|
|
||||||
expect(res).toEqual({
|
|
||||||
permissions: {
|
|
||||||
read: { permissionType: "EXPLICIT", role: STD_ROLE_ID },
|
|
||||||
write: { permissionType: "EXPLICIT", role: HIGHER_ROLE_ID },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const allRes = await request
|
|
||||||
.get(`/api/permission`)
|
|
||||||
.set(config.defaultHeaders())
|
|
||||||
.expect("Content-Type", /json/)
|
|
||||||
.expect(200)
|
|
||||||
expect(allRes.body[table._id]["read"]).toEqual(STD_ROLE_ID)
|
|
||||||
expect(allRes.body[table._id]["write"]).toEqual(HIGHER_ROLE_ID)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("remove", () => {
|
|
||||||
it("should be able to remove the permission", async () => {
|
|
||||||
const res = await config.api.permission.revoke({
|
|
||||||
roleId: STD_ROLE_ID,
|
|
||||||
resourceId: table._id,
|
|
||||||
level: PermissionLevel.READ,
|
|
||||||
})
|
|
||||||
expect(res[0]._id).toEqual(STD_ROLE_ID)
|
|
||||||
const permsRes = await config.api.permission.get(table._id)
|
|
||||||
expect(permsRes.permissions[STD_ROLE_ID]).toBeUndefined()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("check public user allowed", () => {
|
|
||||||
it("should be able to read the row", async () => {
|
|
||||||
// replicate changes before checking permissions
|
|
||||||
await config.publish()
|
|
||||||
|
|
||||||
const res = await request
|
|
||||||
.get(`/api/${table._id}/rows`)
|
|
||||||
.set(config.publicHeaders())
|
|
||||||
.expect("Content-Type", /json/)
|
|
||||||
.expect(200)
|
|
||||||
expect(res.body[0]._id).toEqual(row._id)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should be able to access the view data when the table is set to public and with no view permissions overrides", async () => {
|
|
||||||
// Make view inherit table permissions. Needed for backwards compatibility with existing views.
|
|
||||||
await config.api.permission.revoke({
|
|
||||||
roleId: STD_ROLE_ID,
|
|
||||||
resourceId: view.id,
|
|
||||||
level: PermissionLevel.READ,
|
|
||||||
})
|
|
||||||
|
|
||||||
// replicate changes before checking permissions
|
|
||||||
await config.publish()
|
|
||||||
|
|
||||||
const res = await config.api.viewV2.publicSearch(view.id)
|
|
||||||
expect(res.rows[0]._id).toEqual(row._id)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should not be able to access the view data when the table is not public and there are no view permissions overrides", async () => {
|
|
||||||
await config.api.permission.revoke({
|
|
||||||
roleId: STD_ROLE_ID,
|
|
||||||
resourceId: table._id,
|
|
||||||
level: PermissionLevel.READ,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Make view inherit table permissions. Needed for backwards compatibility with existing views.
|
|
||||||
await config.api.permission.revoke({
|
|
||||||
roleId: STD_ROLE_ID,
|
|
||||||
resourceId: view.id,
|
|
||||||
level: PermissionLevel.READ,
|
|
||||||
})
|
|
||||||
|
|
||||||
// replicate changes before checking permissions
|
|
||||||
await config.publish()
|
|
||||||
|
|
||||||
await config.api.viewV2.publicSearch(view.id, undefined, { status: 401 })
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should use the view permissions", async () => {
|
|
||||||
await config.api.permission.add({
|
await config.api.permission.add({
|
||||||
roleId: STD_ROLE_ID,
|
roleId: STD_ROLE_ID,
|
||||||
resourceId: view.id,
|
resourceId: tableId,
|
||||||
level: PermissionLevel.READ,
|
level: PermissionLevel.READ,
|
||||||
})
|
})
|
||||||
await config.api.permission.revoke({
|
|
||||||
roleId: STD_ROLE_ID,
|
|
||||||
resourceId: table._id,
|
|
||||||
level: PermissionLevel.READ,
|
|
||||||
})
|
|
||||||
// replicate changes before checking permissions
|
|
||||||
await config.publish()
|
|
||||||
|
|
||||||
const res = await config.api.viewV2.publicSearch(view.id)
|
|
||||||
expect(res.rows[0]._id).toEqual(row._id)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("shouldn't allow writing from a public user", async () => {
|
it("tables should be defaulted to admin", async () => {
|
||||||
const res = await request
|
const table = await config.createTable()
|
||||||
.post(`/api/${table._id}/rows`)
|
const { permissions } = await config.api.permission.get(table._id!)
|
||||||
.send(basicRow(table._id))
|
expect(permissions).toEqual({
|
||||||
.set(config.publicHeaders())
|
read: {
|
||||||
.expect("Content-Type", /json/)
|
permissionType: "EXPLICIT",
|
||||||
.expect(401)
|
role: DEFAULT_TABLE_ROLE_ID,
|
||||||
expect(res.status).toEqual(401)
|
},
|
||||||
|
write: {
|
||||||
|
permissionType: "EXPLICIT",
|
||||||
|
role: DEFAULT_TABLE_ROLE_ID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("add", () => {
|
||||||
|
it("should be able to add permission to a role for the table", async () => {
|
||||||
|
const res = await request
|
||||||
|
.get(`/api/permission/${tableId}`)
|
||||||
|
.set(config.defaultHeaders())
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
.expect(200)
|
||||||
|
expect(res.body).toEqual({
|
||||||
|
permissions: {
|
||||||
|
read: { permissionType: "EXPLICIT", role: STD_ROLE_ID },
|
||||||
|
write: { permissionType: "EXPLICIT", role: DEFAULT_TABLE_ROLE_ID },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should get resource permissions with multiple roles", async () => {
|
||||||
|
await config.api.permission.add({
|
||||||
|
roleId: HIGHER_ROLE_ID,
|
||||||
|
resourceId: tableId,
|
||||||
|
level: PermissionLevel.WRITE,
|
||||||
|
})
|
||||||
|
const res = await config.api.permission.get(tableId)
|
||||||
|
expect(res).toEqual({
|
||||||
|
permissions: {
|
||||||
|
read: { permissionType: "EXPLICIT", role: STD_ROLE_ID },
|
||||||
|
write: { permissionType: "EXPLICIT", role: HIGHER_ROLE_ID },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const allRes = await request
|
||||||
|
.get(`/api/permission`)
|
||||||
|
.set(config.defaultHeaders())
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
.expect(200)
|
||||||
|
expect(allRes.body[tableId]["read"]).toEqual(STD_ROLE_ID)
|
||||||
|
expect(allRes.body[tableId]["write"]).toEqual(HIGHER_ROLE_ID)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("remove", () => {
|
||||||
|
it("should be able to remove the permission", async () => {
|
||||||
|
await config.api.permission.revoke({
|
||||||
|
roleId: STD_ROLE_ID,
|
||||||
|
resourceId: tableId,
|
||||||
|
level: PermissionLevel.READ,
|
||||||
|
})
|
||||||
|
|
||||||
|
const permsRes = await config.api.permission.get(tableId)
|
||||||
|
expect(permsRes.permissions[STD_ROLE_ID]).toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("check public user allowed", () => {
|
||||||
|
let viewId: string
|
||||||
|
let row: Row
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const view = await config.api.viewV2.create({
|
||||||
|
tableId,
|
||||||
|
name: generator.guid(),
|
||||||
|
})
|
||||||
|
viewId = view.id
|
||||||
|
row = await config.createRow()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should be able to read the row", async () => {
|
||||||
|
// replicate changes before checking permissions
|
||||||
|
await config.publish()
|
||||||
|
|
||||||
|
const res = await request
|
||||||
|
.get(`/api/${tableId}/rows`)
|
||||||
|
.set(config.publicHeaders())
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
.expect(200)
|
||||||
|
expect(res.body[0]._id).toEqual(row._id)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should be able to access the view data when the table is set to public and with no view permissions overrides", async () => {
|
||||||
|
// Make view inherit table permissions. Needed for backwards compatibility with existing views.
|
||||||
|
await config.api.permission.revoke({
|
||||||
|
roleId: STD_ROLE_ID,
|
||||||
|
resourceId: viewId,
|
||||||
|
level: PermissionLevel.READ,
|
||||||
|
})
|
||||||
|
|
||||||
|
// replicate changes before checking permissions
|
||||||
|
await config.publish()
|
||||||
|
|
||||||
|
const res = await config.api.viewV2.publicSearch(viewId)
|
||||||
|
expect(res.rows[0]._id).toEqual(row._id)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should not be able to access the view data when the table is not public and there are no view permissions overrides", async () => {
|
||||||
|
await config.api.permission.revoke({
|
||||||
|
roleId: STD_ROLE_ID,
|
||||||
|
resourceId: tableId,
|
||||||
|
level: PermissionLevel.READ,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Make view inherit table permissions. Needed for backwards compatibility with existing views.
|
||||||
|
await config.api.permission.revoke({
|
||||||
|
roleId: STD_ROLE_ID,
|
||||||
|
resourceId: viewId,
|
||||||
|
level: PermissionLevel.READ,
|
||||||
|
})
|
||||||
|
|
||||||
|
// replicate changes before checking permissions
|
||||||
|
await config.publish()
|
||||||
|
|
||||||
|
await config.api.viewV2.publicSearch(viewId, undefined, {
|
||||||
|
status: 401,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should use the view permissions", async () => {
|
||||||
|
await config.api.permission.add({
|
||||||
|
roleId: STD_ROLE_ID,
|
||||||
|
resourceId: viewId,
|
||||||
|
level: PermissionLevel.READ,
|
||||||
|
})
|
||||||
|
await config.api.permission.revoke({
|
||||||
|
roleId: STD_ROLE_ID,
|
||||||
|
resourceId: tableId,
|
||||||
|
level: PermissionLevel.READ,
|
||||||
|
})
|
||||||
|
// replicate changes before checking permissions
|
||||||
|
await config.publish()
|
||||||
|
|
||||||
|
const res = await config.api.viewV2.publicSearch(viewId)
|
||||||
|
expect(res.rows[0]._id).toEqual(row._id)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("shouldn't allow writing from a public user", async () => {
|
||||||
|
const res = await request
|
||||||
|
.post(`/api/${tableId}/rows`)
|
||||||
|
.send(basicRow(tableId))
|
||||||
|
.set(config.publicHeaders())
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
.expect(401)
|
||||||
|
expect(res.status).toEqual(401)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("view permissions", () => {
|
||||||
|
let tableId: string
|
||||||
|
let viewId: string
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const table = await config.createTable()
|
||||||
|
tableId = table._id!
|
||||||
|
|
||||||
|
const view = await config.api.viewV2.create({
|
||||||
|
tableId,
|
||||||
|
name: generator.guid(),
|
||||||
|
})
|
||||||
|
viewId = view.id
|
||||||
|
})
|
||||||
|
|
||||||
|
it("default permissions inherits and persists the table default value", async () => {
|
||||||
|
const { permissions } = await config.api.permission.get(viewId)
|
||||||
|
expect(permissions).toEqual({
|
||||||
|
read: {
|
||||||
|
permissionType: "EXPLICIT",
|
||||||
|
role: DEFAULT_TABLE_ROLE_ID,
|
||||||
|
inheritablePermission: DEFAULT_TABLE_ROLE_ID,
|
||||||
|
},
|
||||||
|
write: {
|
||||||
|
permissionType: "EXPLICIT",
|
||||||
|
role: DEFAULT_TABLE_ROLE_ID,
|
||||||
|
inheritablePermission: DEFAULT_TABLE_ROLE_ID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("does not update view permissions once persisted, even if table permissions change", async () => {
|
||||||
|
await config.api.permission.add({
|
||||||
|
roleId: STD_ROLE_ID,
|
||||||
|
resourceId: tableId,
|
||||||
|
level: PermissionLevel.READ,
|
||||||
|
})
|
||||||
|
|
||||||
|
const { permissions } = await config.api.permission.get(viewId)
|
||||||
|
expect(permissions).toEqual({
|
||||||
|
read: {
|
||||||
|
permissionType: "EXPLICIT",
|
||||||
|
role: DEFAULT_TABLE_ROLE_ID,
|
||||||
|
inheritablePermission: STD_ROLE_ID,
|
||||||
|
},
|
||||||
|
write: {
|
||||||
|
permissionType: "EXPLICIT",
|
||||||
|
role: DEFAULT_TABLE_ROLE_ID,
|
||||||
|
inheritablePermission: DEFAULT_TABLE_ROLE_ID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("can sets permissions inherits explicit view permissions", async () => {
|
||||||
|
await config.api.permission.add({
|
||||||
|
roleId: HIGHER_ROLE_ID,
|
||||||
|
resourceId: viewId,
|
||||||
|
level: PermissionLevel.WRITE,
|
||||||
|
})
|
||||||
|
|
||||||
|
const { permissions } = await config.api.permission.get(viewId)
|
||||||
|
expect(permissions).toEqual({
|
||||||
|
read: {
|
||||||
|
permissionType: "EXPLICIT",
|
||||||
|
role: DEFAULT_TABLE_ROLE_ID,
|
||||||
|
inheritablePermission: DEFAULT_TABLE_ROLE_ID,
|
||||||
|
},
|
||||||
|
write: {
|
||||||
|
permissionType: "EXPLICIT",
|
||||||
|
role: HIGHER_ROLE_ID,
|
||||||
|
inheritablePermission: DEFAULT_TABLE_ROLE_ID,
|
||||||
|
},
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -71,7 +71,7 @@ describe("migrations", () => {
|
||||||
expect(events.datasource.created).toHaveBeenCalledTimes(2)
|
expect(events.datasource.created).toHaveBeenCalledTimes(2)
|
||||||
expect(events.layout.created).toHaveBeenCalledTimes(1)
|
expect(events.layout.created).toHaveBeenCalledTimes(1)
|
||||||
expect(events.query.created).toHaveBeenCalledTimes(2)
|
expect(events.query.created).toHaveBeenCalledTimes(2)
|
||||||
expect(events.role.created).toHaveBeenCalledTimes(2)
|
expect(events.role.created).toHaveBeenCalledTimes(3) // created roles + admin (created on table creation)
|
||||||
expect(events.table.created).toHaveBeenCalledTimes(3)
|
expect(events.table.created).toHaveBeenCalledTimes(3)
|
||||||
expect(events.view.created).toHaveBeenCalledTimes(2)
|
expect(events.view.created).toHaveBeenCalledTimes(2)
|
||||||
expect(events.view.calculationCreated).toHaveBeenCalledTimes(1)
|
expect(events.view.calculationCreated).toHaveBeenCalledTimes(1)
|
||||||
|
@ -82,7 +82,7 @@ describe("migrations", () => {
|
||||||
// to make sure caching is working as expected
|
// to make sure caching is working as expected
|
||||||
expect(
|
expect(
|
||||||
events.processors.analyticsProcessor.processEvent
|
events.processors.analyticsProcessor.processEvent
|
||||||
).toHaveBeenCalledTimes(23)
|
).toHaveBeenCalledTimes(24) // Addtion of of the events above
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -185,6 +185,26 @@ export async function updatePermissionOnRole(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function setPermissions(
|
||||||
|
resourceId: string,
|
||||||
|
{
|
||||||
|
writeRole,
|
||||||
|
readRole,
|
||||||
|
}: {
|
||||||
|
writeRole: string
|
||||||
|
readRole: string
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
await updatePermissionOnRole(
|
||||||
|
{ roleId: writeRole, resourceId, level: PermissionLevel.WRITE },
|
||||||
|
PermissionUpdateType.ADD
|
||||||
|
)
|
||||||
|
await updatePermissionOnRole(
|
||||||
|
{ roleId: readRole, resourceId, level: PermissionLevel.READ },
|
||||||
|
PermissionUpdateType.ADD
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// utility function to stop this repetition - permissions always stored under roles
|
// utility function to stop this repetition - permissions always stored under roles
|
||||||
export async function getAllDBRoles(db: Database) {
|
export async function getAllDBRoles(db: Database) {
|
||||||
const body = await db.allDocs<Role>(
|
const body = await db.allDocs<Role>(
|
||||||
|
|
|
@ -3,6 +3,8 @@ import { Row, Table } from "@budibase/types"
|
||||||
import * as external from "./external"
|
import * as external from "./external"
|
||||||
import * as internal from "./internal"
|
import * as internal from "./internal"
|
||||||
import { isExternal } from "./utils"
|
import { isExternal } from "./utils"
|
||||||
|
import { setPermissions } from "../permissions"
|
||||||
|
import { roles } from "@budibase/backend-core"
|
||||||
|
|
||||||
export async function create(
|
export async function create(
|
||||||
table: Omit<Table, "_id" | "_rev">,
|
table: Omit<Table, "_id" | "_rev">,
|
||||||
|
@ -15,5 +17,11 @@ export async function create(
|
||||||
} else {
|
} else {
|
||||||
createdTable = await internal.create(table, rows, userId)
|
createdTable = await internal.create(table, rows, userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await setPermissions(createdTable._id!, {
|
||||||
|
writeRole: roles.BUILTIN_ROLE_IDS.ADMIN,
|
||||||
|
readRole: roles.BUILTIN_ROLE_IDS.ADMIN,
|
||||||
|
})
|
||||||
|
|
||||||
return createdTable
|
return createdTable
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import {
|
||||||
ViewV2ColumnEnriched,
|
ViewV2ColumnEnriched,
|
||||||
ViewV2Enriched,
|
ViewV2Enriched,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { context, docIds, HTTPError, roles } from "@budibase/backend-core"
|
import { context, docIds, HTTPError } from "@budibase/backend-core"
|
||||||
import {
|
import {
|
||||||
helpers,
|
helpers,
|
||||||
PROTECTED_EXTERNAL_COLUMNS,
|
PROTECTED_EXTERNAL_COLUMNS,
|
||||||
|
@ -26,7 +26,6 @@ import { isExternalTableID } from "../../../integrations/utils"
|
||||||
import * as internal from "./internal"
|
import * as internal from "./internal"
|
||||||
import * as external from "./external"
|
import * as external from "./external"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
import { PermissionUpdateType, updatePermissionOnRole } from "../permissions"
|
|
||||||
|
|
||||||
function pickApi(tableId: any) {
|
function pickApi(tableId: any) {
|
||||||
if (isExternalTableID(tableId)) {
|
if (isExternalTableID(tableId)) {
|
||||||
|
@ -247,24 +246,10 @@ export async function create(
|
||||||
|
|
||||||
// Set permissions to be the same as the table
|
// Set permissions to be the same as the table
|
||||||
const tablePerms = await sdk.permissions.getResourcePerms(tableId)
|
const tablePerms = await sdk.permissions.getResourcePerms(tableId)
|
||||||
const readRole = tablePerms[PermissionLevel.READ]?.role
|
await sdk.permissions.setPermissions(view.id, {
|
||||||
const writeRole = tablePerms[PermissionLevel.WRITE]?.role
|
writeRole: tablePerms[PermissionLevel.WRITE].role,
|
||||||
await updatePermissionOnRole(
|
readRole: tablePerms[PermissionLevel.READ].role,
|
||||||
{
|
})
|
||||||
roleId: readRole || roles.BUILTIN_ROLE_IDS.BASIC,
|
|
||||||
resourceId: view.id,
|
|
||||||
level: PermissionLevel.READ,
|
|
||||||
},
|
|
||||||
PermissionUpdateType.ADD
|
|
||||||
)
|
|
||||||
await updatePermissionOnRole(
|
|
||||||
{
|
|
||||||
roleId: writeRole || roles.BUILTIN_ROLE_IDS.BASIC,
|
|
||||||
resourceId: view.id,
|
|
||||||
level: PermissionLevel.WRITE,
|
|
||||||
},
|
|
||||||
PermissionUpdateType.ADD
|
|
||||||
)
|
|
||||||
|
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {
|
import {
|
||||||
AddPermissionRequest,
|
AddPermissionRequest,
|
||||||
AddPermissionResponse,
|
AddPermissionResponse,
|
||||||
|
FetchResourcePermissionInfoResponse,
|
||||||
GetResourcePermsResponse,
|
GetResourcePermsResponse,
|
||||||
RemovePermissionRequest,
|
RemovePermissionRequest,
|
||||||
RemovePermissionResponse,
|
RemovePermissionResponse,
|
||||||
|
@ -26,6 +27,15 @@ export class PermissionAPI extends TestAPI {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fetch = async (
|
||||||
|
expectations?: Expectations
|
||||||
|
): Promise<FetchResourcePermissionInfoResponse> => {
|
||||||
|
return await this._get<FetchResourcePermissionInfoResponse>(
|
||||||
|
`/api/permission`,
|
||||||
|
{ expectations }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
revoke = async (
|
revoke = async (
|
||||||
request: RemovePermissionRequest,
|
request: RemovePermissionRequest,
|
||||||
expectations?: Expectations
|
expectations?: Expectations
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { permissions, roles } from "@budibase/backend-core"
|
import { permissions, roles } from "@budibase/backend-core"
|
||||||
import { DocumentType, VirtualDocumentType } from "../db/utils"
|
import { DocumentType, VirtualDocumentType } from "../db/utils"
|
||||||
|
import { getDocumentType, getVirtualDocumentType } from "@budibase/types"
|
||||||
|
|
||||||
export const CURRENTLY_SUPPORTED_LEVELS: string[] = [
|
export const CURRENTLY_SUPPORTED_LEVELS: string[] = [
|
||||||
permissions.PermissionLevel.WRITE,
|
permissions.PermissionLevel.WRITE,
|
||||||
|
@ -8,13 +9,16 @@ export const CURRENTLY_SUPPORTED_LEVELS: string[] = [
|
||||||
]
|
]
|
||||||
|
|
||||||
export function getPermissionType(resourceId: string) {
|
export function getPermissionType(resourceId: string) {
|
||||||
const docType = Object.values(DocumentType).filter(docType =>
|
const virtualDocType = getVirtualDocumentType(resourceId)
|
||||||
resourceId.startsWith(docType)
|
switch (virtualDocType) {
|
||||||
)[0]
|
case VirtualDocumentType.VIEW:
|
||||||
switch (docType as DocumentType | VirtualDocumentType) {
|
return permissions.PermissionType.TABLE
|
||||||
|
}
|
||||||
|
|
||||||
|
const docType = getDocumentType(resourceId)
|
||||||
|
switch (docType) {
|
||||||
case DocumentType.TABLE:
|
case DocumentType.TABLE:
|
||||||
case DocumentType.ROW:
|
case DocumentType.ROW:
|
||||||
case VirtualDocumentType.VIEW:
|
|
||||||
return permissions.PermissionType.TABLE
|
return permissions.PermissionType.TABLE
|
||||||
case DocumentType.AUTOMATION:
|
case DocumentType.AUTOMATION:
|
||||||
return permissions.PermissionType.AUTOMATION
|
return permissions.PermissionType.AUTOMATION
|
||||||
|
@ -32,22 +36,25 @@ export function getPermissionType(resourceId: string) {
|
||||||
/**
|
/**
|
||||||
* works out the basic permissions based on builtin roles for a resource, using its ID
|
* works out the basic permissions based on builtin roles for a resource, using its ID
|
||||||
*/
|
*/
|
||||||
export function getBasePermissions(resourceId: string) {
|
export function getBasePermissions(resourceId: string): Record<string, string> {
|
||||||
const type = getPermissionType(resourceId)
|
const type = getPermissionType(resourceId)
|
||||||
const basePermissions: { [key: string]: string } = {}
|
const basePermissions: Record<string, string> = {}
|
||||||
for (let [roleId, role] of Object.entries(roles.getBuiltinRoles())) {
|
for (let [roleId, role] of Object.entries(roles.getBuiltinRoles())) {
|
||||||
if (!role.permissionId) {
|
if (!role.permissionId) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const perms = permissions.getBuiltinPermissionByID(role.permissionId)
|
const perms = permissions.getBuiltinPermissionByID(role.permissionId)
|
||||||
if (!perms) {
|
if (!perms) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const typedPermission = perms.permissions.find(perm => perm.type === type)
|
const typedPermission = perms.permissions.find(perm => perm.type === type)
|
||||||
if (
|
if (!typedPermission) {
|
||||||
typedPermission &&
|
continue
|
||||||
CURRENTLY_SUPPORTED_LEVELS.indexOf(typedPermission.level) !== -1
|
}
|
||||||
) {
|
|
||||||
|
if (CURRENTLY_SUPPORTED_LEVELS.includes(typedPermission.level)) {
|
||||||
const level = typedPermission.level
|
const level = typedPermission.level
|
||||||
basePermissions[level] = roles.lowerBuiltinRoleID(
|
basePermissions[level] = roles.lowerBuiltinRoleID(
|
||||||
basePermissions[level],
|
basePermissions[level],
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
import { PermissionLevel } from "../../../sdk"
|
import { PermissionLevel } from "../../../sdk"
|
||||||
|
|
||||||
|
export interface FetchResourcePermissionInfoResponse {
|
||||||
|
[key: string]: Record<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
export interface ResourcePermissionInfo {
|
export interface ResourcePermissionInfo {
|
||||||
role: string
|
role: string
|
||||||
permissionType: string
|
permissionType: string
|
||||||
|
@ -21,7 +25,7 @@ export interface AddedPermission {
|
||||||
reason?: string
|
reason?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AddPermissionResponse = AddedPermission[]
|
export interface AddPermissionResponse {}
|
||||||
|
|
||||||
export interface AddPermissionRequest {
|
export interface AddPermissionRequest {
|
||||||
roleId: string
|
roleId: string
|
||||||
|
@ -30,4 +34,4 @@ export interface AddPermissionRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RemovePermissionRequest extends AddPermissionRequest {}
|
export interface RemovePermissionRequest extends AddPermissionRequest {}
|
||||||
export interface RemovePermissionResponse extends AddPermissionResponse {}
|
export interface RemovePermissionResponse {}
|
||||||
|
|
|
@ -42,6 +42,17 @@ export enum DocumentType {
|
||||||
ROW_ACTIONS = "ra",
|
ROW_ACTIONS = "ra",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Because DocumentTypes can overlap, we need to make sure that we search
|
||||||
|
// longest first to ensure we get the correct type.
|
||||||
|
const sortedDocumentTypes = Object.values(DocumentType).sort(
|
||||||
|
(a, b) => b.length - a.length // descending
|
||||||
|
)
|
||||||
|
export function getDocumentType(id: string): DocumentType | undefined {
|
||||||
|
return sortedDocumentTypes.find(docType =>
|
||||||
|
id.startsWith(`${docType}${SEPARATOR}`)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// these are the core documents that make up the data, design
|
// these are the core documents that make up the data, design
|
||||||
// and automation sections of an app. This excludes any internal
|
// and automation sections of an app. This excludes any internal
|
||||||
// rows as we shouldn't import data.
|
// rows as we shouldn't import data.
|
||||||
|
@ -72,6 +83,19 @@ export enum VirtualDocumentType {
|
||||||
ROW_ACTION = "row_action",
|
ROW_ACTION = "row_action",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Because VirtualDocumentTypes can overlap, we need to make sure that we search
|
||||||
|
// longest first to ensure we get the correct type.
|
||||||
|
const sortedVirtualDocumentTypes = Object.values(VirtualDocumentType).sort(
|
||||||
|
(a, b) => b.length - a.length // descending
|
||||||
|
)
|
||||||
|
export function getVirtualDocumentType(
|
||||||
|
id: string
|
||||||
|
): VirtualDocumentType | undefined {
|
||||||
|
return sortedVirtualDocumentTypes.find(docType =>
|
||||||
|
id.startsWith(`${docType}${SEPARATOR}`)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export interface Document {
|
export interface Document {
|
||||||
_id?: string
|
_id?: string
|
||||||
_rev?: string
|
_rev?: string
|
||||||
|
|
Loading…
Reference in New Issue