diff --git a/bitwarden_license/bit-common/src/dirt/integrations/index.ts b/bitwarden_license/bit-common/src/dirt/integrations/index.ts new file mode 100644 index 0000000000..b2221a94a8 --- /dev/null +++ b/bitwarden_license/bit-common/src/dirt/integrations/index.ts @@ -0,0 +1 @@ +export * from "./services"; diff --git a/bitwarden_license/bit-common/src/dirt/integrations/models/organization-integration-configuration-request.ts b/bitwarden_license/bit-common/src/dirt/integrations/models/organization-integration-configuration-request.ts new file mode 100644 index 0000000000..58c5930447 --- /dev/null +++ b/bitwarden_license/bit-common/src/dirt/integrations/models/organization-integration-configuration-request.ts @@ -0,0 +1,20 @@ +import { EventType } from "@bitwarden/common/enums"; + +export class OrganizationIntegrationConfigurationRequest { + eventType?: EventType | null = null; + configuration?: string | null = null; + filters?: string | null = null; + template?: string | null = null; + + constructor( + eventType?: EventType | null, + configuration?: string | null, + filters?: string | null, + template?: string | null, + ) { + this.eventType = eventType; + this.configuration = configuration; + this.filters = filters; + this.template = template; + } +} diff --git a/bitwarden_license/bit-common/src/dirt/integrations/models/organization-integration-configuration-response.ts b/bitwarden_license/bit-common/src/dirt/integrations/models/organization-integration-configuration-response.ts new file mode 100644 index 0000000000..47baf3276a --- /dev/null +++ b/bitwarden_license/bit-common/src/dirt/integrations/models/organization-integration-configuration-response.ts @@ -0,0 +1,20 @@ +import { EventType } from "@bitwarden/common/enums"; +import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { OrganizationIntegrationConfigurationId } from "@bitwarden/common/types/guid"; + +export class OrganizationIntegrationConfigurationResponse extends BaseResponse { + id: OrganizationIntegrationConfigurationId; + eventType?: EventType; + configuration?: string; + filters?: string; + template?: string; + + constructor(response: any) { + super(response); + this.id = this.getResponseProperty("Id"); + this.eventType = this.getResponseProperty("EventType"); + this.configuration = this.getResponseProperty("Configuration"); + this.filters = this.getResponseProperty("Filters"); + this.template = this.getResponseProperty("Template"); + } +} diff --git a/bitwarden_license/bit-common/src/dirt/integrations/models/organization-integration-request.ts b/bitwarden_license/bit-common/src/dirt/integrations/models/organization-integration-request.ts new file mode 100644 index 0000000000..95f7d180da --- /dev/null +++ b/bitwarden_license/bit-common/src/dirt/integrations/models/organization-integration-request.ts @@ -0,0 +1,11 @@ +import { OrganizationIntegrationType } from "./organization-integration-type"; + +export class OrganizationIntegrationRequest { + type: OrganizationIntegrationType; + configuration?: string; + + constructor(integrationType: OrganizationIntegrationType, configuration?: string) { + this.type = integrationType; + this.configuration = configuration; + } +} diff --git a/bitwarden_license/bit-common/src/dirt/integrations/models/organization-integration-response.ts b/bitwarden_license/bit-common/src/dirt/integrations/models/organization-integration-response.ts new file mode 100644 index 0000000000..00880ea474 --- /dev/null +++ b/bitwarden_license/bit-common/src/dirt/integrations/models/organization-integration-response.ts @@ -0,0 +1,15 @@ +import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { OrganizationIntegrationId } from "@bitwarden/common/types/guid"; + +import { OrganizationIntegrationType } from "./organization-integration-type"; + +export class OrganizationIntegrationResponse extends BaseResponse { + id: OrganizationIntegrationId; + organizationIntegrationType: OrganizationIntegrationType; + + constructor(response: any) { + super(response); + this.id = this.getResponseProperty("Id"); + this.organizationIntegrationType = this.getResponseProperty("Type"); + } +} diff --git a/bitwarden_license/bit-common/src/dirt/integrations/models/organization-integration-service-type.ts b/bitwarden_license/bit-common/src/dirt/integrations/models/organization-integration-service-type.ts new file mode 100644 index 0000000000..dd1b4fb3f6 --- /dev/null +++ b/bitwarden_license/bit-common/src/dirt/integrations/models/organization-integration-service-type.ts @@ -0,0 +1,6 @@ +export const OrganizationIntegrationServiceType = Object.freeze({ + CrowdStrike: "CrowdStrike", +} as const); + +export type OrganizationIntegrationServiceType = + (typeof OrganizationIntegrationServiceType)[keyof typeof OrganizationIntegrationServiceType]; diff --git a/bitwarden_license/bit-common/src/dirt/integrations/models/organization-integration-type.ts b/bitwarden_license/bit-common/src/dirt/integrations/models/organization-integration-type.ts new file mode 100644 index 0000000000..1c98e17483 --- /dev/null +++ b/bitwarden_license/bit-common/src/dirt/integrations/models/organization-integration-type.ts @@ -0,0 +1,10 @@ +export const OrganizationIntegrationType = Object.freeze({ + CloudBillingSync: 1, + Scim: 2, + Slack: 3, + Webhook: 4, + Hec: 5, +} as const); + +export type OrganizationIntegrationType = + (typeof OrganizationIntegrationType)[keyof typeof OrganizationIntegrationType]; diff --git a/bitwarden_license/bit-common/src/dirt/integrations/services/index.ts b/bitwarden_license/bit-common/src/dirt/integrations/services/index.ts new file mode 100644 index 0000000000..68a673854a --- /dev/null +++ b/bitwarden_license/bit-common/src/dirt/integrations/services/index.ts @@ -0,0 +1,2 @@ +export * from "./organization-integration-api.service"; +export * from "./organization-integration-configuration-api.service"; diff --git a/bitwarden_license/bit-common/src/dirt/integrations/services/organization-integration-api.service.spec.ts b/bitwarden_license/bit-common/src/dirt/integrations/services/organization-integration-api.service.spec.ts new file mode 100644 index 0000000000..bf3e16ed43 --- /dev/null +++ b/bitwarden_license/bit-common/src/dirt/integrations/services/organization-integration-api.service.spec.ts @@ -0,0 +1,115 @@ +import { mock } from "jest-mock-extended"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationId, OrganizationIntegrationId } from "@bitwarden/common/types/guid"; + +import { OrganizationIntegrationRequest } from "../models/organization-integration-request"; +import { OrganizationIntegrationServiceType } from "../models/organization-integration-service-type"; +import { OrganizationIntegrationType } from "../models/organization-integration-type"; + +import { OrganizationIntegrationApiService } from "./organization-integration-api.service"; + +export const mockIntegrationResponse: any = { + id: "1" as OrganizationIntegrationId, + organizationIntegrationType: OrganizationIntegrationType.Hec, +}; + +export const mockIntegrationResponses: any[] = [ + { + id: "1" as OrganizationIntegrationId, + OrganizationIntegrationType: OrganizationIntegrationType.Hec, + }, + { + id: "2" as OrganizationIntegrationId, + OrganizationIntegrationType: OrganizationIntegrationType.Webhook, + }, +]; + +describe("OrganizationIntegrationApiService", () => { + let service: OrganizationIntegrationApiService; + const apiService = mock(); + + beforeEach(() => { + service = new OrganizationIntegrationApiService(apiService); + }); + + it("should be created", () => { + expect(service).toBeTruthy(); + }); + + it("should call apiService.send with correct parameters for getOrganizationIntegrations", async () => { + const orgId = "org1" as OrganizationId; + + apiService.send.mockReturnValue(Promise.resolve(mockIntegrationResponses)); + + const result = await service.getOrganizationIntegrations(orgId); + expect(result).toEqual(mockIntegrationResponses); + expect(apiService.send).toHaveBeenCalledWith( + "GET", + `organizations/${orgId}/integrations`, + null, + true, + true, + ); + }); + + it("should call apiService.send with correct parameters for createOrganizationIntegration", async () => { + const request = new OrganizationIntegrationRequest( + OrganizationIntegrationType.Hec, + `{ 'uri:' 'test.com', 'scheme:' 'bearer', 'token:' '123456789', 'service:' '${OrganizationIntegrationServiceType.CrowdStrike}' }`, + ); + const orgId = "org1" as OrganizationId; + + apiService.send.mockReturnValue(Promise.resolve(mockIntegrationResponse)); + + const result = await service.createOrganizationIntegration(orgId, request); + expect(result.organizationIntegrationType).toEqual( + mockIntegrationResponse.organizationIntegrationType, + ); + expect(apiService.send).toHaveBeenCalledWith( + "POST", + `organizations/${orgId.toString()}/integrations`, + request, + true, + true, + ); + }); + + it("should call apiService.send with the correct parameters for updateOrganizationIntegration", async () => { + const request = new OrganizationIntegrationRequest( + OrganizationIntegrationType.Hec, + `{ 'uri:' 'test.com', 'scheme:' 'bearer', 'token:' '123456789', 'service:' '${OrganizationIntegrationServiceType.CrowdStrike}' }`, + ); + const orgId = "org1" as OrganizationId; + const integrationId = "integration1" as OrganizationIntegrationId; + + apiService.send.mockReturnValue(Promise.resolve(mockIntegrationResponse)); + + const result = await service.updateOrganizationIntegration(orgId, integrationId, request); + expect(result.organizationIntegrationType).toEqual( + mockIntegrationResponse.organizationIntegrationType, + ); + expect(apiService.send).toHaveBeenCalledWith( + "PUT", + `organizations/${orgId}/integrations/${integrationId}`, + request, + true, + true, + ); + }); + + it("should call apiService.send with the correct parameters for deleteOrganizationIntegration", async () => { + const orgId = "org1" as OrganizationId; + const integrationId = "integration1" as OrganizationIntegrationId; + + await service.deleteOrganizationIntegration(orgId, integrationId); + + expect(apiService.send).toHaveBeenCalledWith( + "DELETE", + `organizations/${orgId}/integrations/${integrationId}`, + null, + true, + false, + ); + }); +}); diff --git a/bitwarden_license/bit-common/src/dirt/integrations/services/organization-integration-api.service.ts b/bitwarden_license/bit-common/src/dirt/integrations/services/organization-integration-api.service.ts new file mode 100644 index 0000000000..5cf8efefb0 --- /dev/null +++ b/bitwarden_license/bit-common/src/dirt/integrations/services/organization-integration-api.service.ts @@ -0,0 +1,67 @@ +import { Injectable } from "@angular/core"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationId, OrganizationIntegrationId } from "@bitwarden/common/types/guid"; + +import { OrganizationIntegrationRequest } from "../models/organization-integration-request"; +import { OrganizationIntegrationResponse } from "../models/organization-integration-response"; + +@Injectable() +export class OrganizationIntegrationApiService { + constructor(private apiService: ApiService) {} + + async getOrganizationIntegrations( + orgId: OrganizationId, + ): Promise { + const response = await this.apiService.send( + "GET", + `organizations/${orgId}/integrations`, + null, + true, + true, + ); + return response; + } + + async createOrganizationIntegration( + orgId: OrganizationId, + request: OrganizationIntegrationRequest, + ): Promise { + const response = await this.apiService.send( + "POST", + `organizations/${orgId}/integrations`, + request, + true, + true, + ); + return response; + } + + async updateOrganizationIntegration( + orgId: OrganizationId, + integrationId: OrganizationIntegrationId, + request: OrganizationIntegrationRequest, + ): Promise { + const response = await this.apiService.send( + "PUT", + `organizations/${orgId}/integrations/${integrationId}`, + request, + true, + true, + ); + return response; + } + + async deleteOrganizationIntegration( + orgId: OrganizationId, + integrationId: OrganizationIntegrationId, + ): Promise { + await this.apiService.send( + "DELETE", + `organizations/${orgId}/integrations/${integrationId}`, + null, + true, + false, + ); + } +} diff --git a/bitwarden_license/bit-common/src/dirt/integrations/services/organization-integration-configuration-api.service.spec.ts b/bitwarden_license/bit-common/src/dirt/integrations/services/organization-integration-configuration-api.service.spec.ts new file mode 100644 index 0000000000..48612efdd1 --- /dev/null +++ b/bitwarden_license/bit-common/src/dirt/integrations/services/organization-integration-configuration-api.service.spec.ts @@ -0,0 +1,132 @@ +import { mock } from "jest-mock-extended"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { + OrganizationId, + OrganizationIntegrationId, + OrganizationIntegrationConfigurationId, +} from "@bitwarden/common/types/guid"; + +import { OrganizationIntegrationConfigurationRequest } from "../models/organization-integration-configuration-request"; + +import { OrganizationIntegrationConfigurationApiService } from "./organization-integration-configuration-api.service"; + +export const mockConfigurationResponse: any = { + id: "1" as OrganizationIntegrationConfigurationId, + template: "{ 'event': '#EventMessage#', 'source': 'Bitwarden', 'index': 'testIndex' }", +}; + +export const mockConfigurationResponses: any[] = [ + { + id: "1" as OrganizationIntegrationConfigurationId, + template: "{ 'event': '#EventMessage#', 'source': 'Bitwarden', 'index': 'testIndex' }", + }, + { + id: "2" as OrganizationIntegrationConfigurationId, + template: "{ 'event': '#EventMessage#', 'source': 'Bitwarden', 'index': 'otherIndex' }", + }, +]; + +describe("OrganizationIntegrationConfigurationApiService", () => { + let service: OrganizationIntegrationConfigurationApiService; + const apiService = mock(); + + beforeEach(() => { + service = new OrganizationIntegrationConfigurationApiService(apiService); + }); + + it("should be created", () => { + expect(service).toBeTruthy(); + }); + + it("should call apiService.send with correct parameters for getOrganizationIntegrationConfigurations", async () => { + const orgId = "org1" as OrganizationId; + const integrationId = "integration1" as OrganizationIntegrationId; + + apiService.send.mockReturnValue(Promise.resolve(mockConfigurationResponses)); + + const result = await service.getOrganizationIntegrationConfigurations(orgId, integrationId); + expect(result).toEqual(mockConfigurationResponses); + expect(apiService.send).toHaveBeenCalledWith( + "GET", + `organizations/${orgId}/integrations/${integrationId}/configurations`, + null, + true, + true, + ); + }); + + it("should call apiService.send with correct parameters for createOrganizationIntegrationConfiguration", async () => { + const request = new OrganizationIntegrationConfigurationRequest( + null, + null, + null, + "{ 'event': '#EventMessage#', 'source': 'Bitwarden', 'index': 'testIndex' }", + ); + const orgId = "org1" as OrganizationId; + const integrationId = "integration1" as OrganizationIntegrationId; + + apiService.send.mockReturnValue(Promise.resolve(mockConfigurationResponse)); + + const result = await service.createOrganizationIntegrationConfiguration( + orgId, + integrationId, + request, + ); + expect(result.eventType).toEqual(mockConfigurationResponse.eventType); + expect(result.template).toEqual(mockConfigurationResponse.template); + expect(apiService.send).toHaveBeenCalledWith( + "POST", + `organizations/${orgId}/integrations/${integrationId}/configurations`, + request, + true, + true, + ); + }); + + it("should call apiService.send with correct parameters for updateOrganizationIntegrationConfiguration", async () => { + const request = new OrganizationIntegrationConfigurationRequest( + null, + null, + null, + "{ 'event': '#EventMessage#', 'source': 'Bitwarden', 'index': 'testIndex' }", + ); + const orgId = "org1" as OrganizationId; + const integrationId = "integration1" as OrganizationIntegrationId; + const configurationId = "configurationId" as OrganizationIntegrationConfigurationId; + + apiService.send.mockReturnValue(Promise.resolve(mockConfigurationResponse)); + + const result = await service.updateOrganizationIntegrationConfiguration( + orgId, + integrationId, + configurationId, + request, + ); + expect(result.eventType).toEqual(mockConfigurationResponse.eventType); + expect(result.template).toEqual(mockConfigurationResponse.template); + expect(apiService.send).toHaveBeenCalledWith( + "PUT", + `organizations/${orgId}/integrations/${integrationId}/configurations/${configurationId}`, + request, + true, + true, + ); + }); + + it("should call apiService.send with correct parameters for deleteOrganizationIntegrationConfiguration", async () => { + const orgId = "org1" as OrganizationId; + const integrationId = "integration1" as OrganizationIntegrationId; + const configurationId = "configurationId" as OrganizationIntegrationConfigurationId; + + await service.deleteOrganizationIntegrationConfiguration(orgId, integrationId, configurationId); + + expect(apiService.send).toHaveBeenCalledWith( + "DELETE", + `organizations/${orgId}/integrations/${integrationId}/configurations/${configurationId}`, + null, + true, + false, + ); + }); +}); diff --git a/bitwarden_license/bit-common/src/dirt/integrations/services/organization-integration-configuration-api.service.ts b/bitwarden_license/bit-common/src/dirt/integrations/services/organization-integration-configuration-api.service.ts new file mode 100644 index 0000000000..b5bac73b28 --- /dev/null +++ b/bitwarden_license/bit-common/src/dirt/integrations/services/organization-integration-configuration-api.service.ts @@ -0,0 +1,75 @@ +import { Injectable } from "@angular/core"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { + OrganizationId, + OrganizationIntegrationConfigurationId, + OrganizationIntegrationId, +} from "@bitwarden/common/types/guid"; + +import { OrganizationIntegrationConfigurationRequest } from "../models/organization-integration-configuration-request"; +import { OrganizationIntegrationConfigurationResponse } from "../models/organization-integration-configuration-response"; + +@Injectable() +export class OrganizationIntegrationConfigurationApiService { + constructor(private apiService: ApiService) {} + + async getOrganizationIntegrationConfigurations( + orgId: OrganizationId, + integrationId: OrganizationIntegrationId, + ): Promise { + const responses = await this.apiService.send( + "GET", + `organizations/${orgId}/integrations/${integrationId}/configurations`, + null, + true, + true, + ); + return responses; + } + + async createOrganizationIntegrationConfiguration( + orgId: OrganizationId, + integrationId: OrganizationIntegrationId, + request: OrganizationIntegrationConfigurationRequest, + ): Promise { + const response = await this.apiService.send( + "POST", + `organizations/${orgId}/integrations/${integrationId}/configurations`, + request, + true, + true, + ); + return response; + } + + async updateOrganizationIntegrationConfiguration( + orgId: OrganizationId, + integrationId: OrganizationIntegrationId, + configurationId: OrganizationIntegrationConfigurationId, + request: OrganizationIntegrationConfigurationRequest, + ): Promise { + const response = await this.apiService.send( + "PUT", + `organizations/${orgId}/integrations/${integrationId}/configurations/${configurationId}`, + request, + true, + true, + ); + return response; + } + + async deleteOrganizationIntegrationConfiguration( + orgId: OrganizationId, + integrationId: OrganizationIntegrationId, + configurationId: OrganizationIntegrationConfigurationId, + ): Promise { + await this.apiService.send( + "DELETE", + `organizations/${orgId}/integrations/${integrationId}/configurations/${configurationId}`, + null, + true, + false, + ); + } +} diff --git a/libs/common/src/types/guid.ts b/libs/common/src/types/guid.ts index 5edd34e4fc..bd0980cd36 100644 --- a/libs/common/src/types/guid.ts +++ b/libs/common/src/types/guid.ts @@ -15,3 +15,8 @@ export type IndexedEntityId = Opaque; export type SecurityTaskId = Opaque; export type NotificationId = Opaque; export type EmergencyAccessId = Opaque; +export type OrganizationIntegrationId = Opaque; +export type OrganizationIntegrationConfigurationId = Opaque< + string, + "OrganizationIntegrationConfigurationId" +>;