1
0
Fork 0
mirror of synced 2024-07-08 15:56:23 +12:00

Migrate PermissionAPI

This commit is contained in:
Sam Rose 2024-02-29 17:33:36 +00:00
parent 1a2a77fc91
commit 46bec3c515
No known key found for this signature in database
5 changed files with 99 additions and 79 deletions

View file

@ -7,6 +7,10 @@ import {
GetResourcePermsResponse, GetResourcePermsResponse,
ResourcePermissionInfo, ResourcePermissionInfo,
GetDependantResourcesResponse, GetDependantResourcesResponse,
AddPermissionResponse,
AddPermissionRequest,
RemovePermissionRequest,
RemovePermissionResponse,
} from "@budibase/types" } from "@budibase/types"
import { getRoleParams } from "../../db/utils" import { getRoleParams } from "../../db/utils"
import { import {
@ -16,9 +20,9 @@ import {
import { removeFromArray } from "../../utilities" import { removeFromArray } from "../../utilities"
import sdk from "../../sdk" import sdk from "../../sdk"
const PermissionUpdateType = { enum PermissionUpdateType {
REMOVE: "remove", REMOVE = "remove",
ADD: "add", ADD = "add",
} }
const SUPPORTED_LEVELS = CURRENTLY_SUPPORTED_LEVELS const SUPPORTED_LEVELS = CURRENTLY_SUPPORTED_LEVELS
@ -39,7 +43,7 @@ async function updatePermissionOnRole(
resourceId, resourceId,
level, level,
}: { roleId: string; resourceId: string; level: PermissionLevel }, }: { roleId: string; resourceId: string; level: PermissionLevel },
updateType: string updateType: PermissionUpdateType
) { ) {
const allowedAction = await sdk.permissions.resourceActionAllowed({ const allowedAction = await sdk.permissions.resourceActionAllowed({
resourceId, resourceId,
@ -107,11 +111,15 @@ async function updatePermissionOnRole(
} }
const response = await db.bulkDocs(docUpdates) const response = await db.bulkDocs(docUpdates)
return response.map((resp: any) => { return response.map(resp => {
const version = docUpdates.find(role => role._id === resp.id)?.version const version = docUpdates.find(role => role._id === resp.id)?.version
resp._id = roles.getExternalRoleID(resp.id, version) const _id = roles.getExternalRoleID(resp.id, version)
delete resp.id return {
return resp _id,
rev: resp.rev,
error: resp.error,
reason: resp.reason,
}
}) })
} }
@ -189,13 +197,14 @@ export async function getDependantResources(
} }
} }
export async function addPermission(ctx: UserCtx) { export async function addPermission(ctx: UserCtx<void, AddPermissionResponse>) {
ctx.body = await updatePermissionOnRole(ctx.params, PermissionUpdateType.ADD) const params: AddPermissionRequest = ctx.params
ctx.body = await updatePermissionOnRole(params, PermissionUpdateType.ADD)
} }
export async function removePermission(ctx: UserCtx) { export async function removePermission(
ctx.body = await updatePermissionOnRole( ctx: UserCtx<void, RemovePermissionResponse>
ctx.params, ) {
PermissionUpdateType.REMOVE const params: RemovePermissionRequest = ctx.params
) ctx.body = await updatePermissionOnRole(params, PermissionUpdateType.REMOVE)
} }

View file

