diff --git a/packages/types/src/api/web/global/index.ts b/packages/types/src/api/web/global/index.ts index 21a5de3727..bf4b43f0ba 100644 --- a/packages/types/src/api/web/global/index.ts +++ b/packages/types/src/api/web/global/index.ts @@ -2,3 +2,4 @@ export * from "./environmentVariables" export * from "./auditLogs" export * from "./events" export * from "./configs" +export * from "./scim" diff --git a/packages/types/src/api/web/global/scim/index.ts b/packages/types/src/api/web/global/scim/index.ts new file mode 100644 index 0000000000..056d6e5675 --- /dev/null +++ b/packages/types/src/api/web/global/scim/index.ts @@ -0,0 +1 @@ +export * from "./users" diff --git a/packages/types/src/api/web/global/scim/users.ts b/packages/types/src/api/web/global/scim/users.ts new file mode 100644 index 0000000000..079436e9ae --- /dev/null +++ b/packages/types/src/api/web/global/scim/users.ts @@ -0,0 +1,32 @@ +export interface ScimUser { + schemas: ["urn:ietf:params:scim:schemas:core:2.0:User"] + id: string + externalId: string + meta: { + resourceType: "User" + created: string + lastModified: string + } + userName: string + name: { + formatted: string + familyName: string + givenName: string + } + active: boolean + emails: [ + { + value: string + type: "work" + primary: true + } + ] +} + +export interface ScimListResponse { + schemas: ["urn:ietf:params:scim:api:messages:2.0:ListResponse"] + totalResults: number + Resources: ScimUser[] + startIndex: number + itemsPerPage: number +} diff --git a/packages/worker/src/api/routes/global/tests/scim/users.spec.ts b/packages/worker/src/api/routes/global/tests/scim/users.spec.ts new file mode 100644 index 0000000000..21bef3d5a5 --- /dev/null +++ b/packages/worker/src/api/routes/global/tests/scim/users.spec.ts @@ -0,0 +1,41 @@ +import { InviteUsersResponse, User } from "@budibase/types" + +jest.mock("nodemailer") +import { TestConfiguration, mocks, structures } from "../../../../../tests" +const sendMailMock = mocks.email.mock() +import { events, tenancy, accounts as _accounts } from "@budibase/backend-core" +import * as userSdk from "../../../../../sdk/users" + +const accounts = jest.mocked(_accounts) + +describe("/api/global/scim/v2/users", () => { + const config = new TestConfiguration() + + beforeAll(async () => { + await config.beforeAll() + }) + + afterAll(async () => { + await config.afterAll() + }) + + beforeEach(() => { + jest.clearAllMocks() + }) + + describe("GET /api/global/scim/v2/users", () => { + describe("no users exist", () => { + it("should retrieve empty list", async () => { + const response = await config.api.scimUsersAPI.get() + + expect(response).toEqual({ + Resources: [], + itemsPerPage: 20, + schemas: ["urn:ietf:params:scim:api:messages:2.0:ListResponse"], + startIndex: 1, + totalResults: 0, + }) + }) + }) + }) +}) diff --git a/packages/worker/src/tests/TestConfiguration.ts b/packages/worker/src/tests/TestConfiguration.ts index e612742047..c1098729b1 100644 --- a/packages/worker/src/tests/TestConfiguration.ts +++ b/packages/worker/src/tests/TestConfiguration.ts @@ -20,6 +20,9 @@ import { auth, constants, env as coreEnv, + db as dbCore, + encryption, + utils, } from "@budibase/backend-core" import structures, { CSRF_TOKEN } from "./structures" import { SaveUserResponse, User, AuthToken } from "@budibase/types" @@ -31,6 +34,7 @@ class TestConfiguration { api: API tenantId: string user?: User + apiKey?: string userPassword = "test" constructor(opts: { openServer: boolean } = { openServer: true }) { @@ -201,6 +205,12 @@ class TestConfiguration { return { [constants.Header.API_KEY]: coreEnv.INTERNAL_API_KEY } } + bearerAPIHeaders() { + return { + [constants.Header.AUTHORIZATION]: `Bearer ${this.apiKey}`, + } + } + adminOnlyResponse = () => { return { message: "Admin user only endpoint.", status: 403 } } @@ -213,6 +223,20 @@ class TestConfiguration { }) await context.doInTenant(this.tenantId!, async () => { this.user = await this.createUser(user) + + const db = context.getGlobalDB() + + const id = dbCore.generateDevInfoID(this.user._id) + // TODO: dry + this.apiKey = encryption.encrypt( + `${this.tenantId}${dbCore.SEPARATOR}${utils.newid()}` + ) + const devInfo = { + _id: id, + userId: this.user._id, + apiKey: this.apiKey, + } + await db.put(devInfo) }) } diff --git a/packages/worker/src/tests/api/index.ts b/packages/worker/src/tests/api/index.ts index 166996e792..89f38d63fb 100644 --- a/packages/worker/src/tests/api/index.ts +++ b/packages/worker/src/tests/api/index.ts @@ -15,6 +15,8 @@ import { RolesAPI } from "./roles" import { TemplatesAPI } from "./templates" import { LicenseAPI } from "./license" import { AuditLogAPI } from "./auditLogs" +import { ScimUsersAPI } from "./scim/users" + export default class API { accounts: AccountAPI auth: AuthAPI @@ -32,6 +34,7 @@ export default class API { templates: TemplatesAPI license: LicenseAPI auditLogs: AuditLogAPI + scimUsersAPI: ScimUsersAPI constructor(config: TestConfiguration) { this.accounts = new AccountAPI(config) @@ -50,5 +53,6 @@ export default class API { this.templates = new TemplatesAPI(config) this.license = new LicenseAPI(config) this.auditLogs = new AuditLogAPI(config) + this.scimUsersAPI = new ScimUsersAPI(config) } } diff --git a/packages/worker/src/tests/api/scim/users.ts b/packages/worker/src/tests/api/scim/users.ts new file mode 100644 index 0000000000..fd0a99b671 --- /dev/null +++ b/packages/worker/src/tests/api/scim/users.ts @@ -0,0 +1,19 @@ +import { AccountMetadata, ScimListResponse } from "@budibase/types" +import TestConfiguration from "../../TestConfiguration" +import { TestAPI } from "../base" + +export class ScimUsersAPI extends TestAPI { + constructor(config: TestConfiguration) { + super(config) + } + + get = async ({ expect }: { expect: number } = { expect: 200 }) => { + const res = await this.request + .get(`/api/global/scim/v2/users`) + .set(this.config.bearerAPIHeaders()) + .expect("Content-Type", /json/) + .expect(expect) + + return res.body as ScimListResponse + } +}