1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-05 19:23:19 +00:00

PM-23824 updated PR to move as much code to the service and remove code from components

This commit is contained in:
voommen-livefront
2025-08-13 10:06:17 -05:00
parent bdddaedab2
commit 054eb6c091
8 changed files with 213 additions and 385 deletions

View File

@@ -2,10 +2,10 @@
// @ts-strict-ignore
import { Component, OnDestroy, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { Observable, Subject, switchMap, takeUntil, combineLatest } from "rxjs";
import { Observable, Subject, switchMap, takeUntil } from "rxjs";
// eslint-disable-next-line no-restricted-imports
import { HecConfiguration } from "@bitwarden/bit-common/dirt/integrations";
import { OrganizationIntegrationServiceType } from "@bitwarden/bit-common/dirt/integrations";
import {
getOrganizationById,
OrganizationService,
@@ -36,7 +36,6 @@ import { OrganizationIntegrationService } from "../shared/components/integration
],
})
export class AdminConsoleIntegrationsComponent implements OnInit, OnDestroy {
// integrationsList: Integration[] = [];
tabIndex: number;
organization$: Observable<Organization>;
isEventBasedIntegrationsEnabled: boolean = false;
@@ -222,7 +221,7 @@ export class AdminConsoleIntegrationsComponent implements OnInit, OnDestroy {
async ngOnInit(): Promise<void> {
const orgId = this.route.snapshot.params.organizationId;
await this.organizationIntegrationService.getIntegrationsAndConfigurations(orgId);
await this.organizationIntegrationService.setOrganizationId(orgId, this.integrationsList);
this.organization$ = this.route.params.pipe(
switchMap((params) =>
@@ -236,41 +235,10 @@ export class AdminConsoleIntegrationsComponent implements OnInit, OnDestroy {
),
);
combineLatest([
this.organizationIntegrationService.integrations$,
this.organizationIntegrationService.integrationConfigurations$,
])
this.organizationIntegrationService.integrationList$
.pipe(takeUntil(this.destroy$))
.subscribe(([integrations, integrationConfigurations]) => {
const existingIntegrations = [...this.integrationsList];
// Update the integrations list with the fetched integrations
if (integrations && integrations.length > 0) {
integrations.forEach((integration) => {
const configJson = this.organizationIntegrationService.convertToJson<HecConfiguration>(
integration.configuration,
);
const serviceName = configJson?.service ?? "";
const existingIntegration = existingIntegrations.find((i) => i.name === serviceName);
if (existingIntegration) {
// update integrations
existingIntegration.isConnected = !!integration.configuration;
existingIntegration.configuration = integration.configuration || "";
const template = this.organizationIntegrationService.getIntegrationConfiguration(
integration.id,
serviceName,
integrationConfigurations,
);
existingIntegration.template = JSON.stringify(template || {});
}
});
}
// update the integrations list
this.integrationsList = existingIntegrations;
.subscribe((integrations) => {
this.integrationsList = integrations;
});
}
@@ -290,7 +258,7 @@ export class AdminConsoleIntegrationsComponent implements OnInit, OnDestroy {
if (this.isEventBasedIntegrationsEnabled) {
this.integrationsList.push({
name: "Crowdstrike",
name: OrganizationIntegrationServiceType.CrowdStrike,
linkURL: "",
image: "../../../../../../../images/integrations/logo-crowdstrike-black.svg",
type: IntegrationType.EVENT,

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import {
AfterViewInit,
Component,
@@ -13,11 +11,6 @@ import { ActivatedRoute } from "@angular/router";
import { Observable, Subject, combineLatest, lastValueFrom, takeUntil } from "rxjs";
import { SYSTEM_THEME_OBSERVABLE } from "@bitwarden/angular/services/injection-tokens";
// eslint-disable-next-line no-restricted-imports
import {
HecConfiguration,
HecConfigurationTemplate,
} from "@bitwarden/bit-common/dirt/integrations";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { ThemeType } from "@bitwarden/common/platform/enums";
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
@@ -26,7 +19,7 @@ import { DialogService, ToastService } from "@bitwarden/components";
import { SharedModule } from "../../../../../../shared/shared.module";
import { openHecConnectDialog } from "../integration-dialog/index";
import { Integration } from "../models";
import { HecConfiguration, HecConfigurationTemplate, Integration } from "../models";
import { OrganizationIntegrationService } from "../services/organization-integration.service";
@Component({
@@ -36,13 +29,13 @@ import { OrganizationIntegrationService } from "../services/organization-integra
})
export class IntegrationCardComponent implements AfterViewInit, OnDestroy {
private destroyed$: Subject<void> = new Subject();
@ViewChild("imageEle") imageEle: ElementRef<HTMLImageElement>;
@ViewChild("imageEle") imageEle!: ElementRef<HTMLImageElement>;
@Input() name: string;
@Input() image: string;
@Input() imageDarkMode?: string;
@Input() linkURL: string;
@Input() integrationSettings: Integration;
@Input() name: string = "";
@Input() image: string = "";
@Input() imageDarkMode: string = "";
@Input() linkURL: string = "";
@Input() integrationSettings!: Integration;
/** Adds relevant `rel` attribute to external links */
@Input() externalURL?: boolean;
@@ -69,13 +62,13 @@ export class IntegrationCardComponent implements AfterViewInit, OnDestroy {
private organizationIntegrationService: OrganizationIntegrationService,
private toastService: ToastService,
private i18nService: I18nService,
) {}
ngAfterViewInit() {
) {
this.organizationId = this.activatedRoute.snapshot.paramMap.get(
"organizationId",
) as OrganizationId;
}
ngAfterViewInit() {
combineLatest([this.themeStateService.selectedTheme$, this.systemTheme$])
.pipe(takeUntil(this.destroyed$))
.subscribe(([theme, systemTheme]) => {
@@ -134,12 +127,6 @@ export class IntegrationCardComponent implements AfterViewInit, OnDestroy {
const dialog = openHecConnectDialog(this.dialogService, {
data: {
settings: this.integrationSettings,
configuration: this.organizationIntegrationService.convertToJson<HecConfiguration>(
this.integrationSettings.configuration,
),
template: this.organizationIntegrationService.convertToJson<HecConfigurationTemplate>(
this.integrationSettings.template,
),
},
});
@@ -156,26 +143,16 @@ export class IntegrationCardComponent implements AfterViewInit, OnDestroy {
// save the integration
try {
const dbResponse = await this.organizationIntegrationService.saveHec(
await this.organizationIntegrationService.saveHec(
this.organizationId,
this.integrationSettings.name,
integration,
configurationTemplate,
);
if (!!dbResponse.integration && !!dbResponse.configuration) {
this.isConnected = true;
this.integrationSettings.configuration = dbResponse.integration.configuration;
this.integrationSettings.template = dbResponse.configuration.template;
}
} catch (err) {
this.isConnected = false;
// eslint-disable-next-line no-console
console.error("Failed to save integration", err);
} catch {
this.toastService.showToast({
variant: "error",
title: null,
title: "",
message: this.i18nService.t("failedToSaveIntegration"),
});
return;

View File

@@ -1,11 +1,6 @@
import { Component, Inject, OnInit } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
// eslint-disable-next-line no-restricted-imports
import {
HecConfiguration,
HecConfigurationTemplate,
} from "@bitwarden/bit-common/dirt/integrations";
import { DIALOG_DATA, DialogConfig, DialogRef, DialogService } from "@bitwarden/components";
import { SharedModule } from "@bitwarden/web-vault/app/shared";
@@ -13,8 +8,6 @@ import { Integration } from "../../models";
export type HecConnectDialogParams = {
settings: Integration;
configuration: HecConfiguration | null;
template: HecConfigurationTemplate | null;
};
export interface HecConnectDialogResult {
@@ -48,15 +41,15 @@ export class ConnectHecDialogComponent implements OnInit {
ngOnInit(): void {
this.formGroup.patchValue({
url: this.connectInfo.configuration?.uri || "",
bearerToken: this.connectInfo.configuration?.token || "",
index: this.connectInfo.template?.index || "",
url: this.connectInfo.settings.HecConfiguration?.uri || "",
bearerToken: this.connectInfo.settings.HecConfiguration?.token || "",
index: this.connectInfo.settings.HecConfigurationTemplate?.index || "",
service: this.connectInfo.settings.name,
});
}
isUpdateAvailable(): boolean {
return !!this.connectInfo.configuration;
return !!this.connectInfo.settings.HecConfiguration;
}
getSettingsAsJson(configuration: string) {

View File

@@ -1,3 +1,5 @@
// eslint-disable-next-line no-restricted-imports
import { OrganizationIntegrationServiceType } from "@bitwarden/bit-common/dirt/integrations";
import { IntegrationType } from "@bitwarden/common/enums";
/** Integration or SDK */
@@ -22,4 +24,48 @@ export type Integration = {
canSetupConnection?: boolean;
configuration?: string;
template?: string;
HecConfiguration?: HecConfiguration | null;
HecConfigurationTemplate?: HecConfigurationTemplate | null;
};
/*
* Represents the configuration for a HEC (HTTP Event Collector) integration.
* Configuration model that is required by OrganizationIntegration.
*/
export class HecConfiguration {
uri: string;
scheme = "Bearer";
token: string;
service: OrganizationIntegrationServiceType;
constructor(uri: string, token: string, service: string) {
this.uri = uri;
this.token = token;
this.service = service as OrganizationIntegrationServiceType;
}
toString(): string {
return JSON.stringify(this);
}
}
/**
* Represents the configuration template for a HEC (HTTP Event Collector) integration.
* from OrganizationIntegrationConfiguration
*/
export class HecConfigurationTemplate {
event = "#EventMessage";
source = "Bitwarden";
index: string;
service: OrganizationIntegrationServiceType;
constructor(index: string, service: string) {
this.index = index;
this.service = service as OrganizationIntegrationServiceType;
}
toString(): string {
return JSON.stringify(this);
}
}

View File

@@ -32,92 +32,6 @@ describe("OrganizationIntegrationService", () => {
expect(service).toBeTruthy();
});
describe("getIntegrationsAndConfigurations", () => {
const orgId = "org-123" as any;
beforeEach(() => {
mockOrgIntegrationApiService.getOrganizationIntegrations.mockReset();
mockOrgIntegrationConfigurationApiService.getOrganizationIntegrationConfigurations.mockReset();
service["integrations"].next([]);
service["integrationConfigurations"].next([]);
});
it("should fetch integrations and their configurations and update observables", async () => {
const integration1 = { id: "int-1", type: "type1" } as any;
const integration2 = { id: "int-2", type: "type2" } as any;
const config1 = [{ id: "conf-1", template: "{}" }] as any;
const config2 = [{ id: "conf-2", template: "{}" }] as any;
mockOrgIntegrationApiService.getOrganizationIntegrations.mockResolvedValue([
integration1,
integration2,
]);
mockOrgIntegrationConfigurationApiService.getOrganizationIntegrationConfigurations.mockImplementation(
(_, integrationId) => {
if (integrationId === "int-1") {
return Promise.resolve(config1);
}
if (integrationId === "int-2") {
return Promise.resolve(config2);
}
return Promise.resolve([]);
},
);
const result = await service.getIntegrationsAndConfigurations(orgId);
expect(mockOrgIntegrationApiService.getOrganizationIntegrations).toHaveBeenCalledWith(orgId);
expect(
mockOrgIntegrationConfigurationApiService.getOrganizationIntegrationConfigurations,
).toHaveBeenCalledWith(orgId, "int-1");
expect(
mockOrgIntegrationConfigurationApiService.getOrganizationIntegrationConfigurations,
).toHaveBeenCalledWith(orgId, "int-2");
expect(result.integrations).toEqual([integration1, integration2]);
expect(result.configurations.length).toBe(2);
expect(result.configurations[0].integrationId).toBe("int-1");
expect(result.configurations[1].integrationId).toBe("int-2");
// Check observables updated
service.integrations$.subscribe((integrations) => {
expect(integrations).toEqual([integration1, integration2]);
});
service.integrationConfigurations$.subscribe((configs) => {
expect(configs.length).toBe(2);
});
});
it("should handle no integrations", async () => {
mockOrgIntegrationApiService.getOrganizationIntegrations.mockResolvedValue([]);
const result = await service.getIntegrationsAndConfigurations(orgId);
expect(result.integrations).toEqual([]);
expect(result.configurations).toEqual([]);
service.integrations$.subscribe((integrations) => {
expect(integrations).toEqual([]);
});
service.integrationConfigurations$.subscribe((configs) => {
expect(configs).toEqual([]);
});
});
it("should handle integrations with no configurations", async () => {
const integration = { id: "int-1", type: "type1" } as any;
mockOrgIntegrationApiService.getOrganizationIntegrations.mockResolvedValue([integration]);
mockOrgIntegrationConfigurationApiService.getOrganizationIntegrationConfigurations.mockResolvedValue(
[],
);
const result = await service.getIntegrationsAndConfigurations(orgId);
expect(result.integrations).toEqual([integration]);
expect(result.configurations.length).toBe(1);
expect(result.configurations[0].integrationId).toBe("int-1");
expect(result.configurations[0].configurationResponses).toEqual([]);
});
});
describe("saveHec", () => {
const organizationId = "org-123" as any;
const serviceName = "splunk";
@@ -127,8 +41,6 @@ describe("OrganizationIntegrationService", () => {
beforeEach(() => {
mockOrgIntegrationApiService.getOrganizationIntegrations.mockReset();
mockOrgIntegrationConfigurationApiService.getOrganizationIntegrationConfigurations.mockReset();
service["integrations"].next([]);
service["integrationConfigurations"].next([]);
jest.clearAllMocks();
});
@@ -185,123 +97,6 @@ describe("OrganizationIntegrationService", () => {
});
});
describe("getIntegrationConfiguration", () => {
const integrationId = "int-1" as any;
const serviceName = "splunk";
const otherServiceName = "datadog";
it("should return null if integrationConfigurations is empty", () => {
const result = service.getIntegrationConfiguration(integrationId, serviceName, []);
expect(result).toBeNull();
});
it("should return null if no integrationConfigs found for integrationId", () => {
const integrationConfigurations = [
{
integrationId: "int-2",
configurationResponses: [{ id: "conf-1", template: '{"service":"splunk"}' }],
},
] as any;
const result = service.getIntegrationConfiguration(
integrationId,
serviceName,
integrationConfigurations,
);
expect(result).toBeNull();
});
it("should return null if no configurationResponses match the service", () => {
const sampleIntegrationConfigurations = [
{
integrationId,
configurationResponses: [{ id: "conf-1", template: '{"service":"splunk"}' }],
},
] as any;
const result = service.getIntegrationConfiguration(
integrationId,
otherServiceName,
sampleIntegrationConfigurations,
);
expect(result).toBeNull();
});
it("should return the configuration template if service matches", () => {
const template = { service: "splunk", token: "abc" };
const integrationConfigurations = [
{
integrationId,
configurationResponses: [
{ id: "conf-1", template: JSON.stringify(template) },
{ id: "conf-2", template: '{"service":"datadog"}' },
],
},
] as any;
const result = service.getIntegrationConfiguration(
integrationId,
serviceName,
integrationConfigurations,
);
expect(result).toEqual(template);
});
it("should return the first matching configuration if multiple match", () => {
const template1 = { service: "splunk", token: "abc" };
const template2 = { service: "splunk", token: "def" };
const integrationConfigurations = [
{
integrationId,
configurationResponses: [
{ id: "conf-1", template: JSON.stringify(template1) },
{ id: "conf-2", template: JSON.stringify(template2) },
],
},
] as any;
const result = service.getIntegrationConfiguration(
integrationId,
serviceName,
integrationConfigurations,
);
expect(result).toEqual(template1);
});
it("should skip invalid JSON templates", () => {
const template = { service: "splunk", token: "abc" };
const integrationConfigurations = [
{
integrationId,
configurationResponses: [
{ id: "conf-1", template: "invalid-json" },
{ id: "conf-2", template: JSON.stringify(template) },
],
},
] as any;
const result = service.getIntegrationConfiguration(
integrationId,
serviceName,
integrationConfigurations,
);
expect(result).toEqual(template);
});
it("should return null if all templates are invalid JSON", () => {
const integrationConfigurations = [
{
integrationId,
configurationResponses: [
{ id: "conf-1", template: "invalid-json" },
{ id: "conf-2", template: "" },
],
},
] as any;
const result = service.getIntegrationConfiguration(
integrationId,
serviceName,
integrationConfigurations,
);
expect(result).toBeNull();
});
});
describe("convertToJson", () => {
it("should parse valid JSON string and return object", () => {
const jsonString = '{"foo":"bar","num":42}';

View File

@@ -1,5 +1,13 @@
import { Injectable } from "@angular/core";
import { BehaviorSubject } from "rxjs";
import {
BehaviorSubject,
combineLatest,
firstValueFrom,
Subject,
switchMap,
takeUntil,
zip,
} from "rxjs";
// eslint-disable-next-line no-restricted-imports
import {
@@ -7,82 +15,152 @@ import {
OrganizationIntegrationConfigurationApiService,
OrganizationIntegrationConfigurationRequest,
OrganizationIntegrationConfigurationResponse,
HecConfiguration,
OrganizationIntegrationRequest,
OrganizationIntegrationResponse,
OrganizationIntegrationType,
HecConfigurationTemplate,
OrganizationIntegrationConfigurationResponseWithIntegrationId,
} from "@bitwarden/bit-common/dirt/integrations";
import { OrganizationId, OrganizationIntegrationId } from "@bitwarden/common/types/guid";
import { HecConfiguration, HecConfigurationTemplate, Integration } from "../models";
@Injectable({
providedIn: "root",
})
export class OrganizationIntegrationService {
private integrations = new BehaviorSubject<OrganizationIntegrationResponse[]>([]);
integrations$ = this.integrations.asObservable();
private integrationConfigurations = new BehaviorSubject<
private destroy$ = new Subject<void>();
private organizationId$ = new BehaviorSubject<OrganizationId | null>(null);
private integrations$ = new BehaviorSubject<OrganizationIntegrationResponse[]>([]);
private integrationConfigurations$ = new BehaviorSubject<
OrganizationIntegrationConfigurationResponseWithIntegrationId[]
>([]);
integrationConfigurations$ = this.integrationConfigurations.asObservable();
private masterIntegrationList$ = new BehaviorSubject<Integration[]>([]);
integrationList$ = this.masterIntegrationList$.asObservable();
// retrieve integrations and configurations from the DB
private fetch$ = this.organizationId$
.pipe(
switchMap(async (orgId) => {
if (orgId) {
const data$ = await this.getIntegrationsAndConfigurations(orgId);
return await firstValueFrom(data$);
} else {
return {
integrations: this.integrations$.value,
configurations: this.integrationConfigurations$.value,
};
}
}),
takeUntil(this.destroy$),
)
.subscribe({
next: ({ integrations, configurations }) => {
// update the integrations
this.integrations$.next(integrations);
this.integrationConfigurations$.next(configurations);
},
});
// Update the master Integration list - if any of the integrations or configurations change
private mapping$ = combineLatest([this.integrations$, this.integrationConfigurations$])
.pipe(takeUntil(this.destroy$))
.subscribe(([integrations, configurations]) => {
const existingIntegrations = [...this.masterIntegrationList$.value];
// Update the integrations list with the fetched integrations
if (integrations && integrations.length > 0) {
integrations.forEach((integration) => {
const hecConfigJson = this.convertToJson<HecConfiguration>(integration.configuration);
const serviceName = hecConfigJson?.service ?? "";
const existingIntegration = existingIntegrations.find((i) => i.name === serviceName);
if (existingIntegration) {
// update integrations
existingIntegration.isConnected = !!integration.configuration;
existingIntegration.configuration = integration.configuration || "";
existingIntegration.HecConfiguration = hecConfigJson;
const template = this.getIntegrationConfiguration(
integration.id,
serviceName,
configurations,
);
existingIntegration.HecConfigurationTemplate = template;
existingIntegration.template = JSON.stringify(template || {});
}
});
}
// update the integrations list
this.masterIntegrationList$.next(existingIntegrations);
});
constructor(
private integrationApiService: OrganizationIntegrationApiService,
private integrationConfigurationApiService: OrganizationIntegrationConfigurationApiService,
) {}
/*
* Fetches the integrations and their configurations for a specific organization.
* @param orgId The ID of the organization.
* Invoke this method to retrieve the integrations into observable.
*/
async getIntegrationsAndConfigurations(orgId: OrganizationId) {
const promises: Promise<void>[] = [];
const integrations = await this.integrationApiService.getOrganizationIntegrations(orgId);
const integrationConfigurations: OrganizationIntegrationConfigurationResponseWithIntegrationId[] =
[];
integrations.forEach((integration) => {
const promise = this.integrationConfigurationApiService
.getOrganizationIntegrationConfigurations(orgId, integration.id)
.then((configs) => {
const mappedConfigurations =
new OrganizationIntegrationConfigurationResponseWithIntegrationId(
integration.id,
configs,
);
integrationConfigurations.push(mappedConfigurations);
});
promises.push(promise);
});
await Promise.all(promises);
this.integrations.next(integrations);
this.integrationConfigurations.next(integrationConfigurations);
return {
integrations: integrations,
configurations: integrationConfigurations,
};
setOrganizationId(orgId: OrganizationId, integrationList: Integration[]) {
this.organizationId$.next(orgId);
this.masterIntegrationList$.next(integrationList);
}
private async getIntegrationsAndConfigurations(orgId: OrganizationId) {
const results$ = zip(this.integrationApiService.getOrganizationIntegrations(orgId)).pipe(
switchMap(([integrations]) => {
const integrationConfigurations: OrganizationIntegrationConfigurationResponseWithIntegrationId[] =
[];
const promises: Promise<void>[] = [];
integrations.forEach((integration) => {
const promise = this.integrationConfigurationApiService
.getOrganizationIntegrationConfigurations(orgId, integration.id)
.then((configs) => {
const mappedConfigurations =
new OrganizationIntegrationConfigurationResponseWithIntegrationId(
integration.id,
configs,
);
integrationConfigurations.push(mappedConfigurations);
});
promises.push(promise);
});
return Promise.all(promises).then(() => {
return { integrations, configurations: integrationConfigurations };
});
}),
);
return results$;
}
/*
* Saves the HEC integration configuration for a specific organization.
* @param organizationId The ID of the organization.
* @param service The name of the service.
* @param hecConfiguration The HEC integration configuration.
* @returns The saved or updated integration response.
*/
async saveHec(
organizationId: OrganizationId,
service: string,
hecConfiguration: HecConfiguration,
hecConfigurationTemplate: HecConfigurationTemplate,
) {
): Promise<{
integration: OrganizationIntegrationResponse;
configuration: OrganizationIntegrationConfigurationResponse;
}> {
// save the Hec Integration record
const integrationResponse = await this.saveHecIntegration(organizationId, hecConfiguration);
if (!integrationResponse.id) {
throw new Error("Failed to save HEC integration");
}
// Save the configuration for the HEC integration
// Save the configuration for the HEC integration record
const configurationResponse = await this.saveHecIntegrationConfiguration(
organizationId,
integrationResponse.id,
@@ -121,7 +199,7 @@ export class OrganizationIntegrationService {
);
// find the existing integration
const existingIntegration = this.integrations.value.find(
const existingIntegration = this.integrations$.value.find(
(i) => i.type === OrganizationIntegrationType.Hec,
);
@@ -134,14 +212,14 @@ export class OrganizationIntegrationService {
);
// update our observable with the updated integration
const updatedIntegrations = this.integrations.value.map((integration) => {
const updatedIntegrations = this.integrations$.value.map((integration) => {
if (integration.id === existingIntegration.id) {
return updatedIntegration;
}
return integration;
});
this.integrations.next(updatedIntegrations);
this.integrations$.next(updatedIntegrations);
return updatedIntegration;
} else {
@@ -152,7 +230,7 @@ export class OrganizationIntegrationService {
);
// add this to our integrations observable
this.integrations.next([...this.integrations.value, newIntegration]);
this.integrations$.next([...this.integrations$.value, newIntegration]);
return newIntegration;
}
}
@@ -180,14 +258,15 @@ export class OrganizationIntegrationService {
configurationTemplate.toString(),
);
// check if we have an existing configuration for this integration - in case of new records
const integrationConfigurations = this.integrationConfigurations.value;
// check if we have an existing configuration for this integration
const integrationConfigurations = this.integrationConfigurations$.value;
// find the existing configuration
// find the existing configuration by integrationId
const existingConfigurations = integrationConfigurations
.filter((config) => config.integrationId === integrationId)
.flatMap((config) => config.configurationResponses || []);
// find the configuration by service
const existingConfiguration =
existingConfigurations.length > 0
? existingConfigurations.find(
@@ -218,7 +297,7 @@ export class OrganizationIntegrationService {
}
});
this.integrationConfigurations.next(integrationConfigurations);
this.integrationConfigurations$.next(integrationConfigurations);
return updatedConfiguration;
} else {
@@ -234,16 +313,22 @@ export class OrganizationIntegrationService {
const integrationConfig = integrationConfigurations.find(
(config) => config.integrationId === integrationId,
);
if (integrationConfig) {
integrationConfig.configurationResponses.push(newConfiguration);
} else {
integrationConfigurations.push({
integrationId,
configurationResponses: [newConfiguration],
});
}
this.integrationConfigurations.next(integrationConfigurations);
this.integrationConfigurations$.next(integrationConfigurations);
return newConfiguration;
}
}
getIntegrationConfiguration(
private getIntegrationConfiguration(
integrationId: OrganizationIntegrationId,
service: string,
integrationConfigurations: OrganizationIntegrationConfigurationResponseWithIntegrationId[],
@@ -270,9 +355,9 @@ export class OrganizationIntegrationService {
return null;
}
convertToJson<T>(jsonString: string): T | null {
convertToJson<T>(jsonString?: string): T | null {
try {
return JSON.parse(jsonString) as T;
return JSON.parse(jsonString || "") as T;
} catch {
return null;
}

View File

@@ -4,3 +4,4 @@ export * from "./models/organization-integration-request";
export * from "./models/organization-integration-response";
export * from "./models/organization-integration-configuration-request";
export * from "./models/organization-integration-configuration-response";
export * from "./models/organization-integration-service-type";

View File

@@ -9,40 +9,3 @@ export class OrganizationIntegrationRequest {
this.configuration = configuration;
}
}
/*
* Represents the configuration for a HEC (HTTP Event Collector) integration.
* Configuration model that is required by OrganizationIntegration.
*/
export class HecConfiguration {
uri: string;
scheme = "Bearer";
token: string;
service: string;
constructor(uri: string, token: string, service: string) {
this.uri = uri;
this.token = token;
this.service = service;
}
toString(): string {
return JSON.stringify(this);
}
}
export class HecConfigurationTemplate {
event = "#EventMessage";
source = "Bitwarden";
index: string;
service: string;
constructor(index: string, service: string) {
this.index = index;
this.service = service;
}
toString(): string {
return JSON.stringify(this);
}
}