From b6fd3b7cf77ea26f9c80556a0b7d16fa9658963a Mon Sep 17 00:00:00 2001 From: Mateus Badan de Pieri Date: Mon, 3 Apr 2023 11:28:20 +0100 Subject: [PATCH] primary tests cases --- .../src/security/tests/encryption.spec.ts | 32 +++++++ .../src/security/tests/permissions.spec.ts | 84 ++++++++++++++++ .../src/security/tests/roles.spec.ts | 33 +++++++ .../src/tenancy/tests/tenancy.spec.ts | 95 ++++++++++++++++++- 4 files changed, 243 insertions(+), 1 deletion(-) create mode 100644 packages/backend-core/src/security/tests/encryption.spec.ts create mode 100644 packages/backend-core/src/security/tests/permissions.spec.ts create mode 100644 packages/backend-core/src/security/tests/roles.spec.ts diff --git a/packages/backend-core/src/security/tests/encryption.spec.ts b/packages/backend-core/src/security/tests/encryption.spec.ts new file mode 100644 index 0000000000..d48f73a87e --- /dev/null +++ b/packages/backend-core/src/security/tests/encryption.spec.ts @@ -0,0 +1,32 @@ +import { encrypt, decrypt, SecretOption, getSecret } from "../encryption" +jest.mock("../../environment", () => ({ + API_ENCRYPTION_KEY: "mock-api-encryption-key", + ENCRYPTION_KEY: "mock-encryption-key", +})) +describe("encryption", () => { + const plaintext = "budibase" + const apiEncrypted = encrypt(plaintext, SecretOption.API) + const encryptionEncrypted = encrypt(plaintext, SecretOption.ENCRYPTION) + + it("should encrypt and decrypt a string using API encryption key", () => { + const decrypted = decrypt(apiEncrypted, SecretOption.API) + expect(decrypted).toEqual(plaintext) + }) + + it("should encrypt and decrypt a string using encryption key", () => { + const decrypted = decrypt(encryptionEncrypted, SecretOption.ENCRYPTION) + expect(decrypted).toEqual(plaintext) + }) + + it("should throw an error if encryption key is not set", () => { + expect(() => getSecret(SecretOption.ENCRYPTION)).toThrow( + 'Secret "ENCRYPTION_KEY" has not been set in environment.' + ) + }) + + it("should throw an error if API encryption key is not set", () => { + expect(() => getSecret(SecretOption.API)).toThrow( + 'Secret "API_ENCRYPTION_KEY" has not been set in environment.' + ) + }) +}) diff --git a/packages/backend-core/src/security/tests/permissions.spec.ts b/packages/backend-core/src/security/tests/permissions.spec.ts new file mode 100644 index 0000000000..bf13c11ee0 --- /dev/null +++ b/packages/backend-core/src/security/tests/permissions.spec.ts @@ -0,0 +1,84 @@ +import { + getBuiltinPermissions, + getBuiltinPermissionByID, + doesHaveBasePermission, + isPermissionLevelHigherThanRead, +} from "./permissions" + +describe("getBuiltinPermissions", () => { + it("should return a deep clone of BUILTIN_PERMISSIONS", () => { + const permissions = getBuiltinPermissions() + expect(permissions).toEqual(BUILTIN_PERMISSIONS) + expect(permissions).not.toBe(BUILTIN_PERMISSIONS) + }) +}) + +describe("getBuiltinPermissionByID", () => { + it("should return the correct permission object", () => { + const id = "123" + const permission = { _id: id, name: "Test Permission" } + const permissions = { "0": permission } + expect(getBuiltinPermissionByID(id)).toEqual(permission) + expect(getBuiltinPermissionByID("456")).toBeUndefined() + }) +}) + +describe("doesHaveBasePermission", () => { + it("should return true if base permission has the required level", () => { + const permType = "someType" + const permLevel = "write" + const rolesHierarchy = [ + { roleId: "role1", permissionId: "permission1" }, + { roleId: "role2", permissionId: "permission2" }, + ] + const permissions = [{ type: permType, level: permLevel }] + const builtinPermissions = { + permission1: { _id: "permission1", permissions: permissions }, + permission2: { _id: "permission2", permissions: [] }, + permission3: { _id: "permission3", permissions: [] }, + } + expect( + doesHaveBasePermission( + permType, + permLevel, + rolesHierarchy, + builtinPermissions + ) + ).toBeTruthy() + }) + + it("should return false if base permission does not have the required level", () => { + const permType = "someType" + const permLevel = "write" + const rolesHierarchy = [ + { roleId: "role1", permissionId: "permission1" }, + { roleId: "role2", permissionId: "permission2" }, + ] + const permissions = [{ type: permType, level: "read" }] + const builtinPermissions = { + permission1: { _id: "permission1", permissions: permissions }, + permission2: { _id: "permission2", permissions: [] }, + permission3: { _id: "permission3", permissions: [] }, + } + expect( + doesHaveBasePermission( + permType, + permLevel, + rolesHierarchy, + builtinPermissions + ) + ).toBeFalsy() + }) +}) + +describe("isPermissionLevelHigherThanRead", () => { + it("should return true if level is higher than read", () => { + expect(isPermissionLevelHigherThanRead("write")).toBeTruthy() + expect(isPermissionLevelHigherThanRead("admin")).toBeTruthy() + }) + + it("should return false if level is read or lower", () => { + expect(isPermissionLevelHigherThanRead("read")).toBeFalsy() + expect(isPermissionLevelHigherThanRead("none")).toBeFalsy() + }) +}) diff --git a/packages/backend-core/src/security/tests/roles.spec.ts b/packages/backend-core/src/security/tests/roles.spec.ts new file mode 100644 index 0000000000..a5fcb5098b --- /dev/null +++ b/packages/backend-core/src/security/tests/roles.spec.ts @@ -0,0 +1,33 @@ +import { PermissionLevel } from "../permissions" +import { Role, getBuiltinRoles } from "../roles" + +describe("Role", () => { + describe("constructor", () => { + test("it should initialize _id, name, and permissionId", () => { + const role = new Role("my-role", "My Role", PermissionLevel.READ) + expect(role._id).toEqual("my-role") + expect(role.name).toEqual("My Role") + expect(role.permissionId).toEqual(PermissionLevel.READ) + }) + }) + + describe("addInheritance", () => { + test("it should add the inheritance property to the role", () => { + const role = new Role("my-role", "My Role", PermissionLevel.READ) + const newRole = role.addInheritance("other-role") + expect(newRole).toEqual(role) + expect(role.inherits).toEqual("other-role") + }) + }) + + describe("getBuiltinRoles", () => { + test("it should return an object of builtin roles", () => { + const builtinRoles = getBuiltinRoles() + expect(builtinRoles).toHaveProperty("ADMIN") + expect(builtinRoles).toHaveProperty("POWER") + expect(builtinRoles).toHaveProperty("BASIC") + expect(builtinRoles).toHaveProperty("PUBLIC") + expect(builtinRoles).not.toHaveProperty("BUILDER") + }) + }) +}) diff --git a/packages/backend-core/src/tenancy/tests/tenancy.spec.ts b/packages/backend-core/src/tenancy/tests/tenancy.spec.ts index f6dda3037f..3f249d5164 100644 --- a/packages/backend-core/src/tenancy/tests/tenancy.spec.ts +++ b/packages/backend-core/src/tenancy/tests/tenancy.spec.ts @@ -69,4 +69,97 @@ describe("isUserInAppTenant", () => { }) }) -describe("getTenantIDFromCtx", () => {}) +const mockCtx = { + user: { tenantId: "123" }, + request: { + headers: { "X-Tenant-ID": "456" }, + query: { tenantId: "789" }, + }, + host: "tenant.budibase.app", + originalUrl: "/tenant/123", + matched: [ + { + paramNames: [{ name: "tenantId" }], + params: (url: any, captures: any, ctx: any) => ({ tenantId: "456" }), + }, + ], +} +const mockOpts = { + allowNoTenant: false, + includeStrategies: ["USER", "HEADER", "QUERY", "SUBDOMAIN", "PATH"], + excludeStrategies: ["QUERY"], +} +// mock the `getTenantId` and `isMultiTenant` functions +jest.mock("../../context", () => ({ + isMultiTenant: jest.fn(() => true), +})) +describe("getTenantIDFromCtx", () => { + describe("when isMultiTenant() returns true", () => { + beforeEach(() => { + jest.spyOn(global, "isMultiTenant").mockReturnValue(true) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + describe("when tenant can be found", () => { + it("returns the tenant ID from the user object", () => { + expect(getTenantIDFromCtx(mockCtx, mockOpts)).toEqual("123") + }) + + it("returns the tenant ID from the header", () => { + mockCtx.user = null + expect(getTenantIDFromCtx(mockCtx, mockOpts)).toEqual("456") + }) + + it("returns the tenant ID from the query param", () => { + mockCtx.user = null + mockCtx.request.headers = {} + expect(getTenantIDFromCtx(mockCtx, mockOpts)).toEqual("789") + }) + + it("returns the tenant ID from the subdomain", () => { + mockCtx.user = null + mockCtx.request.headers = {} + mockCtx.request.query = {} + expect(getTenantIDFromCtx(mockCtx, mockOpts)).toEqual("tenant") + }) + + it("returns the tenant ID from the path", () => { + mockCtx.user = null + mockCtx.request.headers = {} + mockCtx.request.query = {} + mockCtx.host = "budibase.app" + expect(getTenantIDFromCtx(mockCtx, mockOpts)).toEqual("123") + }) + }) + + describe("when tenant cannot be found", () => { + it("throws a 403 error if allowNoTenant is false", () => { + mockCtx.user = null + mockCtx.request.headers = {} + mockCtx.request.query = {} + mockCtx.host = "budibase.app" + mockOpts.allowNoTenant = false + expect(() => getTenantIDFromCtx(mockCtx, mockOpts)).toThrowError( + "Tenant id not set" + ) + }) + + it("returns null if allowNoTenant is true", () => { + mockCtx.user = null + mockCtx.request.headers = {} + mockCtx.request.query = {} + mockCtx.host = "budibase.app" + mockOpts.allowNoTenant = true + expect(getTenantIDFromCtx(mockCtx, mockOpts)).toBeNull() + }) + }) + }) + + it("returns the default tenant ID when isMultiTenant() returns false", () => { + mockedIsMultiTenant.mockImplementation(() => false) + expect(getTenantIDFromCtx(mockCtx, mockOpts)).toEqual("default") + }) +})