1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-26 14:23:46 +00:00

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.
This commit is contained in:
JaredScar
2026-01-22 14:07:42 -05:00
parent 43aa21ee2a
commit 81ca495738
14 changed files with 401 additions and 315 deletions

View File

@@ -79,7 +79,7 @@
<bit-nav-item
icon="bwi-msp"
[text]="'integrations' | i18n"
route="integrations"
route="integrations/single-sign-on"
*ngIf="integrationPageEnabled$ | async"
></bit-nav-item>
<bit-nav-group

View File

@@ -1,3 +1,5 @@
@let integrationsList = integrations$ | async;
<section class="tw-mb-9">
<h2 bitTypography="h2">
{{ "deviceManagement" | i18n }}

View File

@@ -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;
}
}

View File

@@ -1,3 +1,5 @@
@let integrationsList = integrations$ | async;
<section class="tw-mb-9">
<h2 bitTypography="h2">
{{ "eventManagement" | i18n }}

View File

@@ -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;
}
}

View File

@@ -1,11 +1,9 @@
@let organization = organization$ | async;
<app-header>
@if (organization) {
<bit-tab-nav-bar slot="tabs">
<bit-tab-link route="sso">{{ "singleSignOn" | i18n }}</bit-tab-link>
<bit-tab-link route="single-sign-on">{{ "singleSignOn" | i18n }}</bit-tab-link>
@if (organization?.useScim || organization?.useDirectory) {
<bit-tab-link route="provisioning">{{ "userProvisioning" | i18n }}</bit-tab-link>
<bit-tab-link route="user-provisioning">{{ "userProvisioning" | i18n }}</bit-tab-link>
}
@if (organization?.useEvents) {
<bit-tab-link route="event-management">{{ "eventManagement" | i18n }}</bit-tab-link>

View File

@@ -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<Organization> = new Observable<Organization>();
isEventManagementForDataDogAndCrowdStrikeEnabled: boolean = false;
private destroy$ = new Subject<void>();
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) {}
}

View File

@@ -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 },
],
},
];

View File

@@ -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 {}

View File

@@ -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<boolean> {
constructor(
private organizationService: OrganizationService,
private accountService: AccountService,
private configService: ConfigService,
private organizationIntegrationService: OrganizationIntegrationService,
private state: OrganizationIntegrationsState,
) {}
async resolve(route: ActivatedRouteSnapshot): Promise<boolean> {
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;
}
}

View File

@@ -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<Integration[]>([]);
private organizationSource = new BehaviorSubject<Organization>(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;
}
}

View File

@@ -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;
}
}

View File

@@ -1,3 +1,6 @@
@let organization = organization$ | async;
@let integrationsList = integrations$ | async;
<section class="tw-mb-9" *ngIf="organization?.useScim">
<h2 bitTypography="h2">
{{ "scimIntegration" | i18n }}

View File

@@ -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;
}
}