diff --git a/packages/backend-core/tests/core/utilities/mocks/licenses.ts b/packages/backend-core/tests/core/utilities/mocks/licenses.ts index 6747282040..14a1f1f4d3 100644 --- a/packages/backend-core/tests/core/utilities/mocks/licenses.ts +++ b/packages/backend-core/tests/core/utilities/mocks/licenses.ts @@ -86,6 +86,10 @@ export const useAuditLogs = () => { return useFeature(Feature.AUDIT_LOGS) } +export const usePublicApiUserRoles = () => { + return useFeature(Feature.USER_ROLE_PUBLIC_API) +} + export const useScimIntegration = () => { return useFeature(Feature.SCIM) } diff --git a/packages/pro b/packages/pro index bf719cb968..162b4efd14 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit bf719cb968a13183225696a74fb40a78668a54a6 +Subproject commit 162b4efd148fc31aa50b76318c6d4a6eee2a8074 diff --git a/packages/server/src/api/routes/public/tests/users.spec.js b/packages/server/src/api/routes/public/tests/users.spec.js deleted file mode 100644 index 1daa611df8..0000000000 --- a/packages/server/src/api/routes/public/tests/users.spec.js +++ /dev/null @@ -1,38 +0,0 @@ -const setup = require("../../tests/utilities") -const { generateMakeRequest } = require("./utils") - -const workerRequests = require("../../../../utilities/workerRequests") - -let config = setup.getConfig() -let apiKey, globalUser, makeRequest - -beforeAll(async () => { - await config.init() - globalUser = await config.globalUser() - apiKey = await config.generateApiKey(globalUser._id) - makeRequest = generateMakeRequest(apiKey) - workerRequests.readGlobalUser.mockReturnValue(globalUser) -}) - -afterAll(setup.afterAll) - -describe("check user endpoints", () => { - it("should not allow a user to update their own roles", async () => { - const res = await makeRequest("put", `/users/${globalUser._id}`, { - ...globalUser, - roles: { - "app_1": "ADMIN", - } - }) - expect(workerRequests.saveGlobalUser.mock.lastCall[0].body.data.roles["app_1"]).toBeUndefined() - expect(res.status).toBe(200) - expect(res.body.data.roles["app_1"]).toBeUndefined() - }) - - it("should not allow a user to delete themselves", async () => { - const res = await makeRequest("delete", `/users/${globalUser._id}`) - expect(res.status).toBe(405) - expect(workerRequests.deleteGlobalUser.mock.lastCall).toBeUndefined() - }) -}) - diff --git a/packages/server/src/api/routes/public/tests/users.spec.ts b/packages/server/src/api/routes/public/tests/users.spec.ts new file mode 100644 index 0000000000..c81acca1df --- /dev/null +++ b/packages/server/src/api/routes/public/tests/users.spec.ts @@ -0,0 +1,126 @@ +import * as setup from "../../tests/utilities" +import { generateMakeRequest, MakeRequestResponse } from "./utils" +import { User } from "@budibase/types" +import { mocks } from "@budibase/backend-core/tests" + +import * as workerRequests from "../../../../utilities/workerRequests" + +const mockedWorkerReq = jest.mocked(workerRequests) + +let config = setup.getConfig() +let apiKey: string, globalUser: User, makeRequest: MakeRequestResponse + +beforeAll(async () => { + await config.init() + globalUser = await config.globalUser() + apiKey = await config.generateApiKey(globalUser._id) + makeRequest = generateMakeRequest(apiKey) + mockedWorkerReq.readGlobalUser.mockImplementation(() => + Promise.resolve(globalUser) + ) +}) + +afterAll(setup.afterAll) + +function base() { + return { + tenantId: config.getTenantId(), + firstName: "Test", + lastName: "Test", + } +} + +function updateMock() { + mockedWorkerReq.readGlobalUser.mockImplementation(ctx => ctx.request.body) +} + +describe("check user endpoints", () => { + it("should not allow a user to update their own roles", async () => { + const res = await makeRequest("put", `/users/${globalUser._id}`, { + ...globalUser, + roles: { + app_1: "ADMIN", + }, + }) + expect( + mockedWorkerReq.saveGlobalUser.mock.lastCall?.[0].body.data.roles["app_1"] + ).toBeUndefined() + expect(res.status).toBe(200) + expect(res.body.data.roles["app_1"]).toBeUndefined() + }) + + it("should not allow a user to delete themselves", async () => { + const res = await makeRequest("delete", `/users/${globalUser._id}`) + expect(res.status).toBe(405) + expect(mockedWorkerReq.deleteGlobalUser.mock.lastCall).toBeUndefined() + }) +}) + +describe("no user role update in free", () => { + beforeAll(() => { + updateMock() + }) + + it("should not allow 'roles' to be updated", async () => { + const res = await makeRequest("post", "/users", { + ...base(), + roles: { app_a: "BASIC" }, + }) + expect(res.status).toBe(200) + expect(res.body.data.roles["app_a"]).toBeUndefined() + }) + + it("should not allow 'admin' to be updated", async () => { + const res = await makeRequest("post", "/users", { + ...base(), + admin: { global: true }, + }) + expect(res.status).toBe(200) + expect(res.body.data.admin).toBeUndefined() + }) + + it("should not allow 'builder' to be updated", async () => { + const res = await makeRequest("post", "/users", { + ...base(), + builder: { global: true }, + }) + expect(res.status).toBe(200) + expect(res.body.data.builder).toBeUndefined() + }) +}) + +describe("no user role update in business", () => { + beforeAll(() => { + updateMock() + mocks.licenses.usePublicApiUserRoles() + }) + + it("should allow 'roles' to be updated", async () => { + const res = await makeRequest("post", "/users", { + ...base(), + roles: { app_a: "BASIC" }, + }) + expect(res.status).toBe(200) + expect(res.body.data.roles["app_a"]).toBe("BASIC") + }) + + it("should allow 'admin' to be updated", async () => { + mocks.licenses.usePublicApiUserRoles() + const res = await makeRequest("post", "/users", { + ...base(), + admin: { global: true }, + }) + expect(res.status).toBe(200) + expect(res.body.data.admin.global).toBe(true) + }) + + it("should allow 'builder' to be updated", async () => { + mocks.licenses.usePublicApiUserRoles() + const res = await makeRequest("post", "/users", { + ...base(), + builder: { global: true }, + }) + expect(res.status).toBe(200) + expect(res.body.data.builder.global).toBe(true) + }) +}) diff --git a/yarn.lock b/yarn.lock index 827c94a176..4f5e0db2e7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10283,7 +10283,7 @@ denque@^1.1.0: resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf" integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw== -denque@^2.0.1, denque@^2.1.0: +denque@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== @@ -16986,6 +16986,11 @@ long@^5.0.0: resolved "https://registry.yarnpkg.com/long/-/long-5.2.1.tgz#e27595d0083d103d2fa2c20c7699f8e0c92b897f" integrity sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A== +long@^5.2.1: + version "5.2.3" + resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" + integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== + lookpath@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/lookpath/-/lookpath-1.1.0.tgz#932d68371a2f0b4a5644f03d6a2b4728edba96d2" @@ -17052,6 +17057,11 @@ lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== +lru-cache@^8.0.0: + version "8.0.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-8.0.5.tgz#983fe337f3e176667f8e567cfcce7cb064ea214e" + integrity sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA== + lru-cache@^9.0.0: version "9.0.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-9.0.1.tgz#ac061ed291f8b9adaca2b085534bb1d3b61bef83" @@ -17952,17 +17962,17 @@ mute-stream@~1.0.0: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-1.0.0.tgz#e31bd9fe62f0aed23520aa4324ea6671531e013e" integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA== -mysql2@2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-2.3.3.tgz#944f3deca4b16629052ff8614fbf89d5552545a0" - integrity sha512-wxJUev6LgMSgACDkb/InIFxDprRa6T95+VEoR+xPvtngtccNH2dGjEB/fVZ8yg1gWv1510c9CvXuJHi5zUm0ZA== +mysql2@3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.5.2.tgz#a06050e1514e9ac15711a8b883ffd51cb44b2dc8" + integrity sha512-cptobmhYkYeTBIFp2c0piw2+gElpioga1rUw5UidHvo8yaHijMZoo8A3zyBVoo/K71f7ZFvrShA9iMIy9dCzCA== dependencies: - denque "^2.0.1" + denque "^2.1.0" generate-function "^2.3.1" iconv-lite "^0.6.3" - long "^4.0.0" - lru-cache "^6.0.0" - named-placeholders "^1.1.2" + long "^5.2.1" + lru-cache "^8.0.0" + named-placeholders "^1.1.3" seq-queue "^0.0.5" sqlstring "^2.3.2" @@ -17975,7 +17985,7 @@ mz@^2.4.0, mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" -named-placeholders@^1.1.2: +named-placeholders@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/named-placeholders/-/named-placeholders-1.1.3.tgz#df595799a36654da55dda6152ba7a137ad1d9351" integrity sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==