From 81ca4957382e80e71d02ff4069aaa4684668e9b0 Mon Sep 17 00:00:00 2001 From: JaredScar Date: Thu, 22 Jan 2026 14:07:42 -0500 Subject: [PATCH] Implement organization integrations page with routing and state management - Added routing for organization integrations including device management, event management, single sign-on, and user provisioning. - Created OrganizationIntegrationsState to manage integrations and organization data. - Introduced OrganizationIntegrationsResolver for preloading organization and integration data. - Updated components to utilize the new state management and resolver. - Refactored integration routes to follow updated naming conventions. --- .../organization-layout.component.html | 2 +- .../device-management.component.html | 2 + .../device-management.component.ts | 16 +- .../event-management.component.html | 2 + .../event-management.component.ts | 17 +- .../integrations.component.html | 6 +- .../integrations.component.ts | 309 +----------------- ...rganization-integrations-routing.module.ts | 15 +- .../organization-integrations.module.ts | 15 +- .../organization-integrations.resolver.ts | 269 +++++++++++++++ .../organization-integrations.state.ts | 29 ++ .../single-sign-on.component.ts | 14 +- .../user-provisioning.component.html | 3 + .../user-provisioning.component.ts | 17 +- 14 files changed, 401 insertions(+), 315 deletions(-) create mode 100644 bitwarden_license/bit-web/src/app/dirt/organization-integrations/organization-integrations.resolver.ts create mode 100644 bitwarden_license/bit-web/src/app/dirt/organization-integrations/organization-integrations.state.ts 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; + } }