mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 15:53:27 +00:00
[PM-25611][PM-25612] Update components to use persistance code (#16655)
* Add password trigger logic to report service. Also updated api to use classes that properly handle encstring with placeholders for upcoming usage * Fix merged test case conflict * Fix type errors and test cases. Make create data functions for report and summary * Update Risk Insights Report Data Type * Update encryption usage and test cases. Moved mock data * Remove unused variable * Move all-application constructor * Update all applications and risk insights to look at fetched logic * Fix name of variable. Fetch last report run * Cleanup all and critical application tabs drawer dependencies * Rename components from tool to dirt. Hook up all applications to use reportResult summary * Critical application cleanup. Trigger refetch of report for enriching when critical applications change * Fix type errors * Rename loader from tools to dirt. Cleanup * Add activity tab updates using data service * Use safeProviders in access intelligence * Fix refresh button not appearing. Change "refresh" to "run report" * Remove multiple async calls for isRunningReport * Fix report button not showing * Add no report ran message * Fix password change on critical applications
This commit is contained in:
@@ -29,28 +29,32 @@ import { RiskInsightsComponent } from "./risk-insights.component";
|
||||
@NgModule({
|
||||
imports: [RiskInsightsComponent, AccessIntelligenceRoutingModule],
|
||||
providers: [
|
||||
{
|
||||
safeProvider({
|
||||
provide: MemberCipherDetailsApiService,
|
||||
useClass: MemberCipherDetailsApiService,
|
||||
deps: [ApiService],
|
||||
},
|
||||
{
|
||||
}),
|
||||
safeProvider({
|
||||
provide: PasswordHealthService,
|
||||
useClass: PasswordHealthService,
|
||||
deps: [PasswordStrengthServiceAbstraction, AuditService],
|
||||
},
|
||||
{
|
||||
}),
|
||||
safeProvider({
|
||||
provide: RiskInsightsApiService,
|
||||
useClass: RiskInsightsApiService,
|
||||
deps: [ApiService],
|
||||
},
|
||||
{
|
||||
}),
|
||||
safeProvider({
|
||||
provide: RiskInsightsReportService,
|
||||
useClass: RiskInsightsReportService,
|
||||
deps: [
|
||||
CipherService,
|
||||
MemberCipherDetailsApiService,
|
||||
PasswordHealthService,
|
||||
RiskInsightsApiService,
|
||||
RiskInsightsEncryptionService,
|
||||
PasswordHealthService,
|
||||
],
|
||||
},
|
||||
}),
|
||||
safeProvider({
|
||||
provide: RiskInsightsDataService,
|
||||
deps: [
|
||||
@@ -78,7 +82,7 @@ import { RiskInsightsComponent } from "./risk-insights.component";
|
||||
safeProvider({
|
||||
provide: AllActivitiesService,
|
||||
useClass: AllActivitiesService,
|
||||
deps: [],
|
||||
deps: [RiskInsightsDataService],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: SecurityTasksApiService,
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
@if (isLoading$ | async) {
|
||||
<div *ngIf="isLoading$ | async">
|
||||
<tools-risk-insights-loading></tools-risk-insights-loading>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!(isLoading$ | async)) {
|
||||
@if (dataService.isLoading$ | async) {
|
||||
<dirt-risk-insights-loading></dirt-risk-insights-loading>
|
||||
} @else {
|
||||
<ul
|
||||
class="tw-inline-grid tw-grid-cols-3 tw-gap-6 tw-m-0 tw-p-0 tw-w-full tw-auto-cols-auto tw-list-none"
|
||||
>
|
||||
|
||||
@@ -20,7 +20,7 @@ import { ApplicationsLoadingComponent } from "./risk-insights-loading.component"
|
||||
import { RiskInsightsTabType } from "./risk-insights.component";
|
||||
|
||||
@Component({
|
||||
selector: "tools-all-activity",
|
||||
selector: "dirt-all-activity",
|
||||
imports: [
|
||||
ApplicationsLoadingComponent,
|
||||
SharedModule,
|
||||
@@ -30,7 +30,6 @@ import { RiskInsightsTabType } from "./risk-insights.component";
|
||||
templateUrl: "./all-activity.component.html",
|
||||
})
|
||||
export class AllActivityComponent implements OnInit {
|
||||
protected isLoading$ = this.dataService.isLoading$;
|
||||
organization: Organization | null = null;
|
||||
totalCriticalAppsAtRiskMemberCount = 0;
|
||||
totalCriticalAppsCount = 0;
|
||||
|
||||
@@ -1,96 +1,102 @@
|
||||
<div *ngIf="isLoading$ | async">
|
||||
<tools-risk-insights-loading></tools-risk-insights-loading>
|
||||
</div>
|
||||
<div class="tw-mt-4" *ngIf="!(isLoading$ | async) && !dataSource.data.length">
|
||||
<bit-no-items [icon]="noItemsIcon" class="tw-text-main">
|
||||
<ng-container slot="title">
|
||||
<h2 class="tw-font-semibold tw-mt-4">
|
||||
{{ "noAppsInOrgTitle" | i18n: organization?.name }}
|
||||
</h2>
|
||||
</ng-container>
|
||||
<ng-container slot="description">
|
||||
<div class="tw-flex tw-flex-col tw-mb-2">
|
||||
<span class="tw-text-muted">
|
||||
{{ "noAppsInOrgDescription" | i18n }}
|
||||
</span>
|
||||
<a class="tw-text-primary-600" routerLink="/login">{{ "learnMore" | i18n }}</a>
|
||||
@if (dataService.isLoading$ | async) {
|
||||
<dirt-risk-insights-loading></dirt-risk-insights-loading>
|
||||
} @else {
|
||||
@let drawerDetails = dataService.drawerDetails$ | async;
|
||||
@if (!dataSource.data.length) {
|
||||
<div class="tw-mt-4">
|
||||
<bit-no-items [icon]="noItemsIcon" class="tw-text-main">
|
||||
<ng-container slot="title">
|
||||
<h2 class="tw-font-semibold tw-mt-4">
|
||||
{{
|
||||
"noAppsInOrgTitle"
|
||||
| i18n: (dataService.organizationDetails$ | async)?.organizationName || ""
|
||||
}}
|
||||
</h2>
|
||||
</ng-container>
|
||||
<ng-container slot="description">
|
||||
<div class="tw-flex tw-flex-col tw-mb-2">
|
||||
<span class="tw-text-muted">
|
||||
{{ "noAppsInOrgDescription" | i18n }}
|
||||
</span>
|
||||
<a class="tw-text-primary-600" routerLink="/login">{{ "learnMore" | i18n }}</a>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container slot="button">
|
||||
<button (click)="goToCreateNewLoginItem()" bitButton buttonType="primary" type="button">
|
||||
{{ "createNewLoginItem" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
</bit-no-items>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="tw-mt-4 tw-flex tw-flex-col">
|
||||
<h2 class="tw-mb-6" bitTypography="h2">{{ "allApplications" | i18n }}</h2>
|
||||
<div class="tw-flex tw-gap-6">
|
||||
<button
|
||||
type="button"
|
||||
class="tw-flex-1"
|
||||
tabindex="0"
|
||||
(click)="dataService.setDrawerForOrgAtRiskMembers('allAppsOrgAtRiskMembers')"
|
||||
>
|
||||
<dirt-card
|
||||
#allAppsOrgAtRiskMembers
|
||||
class="tw-w-full"
|
||||
[ngClass]="{
|
||||
'tw-bg-primary-100': drawerDetails.invokerId === 'allAppsOrgAtRiskMembers',
|
||||
}"
|
||||
[title]="'atRiskMembers' | i18n"
|
||||
[value]="applicationSummary.totalAtRiskMemberCount"
|
||||
[maxValue]="applicationSummary.totalMemberCount"
|
||||
>
|
||||
</dirt-card>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="tw-flex-1"
|
||||
tabindex="0"
|
||||
(click)="dataService.setDrawerForOrgAtRiskApps('allAppsOrgAtRiskApplications')"
|
||||
>
|
||||
<dirt-card
|
||||
#allAppsOrgAtRiskApplications
|
||||
class="tw-w-full"
|
||||
[ngClass]="{
|
||||
'tw-bg-primary-100': drawerDetails.invokerId === 'allAppsOrgAtRiskApplications',
|
||||
}"
|
||||
[title]="'atRiskApplications' | i18n"
|
||||
[value]="applicationSummary.totalAtRiskApplicationCount"
|
||||
[maxValue]="applicationSummary.totalApplicationCount"
|
||||
>
|
||||
</dirt-card>
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container slot="button">
|
||||
<button (click)="goToCreateNewLoginItem()" bitButton buttonType="primary" type="button">
|
||||
{{ "createNewLoginItem" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
</bit-no-items>
|
||||
</div>
|
||||
<div class="tw-mt-4 tw-flex tw-flex-col" *ngIf="!(isLoading$ | async) && dataSource.data.length">
|
||||
<h2 class="tw-mb-6" bitTypography="h2">{{ "allApplications" | i18n }}</h2>
|
||||
@if (dataService.drawerDetails$ | async; as drawerDetails) {
|
||||
<div class="tw-flex tw-gap-6">
|
||||
<button
|
||||
type="button"
|
||||
class="tw-flex-1"
|
||||
tabindex="0"
|
||||
(click)="showOrgAtRiskMembers('allAppsOrgAtRiskMembers')"
|
||||
>
|
||||
<dirt-card
|
||||
#allAppsOrgAtRiskMembers
|
||||
class="tw-w-full"
|
||||
[ngClass]="{
|
||||
'tw-bg-primary-100': drawerDetails.invokerId === 'allAppsOrgAtRiskMembers',
|
||||
}"
|
||||
[title]="'atRiskMembers' | i18n"
|
||||
[value]="applicationSummary.totalAtRiskMemberCount"
|
||||
[maxValue]="applicationSummary.totalMemberCount"
|
||||
<div class="tw-flex tw-mt-8 tw-mb-4 tw-gap-4">
|
||||
<bit-search
|
||||
[placeholder]="'searchApps' | i18n"
|
||||
class="tw-grow"
|
||||
[formControl]="searchControl"
|
||||
></bit-search>
|
||||
<button
|
||||
type="button"
|
||||
[buttonType]="'primary'"
|
||||
bitButton
|
||||
[disabled]="!selectedUrls.size"
|
||||
[loading]="markingAsCritical"
|
||||
(click)="markAppsAsCritical()"
|
||||
>
|
||||
</dirt-card>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="tw-flex-1"
|
||||
tabindex="0"
|
||||
(click)="showOrgAtRiskApps('allAppsOrgAtRiskApplications')"
|
||||
>
|
||||
<dirt-card
|
||||
#allAppsOrgAtRiskApplications
|
||||
class="tw-w-full"
|
||||
[ngClass]="{
|
||||
'tw-bg-primary-100': drawerDetails.invokerId === 'allAppsOrgAtRiskApplications',
|
||||
}"
|
||||
[title]="'atRiskApplications' | i18n"
|
||||
[value]="applicationSummary.totalAtRiskApplicationCount"
|
||||
[maxValue]="applicationSummary.totalApplicationCount"
|
||||
>
|
||||
</dirt-card>
|
||||
</button>
|
||||
</div>
|
||||
<div class="tw-flex tw-mt-8 tw-mb-4 tw-gap-4">
|
||||
<bit-search
|
||||
[placeholder]="'searchApps' | i18n"
|
||||
class="tw-grow"
|
||||
[formControl]="searchControl"
|
||||
></bit-search>
|
||||
<button
|
||||
type="button"
|
||||
[buttonType]="'primary'"
|
||||
bitButton
|
||||
[disabled]="!selectedUrls.size"
|
||||
[loading]="markingAsCritical"
|
||||
(click)="markAppsAsCritical()"
|
||||
>
|
||||
<i class="bwi tw-mr-2" [ngClass]="selectedUrls.size ? 'bwi-star-f' : 'bwi-star'"></i>
|
||||
{{ "markAppAsCritical" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
<i class="bwi tw-mr-2" [ngClass]="selectedUrls.size ? 'bwi-star-f' : 'bwi-star'"></i>
|
||||
{{ "markAppAsCritical" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<app-table-row-scrollable
|
||||
[dataSource]="dataSource"
|
||||
[showRowCheckBox]="true"
|
||||
[showRowMenuForCriticalApps]="false"
|
||||
[selectedUrls]="selectedUrls"
|
||||
[openApplication]="drawerDetails.invokerId || ''"
|
||||
[checkboxChange]="onCheckboxChange"
|
||||
[showAppAtRiskMembers]="showAppAtRiskMembers"
|
||||
></app-table-row-scrollable>
|
||||
<app-table-row-scrollable
|
||||
[dataSource]="dataSource"
|
||||
[showRowCheckBox]="true"
|
||||
[showRowMenuForCriticalApps]="false"
|
||||
[selectedUrls]="selectedUrls"
|
||||
[openApplication]="drawerDetails.invokerId || ''"
|
||||
[checkboxChange]="onCheckboxChange"
|
||||
[showAppAtRiskMembers]="showAppAtRiskMembers"
|
||||
></app-table-row-scrollable>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -2,33 +2,17 @@ import { Component, DestroyRef, inject, OnInit } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { FormControl } from "@angular/forms";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { combineLatest, debounceTime, firstValueFrom, map, Observable, of, switchMap } from "rxjs";
|
||||
import { debounceTime } from "rxjs";
|
||||
|
||||
import { Security } from "@bitwarden/assets/svg";
|
||||
import {
|
||||
AllActivitiesService,
|
||||
CriticalAppsService,
|
||||
ApplicationHealthReportDetailEnriched,
|
||||
RiskInsightsDataService,
|
||||
RiskInsightsReportService,
|
||||
} from "@bitwarden/bit-common/dirt/reports/risk-insights";
|
||||
import { createNewSummaryData } from "@bitwarden/bit-common/dirt/reports/risk-insights/helpers";
|
||||
import {
|
||||
LEGACY_ApplicationHealthReportDetailWithCriticalFlag,
|
||||
LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher,
|
||||
} from "@bitwarden/bit-common/dirt/reports/risk-insights/models/password-health";
|
||||
import { OrganizationReportSummary } from "@bitwarden/bit-common/dirt/reports/risk-insights/models/report-models";
|
||||
import { RiskInsightsEncryptionService } from "@bitwarden/bit-common/dirt/reports/risk-insights/services/risk-insights-encryption.service";
|
||||
import {
|
||||
getOrganizationById,
|
||||
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 { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import {
|
||||
IconButtonModule,
|
||||
NoItemsModule,
|
||||
@@ -45,7 +29,7 @@ import { AppTableRowScrollableComponent } from "./app-table-row-scrollable.compo
|
||||
import { ApplicationsLoadingComponent } from "./risk-insights-loading.component";
|
||||
|
||||
@Component({
|
||||
selector: "tools-all-applications",
|
||||
selector: "dirt-all-applications",
|
||||
templateUrl: "./all-applications.component.html",
|
||||
imports: [
|
||||
ApplicationsLoadingComponent,
|
||||
@@ -60,97 +44,44 @@ import { ApplicationsLoadingComponent } from "./risk-insights-loading.component"
|
||||
],
|
||||
})
|
||||
export class AllApplicationsComponent implements OnInit {
|
||||
protected dataSource =
|
||||
new TableDataSource<LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher>();
|
||||
protected dataSource = new TableDataSource<ApplicationHealthReportDetailEnriched>();
|
||||
protected selectedUrls: Set<string> = new Set<string>();
|
||||
protected searchControl = new FormControl("", { nonNullable: true });
|
||||
protected loading = true;
|
||||
protected organization = new Organization();
|
||||
noItemsIcon = Security;
|
||||
protected markingAsCritical = false;
|
||||
protected applicationSummary: OrganizationReportSummary = createNewSummaryData();
|
||||
|
||||
destroyRef = inject(DestroyRef);
|
||||
isLoading$: Observable<boolean> = of(false);
|
||||
|
||||
async ngOnInit() {
|
||||
const organizationId = this.activatedRoute.snapshot.paramMap.get("organizationId");
|
||||
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||
|
||||
if (organizationId) {
|
||||
const organization$ = this.organizationService
|
||||
.organizations$(userId)
|
||||
.pipe(getOrganizationById(organizationId));
|
||||
|
||||
combineLatest([
|
||||
this.dataService.applications$,
|
||||
this.criticalAppsService.getAppsListForOrg(organizationId as OrganizationId),
|
||||
organization$,
|
||||
])
|
||||
.pipe(
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
map(([applications, criticalApps, organization]) => {
|
||||
if (applications && applications.length === 0 && criticalApps && criticalApps) {
|
||||
const criticalUrls = criticalApps.map((ca) => ca.uri);
|
||||
const data = applications?.map((app) => ({
|
||||
...app,
|
||||
isMarkedAsCritical: criticalUrls.includes(app.applicationName),
|
||||
})) as LEGACY_ApplicationHealthReportDetailWithCriticalFlag[];
|
||||
return { data, organization };
|
||||
}
|
||||
|
||||
return { data: applications, organization };
|
||||
}),
|
||||
switchMap(async ({ data, organization }) => {
|
||||
if (data && organization) {
|
||||
const dataWithCiphers = await this.reportService.identifyCiphers(
|
||||
data,
|
||||
organization.id as OrganizationId,
|
||||
);
|
||||
|
||||
return {
|
||||
data: dataWithCiphers,
|
||||
organization,
|
||||
};
|
||||
}
|
||||
|
||||
return { data: [], organization };
|
||||
}),
|
||||
)
|
||||
.subscribe(({ data, organization }) => {
|
||||
if (data) {
|
||||
this.dataSource.data = data;
|
||||
this.applicationSummary = this.reportService.generateApplicationsSummary(data);
|
||||
this.allActivitiesService.setAllAppsReportSummary(this.applicationSummary);
|
||||
}
|
||||
if (organization) {
|
||||
this.organization = organization;
|
||||
}
|
||||
});
|
||||
|
||||
this.isLoading$ = this.dataService.isLoading$;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
protected cipherService: CipherService,
|
||||
protected i18nService: I18nService,
|
||||
protected activatedRoute: ActivatedRoute,
|
||||
protected toastService: ToastService,
|
||||
protected configService: ConfigService,
|
||||
protected dataService: RiskInsightsDataService,
|
||||
protected organizationService: OrganizationService,
|
||||
protected reportService: RiskInsightsReportService,
|
||||
private accountService: AccountService,
|
||||
protected criticalAppsService: CriticalAppsService,
|
||||
protected riskInsightsEncryptionService: RiskInsightsEncryptionService,
|
||||
protected allActivitiesService: AllActivitiesService,
|
||||
// protected allActivitiesService: AllActivitiesService,
|
||||
) {
|
||||
this.searchControl.valueChanges
|
||||
.pipe(debounceTime(200), takeUntilDestroyed())
|
||||
.subscribe((v) => (this.dataSource.filter = v));
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.dataService.reportResults$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
|
||||
next: (report) => {
|
||||
this.applicationSummary = report?.summaryData ?? createNewSummaryData();
|
||||
this.dataSource.data = report?.reportData ?? [];
|
||||
},
|
||||
error: () => {
|
||||
this.dataSource.data = [];
|
||||
},
|
||||
});
|
||||
|
||||
// TODO
|
||||
// this.applicationSummary = this.reportService.generateApplicationsSummary(data);
|
||||
// this.allActivitiesService.setAllAppsReportSummary(this.applicationSummary);
|
||||
}
|
||||
|
||||
goToCreateNewLoginItem = async () => {
|
||||
// TODO: implement
|
||||
this.toastService.showToast({
|
||||
@@ -167,41 +98,31 @@ export class AllApplicationsComponent implements OnInit {
|
||||
markAppsAsCritical = async () => {
|
||||
this.markingAsCritical = true;
|
||||
|
||||
try {
|
||||
await this.criticalAppsService.setCriticalApps(
|
||||
this.organization.id as OrganizationId,
|
||||
Array.from(this.selectedUrls),
|
||||
);
|
||||
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: "",
|
||||
message: this.i18nService.t("applicationsMarkedAsCriticalSuccess"),
|
||||
this.dataService
|
||||
.saveCriticalApplications(Array.from(this.selectedUrls))
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: "",
|
||||
message: this.i18nService.t("applicationsMarkedAsCriticalSuccess"),
|
||||
});
|
||||
this.selectedUrls.clear();
|
||||
this.markingAsCritical = false;
|
||||
},
|
||||
error: () => {
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: "",
|
||||
message: this.i18nService.t("applicationsMarkedAsCriticalFail"),
|
||||
});
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
this.selectedUrls.clear();
|
||||
this.markingAsCritical = false;
|
||||
}
|
||||
};
|
||||
|
||||
showAppAtRiskMembers = async (applicationName: string) => {
|
||||
const info = {
|
||||
members:
|
||||
this.dataSource.data.find((app) => app.applicationName === applicationName)
|
||||
?.atRiskMemberDetails ?? [],
|
||||
applicationName,
|
||||
};
|
||||
this.dataService.setDrawerForAppAtRiskMembers(info, applicationName);
|
||||
};
|
||||
|
||||
showOrgAtRiskMembers = async (invokerId: string) => {
|
||||
const dialogData = this.reportService.generateAtRiskMemberList(this.dataSource.data);
|
||||
this.dataService.setDrawerForOrgAtRiskMembers(dialogData, invokerId);
|
||||
};
|
||||
|
||||
showOrgAtRiskApps = async (invokerId: string) => {
|
||||
const data = this.reportService.generateAtRiskApplicationList(this.dataSource.data);
|
||||
this.dataService.setDrawerForOrgAtRiskApps(data, invokerId);
|
||||
await this.dataService.setDrawerForAppAtRiskMembers(applicationName);
|
||||
};
|
||||
|
||||
onCheckboxChange = (applicationName: string, event: Event) => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { CommonModule } from "@angular/common";
|
||||
import { Component, Input } from "@angular/core";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher } from "@bitwarden/bit-common/dirt/reports/risk-insights/models/password-health";
|
||||
import { ApplicationHealthReportDetailEnriched } from "@bitwarden/bit-common/dirt/reports/risk-insights";
|
||||
import { MenuModule, TableDataSource, TableModule } from "@bitwarden/components";
|
||||
import { SharedModule } from "@bitwarden/web-vault/app/shared";
|
||||
import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module";
|
||||
@@ -14,7 +14,7 @@ import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pip
|
||||
})
|
||||
export class AppTableRowScrollableComponent {
|
||||
@Input()
|
||||
dataSource!: TableDataSource<LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher>;
|
||||
dataSource!: TableDataSource<ApplicationHealthReportDetailEnriched>;
|
||||
@Input() showRowMenuForCriticalApps: boolean = false;
|
||||
@Input() showRowCheckBox: boolean = false;
|
||||
@Input() selectedUrls: Set<string> = new Set<string>();
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
type="button"
|
||||
class="tw-flex-1"
|
||||
tabindex="0"
|
||||
(click)="showOrgAtRiskMembers('criticalAppsAtRiskMembers')"
|
||||
(click)="dataService.setDrawerForOrgAtRiskMembers('criticalAppsAtRiskMembers')"
|
||||
>
|
||||
<dirt-card
|
||||
#criticalAppsAtRiskMembers
|
||||
@@ -67,7 +67,7 @@
|
||||
type="button"
|
||||
class="tw-flex-1"
|
||||
tabindex="0"
|
||||
(click)="showOrgAtRiskApps('criticalAppsAtRiskApplications')"
|
||||
(click)="dataService.setDrawerForOrgAtRiskApps('criticalAppsAtRiskApplications')"
|
||||
>
|
||||
<dirt-card
|
||||
#criticalAppsAtRiskApplications
|
||||
@@ -96,7 +96,7 @@
|
||||
[showRowMenuForCriticalApps]="true"
|
||||
[openApplication]="drawerDetails.invokerId || ''"
|
||||
[showAppAtRiskMembers]="showAppAtRiskMembers"
|
||||
[unmarkAsCritical]="unmarkAsCritical"
|
||||
[unmarkAsCritical]="removeCriticalApplication"
|
||||
></app-table-row-scrollable>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -4,23 +4,15 @@ import { Component, DestroyRef, inject, OnInit } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { FormControl } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { combineLatest, debounceTime, firstValueFrom, map, switchMap } from "rxjs";
|
||||
import { debounceTime, EMPTY, map, switchMap } from "rxjs";
|
||||
|
||||
import { Security } from "@bitwarden/assets/svg";
|
||||
import {
|
||||
AllActivitiesService,
|
||||
CriticalAppsService,
|
||||
ApplicationHealthReportDetailEnriched,
|
||||
RiskInsightsDataService,
|
||||
RiskInsightsReportService,
|
||||
} from "@bitwarden/bit-common/dirt/reports/risk-insights";
|
||||
import {
|
||||
LEGACY_ApplicationHealthReportDetailWithCriticalFlag,
|
||||
LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher,
|
||||
} from "@bitwarden/bit-common/dirt/reports/risk-insights/models/password-health";
|
||||
import { createNewSummaryData } from "@bitwarden/bit-common/dirt/reports/risk-insights/helpers";
|
||||
import { OrganizationReportSummary } from "@bitwarden/bit-common/dirt/reports/risk-insights/models/report-models";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { CipherId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { SecurityTaskType } from "@bitwarden/common/vault/tasks";
|
||||
@@ -37,7 +29,7 @@ import { AppTableRowScrollableComponent } from "./app-table-row-scrollable.compo
|
||||
import { RiskInsightsTabType } from "./risk-insights.component";
|
||||
|
||||
@Component({
|
||||
selector: "tools-critical-applications",
|
||||
selector: "dirt-critical-applications",
|
||||
templateUrl: "./critical-applications.component.html",
|
||||
imports: [
|
||||
CardComponent,
|
||||
@@ -51,60 +43,57 @@ import { RiskInsightsTabType } from "./risk-insights.component";
|
||||
providers: [DefaultAdminTaskService],
|
||||
})
|
||||
export class CriticalApplicationsComponent implements OnInit {
|
||||
protected dataSource =
|
||||
new TableDataSource<LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher>();
|
||||
protected selectedIds: Set<number> = new Set<number>();
|
||||
protected searchControl = new FormControl("", { nonNullable: true });
|
||||
private destroyRef = inject(DestroyRef);
|
||||
protected loading = false;
|
||||
protected enableRequestPasswordChange = false;
|
||||
protected organizationId: OrganizationId;
|
||||
protected applicationSummary = {} as OrganizationReportSummary;
|
||||
noItemsIcon = Security;
|
||||
enableRequestPasswordChange = false;
|
||||
|
||||
protected dataSource = new TableDataSource<ApplicationHealthReportDetailEnriched>();
|
||||
protected applicationSummary = {} as OrganizationReportSummary;
|
||||
|
||||
protected selectedIds: Set<number> = new Set<number>();
|
||||
protected searchControl = new FormControl("", { nonNullable: true });
|
||||
|
||||
constructor(
|
||||
protected activatedRoute: ActivatedRoute,
|
||||
protected router: Router,
|
||||
protected toastService: ToastService,
|
||||
protected dataService: RiskInsightsDataService,
|
||||
protected i18nService: I18nService,
|
||||
private adminTaskService: DefaultAdminTaskService,
|
||||
) {
|
||||
this.searchControl.valueChanges
|
||||
.pipe(debounceTime(200), takeUntilDestroyed())
|
||||
.subscribe((v) => (this.dataSource.filter = v));
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.organizationId = this.activatedRoute.snapshot.paramMap.get(
|
||||
"organizationId",
|
||||
) as OrganizationId;
|
||||
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||
this.criticalAppsService.loadOrganizationContext(this.organizationId as OrganizationId, userId);
|
||||
|
||||
if (this.organizationId) {
|
||||
combineLatest([
|
||||
this.dataService.applications$,
|
||||
this.criticalAppsService.getAppsListForOrg(this.organizationId as OrganizationId),
|
||||
])
|
||||
.pipe(
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
map(([applications, criticalApps]) => {
|
||||
const criticalUrls = criticalApps.map((ca) => ca.uri);
|
||||
const data = applications?.map((app) => ({
|
||||
...app,
|
||||
isMarkedAsCritical: criticalUrls.includes(app.applicationName),
|
||||
})) as LEGACY_ApplicationHealthReportDetailWithCriticalFlag[];
|
||||
return data?.filter((app) => app.isMarkedAsCritical);
|
||||
}),
|
||||
switchMap(async (data) => {
|
||||
if (data) {
|
||||
const dataWithCiphers = await this.reportService.identifyCiphers(
|
||||
data,
|
||||
this.organizationId,
|
||||
);
|
||||
return dataWithCiphers;
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
)
|
||||
.subscribe((applications) => {
|
||||
if (applications) {
|
||||
this.dataSource.data = applications;
|
||||
this.applicationSummary = this.reportService.generateApplicationsSummary(applications);
|
||||
this.enableRequestPasswordChange = this.applicationSummary.totalAtRiskMemberCount > 0;
|
||||
this.allActivitiesService.setCriticalAppsReportSummary(this.applicationSummary);
|
||||
this.allActivitiesService.setAllAppsReportDetails(applications);
|
||||
this.dataService.criticalReportResults$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
|
||||
next: (criticalReport) => {
|
||||
this.dataSource.data = criticalReport?.reportData ?? [];
|
||||
this.applicationSummary = criticalReport?.summaryData ?? createNewSummaryData();
|
||||
this.enableRequestPasswordChange = criticalReport?.summaryData?.totalAtRiskMemberCount > 0;
|
||||
},
|
||||
error: () => {
|
||||
this.dataSource.data = [];
|
||||
this.applicationSummary = createNewSummaryData();
|
||||
this.enableRequestPasswordChange = false;
|
||||
},
|
||||
});
|
||||
this.activatedRoute.paramMap
|
||||
.pipe(
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
map((params) => params.get("organizationId")),
|
||||
switchMap(async (orgId) => {
|
||||
if (orgId) {
|
||||
this.organizationId = orgId as OrganizationId;
|
||||
} else {
|
||||
return EMPTY;
|
||||
}
|
||||
});
|
||||
}
|
||||
}),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
goToAllAppsTab = async () => {
|
||||
@@ -117,26 +106,25 @@ export class CriticalApplicationsComponent implements OnInit {
|
||||
);
|
||||
};
|
||||
|
||||
unmarkAsCritical = async (hostname: string) => {
|
||||
try {
|
||||
await this.criticalAppsService.dropCriticalApp(
|
||||
this.organizationId as OrganizationId,
|
||||
hostname,
|
||||
);
|
||||
} catch {
|
||||
this.toastService.showToast({
|
||||
message: this.i18nService.t("unexpectedError"),
|
||||
variant: "error",
|
||||
title: this.i18nService.t("error"),
|
||||
removeCriticalApplication = async (hostname: string) => {
|
||||
this.dataService
|
||||
.removeCriticalApplication(hostname)
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.toastService.showToast({
|
||||
message: this.i18nService.t("criticalApplicationUnmarkedSuccessfully"),
|
||||
variant: "success",
|
||||
});
|
||||
},
|
||||
error: () => {
|
||||
this.toastService.showToast({
|
||||
message: this.i18nService.t("unexpectedError"),
|
||||
variant: "error",
|
||||
title: this.i18nService.t("error"),
|
||||
});
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.toastService.showToast({
|
||||
message: this.i18nService.t("criticalApplicationUnmarkedSuccessfully"),
|
||||
variant: "success",
|
||||
});
|
||||
this.dataSource.data = this.dataSource.data.filter((app) => app.applicationName !== hostname);
|
||||
};
|
||||
|
||||
async requestPasswordChange() {
|
||||
@@ -167,42 +155,7 @@ export class CriticalApplicationsComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
protected activatedRoute: ActivatedRoute,
|
||||
protected router: Router,
|
||||
protected toastService: ToastService,
|
||||
protected dataService: RiskInsightsDataService,
|
||||
protected criticalAppsService: CriticalAppsService,
|
||||
protected reportService: RiskInsightsReportService,
|
||||
protected i18nService: I18nService,
|
||||
private configService: ConfigService,
|
||||
private adminTaskService: DefaultAdminTaskService,
|
||||
private accountService: AccountService,
|
||||
private allActivitiesService: AllActivitiesService,
|
||||
) {
|
||||
this.searchControl.valueChanges
|
||||
.pipe(debounceTime(200), takeUntilDestroyed())
|
||||
.subscribe((v) => (this.dataSource.filter = v));
|
||||
}
|
||||
|
||||
showAppAtRiskMembers = async (applicationName: string) => {
|
||||
const data = {
|
||||
members:
|
||||
this.dataSource.data.find((app) => app.applicationName === applicationName)
|
||||
?.atRiskMemberDetails ?? [],
|
||||
applicationName,
|
||||
};
|
||||
this.dataService.setDrawerForAppAtRiskMembers(data, applicationName);
|
||||
};
|
||||
|
||||
showOrgAtRiskMembers = async (invokerId: string) => {
|
||||
const data = this.reportService.generateAtRiskMemberList(this.dataSource.data);
|
||||
this.dataService.setDrawerForOrgAtRiskMembers(data, invokerId);
|
||||
};
|
||||
|
||||
showOrgAtRiskApps = async (invokerId: string) => {
|
||||
const data = this.reportService.generateAtRiskApplicationList(this.dataSource.data);
|
||||
this.dataService.setDrawerForOrgAtRiskApps(data, invokerId);
|
||||
await this.dataService.setDrawerForAppAtRiskMembers(applicationName);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Component } from "@angular/core";
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
|
||||
@Component({
|
||||
selector: "tools-risk-insights-loading",
|
||||
selector: "dirt-risk-insights-loading",
|
||||
imports: [CommonModule, JslibModule],
|
||||
templateUrl: "./risk-insights-loading.component.html",
|
||||
})
|
||||
|
||||
@@ -4,19 +4,23 @@
|
||||
{{ "reviewAtRiskPasswords" | i18n }}
|
||||
</div>
|
||||
<div
|
||||
*ngIf="dataLastUpdated$ | async"
|
||||
class="tw-bg-primary-100 tw-rounded-lg tw-w-full tw-px-8 tw-py-4 tw-my-4 tw-flex tw-items-center"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-exclamation-triangle bwi-lg tw-text-[1.2rem] tw-text-muted"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="tw-mx-4">{{
|
||||
"dataLastUpdated" | i18n: (dataLastUpdated$ | async | date: "MMMM d, y 'at' h:mm a")
|
||||
}}</span>
|
||||
<span class="tw-flex tw-justify-center tw-w-16">
|
||||
@if (dataLastUpdated) {
|
||||
<span class="tw-mx-4">{{
|
||||
"dataLastUpdated" | i18n: (dataLastUpdated | date: "MMMM d, y 'at' h:mm a")
|
||||
}}</span>
|
||||
} @else {
|
||||
<span class="tw-mx-4">{{ "noReportRan" | i18n }}</span>
|
||||
}
|
||||
@let isRunningReport = dataService.isRunningReport$ | async;
|
||||
<span class="tw-flex tw-justify-center">
|
||||
<button
|
||||
*ngIf="!(isRefreshing$ | async)"
|
||||
*ngIf="!isRunningReport"
|
||||
type="button"
|
||||
bitButton
|
||||
buttonType="secondary"
|
||||
@@ -24,11 +28,11 @@
|
||||
tabindex="0"
|
||||
[bitAction]="refreshData.bind(this)"
|
||||
>
|
||||
{{ "refresh" | i18n }}
|
||||
{{ "riskInsightsRunReport" | i18n }}
|
||||
</button>
|
||||
<span>
|
||||
<i
|
||||
*ngIf="isRefreshing$ | async"
|
||||
*ngIf="isRunningReport"
|
||||
class="bwi bwi-spinner bwi-spin tw-text-muted tw-text-[1.2rem]"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
@@ -38,18 +42,21 @@
|
||||
<bit-tab-group [(selectedIndex)]="tabIndex" (selectedIndexChange)="onTabChange($event)">
|
||||
@if (isRiskInsightsActivityTabFeatureEnabled) {
|
||||
<bit-tab label="{{ 'activity' | i18n }}">
|
||||
<tools-all-activity></tools-all-activity>
|
||||
<dirt-all-activity></dirt-all-activity>
|
||||
</bit-tab>
|
||||
}
|
||||
<bit-tab label="{{ 'allApplicationsWithCount' | i18n: appsCount }}">
|
||||
<tools-all-applications></tools-all-applications>
|
||||
<dirt-all-applications></dirt-all-applications>
|
||||
</bit-tab>
|
||||
<bit-tab>
|
||||
<ng-template bitTabLabel>
|
||||
<i class="bwi bwi-star"></i>
|
||||
{{ "criticalApplicationsWithCount" | i18n: (criticalApps$ | async)?.length ?? 0 }}
|
||||
{{
|
||||
"criticalApplicationsWithCount"
|
||||
| i18n: (dataService.criticalReportResults$ | async)?.reportData?.length ?? 0
|
||||
}}
|
||||
</ng-template>
|
||||
<tools-critical-applications></tools-critical-applications>
|
||||
<dirt-critical-applications></dirt-critical-applications>
|
||||
</bit-tab>
|
||||
</bit-tab-group>
|
||||
|
||||
@@ -69,7 +76,9 @@
|
||||
}}</span>
|
||||
<ng-container *ngIf="drawerDetails.atRiskMemberDetails.length > 0">
|
||||
<div class="tw-flex tw-justify-between tw-mt-2 tw-text-muted">
|
||||
<div bitTypography="body2" class="tw-text-sm tw-font-bold">{{ "email" | i18n }}</div>
|
||||
<div bitTypography="body2" class="tw-text-sm tw-font-bold">
|
||||
{{ "email" | i18n }}
|
||||
</div>
|
||||
<div bitTypography="body2" class="tw-text-sm tw-font-bold">
|
||||
{{ "atRiskPasswords" | i18n }}
|
||||
</div>
|
||||
|
||||
@@ -2,21 +2,12 @@ import { CommonModule } from "@angular/common";
|
||||
import { Component, DestroyRef, OnInit, inject } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { EMPTY, firstValueFrom, Observable } from "rxjs";
|
||||
import { EMPTY } from "rxjs";
|
||||
import { map, switchMap } from "rxjs/operators";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import {
|
||||
CriticalAppsService,
|
||||
RiskInsightsDataService,
|
||||
} from "@bitwarden/bit-common/dirt/reports/risk-insights";
|
||||
import { PasswordHealthReportApplicationsResponse } from "@bitwarden/bit-common/dirt/reports/risk-insights/models/api-models.types";
|
||||
import {
|
||||
ApplicationHealthReportDetail,
|
||||
DrawerType,
|
||||
} from "@bitwarden/bit-common/dirt/reports/risk-insights/models/report-models";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { RiskInsightsDataService } from "@bitwarden/bit-common/dirt/reports/risk-insights";
|
||||
import { DrawerType } from "@bitwarden/bit-common/dirt/reports/risk-insights/models/report-models";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
@@ -67,19 +58,13 @@ export class RiskInsightsComponent implements OnInit {
|
||||
tabIndex: RiskInsightsTabType = RiskInsightsTabType.AllApps;
|
||||
isRiskInsightsActivityTabFeatureEnabled: boolean = false;
|
||||
|
||||
dataLastUpdated: Date = new Date();
|
||||
|
||||
criticalApps$: Observable<PasswordHealthReportApplicationsResponse[]> = new Observable();
|
||||
|
||||
appsCount: number = 0;
|
||||
criticalAppsCount: number = 0;
|
||||
notifiedMembersCount: number = 0;
|
||||
// Leaving this commented because it's not used but seems important
|
||||
// notifiedMembersCount: number = 0;
|
||||
|
||||
private organizationId: OrganizationId = "" as OrganizationId;
|
||||
|
||||
isLoading$: Observable<boolean> = new Observable<boolean>();
|
||||
isRefreshing$: Observable<boolean> = new Observable<boolean>();
|
||||
dataLastUpdated$: Observable<Date | null> = new Observable<Date | null>();
|
||||
dataLastUpdated: Date | null = null;
|
||||
refetching: boolean = false;
|
||||
|
||||
constructor(
|
||||
@@ -87,8 +72,6 @@ export class RiskInsightsComponent implements OnInit {
|
||||
private router: Router,
|
||||
private configService: ConfigService,
|
||||
protected dataService: RiskInsightsDataService,
|
||||
private criticalAppsService: CriticalAppsService,
|
||||
private accountService: AccountService,
|
||||
) {
|
||||
this.route.queryParams.pipe(takeUntilDestroyed()).subscribe(({ tabIndex }) => {
|
||||
this.tabIndex = !isNaN(Number(tabIndex)) ? Number(tabIndex) : RiskInsightsTabType.AllApps;
|
||||
@@ -104,39 +87,29 @@ export class RiskInsightsComponent implements OnInit {
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||
|
||||
this.route.paramMap
|
||||
.pipe(
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
map((params) => params.get("organizationId")),
|
||||
switchMap((orgId) => {
|
||||
switchMap(async (orgId) => {
|
||||
if (orgId) {
|
||||
// Initialize Data Service
|
||||
await this.dataService.initializeForOrganization(orgId as OrganizationId);
|
||||
|
||||
this.organizationId = orgId as OrganizationId;
|
||||
this.dataService.fetchApplicationsReport(this.organizationId);
|
||||
this.isLoading$ = this.dataService.isLoading$;
|
||||
this.isRefreshing$ = this.dataService.isRefreshing$;
|
||||
this.dataLastUpdated$ = this.dataService.dataLastUpdated$;
|
||||
return this.dataService.applications$;
|
||||
} else {
|
||||
return EMPTY;
|
||||
}
|
||||
}),
|
||||
)
|
||||
.subscribe({
|
||||
next: (applications: ApplicationHealthReportDetail[] | null) => {
|
||||
if (applications) {
|
||||
this.appsCount = applications.length;
|
||||
}
|
||||
.subscribe();
|
||||
|
||||
this.criticalAppsService.loadOrganizationContext(
|
||||
this.organizationId as OrganizationId,
|
||||
userId,
|
||||
);
|
||||
this.criticalApps$ = this.criticalAppsService.getAppsListForOrg(
|
||||
this.organizationId as OrganizationId,
|
||||
);
|
||||
},
|
||||
// Subscribe to report result details
|
||||
this.dataService.reportResults$
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe((report) => {
|
||||
this.appsCount = report?.reportData.length ?? 0;
|
||||
this.dataLastUpdated = report?.creationDate ?? null;
|
||||
});
|
||||
|
||||
// Subscribe to drawer state changes
|
||||
@@ -156,7 +129,7 @@ export class RiskInsightsComponent implements OnInit {
|
||||
*/
|
||||
refreshData(): void {
|
||||
if (this.organizationId) {
|
||||
this.dataService.refreshApplicationsReport(this.organizationId);
|
||||
this.dataService.triggerReport();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user