mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 16:23:44 +00:00
[PM-23826] Crowdstrike integration dialog (#15757)
This commit is contained in:
@@ -2,8 +2,10 @@
|
|||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import { Observable, Subject, switchMap, takeUntil } from "rxjs";
|
import { Observable, Subject, switchMap, takeUntil, scheduled, asyncScheduler } from "rxjs";
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-imports
|
||||||
|
import { OrganizationIntegrationApiService } from "@bitwarden/bit-common/dirt/integrations";
|
||||||
import {
|
import {
|
||||||
getOrganizationById,
|
getOrganizationById,
|
||||||
OrganizationService,
|
OrganizationService,
|
||||||
@@ -33,13 +35,192 @@ import { Integration } from "../shared/components/integrations/models";
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AdminConsoleIntegrationsComponent implements OnInit, OnDestroy {
|
export class AdminConsoleIntegrationsComponent implements OnInit, OnDestroy {
|
||||||
integrationsList: Integration[] = [];
|
// integrationsList: Integration[] = [];
|
||||||
tabIndex: number;
|
tabIndex: number;
|
||||||
organization$: Observable<Organization>;
|
organization$: Observable<Organization>;
|
||||||
isEventBasedIntegrationsEnabled: boolean = false;
|
isEventBasedIntegrationsEnabled: boolean = false;
|
||||||
private destroy$ = new Subject<void>();
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
|
// 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: "Microsoft Intune",
|
||||||
|
linkURL: "https://bitwarden.com/help/deploy-browser-extensions-with-intune/",
|
||||||
|
image: "../../../../../../../images/integrations/logo-microsoft-intune-color.svg",
|
||||||
|
type: IntegrationType.DEVICE,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
const orgId = this.route.snapshot.params.organizationId;
|
||||||
|
|
||||||
this.organization$ = this.route.params.pipe(
|
this.organization$ = this.route.params.pipe(
|
||||||
switchMap((params) =>
|
switchMap((params) =>
|
||||||
this.accountService.activeAccount$.pipe(
|
this.accountService.activeAccount$.pipe(
|
||||||
@@ -51,6 +232,25 @@ export class AdminConsoleIntegrationsComponent implements OnInit, OnDestroy {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
scheduled(this.orgIntegrationApiService.getOrganizationIntegrations(orgId), asyncScheduler)
|
||||||
|
.pipe(takeUntil(this.destroy$))
|
||||||
|
.subscribe((integrations) => {
|
||||||
|
// Update the integrations list with the fetched integrations
|
||||||
|
if (integrations && integrations.length > 0) {
|
||||||
|
integrations.forEach((integration) => {
|
||||||
|
const configJson = JSON.parse(integration.configuration || "{}");
|
||||||
|
const serviceName = configJson.service ?? "";
|
||||||
|
const existingIntegration = this.integrationsList.find((i) => i.name === serviceName);
|
||||||
|
|
||||||
|
if (existingIntegration) {
|
||||||
|
// if a configuration exists, then it is connected
|
||||||
|
existingIntegration.isConnected = !!integration.configuration;
|
||||||
|
existingIntegration.configuration = integration.configuration || "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -58,6 +258,7 @@ export class AdminConsoleIntegrationsComponent implements OnInit, OnDestroy {
|
|||||||
private organizationService: OrganizationService,
|
private organizationService: OrganizationService,
|
||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
|
private orgIntegrationApiService: OrganizationIntegrationApiService,
|
||||||
) {
|
) {
|
||||||
this.configService
|
this.configService
|
||||||
.getFeatureFlag$(FeatureFlag.EventBasedOrganizationIntegrations)
|
.getFeatureFlag$(FeatureFlag.EventBasedOrganizationIntegrations)
|
||||||
@@ -66,182 +267,6 @@ export class AdminConsoleIntegrationsComponent implements OnInit, OnDestroy {
|
|||||||
this.isEventBasedIntegrationsEnabled = isEnabled;
|
this.isEventBasedIntegrationsEnabled = isEnabled;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.integrationsList = [
|
|
||||||
{
|
|
||||||
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: "Microsoft Intune",
|
|
||||||
linkURL: "https://bitwarden.com/help/deploy-browser-extensions-with-intune/",
|
|
||||||
image: "../../../../../../../images/integrations/logo-microsoft-intune-color.svg",
|
|
||||||
type: IntegrationType.DEVICE,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
if (this.isEventBasedIntegrationsEnabled) {
|
if (this.isEventBasedIntegrationsEnabled) {
|
||||||
this.integrationsList.push({
|
this.integrationsList.push({
|
||||||
name: "Crowdstrike",
|
name: "Crowdstrike",
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<p class="tw-mb-0">{{ description }}</p>
|
<p class="tw-mb-0">{{ description }}</p>
|
||||||
|
|
||||||
@if (canSetupConnection) {
|
@if (canSetupConnection) {
|
||||||
<button type="button" class="tw-mt-3" bitButton (click)="setupConnection(name)">
|
<button type="button" class="tw-mt-3" bitButton (click)="setupConnection()">
|
||||||
<span>{{ "connectIntegrationButtonDesc" | i18n: name }}</span>
|
<span>{{ "connectIntegrationButtonDesc" | i18n: name }}</span>
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||||
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import { mock } from "jest-mock-extended";
|
import { mock } from "jest-mock-extended";
|
||||||
import { BehaviorSubject } from "rxjs";
|
import { BehaviorSubject } from "rxjs";
|
||||||
|
|
||||||
import { SYSTEM_THEME_OBSERVABLE } from "@bitwarden/angular/services/injection-tokens";
|
import { SYSTEM_THEME_OBSERVABLE } from "@bitwarden/angular/services/injection-tokens";
|
||||||
|
// eslint-disable-next-line no-restricted-imports
|
||||||
|
import { OrganizationIntegrationApiService } from "@bitwarden/bit-common/dirt/integrations/services";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { ThemeType } from "@bitwarden/common/platform/enums";
|
import { ThemeType } from "@bitwarden/common/platform/enums";
|
||||||
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
||||||
// FIXME: remove `src` and fix import
|
import { ToastService } from "@bitwarden/components";
|
||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import { SharedModule } from "@bitwarden/components/src/shared";
|
import { SharedModule } from "@bitwarden/components/src/shared";
|
||||||
import { I18nPipe } from "@bitwarden/ui-common";
|
import { I18nPipe } from "@bitwarden/ui-common";
|
||||||
@@ -17,6 +20,8 @@ describe("IntegrationCardComponent", () => {
|
|||||||
let component: IntegrationCardComponent;
|
let component: IntegrationCardComponent;
|
||||||
let fixture: ComponentFixture<IntegrationCardComponent>;
|
let fixture: ComponentFixture<IntegrationCardComponent>;
|
||||||
const mockI18nService = mock<I18nService>();
|
const mockI18nService = mock<I18nService>();
|
||||||
|
const activatedRoute = mock<ActivatedRoute>();
|
||||||
|
const mockOrgIntegrationApiService = mock<OrganizationIntegrationApiService>();
|
||||||
|
|
||||||
const systemTheme$ = new BehaviorSubject<ThemeType>(ThemeType.Light);
|
const systemTheme$ = new BehaviorSubject<ThemeType>(ThemeType.Light);
|
||||||
const usersPreferenceTheme$ = new BehaviorSubject<ThemeType>(ThemeType.Light);
|
const usersPreferenceTheme$ = new BehaviorSubject<ThemeType>(ThemeType.Light);
|
||||||
@@ -24,26 +29,22 @@ describe("IntegrationCardComponent", () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
// reset system theme
|
// reset system theme
|
||||||
systemTheme$.next(ThemeType.Light);
|
systemTheme$.next(ThemeType.Light);
|
||||||
|
activatedRoute.snapshot = {
|
||||||
|
paramMap: {
|
||||||
|
get: jest.fn().mockReturnValue("test-organization-id"),
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [IntegrationCardComponent, SharedModule],
|
imports: [IntegrationCardComponent, SharedModule],
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{ provide: ThemeStateService, useValue: { selectedTheme$: usersPreferenceTheme$ } },
|
||||||
provide: ThemeStateService,
|
{ provide: SYSTEM_THEME_OBSERVABLE, useValue: systemTheme$ },
|
||||||
useValue: { selectedTheme$: usersPreferenceTheme$ },
|
{ provide: I18nPipe, useValue: mock<I18nPipe>() },
|
||||||
},
|
{ provide: I18nService, useValue: mockI18nService },
|
||||||
{
|
{ provide: ActivatedRoute, useValue: activatedRoute },
|
||||||
provide: SYSTEM_THEME_OBSERVABLE,
|
{ provide: OrganizationIntegrationApiService, useValue: mockOrgIntegrationApiService },
|
||||||
useValue: systemTheme$,
|
{ provide: ToastService, useValue: mock<ToastService>() },
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: I18nPipe,
|
|
||||||
useValue: mock<I18nPipe>(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: I18nService,
|
|
||||||
useValue: mockI18nService,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,13 +9,26 @@ import {
|
|||||||
OnDestroy,
|
OnDestroy,
|
||||||
ViewChild,
|
ViewChild,
|
||||||
} from "@angular/core";
|
} from "@angular/core";
|
||||||
import { Observable, Subject, combineLatest, takeUntil } from "rxjs";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
|
import { Observable, Subject, combineLatest, lastValueFrom, takeUntil } from "rxjs";
|
||||||
|
|
||||||
import { SYSTEM_THEME_OBSERVABLE } from "@bitwarden/angular/services/injection-tokens";
|
import { SYSTEM_THEME_OBSERVABLE } from "@bitwarden/angular/services/injection-tokens";
|
||||||
|
// eslint-disable-next-line no-restricted-imports
|
||||||
|
import {
|
||||||
|
OrganizationIntegrationType,
|
||||||
|
OrganizationIntegrationRequest,
|
||||||
|
OrganizationIntegrationResponse,
|
||||||
|
OrganizationIntegrationApiService,
|
||||||
|
} from "@bitwarden/bit-common/dirt/integrations/index";
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { ThemeType } from "@bitwarden/common/platform/enums";
|
import { ThemeType } from "@bitwarden/common/platform/enums";
|
||||||
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
||||||
|
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||||
|
import { DialogService, ToastService } from "@bitwarden/components";
|
||||||
|
|
||||||
import { SharedModule } from "../../../../../../shared/shared.module";
|
import { SharedModule } from "../../../../../../shared/shared.module";
|
||||||
|
import { openHecConnectDialog } from "../integration-dialog/index";
|
||||||
|
import { Integration } from "../models";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-integration-card",
|
selector: "app-integration-card",
|
||||||
@@ -30,6 +43,7 @@ export class IntegrationCardComponent implements AfterViewInit, OnDestroy {
|
|||||||
@Input() image: string;
|
@Input() image: string;
|
||||||
@Input() imageDarkMode?: string;
|
@Input() imageDarkMode?: string;
|
||||||
@Input() linkURL: string;
|
@Input() linkURL: string;
|
||||||
|
@Input() integrationSettings: Integration;
|
||||||
|
|
||||||
/** Adds relevant `rel` attribute to external links */
|
/** Adds relevant `rel` attribute to external links */
|
||||||
@Input() externalURL?: boolean;
|
@Input() externalURL?: boolean;
|
||||||
@@ -49,6 +63,11 @@ export class IntegrationCardComponent implements AfterViewInit, OnDestroy {
|
|||||||
private themeStateService: ThemeStateService,
|
private themeStateService: ThemeStateService,
|
||||||
@Inject(SYSTEM_THEME_OBSERVABLE)
|
@Inject(SYSTEM_THEME_OBSERVABLE)
|
||||||
private systemTheme$: Observable<ThemeType>,
|
private systemTheme$: Observable<ThemeType>,
|
||||||
|
private dialogService: DialogService,
|
||||||
|
private activatedRoute: ActivatedRoute,
|
||||||
|
private apiService: OrganizationIntegrationApiService,
|
||||||
|
private toastService: ToastService,
|
||||||
|
private i18nService: I18nService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {
|
||||||
@@ -101,9 +120,58 @@ export class IntegrationCardComponent implements AfterViewInit, OnDestroy {
|
|||||||
return this.isConnected !== undefined;
|
return this.isConnected !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
setupConnection(app: string) {
|
async setupConnection() {
|
||||||
// This method can be used to handle the connection logic for the integration
|
// invoke the dialog to connect the integration
|
||||||
// For example, it could open a modal or redirect to a setup page
|
const dialog = openHecConnectDialog(this.dialogService, {
|
||||||
this.isConnected = !this.isConnected; // Toggle connection state for demonstration
|
data: {
|
||||||
|
settings: this.integrationSettings,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await lastValueFrom(dialog.closed);
|
||||||
|
|
||||||
|
// the dialog was cancelled
|
||||||
|
if (!result || !result.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// save the integration
|
||||||
|
try {
|
||||||
|
const dbResponse = await this.saveHecIntegration(result.configuration);
|
||||||
|
this.isConnected = !!dbResponse.id;
|
||||||
|
} catch {
|
||||||
|
this.toastService.showToast({
|
||||||
|
variant: "error",
|
||||||
|
title: null,
|
||||||
|
message: this.i18nService.t("failedToSaveIntegration"),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveHecIntegration(configuration: string): Promise<OrganizationIntegrationResponse> {
|
||||||
|
const organizationId = this.activatedRoute.snapshot.paramMap.get(
|
||||||
|
"organizationId",
|
||||||
|
) as OrganizationId;
|
||||||
|
|
||||||
|
const request = new OrganizationIntegrationRequest(
|
||||||
|
OrganizationIntegrationType.Hec,
|
||||||
|
configuration,
|
||||||
|
);
|
||||||
|
|
||||||
|
const integrations = await this.apiService.getOrganizationIntegrations(organizationId);
|
||||||
|
const existingIntegration = integrations.find(
|
||||||
|
(i) => i.type === OrganizationIntegrationType.Hec,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingIntegration) {
|
||||||
|
return await this.apiService.updateOrganizationIntegration(
|
||||||
|
organizationId,
|
||||||
|
existingIntegration.id,
|
||||||
|
request,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return await this.apiService.createOrganizationIntegration(organizationId, request);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
import { importProvidersFrom } from "@angular/core";
|
|
||||||
import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/angular";
|
|
||||||
import { of } from "rxjs";
|
|
||||||
|
|
||||||
import { SYSTEM_THEME_OBSERVABLE } from "@bitwarden/angular/services/injection-tokens";
|
|
||||||
import { ThemeTypes } from "@bitwarden/common/platform/enums";
|
|
||||||
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
|
||||||
|
|
||||||
import { PreloadedEnglishI18nModule } from "../../../../../../core/tests";
|
|
||||||
|
|
||||||
import { IntegrationCardComponent } from "./integration-card.component";
|
|
||||||
|
|
||||||
class MockThemeService implements Partial<ThemeStateService> {}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: "Web/Integration Layout/Integration Card",
|
|
||||||
component: IntegrationCardComponent,
|
|
||||||
decorators: [
|
|
||||||
applicationConfig({
|
|
||||||
providers: [importProvidersFrom(PreloadedEnglishI18nModule)],
|
|
||||||
}),
|
|
||||||
moduleMetadata({
|
|
||||||
providers: [
|
|
||||||
{
|
|
||||||
provide: ThemeStateService,
|
|
||||||
useClass: MockThemeService,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: SYSTEM_THEME_OBSERVABLE,
|
|
||||||
useValue: of(ThemeTypes.Light),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
args: {
|
|
||||||
integrations: [],
|
|
||||||
},
|
|
||||||
} as Meta;
|
|
||||||
|
|
||||||
type Story = StoryObj<IntegrationCardComponent>;
|
|
||||||
|
|
||||||
export const Default: Story = {
|
|
||||||
render: (args) => ({
|
|
||||||
props: args,
|
|
||||||
template: /*html*/ `
|
|
||||||
<app-integration-card
|
|
||||||
[name]="name"
|
|
||||||
[image]="image"
|
|
||||||
[linkURL]="linkURL"
|
|
||||||
></app-integration-card>
|
|
||||||
`,
|
|
||||||
}),
|
|
||||||
args: {
|
|
||||||
name: "Bitwarden",
|
|
||||||
image: "/integrations/bitwarden-vertical-blue.svg",
|
|
||||||
linkURL: "https://bitwarden.com",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
||||||
|
<bit-dialog dialogSize="large" [loading]="loading">
|
||||||
|
<span bitDialogTitle>
|
||||||
|
{{ "connectIntegrationButtonDesc" | i18n: connectInfo.settings.name }}
|
||||||
|
</span>
|
||||||
|
<div bitDialogContent class="tw-flex tw-flex-col tw-gap-4">
|
||||||
|
@if (loading) {
|
||||||
|
<ng-container #spinner>
|
||||||
|
<i class="bwi bwi-spinner bwi-lg bwi-spin" aria-hidden="true"></i>
|
||||||
|
</ng-container>
|
||||||
|
}
|
||||||
|
@if (!loading) {
|
||||||
|
<ng-container>
|
||||||
|
<bit-form-field>
|
||||||
|
<bit-label>{{ "url" | i18n }}</bit-label>
|
||||||
|
<input bitInput formControlName="url" />
|
||||||
|
</bit-form-field>
|
||||||
|
<bit-form-field>
|
||||||
|
<bit-label>{{ "bearerToken" | i18n }}</bit-label>
|
||||||
|
<input bitInput formControlName="bearerToken" />
|
||||||
|
</bit-form-field>
|
||||||
|
<bit-form-field>
|
||||||
|
<bit-label>{{ "index" | i18n }}</bit-label>
|
||||||
|
<input bitInput formControlName="index" />
|
||||||
|
</bit-form-field>
|
||||||
|
</ng-container>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<ng-container bitDialogFooter>
|
||||||
|
<button type="submit" bitButton bitFormButton buttonType="primary" [disabled]="loading">
|
||||||
|
{{ "save" | i18n }}
|
||||||
|
</button>
|
||||||
|
<button type="button" bitButton bitDialogClose buttonType="secondary" [disabled]="loading">
|
||||||
|
{{ "cancel" | i18n }}
|
||||||
|
</button>
|
||||||
|
</ng-container>
|
||||||
|
</bit-dialog>
|
||||||
|
</form>
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||||
|
import { FormBuilder, ReactiveFormsModule } from "@angular/forms";
|
||||||
|
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||||
|
import { mock } from "jest-mock-extended";
|
||||||
|
|
||||||
|
import { IntegrationType } from "@bitwarden/common/enums";
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
import { DIALOG_DATA, DialogConfig, DialogRef, DialogService } from "@bitwarden/components";
|
||||||
|
import { I18nPipe } from "@bitwarden/ui-common";
|
||||||
|
import { SharedModule } from "@bitwarden/web-vault/app/shared";
|
||||||
|
|
||||||
|
import { Integration } from "../../models";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ConnectHecDialogComponent,
|
||||||
|
HecConnectDialogParams,
|
||||||
|
HecConnectDialogResult,
|
||||||
|
openHecConnectDialog,
|
||||||
|
} from "./connect-dialog-hec.component";
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
// Mock element.animate for jsdom
|
||||||
|
// the animate function is not available in jsdom, so we provide a mock implementation
|
||||||
|
// This is necessary for tests that rely on animations
|
||||||
|
// This mock does not perform any actual animations, it just provides a structure that allows tests
|
||||||
|
// to run without throwing errors related to missing animate function
|
||||||
|
if (!HTMLElement.prototype.animate) {
|
||||||
|
HTMLElement.prototype.animate = function () {
|
||||||
|
return {
|
||||||
|
play: () => {},
|
||||||
|
pause: () => {},
|
||||||
|
finish: () => {},
|
||||||
|
cancel: () => {},
|
||||||
|
reverse: () => {},
|
||||||
|
addEventListener: () => {},
|
||||||
|
removeEventListener: () => {},
|
||||||
|
dispatchEvent: () => false,
|
||||||
|
onfinish: null,
|
||||||
|
oncancel: null,
|
||||||
|
startTime: 0,
|
||||||
|
currentTime: 0,
|
||||||
|
playbackRate: 1,
|
||||||
|
playState: "idle",
|
||||||
|
replaceState: "active",
|
||||||
|
effect: null,
|
||||||
|
finished: Promise.resolve(),
|
||||||
|
id: "",
|
||||||
|
remove: () => {},
|
||||||
|
timeline: null,
|
||||||
|
ready: Promise.resolve(),
|
||||||
|
} as unknown as Animation;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("ConnectDialogHecComponent", () => {
|
||||||
|
let component: ConnectHecDialogComponent;
|
||||||
|
let fixture: ComponentFixture<ConnectHecDialogComponent>;
|
||||||
|
let dialogRefMock = mock<DialogRef<HecConnectDialogResult>>();
|
||||||
|
const mockI18nService = mock<I18nService>();
|
||||||
|
|
||||||
|
const integrationMock: Integration = {
|
||||||
|
name: "Test Integration",
|
||||||
|
image: "test-image.png",
|
||||||
|
linkURL: "https://example.com",
|
||||||
|
imageDarkMode: "test-image-dark.png",
|
||||||
|
newBadgeExpiration: "2024-12-31",
|
||||||
|
description: "Test Description",
|
||||||
|
isConnected: false,
|
||||||
|
canSetupConnection: true,
|
||||||
|
type: IntegrationType.EVENT,
|
||||||
|
} as Integration;
|
||||||
|
const connectInfo: HecConnectDialogParams = { settings: integrationMock };
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
dialogRefMock = mock<DialogRef<HecConnectDialogResult>>();
|
||||||
|
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [ReactiveFormsModule, SharedModule, BrowserAnimationsModule],
|
||||||
|
providers: [
|
||||||
|
FormBuilder,
|
||||||
|
{ provide: DIALOG_DATA, useValue: connectInfo },
|
||||||
|
{ provide: DialogRef, useValue: dialogRefMock },
|
||||||
|
{ provide: I18nPipe, useValue: mock<I18nPipe>() },
|
||||||
|
{ provide: I18nService, useValue: mockI18nService },
|
||||||
|
],
|
||||||
|
}).compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ConnectHecDialogComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
mockI18nService.t.mockImplementation((key) => key);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create the component", () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should initialize form with empty values", () => {
|
||||||
|
expect(component.formGroup.value).toEqual({
|
||||||
|
url: "",
|
||||||
|
bearerToken: "",
|
||||||
|
index: "",
|
||||||
|
service: "Test Integration",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have required validators for all fields", () => {
|
||||||
|
component.formGroup.setValue({ url: "", bearerToken: "", index: "", service: "" });
|
||||||
|
expect(component.formGroup.valid).toBeFalsy();
|
||||||
|
|
||||||
|
component.formGroup.setValue({
|
||||||
|
url: "https://test.com",
|
||||||
|
bearerToken: "token",
|
||||||
|
index: "1",
|
||||||
|
service: "Test Service",
|
||||||
|
});
|
||||||
|
expect(component.formGroup.valid).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should invalidate url if not matching pattern", () => {
|
||||||
|
component.formGroup.setValue({
|
||||||
|
url: "ftp://test.com",
|
||||||
|
bearerToken: "token",
|
||||||
|
index: "1",
|
||||||
|
service: "Test Service",
|
||||||
|
});
|
||||||
|
expect(component.formGroup.valid).toBeFalsy();
|
||||||
|
|
||||||
|
component.formGroup.setValue({
|
||||||
|
url: "https://test.com",
|
||||||
|
bearerToken: "token",
|
||||||
|
index: "1",
|
||||||
|
service: "Test Service",
|
||||||
|
});
|
||||||
|
expect(component.formGroup.valid).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call dialogRef.close with correct result on submit", async () => {
|
||||||
|
component.formGroup.setValue({
|
||||||
|
url: "https://test.com",
|
||||||
|
bearerToken: "token",
|
||||||
|
index: "1",
|
||||||
|
service: "Test Service",
|
||||||
|
});
|
||||||
|
|
||||||
|
await component.submit();
|
||||||
|
|
||||||
|
expect(dialogRefMock.close).toHaveBeenCalledWith({
|
||||||
|
integrationSettings: integrationMock,
|
||||||
|
configuration: JSON.stringify({
|
||||||
|
url: "https://test.com",
|
||||||
|
bearerToken: "token",
|
||||||
|
index: "1",
|
||||||
|
service: "Test Service",
|
||||||
|
}),
|
||||||
|
success: true,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("openCrowdstrikeConnectDialog", () => {
|
||||||
|
it("should call dialogService.open with correct params", () => {
|
||||||
|
const dialogServiceMock = mock<DialogService>();
|
||||||
|
const config: DialogConfig<HecConnectDialogParams, DialogRef<HecConnectDialogResult>> = {
|
||||||
|
data: { settings: { name: "Test" } as Integration },
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
openHecConnectDialog(dialogServiceMock, config);
|
||||||
|
|
||||||
|
expect(dialogServiceMock.open).toHaveBeenCalledWith(ConnectHecDialogComponent, config);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import { Component, Inject, OnInit } from "@angular/core";
|
||||||
|
import { FormBuilder, Validators } from "@angular/forms";
|
||||||
|
|
||||||
|
import { DIALOG_DATA, DialogConfig, DialogRef, DialogService } from "@bitwarden/components";
|
||||||
|
import { SharedModule } from "@bitwarden/web-vault/app/shared";
|
||||||
|
|
||||||
|
import { Integration } from "../../models";
|
||||||
|
|
||||||
|
export type HecConnectDialogParams = {
|
||||||
|
settings: Integration;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface HecConnectDialogResult {
|
||||||
|
integrationSettings: Integration;
|
||||||
|
configuration: string;
|
||||||
|
success: boolean;
|
||||||
|
error: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
templateUrl: "./connect-dialog-hec.component.html",
|
||||||
|
imports: [SharedModule],
|
||||||
|
})
|
||||||
|
export class ConnectHecDialogComponent implements OnInit {
|
||||||
|
loading = false;
|
||||||
|
formGroup = this.formBuilder.group({
|
||||||
|
url: ["", [Validators.required, Validators.pattern("https?://.+")]],
|
||||||
|
bearerToken: ["", Validators.required],
|
||||||
|
index: ["", Validators.required],
|
||||||
|
service: ["", Validators.required],
|
||||||
|
});
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(DIALOG_DATA) protected connectInfo: HecConnectDialogParams,
|
||||||
|
protected formBuilder: FormBuilder,
|
||||||
|
private dialogRef: DialogRef<HecConnectDialogResult>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
const settings = this.getSettingsAsJson(this.connectInfo.settings.configuration ?? "");
|
||||||
|
|
||||||
|
if (settings) {
|
||||||
|
this.formGroup.patchValue({
|
||||||
|
url: settings?.url || "",
|
||||||
|
bearerToken: settings?.bearerToken || "",
|
||||||
|
index: settings?.index || "",
|
||||||
|
service: this.connectInfo.settings.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getSettingsAsJson(configuration: string) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(configuration);
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
submit = async (): Promise<void> => {
|
||||||
|
const formJson = this.formGroup.getRawValue();
|
||||||
|
|
||||||
|
const result: HecConnectDialogResult = {
|
||||||
|
integrationSettings: this.connectInfo.settings,
|
||||||
|
configuration: JSON.stringify(formJson),
|
||||||
|
success: true,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.dialogRef.close(result);
|
||||||
|
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function openHecConnectDialog(
|
||||||
|
dialogService: DialogService,
|
||||||
|
config: DialogConfig<HecConnectDialogParams, DialogRef<HecConnectDialogResult>>,
|
||||||
|
) {
|
||||||
|
return dialogService.open<HecConnectDialogResult>(ConnectHecDialogComponent, config);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./connect-dialog/connect-dialog-hec.component";
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
[description]="integration.description | i18n"
|
[description]="integration.description | i18n"
|
||||||
[isConnected]="integration.isConnected"
|
[isConnected]="integration.isConnected"
|
||||||
[canSetupConnection]="integration.canSetupConnection"
|
[canSetupConnection]="integration.canSetupConnection"
|
||||||
|
[integrationSettings]="integration"
|
||||||
></app-integration-card>
|
></app-integration-card>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||||
import { By } from "@angular/platform-browser";
|
import { By } from "@angular/platform-browser";
|
||||||
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import { mock } from "jest-mock-extended";
|
import { mock } from "jest-mock-extended";
|
||||||
import { of } from "rxjs";
|
import { of } from "rxjs";
|
||||||
|
|
||||||
import { SYSTEM_THEME_OBSERVABLE } from "@bitwarden/angular/services/injection-tokens";
|
import { SYSTEM_THEME_OBSERVABLE } from "@bitwarden/angular/services/injection-tokens";
|
||||||
|
// eslint-disable-next-line no-restricted-imports
|
||||||
|
import { OrganizationIntegrationApiService } from "@bitwarden/bit-common/dirt/integrations/services";
|
||||||
import { IntegrationType } from "@bitwarden/common/enums";
|
import { IntegrationType } from "@bitwarden/common/enums";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { ThemeTypes } from "@bitwarden/common/platform/enums";
|
import { ThemeTypes } from "@bitwarden/common/platform/enums";
|
||||||
|
// eslint-disable-next-line import/order
|
||||||
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
||||||
// FIXME: remove `src` and fix import
|
// FIXME: remove `src` and fix import
|
||||||
|
|
||||||
|
import { ToastService } from "@bitwarden/components";
|
||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import { SharedModule } from "@bitwarden/components/src/shared";
|
import { SharedModule } from "@bitwarden/components/src/shared";
|
||||||
import { I18nPipe } from "@bitwarden/ui-common";
|
import { I18nPipe } from "@bitwarden/ui-common";
|
||||||
@@ -21,6 +27,8 @@ import { IntegrationGridComponent } from "./integration-grid.component";
|
|||||||
describe("IntegrationGridComponent", () => {
|
describe("IntegrationGridComponent", () => {
|
||||||
let component: IntegrationGridComponent;
|
let component: IntegrationGridComponent;
|
||||||
let fixture: ComponentFixture<IntegrationGridComponent>;
|
let fixture: ComponentFixture<IntegrationGridComponent>;
|
||||||
|
const mockActivatedRoute = mock<ActivatedRoute>();
|
||||||
|
const mockOrgIntegrationApiService = mock<OrganizationIntegrationApiService>();
|
||||||
const integrations: Integration[] = [
|
const integrations: Integration[] = [
|
||||||
{
|
{
|
||||||
name: "Integration 1",
|
name: "Integration 1",
|
||||||
@@ -37,6 +45,12 @@ describe("IntegrationGridComponent", () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
mockActivatedRoute.snapshot = {
|
||||||
|
paramMap: {
|
||||||
|
get: jest.fn().mockReturnValue("test-organization-id"),
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [IntegrationGridComponent, IntegrationCardComponent, SharedModule],
|
imports: [IntegrationGridComponent, IntegrationCardComponent, SharedModule],
|
||||||
providers: [
|
providers: [
|
||||||
@@ -56,6 +70,18 @@ describe("IntegrationGridComponent", () => {
|
|||||||
provide: I18nService,
|
provide: I18nService,
|
||||||
useValue: mock<I18nService>({ t: (key, p1) => key + " " + p1 }),
|
useValue: mock<I18nService>({ t: (key, p1) => key + " " + p1 }),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: ActivatedRoute,
|
||||||
|
useValue: mockActivatedRoute,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: OrganizationIntegrationApiService,
|
||||||
|
useValue: mockOrgIntegrationApiService,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: ToastService,
|
||||||
|
useValue: mock<ToastService>(),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
import { importProvidersFrom } from "@angular/core";
|
|
||||||
import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/angular";
|
|
||||||
import { of } from "rxjs";
|
|
||||||
|
|
||||||
import { SYSTEM_THEME_OBSERVABLE } from "@bitwarden/angular/services/injection-tokens";
|
|
||||||
import { IntegrationType } from "@bitwarden/common/enums";
|
|
||||||
import { ThemeTypes } from "@bitwarden/common/platform/enums";
|
|
||||||
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
|
||||||
|
|
||||||
import { PreloadedEnglishI18nModule } from "../../../../../../core/tests";
|
|
||||||
import { IntegrationCardComponent } from "../integration-card/integration-card.component";
|
|
||||||
import { IntegrationGridComponent } from "../integration-grid/integration-grid.component";
|
|
||||||
|
|
||||||
class MockThemeService implements Partial<ThemeStateService> {}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: "Web/Integration Layout/Integration Grid",
|
|
||||||
component: IntegrationGridComponent,
|
|
||||||
decorators: [
|
|
||||||
applicationConfig({
|
|
||||||
providers: [importProvidersFrom(PreloadedEnglishI18nModule)],
|
|
||||||
}),
|
|
||||||
moduleMetadata({
|
|
||||||
imports: [IntegrationCardComponent],
|
|
||||||
providers: [
|
|
||||||
{
|
|
||||||
provide: ThemeStateService,
|
|
||||||
useClass: MockThemeService,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: SYSTEM_THEME_OBSERVABLE,
|
|
||||||
useValue: of(ThemeTypes.Dark),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
} as Meta;
|
|
||||||
|
|
||||||
type Story = StoryObj<IntegrationGridComponent>;
|
|
||||||
|
|
||||||
export const Default: Story = {
|
|
||||||
render: (args) => ({
|
|
||||||
props: args,
|
|
||||||
template: /*html*/ `
|
|
||||||
<app-integration-grid [integrations]="integrations"></app-integration-grid>
|
|
||||||
`,
|
|
||||||
}),
|
|
||||||
args: {
|
|
||||||
integrations: [
|
|
||||||
{
|
|
||||||
name: "Card 1",
|
|
||||||
linkURL: "https://bitwarden.com",
|
|
||||||
image: "/integrations/bitwarden-vertical-blue.svg",
|
|
||||||
type: IntegrationType.SSO,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Card 2",
|
|
||||||
linkURL: "https://bitwarden.com",
|
|
||||||
image: "/integrations/bitwarden-vertical-blue.svg",
|
|
||||||
type: IntegrationType.SDK,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Card 3",
|
|
||||||
linkURL: "https://bitwarden.com",
|
|
||||||
image: "/integrations/bitwarden-vertical-blue.svg",
|
|
||||||
type: IntegrationType.SCIM,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -20,4 +20,5 @@ export type Integration = {
|
|||||||
description?: string;
|
description?: string;
|
||||||
isConnected?: boolean;
|
isConnected?: boolean;
|
||||||
canSetupConnection?: boolean;
|
canSetupConnection?: boolean;
|
||||||
|
configuration?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ import {
|
|||||||
InternalUserDecryptionOptionsServiceAbstraction,
|
InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
LoginEmailService,
|
LoginEmailService,
|
||||||
} from "@bitwarden/auth/common";
|
} from "@bitwarden/auth/common";
|
||||||
|
// eslint-disable-next-line no-restricted-imports
|
||||||
|
import { OrganizationIntegrationApiService } from "@bitwarden/bit-common/dirt/integrations";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||||
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
|
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
|
||||||
@@ -392,6 +394,11 @@ const safeProviders: SafeProvider[] = [
|
|||||||
useClass: DefaultDeviceManagementComponentService,
|
useClass: DefaultDeviceManagementComponentService,
|
||||||
deps: [],
|
deps: [],
|
||||||
}),
|
}),
|
||||||
|
safeProvider({
|
||||||
|
provide: OrganizationIntegrationApiService,
|
||||||
|
useClass: OrganizationIntegrationApiService,
|
||||||
|
deps: [ApiService],
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|||||||
@@ -9481,6 +9481,9 @@
|
|||||||
"crowdstrikeEventIntegrationDesc": {
|
"crowdstrikeEventIntegrationDesc": {
|
||||||
"message": "Send event data to your Logscale instance"
|
"message": "Send event data to your Logscale instance"
|
||||||
},
|
},
|
||||||
|
"failedToSaveIntegration": {
|
||||||
|
"message": "Failed to save integration. Please try again later."
|
||||||
|
},
|
||||||
"deviceIdMissing": {
|
"deviceIdMissing": {
|
||||||
"message": "Device ID is missing"
|
"message": "Device ID is missing"
|
||||||
},
|
},
|
||||||
@@ -9562,6 +9565,15 @@
|
|||||||
"createNewClientToManageAsProvider": {
|
"createNewClientToManageAsProvider": {
|
||||||
"message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle."
|
"message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle."
|
||||||
},
|
},
|
||||||
|
"url": {
|
||||||
|
"message": "URL"
|
||||||
|
},
|
||||||
|
"bearerToken": {
|
||||||
|
"message": "Bearer Token"
|
||||||
|
},
|
||||||
|
"index": {
|
||||||
|
"message": "Index"
|
||||||
|
},
|
||||||
"selectAPlan": {
|
"selectAPlan": {
|
||||||
"message": "Select a plan"
|
"message": "Select a plan"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1 +1,6 @@
|
|||||||
export * from "./services";
|
export * from "./services";
|
||||||
|
export * from "./models/organization-integration-type";
|
||||||
|
export * from "./models/organization-integration-request";
|
||||||
|
export * from "./models/organization-integration-response";
|
||||||
|
export * from "./models/organization-integration-configuration-request";
|
||||||
|
export * from "./models/organization-integration-configuration-response";
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ import { OrganizationIntegrationType } from "./organization-integration-type";
|
|||||||
|
|
||||||
export class OrganizationIntegrationResponse extends BaseResponse {
|
export class OrganizationIntegrationResponse extends BaseResponse {
|
||||||
id: OrganizationIntegrationId;
|
id: OrganizationIntegrationId;
|
||||||
organizationIntegrationType: OrganizationIntegrationType;
|
type: OrganizationIntegrationType;
|
||||||
|
configuration: string;
|
||||||
|
|
||||||
constructor(response: any) {
|
constructor(response: any) {
|
||||||
super(response);
|
super(response);
|
||||||
this.id = this.getResponseProperty("Id");
|
this.id = this.getResponseProperty("Id");
|
||||||
this.organizationIntegrationType = this.getResponseProperty("Type");
|
this.type = this.getResponseProperty("Type");
|
||||||
|
this.configuration = this.getResponseProperty("Configuration");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,17 +11,17 @@ import { OrganizationIntegrationApiService } from "./organization-integration-ap
|
|||||||
|
|
||||||
export const mockIntegrationResponse: any = {
|
export const mockIntegrationResponse: any = {
|
||||||
id: "1" as OrganizationIntegrationId,
|
id: "1" as OrganizationIntegrationId,
|
||||||
organizationIntegrationType: OrganizationIntegrationType.Hec,
|
type: OrganizationIntegrationType.Hec,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mockIntegrationResponses: any[] = [
|
export const mockIntegrationResponses: any[] = [
|
||||||
{
|
{
|
||||||
id: "1" as OrganizationIntegrationId,
|
id: "1" as OrganizationIntegrationId,
|
||||||
OrganizationIntegrationType: OrganizationIntegrationType.Hec,
|
type: OrganizationIntegrationType.Hec,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "2" as OrganizationIntegrationId,
|
id: "2" as OrganizationIntegrationId,
|
||||||
OrganizationIntegrationType: OrganizationIntegrationType.Webhook,
|
type: OrganizationIntegrationType.Webhook,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ describe("OrganizationIntegrationApiService", () => {
|
|||||||
expect(result).toEqual(mockIntegrationResponses);
|
expect(result).toEqual(mockIntegrationResponses);
|
||||||
expect(apiService.send).toHaveBeenCalledWith(
|
expect(apiService.send).toHaveBeenCalledWith(
|
||||||
"GET",
|
"GET",
|
||||||
`organizations/${orgId}/integrations`,
|
`/organizations/${orgId}/integrations`,
|
||||||
null,
|
null,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
@@ -63,12 +63,10 @@ describe("OrganizationIntegrationApiService", () => {
|
|||||||
apiService.send.mockReturnValue(Promise.resolve(mockIntegrationResponse));
|
apiService.send.mockReturnValue(Promise.resolve(mockIntegrationResponse));
|
||||||
|
|
||||||
const result = await service.createOrganizationIntegration(orgId, request);
|
const result = await service.createOrganizationIntegration(orgId, request);
|
||||||
expect(result.organizationIntegrationType).toEqual(
|
expect(result.type).toEqual(mockIntegrationResponse.type);
|
||||||
mockIntegrationResponse.organizationIntegrationType,
|
|
||||||
);
|
|
||||||
expect(apiService.send).toHaveBeenCalledWith(
|
expect(apiService.send).toHaveBeenCalledWith(
|
||||||
"POST",
|
"POST",
|
||||||
`organizations/${orgId.toString()}/integrations`,
|
`/organizations/${orgId.toString()}/integrations`,
|
||||||
request,
|
request,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
@@ -86,12 +84,10 @@ describe("OrganizationIntegrationApiService", () => {
|
|||||||
apiService.send.mockReturnValue(Promise.resolve(mockIntegrationResponse));
|
apiService.send.mockReturnValue(Promise.resolve(mockIntegrationResponse));
|
||||||
|
|
||||||
const result = await service.updateOrganizationIntegration(orgId, integrationId, request);
|
const result = await service.updateOrganizationIntegration(orgId, integrationId, request);
|
||||||
expect(result.organizationIntegrationType).toEqual(
|
expect(result.type).toEqual(mockIntegrationResponse.type);
|
||||||
mockIntegrationResponse.organizationIntegrationType,
|
|
||||||
);
|
|
||||||
expect(apiService.send).toHaveBeenCalledWith(
|
expect(apiService.send).toHaveBeenCalledWith(
|
||||||
"PUT",
|
"PUT",
|
||||||
`organizations/${orgId}/integrations/${integrationId}`,
|
`/organizations/${orgId}/integrations/${integrationId}`,
|
||||||
request,
|
request,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
@@ -106,7 +102,7 @@ describe("OrganizationIntegrationApiService", () => {
|
|||||||
|
|
||||||
expect(apiService.send).toHaveBeenCalledWith(
|
expect(apiService.send).toHaveBeenCalledWith(
|
||||||
"DELETE",
|
"DELETE",
|
||||||
`organizations/${orgId}/integrations/${integrationId}`,
|
`/organizations/${orgId}/integrations/${integrationId}`,
|
||||||
null,
|
null,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export class OrganizationIntegrationApiService {
|
|||||||
): Promise<OrganizationIntegrationResponse[]> {
|
): Promise<OrganizationIntegrationResponse[]> {
|
||||||
const response = await this.apiService.send(
|
const response = await this.apiService.send(
|
||||||
"GET",
|
"GET",
|
||||||
`organizations/${orgId}/integrations`,
|
`/organizations/${orgId}/integrations`,
|
||||||
null,
|
null,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
@@ -29,7 +29,7 @@ export class OrganizationIntegrationApiService {
|
|||||||
): Promise<OrganizationIntegrationResponse> {
|
): Promise<OrganizationIntegrationResponse> {
|
||||||
const response = await this.apiService.send(
|
const response = await this.apiService.send(
|
||||||
"POST",
|
"POST",
|
||||||
`organizations/${orgId}/integrations`,
|
`/organizations/${orgId}/integrations`,
|
||||||
request,
|
request,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
@@ -44,7 +44,7 @@ export class OrganizationIntegrationApiService {
|
|||||||
): Promise<OrganizationIntegrationResponse> {
|
): Promise<OrganizationIntegrationResponse> {
|
||||||
const response = await this.apiService.send(
|
const response = await this.apiService.send(
|
||||||
"PUT",
|
"PUT",
|
||||||
`organizations/${orgId}/integrations/${integrationId}`,
|
`/organizations/${orgId}/integrations/${integrationId}`,
|
||||||
request,
|
request,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
@@ -58,7 +58,7 @@ export class OrganizationIntegrationApiService {
|
|||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
await this.apiService.send(
|
await this.apiService.send(
|
||||||
"DELETE",
|
"DELETE",
|
||||||
`organizations/${orgId}/integrations/${integrationId}`,
|
`/organizations/${orgId}/integrations/${integrationId}`,
|
||||||
null,
|
null,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||||
import { By } from "@angular/platform-browser";
|
import { By } from "@angular/platform-browser";
|
||||||
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import { mock } from "jest-mock-extended";
|
import { mock } from "jest-mock-extended";
|
||||||
import { of } from "rxjs";
|
import { of } from "rxjs";
|
||||||
|
|
||||||
@@ -8,9 +9,12 @@ import {} from "@bitwarden/web-vault/app/shared";
|
|||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import { SYSTEM_THEME_OBSERVABLE } from "@bitwarden/angular/services/injection-tokens";
|
import { SYSTEM_THEME_OBSERVABLE } from "@bitwarden/angular/services/injection-tokens";
|
||||||
|
import { OrganizationIntegrationApiService } from "@bitwarden/bit-common/dirt/integrations";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { ThemeType } from "@bitwarden/common/platform/enums";
|
import { ThemeType } from "@bitwarden/common/platform/enums";
|
||||||
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
||||||
|
import { ToastService } from "@bitwarden/components";
|
||||||
|
import { I18nPipe } from "@bitwarden/ui-common";
|
||||||
import { IntegrationCardComponent } from "@bitwarden/web-vault/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component";
|
import { IntegrationCardComponent } from "@bitwarden/web-vault/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component";
|
||||||
import { IntegrationGridComponent } from "@bitwarden/web-vault/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.component";
|
import { IntegrationGridComponent } from "@bitwarden/web-vault/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.component";
|
||||||
|
|
||||||
@@ -33,23 +37,25 @@ class MockNewMenuComponent {}
|
|||||||
describe("IntegrationsComponent", () => {
|
describe("IntegrationsComponent", () => {
|
||||||
let fixture: ComponentFixture<IntegrationsComponent>;
|
let fixture: ComponentFixture<IntegrationsComponent>;
|
||||||
|
|
||||||
|
const mockOrgIntegrationApiService = mock<OrganizationIntegrationApiService>();
|
||||||
|
const activatedRouteMock = {
|
||||||
|
snapshot: { paramMap: { get: jest.fn() } },
|
||||||
|
};
|
||||||
|
const mockI18nService = mock<I18nService>();
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [IntegrationsComponent, MockHeaderComponent, MockNewMenuComponent],
|
declarations: [IntegrationsComponent, MockHeaderComponent, MockNewMenuComponent],
|
||||||
imports: [JslibModule, IntegrationGridComponent, IntegrationCardComponent],
|
imports: [JslibModule, IntegrationGridComponent, IntegrationCardComponent],
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{ provide: I18nService, useValue: mock<I18nService>() },
|
||||||
provide: I18nService,
|
{ provide: ThemeStateService, useValue: mock<ThemeStateService>() },
|
||||||
useValue: mock<I18nService>(),
|
{ provide: SYSTEM_THEME_OBSERVABLE, useValue: of(ThemeType.Light) },
|
||||||
},
|
{ provide: ActivatedRoute, useValue: activatedRouteMock },
|
||||||
{
|
{ provide: OrganizationIntegrationApiService, useValue: mockOrgIntegrationApiService },
|
||||||
provide: ThemeStateService,
|
{ provide: ToastService, useValue: mock<ToastService>() },
|
||||||
useValue: mock<ThemeStateService>(),
|
{ provide: I18nPipe, useValue: mock<I18nPipe>() },
|
||||||
},
|
{ provide: I18nService, useValue: mockI18nService },
|
||||||
{
|
|
||||||
provide: SYSTEM_THEME_OBSERVABLE,
|
|
||||||
useValue: of(ThemeType.Light),
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
fixture = TestBed.createComponent(IntegrationsComponent);
|
fixture = TestBed.createComponent(IntegrationsComponent);
|
||||||
|
|||||||
Reference in New Issue
Block a user