diff --git a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html
index 198cb3a47cd..79cef26042d 100644
--- a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html
+++ b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html
@@ -79,7 +79,7 @@
{{ "deviceManagement" | i18n }}
diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/device-management/device-management.component.ts b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/device-management/device-management.component.ts
index dc9631119a3..755a22e6ee4 100644
--- a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/device-management/device-management.component.ts
+++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/device-management/device-management.component.ts
@@ -1,13 +1,27 @@
import { Component, OnInit } from "@angular/core";
+import { IntegrationType } from "@bitwarden/common/enums/integration-type.enum";
+import { SharedModule } from "@bitwarden/web-vault/app/shared";
+
+import { IntegrationGridComponent } from "../integration-grid/integration-grid.component";
+import { FilterIntegrationsPipe } from "../integrations.pipe";
+import { OrganizationIntegrationsState } from "../organization-integrations.state";
+
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "device-management",
templateUrl: "device-management.component.html",
+ imports: [SharedModule, IntegrationGridComponent, FilterIntegrationsPipe],
})
export class DeviceManagementComponent implements OnInit {
- constructor() {}
+ integrations$ = this.state.integrations$;
+
+ constructor(private state: OrganizationIntegrationsState) {}
ngOnInit() {}
+
+ get IntegrationType(): typeof IntegrationType {
+ return IntegrationType;
+ }
}
diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/event-management/event-management.component.html b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/event-management/event-management.component.html
index c166be6cce8..92456323f15 100644
--- a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/event-management/event-management.component.html
+++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/event-management/event-management.component.html
@@ -1,3 +1,5 @@
+@let integrationsList = integrations$ | async;
+
{{ "eventManagement" | i18n }}
diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/event-management/event-management.component.ts b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/event-management/event-management.component.ts
index 1f5d0f63b27..db6b246dcda 100644
--- a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/event-management/event-management.component.ts
+++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/event-management/event-management.component.ts
@@ -1,13 +1,26 @@
import { Component, OnInit } from "@angular/core";
+import { IntegrationType } from "@bitwarden/common/enums/integration-type.enum";
+import { SharedModule } from "@bitwarden/web-vault/app/shared";
+
+import { IntegrationGridComponent } from "../integration-grid/integration-grid.component";
+import { FilterIntegrationsPipe } from "../integrations.pipe";
+import { OrganizationIntegrationsState } from "../organization-integrations.state";
+
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "event-management",
- template: "event-management.component.html",
+ templateUrl: "event-management.component.html",
+ imports: [SharedModule, IntegrationGridComponent, FilterIntegrationsPipe],
})
export class EventManagementComponent implements OnInit {
- constructor() {}
+ integrations$ = this.state.integrations$;
+ constructor(private state: OrganizationIntegrationsState) {}
ngOnInit() {}
+
+ get IntegrationType(): typeof IntegrationType {
+ return IntegrationType;
+ }
}
diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integrations.component.html b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integrations.component.html
index 43c8eebb59a..1c8c3e9bd0e 100644
--- a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integrations.component.html
+++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integrations.component.html
@@ -1,11 +1,9 @@
-@let organization = organization$ | async;
-
@if (organization) {
- {{ "singleSignOn" | i18n }}
+ {{ "singleSignOn" | i18n }}
@if (organization?.useScim || organization?.useDirectory) {
- {{ "userProvisioning" | i18n }}
+ {{ "userProvisioning" | i18n }}
}
@if (organization?.useEvents) {
{{ "eventManagement" | i18n }}
diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integrations.component.ts b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integrations.component.ts
index 6517182b21e..dcb27461d05 100644
--- a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integrations.component.ts
+++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integrations.component.ts
@@ -1,24 +1,9 @@
-import { Component, OnDestroy, OnInit } from "@angular/core";
-import { ActivatedRoute } from "@angular/router";
-import { firstValueFrom, Observable, Subject, switchMap, takeUntil, takeWhile } from "rxjs";
+import { Component } from "@angular/core";
-import { Integration } from "@bitwarden/bit-common/dirt/organization-integrations/models/integration";
-import { OrganizationIntegrationServiceName } from "@bitwarden/bit-common/dirt/organization-integrations/models/organization-integration-service-type";
-import { OrganizationIntegrationType } from "@bitwarden/bit-common/dirt/organization-integrations/models/organization-integration-type";
-import { OrganizationIntegrationService } from "@bitwarden/bit-common/dirt/organization-integrations/services/organization-integration-service";
-import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
-import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
-import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
-import { getUserId } from "@bitwarden/common/auth/services/account.service";
-import { IntegrationType } from "@bitwarden/common/enums";
-import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
-import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
-import { getById } from "@bitwarden/common/platform/misc";
import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module";
import { SharedModule } from "@bitwarden/web-vault/app/shared";
-import { IntegrationGridComponent } from "./integration-grid/integration-grid.component";
-import { FilterIntegrationsPipe } from "./integrations.pipe";
+import { OrganizationIntegrationsState } from "./organization-integrations.state";
// attempted, but because bit-tab-group is not OnPush, caused more issues than it solved
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
@@ -26,291 +11,11 @@ import { FilterIntegrationsPipe } from "./integrations.pipe";
@Component({
selector: "ac-integrations",
templateUrl: "./integrations.component.html",
- imports: [SharedModule, IntegrationGridComponent, HeaderModule, FilterIntegrationsPipe],
+ imports: [SharedModule, HeaderModule],
})
-export class AdminConsoleIntegrationsComponent implements OnInit, OnDestroy {
- tabIndex: number = 0;
- organization$: Observable = new Observable();
- isEventManagementForDataDogAndCrowdStrikeEnabled: boolean = false;
- private destroy$ = new Subject();
+export class AdminConsoleIntegrationsComponent {
+ integrations = this.state.integrations;
+ organization = this.state.organization;
- // initialize the integrations list with default integrations
- integrationsList: Integration[] = [
- {
- name: "AD FS",
- linkURL: "https://bitwarden.com/help/saml-adfs/",
- image: "../../../../../../../images/integrations/azure-active-directory.svg",
- type: IntegrationType.SSO,
- },
- {
- name: "Auth0",
- linkURL: "https://bitwarden.com/help/saml-auth0/",
- image: "../../../../../../../images/integrations/logo-auth0-badge-color.svg",
- type: IntegrationType.SSO,
- },
- {
- name: "AWS",
- linkURL: "https://bitwarden.com/help/saml-aws/",
- image: "../../../../../../../images/integrations/aws-color.svg",
- imageDarkMode: "../../../../../../../images/integrations/aws-darkmode.svg",
- type: IntegrationType.SSO,
- },
- {
- name: "Microsoft Entra ID",
- linkURL: "https://bitwarden.com/help/saml-azure/",
- image: "../../../../../../../images/integrations/logo-microsoft-entra-id-color.svg",
- type: IntegrationType.SSO,
- },
- {
- name: "Duo",
- linkURL: "https://bitwarden.com/help/saml-duo/",
- image: "../../../../../../../images/integrations/logo-duo-color.svg",
- type: IntegrationType.SSO,
- },
- {
- name: "Google",
- linkURL: "https://bitwarden.com/help/saml-google/",
- image: "../../../../../../../images/integrations/logo-google-badge-color.svg",
- type: IntegrationType.SSO,
- },
- {
- name: "JumpCloud",
- linkURL: "https://bitwarden.com/help/saml-jumpcloud/",
- image: "../../../../../../../images/integrations/logo-jumpcloud-badge-color.svg",
- imageDarkMode: "../../../../../../../images/integrations/jumpcloud-darkmode.svg",
- type: IntegrationType.SSO,
- },
- {
- name: "KeyCloak",
- linkURL: "https://bitwarden.com/help/saml-keycloak/",
- image: "../../../../../../../images/integrations/logo-keycloak-icon.svg",
- type: IntegrationType.SSO,
- },
- {
- name: "Okta",
- linkURL: "https://bitwarden.com/help/saml-okta/",
- image: "../../../../../../../images/integrations/logo-okta-symbol-black.svg",
- imageDarkMode: "../../../../../../../images/integrations/okta-darkmode.svg",
- type: IntegrationType.SSO,
- },
- {
- name: "OneLogin",
- linkURL: "https://bitwarden.com/help/saml-onelogin/",
- image: "../../../../../../../images/integrations/logo-onelogin-badge-color.svg",
- imageDarkMode: "../../../../../../../images/integrations/onelogin-darkmode.svg",
- type: IntegrationType.SSO,
- },
- {
- name: "PingFederate",
- linkURL: "https://bitwarden.com/help/saml-pingfederate/",
- image: "../../../../../../../images/integrations/logo-ping-identity-badge-color.svg",
- type: IntegrationType.SSO,
- },
- {
- name: "Microsoft Entra ID",
- linkURL: "https://bitwarden.com/help/microsoft-entra-id-scim-integration/",
- image: "../../../../../../../images/integrations/logo-microsoft-entra-id-color.svg",
- type: IntegrationType.SCIM,
- },
- {
- name: "Okta",
- linkURL: "https://bitwarden.com/help/okta-scim-integration/",
- image: "../../../../../../../images/integrations/logo-okta-symbol-black.svg",
- imageDarkMode: "../../../../../../../images/integrations/okta-darkmode.svg",
- type: IntegrationType.SCIM,
- },
- {
- name: "OneLogin",
- linkURL: "https://bitwarden.com/help/onelogin-scim-integration/",
- image: "../../../../../../../images/integrations/logo-onelogin-badge-color.svg",
- imageDarkMode: "../../../../../../../images/integrations/onelogin-darkmode.svg",
- type: IntegrationType.SCIM,
- },
- {
- name: "JumpCloud",
- linkURL: "https://bitwarden.com/help/jumpcloud-scim-integration/",
- image: "../../../../../../../images/integrations/logo-jumpcloud-badge-color.svg",
- imageDarkMode: "../../../../../../../images/integrations/jumpcloud-darkmode.svg",
- type: IntegrationType.SCIM,
- },
- {
- name: "Ping Identity",
- linkURL: "https://bitwarden.com/help/ping-identity-scim-integration/",
- image: "../../../../../../../images/integrations/logo-ping-identity-badge-color.svg",
- type: IntegrationType.SCIM,
- },
- {
- name: "Active Directory",
- linkURL: "https://bitwarden.com/help/ldap-directory/",
- image: "../../../../../../../images/integrations/azure-active-directory.svg",
- type: IntegrationType.BWDC,
- },
- {
- name: "Microsoft Entra ID",
- linkURL: "https://bitwarden.com/help/microsoft-entra-id/",
- image: "../../../../../../../images/integrations/logo-microsoft-entra-id-color.svg",
- type: IntegrationType.BWDC,
- },
- {
- name: "Google Workspace",
- linkURL: "https://bitwarden.com/help/workspace-directory/",
- image: "../../../../../../../images/integrations/logo-google-badge-color.svg",
- type: IntegrationType.BWDC,
- },
- {
- name: "Okta",
- linkURL: "https://bitwarden.com/help/okta-directory/",
- image: "../../../../../../../images/integrations/logo-okta-symbol-black.svg",
- imageDarkMode: "../../../../../../../images/integrations/okta-darkmode.svg",
- type: IntegrationType.BWDC,
- },
- {
- name: "OneLogin",
- linkURL: "https://bitwarden.com/help/onelogin-directory/",
- image: "../../../../../../../images/integrations/logo-onelogin-badge-color.svg",
- imageDarkMode: "../../../../../../../images/integrations/onelogin-darkmode.svg",
- type: IntegrationType.BWDC,
- },
- {
- name: "Splunk",
- linkURL: "https://bitwarden.com/help/splunk-siem/",
- image: "../../../../../../../images/integrations/logo-splunk-black.svg",
- imageDarkMode: "../../../../../../../images/integrations/splunk-darkmode.svg",
- type: IntegrationType.EVENT,
- },
- {
- name: "Microsoft Sentinel",
- linkURL: "https://bitwarden.com/help/microsoft-sentinel-siem/",
- image: "../../../../../../../images/integrations/logo-microsoft-sentinel-color.svg",
- type: IntegrationType.EVENT,
- },
- {
- name: "Rapid7",
- linkURL: "https://bitwarden.com/help/rapid7-siem/",
- image: "../../../../../../../images/integrations/logo-rapid7-black.svg",
- imageDarkMode: "../../../../../../../images/integrations/rapid7-darkmode.svg",
- type: IntegrationType.EVENT,
- },
- {
- name: "Elastic",
- linkURL: "https://bitwarden.com/help/elastic-siem/",
- image: "../../../../../../../images/integrations/logo-elastic-badge-color.svg",
- type: IntegrationType.EVENT,
- },
- {
- name: "Panther",
- linkURL: "https://bitwarden.com/help/panther-siem/",
- image: "../../../../../../../images/integrations/logo-panther-round-color.svg",
- type: IntegrationType.EVENT,
- },
- {
- name: "Sumo Logic",
- linkURL: "https://bitwarden.com/help/sumo-logic-siem/",
- image: "../../../../../../../images/integrations/logo-sumo-logic-siem.svg",
- imageDarkMode: "../../../../../../../images/integrations/logo-sumo-logic-siem-darkmode.svg",
- type: IntegrationType.EVENT,
- newBadgeExpiration: "2025-12-31",
- },
- {
- name: "Microsoft Intune",
- linkURL: "https://bitwarden.com/help/deploy-browser-extensions-with-intune/",
- image: "../../../../../../../images/integrations/logo-microsoft-intune-color.svg",
- type: IntegrationType.DEVICE,
- },
- ];
-
- async ngOnInit() {
- const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
- if (!userId) {
- throw new Error("User ID not found");
- }
-
- this.organization$ = this.route.params.pipe(
- switchMap((params) =>
- this.organizationService.organizations$(userId).pipe(
- getById(params.organizationId),
- // Filter out undefined values
- takeWhile((org: Organization | undefined) => !!org),
- ),
- ),
- );
-
- // Sets the organization ID which also loads the integrations$
- this.organization$
- .pipe(
- switchMap((org) => this.organizationIntegrationService.setOrganizationId(org.id)),
- takeUntil(this.destroy$),
- )
- .subscribe();
- }
-
- constructor(
- private route: ActivatedRoute,
- private organizationService: OrganizationService,
- private accountService: AccountService,
- private configService: ConfigService,
- private organizationIntegrationService: OrganizationIntegrationService,
- ) {
- this.configService
- .getFeatureFlag$(FeatureFlag.EventManagementForDataDogAndCrowdStrike)
- .pipe(takeUntil(this.destroy$))
- .subscribe((isEnabled) => {
- this.isEventManagementForDataDogAndCrowdStrikeEnabled = isEnabled;
- });
-
- // Add the new event based items to the list
- if (this.isEventManagementForDataDogAndCrowdStrikeEnabled) {
- const crowdstrikeIntegration: Integration = {
- name: OrganizationIntegrationServiceName.CrowdStrike,
- linkURL: "https://bitwarden.com/help/crowdstrike-siem/",
- image: "../../../../../../../images/integrations/logo-crowdstrike-black.svg",
- type: IntegrationType.EVENT,
- description: "crowdstrikeEventIntegrationDesc",
- canSetupConnection: true,
- integrationType: OrganizationIntegrationType.Hec,
- };
-
- this.integrationsList.push(crowdstrikeIntegration);
-
- const datadogIntegration: Integration = {
- name: OrganizationIntegrationServiceName.Datadog,
- linkURL: "https://bitwarden.com/help/datadog-siem/",
- image: "../../../../../../../images/integrations/logo-datadog-color.svg",
- type: IntegrationType.EVENT,
- description: "datadogEventIntegrationDesc",
- canSetupConnection: true,
- integrationType: OrganizationIntegrationType.Datadog,
- };
-
- this.integrationsList.push(datadogIntegration);
- }
-
- // For all existing event based configurations loop through and assign the
- // organizationIntegration for the correct services.
- this.organizationIntegrationService.integrations$
- .pipe(takeUntil(this.destroy$))
- .subscribe((integrations) => {
- // reset all event based integrations to null first - in case one was deleted
- this.integrationsList.forEach((i) => {
- i.organizationIntegration = null;
- });
-
- integrations.forEach((integration) => {
- const item = this.integrationsList.find((i) => i.name === integration.serviceName);
- if (item) {
- item.organizationIntegration = integration;
- }
- });
- });
- }
-
- ngOnDestroy(): void {
- this.destroy$.next();
- this.destroy$.complete();
- }
-
- // use in the view
- get IntegrationType(): typeof IntegrationType {
- return IntegrationType;
- }
+ constructor(private state: OrganizationIntegrationsState) {}
}
diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/organization-integrations-routing.module.ts b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/organization-integrations-routing.module.ts
index 1667689b186..5392d4b1447 100644
--- a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/organization-integrations-routing.module.ts
+++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/organization-integrations-routing.module.ts
@@ -3,16 +3,29 @@ import { RouterModule, Routes } from "@angular/router";
import { organizationPermissionsGuard } from "@bitwarden/web-vault/app/admin-console/organizations/guards/org-permissions.guard";
+import { DeviceManagementComponent } from "./device-management/device-management.component";
+import { EventManagementComponent } from "./event-management/event-management.component";
import { AdminConsoleIntegrationsComponent } from "./integrations.component";
+import { OrganizationIntegrationsResolver } from "./organization-integrations.resolver";
+import { SingleSignOnComponent } from "./single-sign-on/single-sign-on.component";
+import { UserProvisioningComponent } from "./user-provisioning/user-provisioning.component";
const routes: Routes = [
{
path: "",
canActivate: [organizationPermissionsGuard((org) => org.canAccessIntegrations)],
- component: AdminConsoleIntegrationsComponent,
data: {
titleId: "integrations",
},
+ component: AdminConsoleIntegrationsComponent,
+ resolve: { integrations: OrganizationIntegrationsResolver },
+ children: [
+ { path: "", component: AdminConsoleIntegrationsComponent },
+ { path: "single-sign-on", component: SingleSignOnComponent },
+ { path: "user-provisioning", component: UserProvisioningComponent },
+ { path: "event-management", component: EventManagementComponent },
+ { path: "device-management", component: DeviceManagementComponent },
+ ],
},
];
diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/organization-integrations.module.ts b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/organization-integrations.module.ts
index 789ae548521..c97177803d4 100644
--- a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/organization-integrations.module.ts
+++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/organization-integrations.module.ts
@@ -1,16 +1,28 @@
import { NgModule } from "@angular/core";
+import { DeviceManagementComponent } from "@bitwarden/angular/auth/device-management/device-management.component";
import { OrganizationIntegrationApiService } from "@bitwarden/bit-common/dirt/organization-integrations/services/organization-integration-api.service";
import { OrganizationIntegrationConfigurationApiService } from "@bitwarden/bit-common/dirt/organization-integrations/services/organization-integration-configuration-api.service";
import { OrganizationIntegrationService } from "@bitwarden/bit-common/dirt/organization-integrations/services/organization-integration-service";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { safeProvider } from "@bitwarden/ui-common";
+import { EventManagementComponent } from "./event-management/event-management.component";
import { AdminConsoleIntegrationsComponent } from "./integrations.component";
import { OrganizationIntegrationsRoutingModule } from "./organization-integrations-routing.module";
+import { OrganizationIntegrationsResolver } from "./organization-integrations.resolver";
+import { SingleSignOnComponent } from "./single-sign-on/single-sign-on.component";
+import { UserProvisioningComponent } from "./user-provisioning/user-provisioning.component";
@NgModule({
- imports: [AdminConsoleIntegrationsComponent, OrganizationIntegrationsRoutingModule],
+ imports: [
+ AdminConsoleIntegrationsComponent,
+ OrganizationIntegrationsRoutingModule,
+ SingleSignOnComponent,
+ UserProvisioningComponent,
+ DeviceManagementComponent,
+ EventManagementComponent,
+ ],
providers: [
safeProvider({
provide: OrganizationIntegrationService,
@@ -27,6 +39,7 @@ import { OrganizationIntegrationsRoutingModule } from "./organization-integratio
useClass: OrganizationIntegrationConfigurationApiService,
deps: [ApiService],
}),
+ OrganizationIntegrationsResolver,
],
})
export class OrganizationIntegrationsModule {}
diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/organization-integrations.resolver.ts b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/organization-integrations.resolver.ts
new file mode 100644
index 00000000000..c51797a8ca7
--- /dev/null
+++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/organization-integrations.resolver.ts
@@ -0,0 +1,269 @@
+import { Injectable } from "@angular/core";
+import { ActivatedRouteSnapshot, Resolve } from "@angular/router";
+import { firstValueFrom } from "rxjs";
+import { take, takeWhile } from "rxjs/operators";
+
+import { Integration } from "@bitwarden/bit-common/dirt/organization-integrations/models/integration";
+import { OrganizationIntegrationServiceName } from "@bitwarden/bit-common/dirt/organization-integrations/models/organization-integration-service-type";
+import { OrganizationIntegrationType } from "@bitwarden/bit-common/dirt/organization-integrations/models/organization-integration-type";
+import { OrganizationIntegrationService } from "@bitwarden/bit-common/dirt/organization-integrations/services/organization-integration-service";
+import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
+import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
+import { getUserId } from "@bitwarden/common/auth/services/account.service";
+import { IntegrationType } from "@bitwarden/common/enums";
+import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
+import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
+import { getById } from "@bitwarden/common/platform/misc";
+
+import { OrganizationIntegrationsState } from "./organization-integrations.state";
+
+@Injectable()
+export class OrganizationIntegrationsResolver implements Resolve {
+ constructor(
+ private organizationService: OrganizationService,
+ private accountService: AccountService,
+ private configService: ConfigService,
+ private organizationIntegrationService: OrganizationIntegrationService,
+ private state: OrganizationIntegrationsState,
+ ) {}
+
+ async resolve(route: ActivatedRouteSnapshot): Promise {
+ const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
+
+ if (!userId) {
+ throw new Error("User ID not found");
+ }
+
+ const orgId = route.paramMap.get("organizationId")!;
+ const org = await firstValueFrom(
+ this.organizationService.organizations$(userId).pipe(getById(orgId), takeWhile(Boolean)),
+ );
+
+ this.state.setOrganization(org);
+
+ await firstValueFrom(this.organizationIntegrationService.setOrganizationId(org.id));
+
+ const integrations: Integration[] = [
+ {
+ name: "AD FS",
+ linkURL: "https://bitwarden.com/help/saml-adfs/",
+ image: "../../../../../../../images/integrations/azure-active-directory.svg",
+ type: IntegrationType.SSO,
+ },
+ {
+ name: "Auth0",
+ linkURL: "https://bitwarden.com/help/saml-auth0/",
+ image: "../../../../../../../images/integrations/logo-auth0-badge-color.svg",
+ type: IntegrationType.SSO,
+ },
+ {
+ name: "AWS",
+ linkURL: "https://bitwarden.com/help/saml-aws/",
+ image: "../../../../../../../images/integrations/aws-color.svg",
+ imageDarkMode: "../../../../../../../images/integrations/aws-darkmode.svg",
+ type: IntegrationType.SSO,
+ },
+ {
+ name: "Microsoft Entra ID",
+ linkURL: "https://bitwarden.com/help/saml-azure/",
+ image: "../../../../../../../images/integrations/logo-microsoft-entra-id-color.svg",
+ type: IntegrationType.SSO,
+ },
+ {
+ name: "Duo",
+ linkURL: "https://bitwarden.com/help/saml-duo/",
+ image: "../../../../../../../images/integrations/logo-duo-color.svg",
+ type: IntegrationType.SSO,
+ },
+ {
+ name: "Google",
+ linkURL: "https://bitwarden.com/help/saml-google/",
+ image: "../../../../../../../images/integrations/logo-google-badge-color.svg",
+ type: IntegrationType.SSO,
+ },
+ {
+ name: "JumpCloud",
+ linkURL: "https://bitwarden.com/help/saml-jumpcloud/",
+ image: "../../../../../../../images/integrations/logo-jumpcloud-badge-color.svg",
+ imageDarkMode: "../../../../../../../images/integrations/jumpcloud-darkmode.svg",
+ type: IntegrationType.SSO,
+ },
+ {
+ name: "KeyCloak",
+ linkURL: "https://bitwarden.com/help/saml-keycloak/",
+ image: "../../../../../../../images/integrations/logo-keycloak-icon.svg",
+ type: IntegrationType.SSO,
+ },
+ {
+ name: "Okta",
+ linkURL: "https://bitwarden.com/help/saml-okta/",
+ image: "../../../../../../../images/integrations/logo-okta-symbol-black.svg",
+ imageDarkMode: "../../../../../../../images/integrations/okta-darkmode.svg",
+ type: IntegrationType.SSO,
+ },
+ {
+ name: "OneLogin",
+ linkURL: "https://bitwarden.com/help/saml-onelogin/",
+ image: "../../../../../../../images/integrations/logo-onelogin-badge-color.svg",
+ imageDarkMode: "../../../../../../../images/integrations/onelogin-darkmode.svg",
+ type: IntegrationType.SSO,
+ },
+ {
+ name: "PingFederate",
+ linkURL: "https://bitwarden.com/help/saml-pingfederate/",
+ image: "../../../../../../../images/integrations/logo-ping-identity-badge-color.svg",
+ type: IntegrationType.SSO,
+ },
+ {
+ name: "Microsoft Entra ID",
+ linkURL: "https://bitwarden.com/help/microsoft-entra-id-scim-integration/",
+ image: "../../../../../../../images/integrations/logo-microsoft-entra-id-color.svg",
+ type: IntegrationType.SCIM,
+ },
+ {
+ name: "Okta",
+ linkURL: "https://bitwarden.com/help/okta-scim-integration/",
+ image: "../../../../../../../images/integrations/logo-okta-symbol-black.svg",
+ imageDarkMode: "../../../../../../../images/integrations/okta-darkmode.svg",
+ type: IntegrationType.SCIM,
+ },
+ {
+ name: "OneLogin",
+ linkURL: "https://bitwarden.com/help/onelogin-scim-integration/",
+ image: "../../../../../../../images/integrations/logo-onelogin-badge-color.svg",
+ imageDarkMode: "../../../../../../../images/integrations/onelogin-darkmode.svg",
+ type: IntegrationType.SCIM,
+ },
+ {
+ name: "JumpCloud",
+ linkURL: "https://bitwarden.com/help/jumpcloud-scim-integration/",
+ image: "../../../../../../../images/integrations/logo-jumpcloud-badge-color.svg",
+ imageDarkMode: "../../../../../../../images/integrations/jumpcloud-darkmode.svg",
+ type: IntegrationType.SCIM,
+ },
+ {
+ name: "Ping Identity",
+ linkURL: "https://bitwarden.com/help/ping-identity-scim-integration/",
+ image: "../../../../../../../images/integrations/logo-ping-identity-badge-color.svg",
+ type: IntegrationType.SCIM,
+ },
+ {
+ name: "Active Directory",
+ linkURL: "https://bitwarden.com/help/ldap-directory/",
+ image: "../../../../../../../images/integrations/azure-active-directory.svg",
+ type: IntegrationType.BWDC,
+ },
+ {
+ name: "Microsoft Entra ID",
+ linkURL: "https://bitwarden.com/help/microsoft-entra-id/",
+ image: "../../../../../../../images/integrations/logo-microsoft-entra-id-color.svg",
+ type: IntegrationType.BWDC,
+ },
+ {
+ name: "Google Workspace",
+ linkURL: "https://bitwarden.com/help/workspace-directory/",
+ image: "../../../../../../../images/integrations/logo-google-badge-color.svg",
+ type: IntegrationType.BWDC,
+ },
+ {
+ name: "Okta",
+ linkURL: "https://bitwarden.com/help/okta-directory/",
+ image: "../../../../../../../images/integrations/logo-okta-symbol-black.svg",
+ imageDarkMode: "../../../../../../../images/integrations/okta-darkmode.svg",
+ type: IntegrationType.BWDC,
+ },
+ {
+ name: "OneLogin",
+ linkURL: "https://bitwarden.com/help/onelogin-directory/",
+ image: "../../../../../../../images/integrations/logo-onelogin-badge-color.svg",
+ imageDarkMode: "../../../../../../../images/integrations/onelogin-darkmode.svg",
+ type: IntegrationType.BWDC,
+ },
+ {
+ name: "Splunk",
+ linkURL: "https://bitwarden.com/help/splunk-siem/",
+ image: "../../../../../../../images/integrations/logo-splunk-black.svg",
+ imageDarkMode: "../../../../../../../images/integrations/splunk-darkmode.svg",
+ type: IntegrationType.EVENT,
+ },
+ {
+ name: "Microsoft Sentinel",
+ linkURL: "https://bitwarden.com/help/microsoft-sentinel-siem/",
+ image: "../../../../../../../images/integrations/logo-microsoft-sentinel-color.svg",
+ type: IntegrationType.EVENT,
+ },
+ {
+ name: "Rapid7",
+ linkURL: "https://bitwarden.com/help/rapid7-siem/",
+ image: "../../../../../../../images/integrations/logo-rapid7-black.svg",
+ imageDarkMode: "../../../../../../../images/integrations/rapid7-darkmode.svg",
+ type: IntegrationType.EVENT,
+ },
+ {
+ name: "Elastic",
+ linkURL: "https://bitwarden.com/help/elastic-siem/",
+ image: "../../../../../../../images/integrations/logo-elastic-badge-color.svg",
+ type: IntegrationType.EVENT,
+ },
+ {
+ name: "Panther",
+ linkURL: "https://bitwarden.com/help/panther-siem/",
+ image: "../../../../../../../images/integrations/logo-panther-round-color.svg",
+ type: IntegrationType.EVENT,
+ },
+ {
+ name: "Sumo Logic",
+ linkURL: "https://bitwarden.com/help/sumo-logic-siem/",
+ image: "../../../../../../../images/integrations/logo-sumo-logic-siem.svg",
+ imageDarkMode: "../../../../../../../images/integrations/logo-sumo-logic-siem-darkmode.svg",
+ type: IntegrationType.EVENT,
+ newBadgeExpiration: "2025-12-31",
+ },
+ {
+ name: "Microsoft Intune",
+ linkURL: "https://bitwarden.com/help/deploy-browser-extensions-with-intune/",
+ image: "../../../../../../../images/integrations/logo-microsoft-intune-color.svg",
+ type: IntegrationType.DEVICE,
+ },
+ ];
+
+ const featureEnabled = await firstValueFrom(
+ this.configService.getFeatureFlag$(FeatureFlag.EventManagementForDataDogAndCrowdStrike),
+ );
+
+ if (featureEnabled) {
+ integrations.push(
+ {
+ name: OrganizationIntegrationServiceName.CrowdStrike,
+ linkURL: "https://bitwarden.com/help/crowdstrike-siem/",
+ image: "../../../../../../../images/integrations/logo-crowdstrike-black.svg",
+ type: IntegrationType.EVENT,
+ canSetupConnection: true,
+ integrationType: OrganizationIntegrationType.Hec,
+ },
+ {
+ name: OrganizationIntegrationServiceName.Datadog,
+ linkURL: "https://bitwarden.com/help/datadog-siem/",
+ image: "../../../../../../../images/integrations/logo-datadog-color.svg",
+ type: IntegrationType.EVENT,
+ canSetupConnection: true,
+ integrationType: OrganizationIntegrationType.Datadog,
+ },
+ );
+ }
+
+ // Wait for initial integrations load
+ const orgIntegrations = await firstValueFrom(
+ this.organizationIntegrationService.integrations$.pipe(take(1)),
+ );
+
+ const merged = integrations.map((i) => ({
+ ...i,
+ organizationIntegration: orgIntegrations.find((o) => o.serviceName === i.name) ?? null,
+ }));
+
+ this.state.setIntegrations(merged);
+
+ return true;
+ }
+}
diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/organization-integrations.state.ts b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/organization-integrations.state.ts
new file mode 100644
index 00000000000..7b6e1ac9b6e
--- /dev/null
+++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/organization-integrations.state.ts
@@ -0,0 +1,29 @@
+import { Injectable } from "@angular/core";
+import { BehaviorSubject } from "rxjs";
+
+import { Integration } from "@bitwarden/bit-common/dirt/organization-integrations/models/integration";
+import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
+
+@Injectable({ providedIn: "root" })
+export class OrganizationIntegrationsState {
+ private integrationsSource = new BehaviorSubject([]);
+ private organizationSource = new BehaviorSubject(null);
+ integrations$ = this.integrationsSource.asObservable();
+ organization$ = this.organizationSource.asObservable();
+
+ setOrganization(val: Organization) {
+ this.organizationSource.next(val);
+ }
+
+ setIntegrations(val: Integration[]) {
+ this.integrationsSource.next(val);
+ }
+
+ get organization() {
+ return this.organizationSource.value;
+ }
+
+ get integrations() {
+ return this.integrationsSource.value;
+ }
+}
diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/single-sign-on/single-sign-on.component.ts b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/single-sign-on/single-sign-on.component.ts
index 8a0ae858e3f..7ac9bfebb58 100644
--- a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/single-sign-on/single-sign-on.component.ts
+++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/single-sign-on/single-sign-on.component.ts
@@ -1,17 +1,27 @@
import { Component, OnInit } from "@angular/core";
+import { IntegrationType } from "@bitwarden/common/enums/integration-type.enum";
+import { SharedModule } from "@bitwarden/web-vault/app/shared";
+
import { IntegrationGridComponent } from "../integration-grid/integration-grid.component";
import { FilterIntegrationsPipe } from "../integrations.pipe";
+import { OrganizationIntegrationsState } from "../organization-integrations.state";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "single-sign-on",
templateUrl: "single-sign-on.component.html",
- imports: [IntegrationGridComponent, FilterIntegrationsPipe],
+ imports: [SharedModule, IntegrationGridComponent, FilterIntegrationsPipe],
})
export class SingleSignOnComponent implements OnInit {
- constructor() {}
+ integrationsList = this.state.integrations;
+
+ constructor(private state: OrganizationIntegrationsState) {}
ngOnInit() {}
+
+ get IntegrationType(): typeof IntegrationType {
+ return IntegrationType;
+ }
}
diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/user-provisioning/user-provisioning.component.html b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/user-provisioning/user-provisioning.component.html
index ceb2afae7af..8014ad3bc28 100644
--- a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/user-provisioning/user-provisioning.component.html
+++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/user-provisioning/user-provisioning.component.html
@@ -1,3 +1,6 @@
+@let organization = organization$ | async;
+@let integrationsList = integrations$ | async;
+
{{ "scimIntegration" | i18n }}
diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/user-provisioning/user-provisioning.component.ts b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/user-provisioning/user-provisioning.component.ts
index b4d0794a829..7fc4201d21f 100644
--- a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/user-provisioning/user-provisioning.component.ts
+++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/user-provisioning/user-provisioning.component.ts
@@ -1,13 +1,28 @@
import { Component, OnInit } from "@angular/core";
+import { IntegrationType } from "@bitwarden/common/enums/integration-type.enum";
+import { SharedModule } from "@bitwarden/web-vault/app/shared";
+
+import { IntegrationGridComponent } from "../integration-grid/integration-grid.component";
+import { FilterIntegrationsPipe } from "../integrations.pipe";
+import { OrganizationIntegrationsState } from "../organization-integrations.state";
+
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "user-provisioning",
templateUrl: "user-provisioning.component.html",
+ imports: [SharedModule, IntegrationGridComponent, FilterIntegrationsPipe],
})
export class UserProvisioningComponent implements OnInit {
- constructor() {}
+ organization$ = this.state.organization$;
+ integrations$ = this.state.integrations$;
+
+ constructor(private state: OrganizationIntegrationsState) {}
ngOnInit() {}
+
+ get IntegrationType(): typeof IntegrationType {
+ return IntegrationType;
+ }
}