@ -45,7 +45,7 @@ describe("/permission", () => {
table = (await config.createTable()) as typeof table table = (await config.createTable()) as typeof table
row = await config.createRow() row = await config.createRow()
view = await config.api.viewV2.create({ tableId: table._id }) view = await config.api.viewV2.create({ tableId: table._id })
perms = await config.api.permission.set({ perms = await config.api.permission.add({
roleId: STD_ROLE_ID, roleId: STD_ROLE_ID,
resourceId: table._id, resourceId: table._id,
level: PermissionLevel.READ, level: PermissionLevel.READ,
@ -88,13 +88,13 @@ describe("/permission", () => {
}) })
it("should get resource permissions with multiple roles", async () => { it("should get resource permissions with multiple roles", async () => {
perms = await config.api.permission.set({ perms = await config.api.permission.add({
roleId: HIGHER_ROLE_ID, roleId: HIGHER_ROLE_ID,
resourceId: table._id, resourceId: table._id,
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).toEqual({ expect(res).toEqual({
permissions: { permissions: {
read: { permissionType: "EXPLICIT", role: STD_ROLE_ID }, read: { permissionType: "EXPLICIT", role: STD_ROLE_ID },
write: { permissionType: "EXPLICIT", role: HIGHER_ROLE_ID }, write: { permissionType: "EXPLICIT", role: HIGHER_ROLE_ID },
@ -117,16 +117,19 @@ describe("/permission", () => {
level: PermissionLevel.READ, level: PermissionLevel.READ,
}) })
const response = await config.api.permission.set( await config.api.permission.add(
{ {
roleId: STD_ROLE_ID, roleId: STD_ROLE_ID,
resourceId: table._id, resourceId: table._id,
level: PermissionLevel.EXECUTE, level: PermissionLevel.EXECUTE,
}, },
{ expectStatus: 403 } {
) status: 403,
expect(response.message).toEqual( body: {
"You are not allowed to 'read' the resource type 'datasource'" message:
"You are not allowed to 'read' the resource type 'datasource'",
},
}
) )
}) })
}) })
@ -138,9 +141,9 @@ describe("/permission", () => {
resourceId: table._id, resourceId: table._id,
level: PermissionLevel.READ, level: PermissionLevel.READ,
}) })
expect(res.body[0]._id).toEqual(STD_ROLE_ID) expect(res[0]._id).toEqual(STD_ROLE_ID)
const permsRes = await config.api.permission.get(table._id) const permsRes = await config.api.permission.get(table._id)
expect(permsRes.body[STD_ROLE_ID]).toBeUndefined() expect(permsRes.permissions[STD_ROLE_ID]).toBeUndefined()
}) })
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 () => {
@ -156,10 +159,13 @@ describe("/permission", () => {
resourceId: table._id, resourceId: table._id,
level: PermissionLevel.EXECUTE, level: PermissionLevel.EXECUTE,
}, },
{ expectStatus: 403 } {
) status: 403,
expect(response.body.message).toEqual( body: {
"You are not allowed to 'read' the resource type 'datasource'" message:
"You are not allowed to 'read' the resource type 'datasource'",
},
}
) )
}) })
}) })
@ -203,7 +209,7 @@ describe("/permission", () => {
}) })
it("should ignore the view permissions if the flag is not on", async () => { it("should ignore the view permissions if the flag is not on", async () => {
await config.api.permission.set({ await config.api.permission.add({
roleId: STD_ROLE_ID, roleId: STD_ROLE_ID,
resourceId: view.id, resourceId: view.id,
level: PermissionLevel.READ, level: PermissionLevel.READ,
@ -224,7 +230,7 @@ describe("/permission", () => {
it("should use the view permissions if the flag is on", async () => { it("should use the view permissions if the flag is on", async () => {
mocks.licenses.useViewPermissions() mocks.licenses.useViewPermissions()
await config.api.permission.set({ await config.api.permission.add({
roleId: STD_ROLE_ID, roleId: STD_ROLE_ID,
resourceId: view.id, resourceId: view.id,
level: PermissionLevel.READ, level: PermissionLevel.READ,
@ -277,7 +283,7 @@ describe("/permission", () => {
const res = await config.api.permission.get(legacyView.name) const res = await config.api.permission.get(legacyView.name)
expect(res.body).toEqual({ expect(res).toEqual({
permissions: { permissions: {
read: { read: {
permissionType: "BASE", permissionType: "BASE",

View file

@ -1523,7 +1523,7 @@ describe.each([
}) })
it("allow public users to fetch when permissions are explicit", async () => { it("allow public users to fetch when permissions are explicit", async () => {
await config.api.permission.set({ await config.api.permission.add({
roleId: roles.BUILTIN_ROLE_IDS.PUBLIC, roleId: roles.BUILTIN_ROLE_IDS.PUBLIC,
level: PermissionLevel.READ, level: PermissionLevel.READ,
resourceId: viewId, resourceId: viewId,
@ -1538,7 +1538,7 @@ describe.each([
}) })
it("allow public users to fetch when permissions are inherited", async () => { it("allow public users to fetch when permissions are inherited", async () => {
await config.api.permission.set({ await config.api.permission.add({
roleId: roles.BUILTIN_ROLE_IDS.PUBLIC, roleId: roles.BUILTIN_ROLE_IDS.PUBLIC,
level: PermissionLevel.READ, level: PermissionLevel.READ,
resourceId: tableId, resourceId: tableId,
@ -1553,12 +1553,12 @@ describe.each([
}) })
it("respects inherited permissions, not allowing not public views from public tables", async () => { it("respects inherited permissions, not allowing not public views from public tables", async () => {
await config.api.permission.set({ await config.api.permission.add({
roleId: roles.BUILTIN_ROLE_IDS.PUBLIC, roleId: roles.BUILTIN_ROLE_IDS.PUBLIC,
level: PermissionLevel.READ, level: PermissionLevel.READ,
resourceId: tableId, resourceId: tableId,
}) })
await config.api.permission.set({ await config.api.permission.add({
roleId: roles.BUILTIN_ROLE_IDS.POWER, roleId: roles.BUILTIN_ROLE_IDS.POWER,
level: PermissionLevel.READ, level: PermissionLevel.READ,
resourceId: viewId, resourceId: viewId,

View file

@ -1,52 +1,39 @@
import { AnyDocument, PermissionLevel } from "@budibase/types" import {
import TestConfiguration from "../TestConfiguration" AddPermissionRequest,
import { TestAPI } from "./base" AddPermissionResponse,
GetResourcePermsResponse,
RemovePermissionRequest,
RemovePermissionResponse,
} from "@budibase/types"
import { Expectations, TestAPI } from "./base"
export class PermissionAPI extends TestAPI { export class PermissionAPI extends TestAPI {
constructor(config: TestConfiguration) { get = async (resourceId: string, expectations?: Expectations) => {
super(config) return await this._get<GetResourcePermsResponse>(
`/api/permission/${resourceId}`,
{ expectations }
)
} }
get = async ( add = async (
resourceId: string, request: AddPermissionRequest,
{ expectStatus } = { expectStatus: 200 } expectations?: Expectations
) => { ): Promise<AddPermissionResponse> => {
return this.request const { roleId, resourceId, level } = request
.get(`/api/permission/${resourceId}`) return await this._post<AddPermissionResponse>(
.set(this.config.defaultHeaders()) `/api/permission/${roleId}/${resourceId}/${level}`,
.expect("Content-Type", /json/) { expectations }
.expect(expectStatus) )
}
set = async (
{
roleId,
resourceId,
level,
}: { roleId: string; resourceId: string; level: PermissionLevel },
{ expectStatus } = { expectStatus: 200 }
): Promise<any> => {
const res = await this.request
.post(`/api/permission/${roleId}/${resourceId}/${level}`)
.set(this.config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(expectStatus)
return res.body
} }
revoke = async ( revoke = async (
{ request: RemovePermissionRequest,
roleId, expectations?: Expectations
resourceId,
level,
}: { roleId: string; resourceId: string; level: PermissionLevel },
{ expectStatus } = { expectStatus: 200 }
) => { ) => {
const res = await this.request const { roleId, resourceId, level } = request
.delete(`/api/permission/${roleId}/${resourceId}/${level}`) return await this._delete<RemovePermissionResponse>(
.set(this.config.defaultHeaders()) `/api/permission/${roleId}/${resourceId}/${level}`,
.expect("Content-Type", /json/) { expectations }
.expect(expectStatus) )
return res
} }
} }

View file

@ -1,4 +1,4 @@
import { PlanType } from "../../../sdk" import { PermissionLevel, PlanType } from "../../../sdk"
export interface ResourcePermissionInfo { export interface ResourcePermissionInfo {
role: string role: string
@ -14,3 +14,21 @@ export interface GetResourcePermsResponse {
export interface GetDependantResourcesResponse { export interface GetDependantResourcesResponse {
resourceByType?: Record<string, number> resourceByType?: Record<string, number>
} }
export interface AddedPermission {
_id: string
rev?: string
error?: string
reason?: string
}
export type AddPermissionResponse = AddedPermission[]
export interface AddPermissionRequest {
roleId: string
resourceId: string
level: PermissionLevel
}
export interface RemovePermissionRequest extends AddPermissionRequest {}
export interface RemovePermissionResponse extends AddPermissionResponse {}