From e9161780468747f0790cbb68ab17f26c1aecf2da Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 27 Sep 2023 15:46:04 +0100 Subject: [PATCH] Adding test case for new endpoint and covering public, builder and normal roles. --- packages/backend-core/src/security/roles.ts | 7 ++- .../client/src/components/app/Layout.svelte | 12 +++-- packages/client/src/sdk.js | 2 + packages/server/src/api/controllers/role.ts | 15 +++++-- .../src/api/controllers/static/index.ts | 5 ++- packages/server/src/api/routes/role.ts | 8 +--- .../server/src/api/routes/tests/role.spec.js | 44 +++++++++++++++---- .../src/tests/utilities/TestConfiguration.ts | 9 ++++ 8 files changed, 73 insertions(+), 29 deletions(-) diff --git a/packages/backend-core/src/security/roles.ts b/packages/backend-core/src/security/roles.ts index e87df2e9c9..3497ecc972 100644 --- a/packages/backend-core/src/security/roles.ts +++ b/packages/backend-core/src/security/roles.ts @@ -253,7 +253,10 @@ export function checkForRoleResourceArray( * Given an app ID this will retrieve all of the roles that are currently within that app. * @return {Promise} An array of the role objects that were found. */ -export async function getAllRoles(appId?: string): Promise { +export async function getAllRoles( + appId?: string, + opts?: { idOnly: boolean } +): Promise { if (appId) { return doWithDB(appId, internal) } else { @@ -308,7 +311,7 @@ export async function getAllRoles(appId?: string): Promise { ) } } - return roles + return opts?.idOnly ? roles.map(role => role._id) : roles } } diff --git a/packages/client/src/components/app/Layout.svelte b/packages/client/src/components/app/Layout.svelte index e557874edb..5a0acd25be 100644 --- a/packages/client/src/components/app/Layout.svelte +++ b/packages/client/src/components/app/Layout.svelte @@ -4,15 +4,14 @@ import { Heading, Icon, clickOutside } from "@budibase/bbui" import { FieldTypes } from "constants" import active from "svelte-spa-router/active" - import { RoleUtils } from "@budibase/frontend-core" const sdk = getContext("sdk") const { routeStore, + roleStore, styleable, linkable, builderStore, - currentRole, sidePanelStore, } = sdk const component = getContext("component") @@ -61,7 +60,8 @@ }) setContext("layout", store) - $: validLinks = getValidLinks(links, $currentRole) + $: accessibleRoles = $roleStore + $: validLinks = getValidLinks(links) $: typeClass = NavigationClasses[navigation] || NavigationClasses.None $: navWidthClass = WidthClasses[navWidth || width] || WidthClasses.Large $: pageWidthClass = WidthClasses[pageWidth || width] || WidthClasses.Large @@ -99,14 +99,12 @@ } } - const getValidLinks = (allLinks, role) => { + const getValidLinks = allLinks => { // Strip links missing required info let validLinks = (allLinks || []).filter(link => link.text && link.url) - // Filter to only links allowed by the current role - const priority = RoleUtils.getRolePriority(role) return validLinks.filter(link => { - return !link.roleId || RoleUtils.getRolePriority(link.roleId) <= priority + return accessibleRoles?.find(roleId => roleId === link.roleId) }) } diff --git a/packages/client/src/sdk.js b/packages/client/src/sdk.js index 237334ca57..1d77f3e2da 100644 --- a/packages/client/src/sdk.js +++ b/packages/client/src/sdk.js @@ -13,6 +13,7 @@ import { sidePanelStore, dndIsDragging, confirmationStore, + roleStore, } from "stores" import { styleable } from "utils/styleable" import { linkable } from "utils/linkable" @@ -39,6 +40,7 @@ export default { dndIsDragging, currentRole, confirmationStore, + roleStore, styleable, linkable, getAction, diff --git a/packages/server/src/api/controllers/role.ts b/packages/server/src/api/controllers/role.ts index 7094481934..e72517afc3 100644 --- a/packages/server/src/api/controllers/role.ts +++ b/packages/server/src/api/controllers/role.ts @@ -1,6 +1,7 @@ import { context, db as dbCore, events, roles } from "@budibase/backend-core" import { getUserMetadataParams, InternalTables } from "../../db/utils" import { Database, Role, UserCtx, UserRoles } from "@budibase/types" +import { sdk as sharedSdk } from "@budibase/shared-core" import sdk from "../../sdk" const UpdateRolesOptions = { @@ -128,12 +129,18 @@ export async function destroy(ctx: UserCtx) { role.version ) ctx.message = `Role ${ctx.params.roleId} deleted successfully` + ctx.status = 200 } export async function accessible(ctx: UserCtx) { - const user = ctx.user - if (!user || !user.roleId) { - ctx.throw(400, "User does not have a valid role") + let roleId = ctx.user?.roleId + if (!roleId) { + roleId = roles.BUILTIN_ROLE_IDS.PUBLIC + } + if (ctx.user && sharedSdk.users.isAdminOrBuilder(ctx.user)) { + const appId = context.getAppId() + ctx.body = await roles.getAllRoles(appId, { idOnly: true }) + } else { + ctx.body = await roles.getUserRoleHierarchy(roleId!, { idOnly: true }) } - ctx.body = await roles.getUserRoleHierarchy(user.roleId!) } diff --git a/packages/server/src/api/controllers/static/index.ts b/packages/server/src/api/controllers/static/index.ts index 9c30b8a6a6..bbf9dd34f5 100644 --- a/packages/server/src/api/controllers/static/index.ts +++ b/packages/server/src/api/controllers/static/index.ts @@ -199,8 +199,9 @@ export const serveBuilderPreview = async function (ctx: any) { export const serveClientLibrary = async function (ctx: any) { let rootPath = join(NODE_MODULES_PATH, "@budibase", "client", "dist") - if (!fs.existsSync(rootPath)) { - rootPath = join(__dirname, "..", "..", "..", "..", "client") + // incase running from TS directly + if (env.isDev() && !fs.existsSync(rootPath)) { + rootPath = join(require.resolve("@budibase/client"), "..") } return send(ctx, "budibase-client.js", { root: rootPath, diff --git a/packages/server/src/api/routes/role.ts b/packages/server/src/api/routes/role.ts index f88de41b36..595779bf11 100644 --- a/packages/server/src/api/routes/role.ts +++ b/packages/server/src/api/routes/role.ts @@ -3,17 +3,13 @@ import * as controller from "../controllers/role" import authorized from "../../middleware/authorized" import { permissions } from "@budibase/backend-core" import { roleValidator } from "./utils/validators" -import { PermissionLevel, PermissionType } from "@budibase/types" const router: Router = new Router() router // retrieve a list of the roles a user can access - .get( - "/api/roles/accessible", - authorized(PermissionType.APP, PermissionLevel.READ), - controller.accessible - ) + // needs to be public for public screens + .get("/api/roles/accessible", controller.accessible) .post( "/api/roles", authorized(permissions.BUILDER), diff --git a/packages/server/src/api/routes/tests/role.spec.js b/packages/server/src/api/routes/tests/role.spec.js index a6418c2277..c8e383d5ed 100644 --- a/packages/server/src/api/routes/tests/role.spec.js +++ b/packages/server/src/api/routes/tests/role.spec.js @@ -15,7 +15,7 @@ describe("/roles", () => { await config.init() }) - const createRole = async (role) => { + const createRole = async role => { if (!role) { role = basicRole() } @@ -33,9 +33,6 @@ describe("/roles", () => { const role = basicRole() const res = await createRole(role) - expect(res.res.statusMessage).toEqual( - `Role '${role.name}' created successfully.` - ) expect(res.body._id).toBeDefined() expect(res.body._rev).toBeDefined() expect(events.role.updated).not.toBeCalled() @@ -51,9 +48,6 @@ describe("/roles", () => { jest.clearAllMocks() res = await createRole(res.body) - expect(res.res.statusMessage).toEqual( - `Role '${role.name}' created successfully.` - ) expect(res.body._id).toBeDefined() expect(res.body._rev).toBeDefined() expect(events.role.created).not.toBeCalled() @@ -99,7 +93,11 @@ describe("/roles", () => { it("should be able to get the role with a permission added", async () => { const table = await config.createTable() - await config.api.permission.set({ roleId: BUILTIN_ROLE_IDS.POWER, resourceId: table._id, level: PermissionLevel.READ }) + await config.api.permission.set({ + roleId: BUILTIN_ROLE_IDS.POWER, + resourceId: table._id, + level: PermissionLevel.READ, + }) const res = await request .get(`/api/roles`) .set(config.defaultHeaders()) @@ -131,4 +129,34 @@ describe("/roles", () => { expect(events.role.deleted).toBeCalledWith(customRole) }) }) + + describe("accessible", () => { + it("should be able to fetch accessible roles (with builder)", async () => { + const res = await request + .get("/api/roles/accessible") + .set(config.defaultHeaders()) + .expect(200) + expect(res.body.length).toBe(5) + expect(typeof res.body[0]).toBe("string") + }) + + it("should be able to fetch accessible roles (basic user)", async () => { + const res = await request + .get("/api/roles/accessible") + .set(await config.basicRoleHeaders()) + .expect(200) + expect(res.body.length).toBe(2) + expect(res.body[0]).toBe("BASIC") + expect(res.body[1]).toBe("PUBLIC") + }) + + it("should be able to fetch accessible roles (no user)", async () => { + const res = await request + .get("/api/roles/accessible") + .set(config.publicHeaders()) + .expect(200) + expect(res.body.length).toBe(1) + expect(res.body[0]).toBe("PUBLIC") + }) + }) }) diff --git a/packages/server/src/tests/utilities/TestConfiguration.ts b/packages/server/src/tests/utilities/TestConfiguration.ts index 799e6f34e9..21b6463ce7 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.ts +++ b/packages/server/src/tests/utilities/TestConfiguration.ts @@ -425,6 +425,15 @@ class TestConfiguration { return headers } + async basicRoleHeaders() { + return await this.roleHeaders({ + email: this.defaultUserValues.email, + builder: false, + prodApp: true, + roleId: roles.BUILTIN_ROLE_IDS.BASIC, + }) + } + async roleHeaders({ email = this.defaultUserValues.email, roleId = roles.BUILTIN_ROLE_IDS.ADMIN,