mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 13:53:34 +00:00
[PM-25614] Add Encrichment Logic for Risk Insights Data Service (#16577)
* Add encryption logic. Minor updates to critical apps service * Fix possibly null type
This commit is contained in:
@@ -117,7 +117,7 @@ export type OrganizationReportApplication = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All applications report detail. Application is the cipher
|
* Report details for an application
|
||||||
* uri. Has the at risk, password, and member information
|
* uri. Has the at risk, password, and member information
|
||||||
*/
|
*/
|
||||||
export type ApplicationHealthReportDetail = {
|
export type ApplicationHealthReportDetail = {
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ describe("CriticalAppsService", () => {
|
|||||||
const orgKey$ = new BehaviorSubject(OrgRecords);
|
const orgKey$ = new BehaviorSubject(OrgRecords);
|
||||||
keyService.orgKeys$.mockReturnValue(orgKey$);
|
keyService.orgKeys$.mockReturnValue(orgKey$);
|
||||||
|
|
||||||
service.setOrganizationId(SomeOrganization, SomeUser);
|
service.loadOrganizationContext(SomeOrganization, SomeUser);
|
||||||
|
|
||||||
// act
|
// act
|
||||||
await service.setCriticalApps(SomeOrganization, criticalApps);
|
await service.setCriticalApps(SomeOrganization, criticalApps);
|
||||||
@@ -112,7 +112,7 @@ describe("CriticalAppsService", () => {
|
|||||||
const orgKey$ = new BehaviorSubject(OrgRecords);
|
const orgKey$ = new BehaviorSubject(OrgRecords);
|
||||||
keyService.orgKeys$.mockReturnValue(orgKey$);
|
keyService.orgKeys$.mockReturnValue(orgKey$);
|
||||||
|
|
||||||
service.setOrganizationId(SomeOrganization, SomeUser);
|
service.loadOrganizationContext(SomeOrganization, SomeUser);
|
||||||
|
|
||||||
// act
|
// act
|
||||||
await service.setCriticalApps(SomeOrganization, selectedUrls);
|
await service.setCriticalApps(SomeOrganization, selectedUrls);
|
||||||
@@ -136,7 +136,7 @@ describe("CriticalAppsService", () => {
|
|||||||
const orgKey$ = new BehaviorSubject(OrgRecords);
|
const orgKey$ = new BehaviorSubject(OrgRecords);
|
||||||
keyService.orgKeys$.mockReturnValue(orgKey$);
|
keyService.orgKeys$.mockReturnValue(orgKey$);
|
||||||
|
|
||||||
service.setOrganizationId(SomeOrganization, SomeUser);
|
service.loadOrganizationContext(SomeOrganization, SomeUser);
|
||||||
|
|
||||||
expect(keyService.orgKeys$).toHaveBeenCalledWith(SomeUser);
|
expect(keyService.orgKeys$).toHaveBeenCalledWith(SomeUser);
|
||||||
expect(encryptService.decryptString).toHaveBeenCalledTimes(2);
|
expect(encryptService.decryptString).toHaveBeenCalledTimes(2);
|
||||||
@@ -154,7 +154,7 @@ describe("CriticalAppsService", () => {
|
|||||||
|
|
||||||
const orgKey$ = new BehaviorSubject(OrgRecords);
|
const orgKey$ = new BehaviorSubject(OrgRecords);
|
||||||
keyService.orgKeys$.mockReturnValue(orgKey$);
|
keyService.orgKeys$.mockReturnValue(orgKey$);
|
||||||
service.setOrganizationId(SomeOrganization, SomeUser);
|
service.loadOrganizationContext(SomeOrganization, SomeUser);
|
||||||
service.setAppsInListForOrg(response);
|
service.setAppsInListForOrg(response);
|
||||||
service.getAppsListForOrg(orgId as OrganizationId).subscribe((res) => {
|
service.getAppsListForOrg(orgId as OrganizationId).subscribe((res) => {
|
||||||
expect(res).toHaveLength(2);
|
expect(res).toHaveLength(2);
|
||||||
@@ -173,7 +173,7 @@ describe("CriticalAppsService", () => {
|
|||||||
const orgKey$ = new BehaviorSubject(OrgRecords);
|
const orgKey$ = new BehaviorSubject(OrgRecords);
|
||||||
keyService.orgKeys$.mockReturnValue(orgKey$);
|
keyService.orgKeys$.mockReturnValue(orgKey$);
|
||||||
|
|
||||||
service.setOrganizationId(SomeOrganization, SomeUser);
|
service.loadOrganizationContext(SomeOrganization, SomeUser);
|
||||||
|
|
||||||
service.setAppsInListForOrg(initialList);
|
service.setAppsInListForOrg(initialList);
|
||||||
|
|
||||||
@@ -204,7 +204,7 @@ describe("CriticalAppsService", () => {
|
|||||||
const orgKey$ = new BehaviorSubject(OrgRecords);
|
const orgKey$ = new BehaviorSubject(OrgRecords);
|
||||||
keyService.orgKeys$.mockReturnValue(orgKey$);
|
keyService.orgKeys$.mockReturnValue(orgKey$);
|
||||||
|
|
||||||
service.setOrganizationId(SomeOrganization, SomeUser);
|
service.loadOrganizationContext(SomeOrganization, SomeUser);
|
||||||
|
|
||||||
service.setAppsInListForOrg(initialList);
|
service.setAppsInListForOrg(initialList);
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ import {
|
|||||||
map,
|
map,
|
||||||
Observable,
|
Observable,
|
||||||
of,
|
of,
|
||||||
Subject,
|
|
||||||
switchMap,
|
switchMap,
|
||||||
takeUntil,
|
|
||||||
zip,
|
zip,
|
||||||
} from "rxjs";
|
} from "rxjs";
|
||||||
|
|
||||||
@@ -30,17 +28,16 @@ import { CriticalAppsApiService } from "./critical-apps-api.service";
|
|||||||
* Encrypts and saves data for a given organization
|
* Encrypts and saves data for a given organization
|
||||||
*/
|
*/
|
||||||
export class CriticalAppsService {
|
export class CriticalAppsService {
|
||||||
private orgId = new BehaviorSubject<OrganizationId | null>(null);
|
// -------------------------- Context state --------------------------
|
||||||
|
// The organization ID of the organization the user is currently viewing
|
||||||
|
private organizationId = new BehaviorSubject<OrganizationId | null>(null);
|
||||||
private orgKey$ = new Observable<OrgKey>();
|
private orgKey$ = new Observable<OrgKey>();
|
||||||
private criticalAppsList = new BehaviorSubject<PasswordHealthReportApplicationsResponse[]>([]);
|
|
||||||
private teardown = new Subject<void>();
|
|
||||||
|
|
||||||
private fetchOrg$ = this.orgId
|
// -------------------------- Data ------------------------------------
|
||||||
.pipe(
|
private criticalAppsListSubject$ = new BehaviorSubject<
|
||||||
switchMap((orgId) => this.retrieveCriticalApps(orgId)),
|
PasswordHealthReportApplicationsResponse[]
|
||||||
takeUntil(this.teardown),
|
>([]);
|
||||||
)
|
criticalAppsList$ = this.criticalAppsListSubject$.asObservable();
|
||||||
.subscribe((apps) => this.criticalAppsList.next(apps));
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private keyService: KeyService,
|
private keyService: KeyService,
|
||||||
@@ -48,25 +45,52 @@ export class CriticalAppsService {
|
|||||||
private criticalAppsApiService: CriticalAppsApiService,
|
private criticalAppsApiService: CriticalAppsApiService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
// Set context for the service for a specific organization
|
||||||
|
loadOrganizationContext(orgId: OrganizationId, userId: UserId) {
|
||||||
|
// Fetch the organization key for the user
|
||||||
|
this.orgKey$ = this.keyService.orgKeys$(userId).pipe(
|
||||||
|
filter((OrgKeys) => !!OrgKeys),
|
||||||
|
map((organizationKeysById) => organizationKeysById[orgId as OrganizationId]),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Store organization id for service context
|
||||||
|
this.organizationId.next(orgId);
|
||||||
|
|
||||||
|
// Setup the critical apps fetching for the organization
|
||||||
|
if (orgId) {
|
||||||
|
this.retrieveCriticalApps(orgId).subscribe({
|
||||||
|
next: (result) => {
|
||||||
|
this.criticalAppsListSubject$.next(result);
|
||||||
|
},
|
||||||
|
error: (error: unknown) => {
|
||||||
|
throw error;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get a list of critical apps for a given organization
|
// Get a list of critical apps for a given organization
|
||||||
getAppsListForOrg(orgId: OrganizationId): Observable<PasswordHealthReportApplicationsResponse[]> {
|
getAppsListForOrg(orgId: OrganizationId): Observable<PasswordHealthReportApplicationsResponse[]> {
|
||||||
if (orgId != this.orgId.value) {
|
// [FIXME] Get organization id from context for all functions in this file
|
||||||
throw new Error("Organization ID mismatch");
|
if (orgId != this.organizationId.value) {
|
||||||
|
throw new Error(
|
||||||
|
`Organization ID mismatch: expected ${this.organizationId.value}, got ${orgId}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.criticalAppsList
|
return this.criticalAppsListSubject$
|
||||||
.asObservable()
|
.asObservable()
|
||||||
.pipe(map((apps) => apps.filter((app) => app.organizationId === orgId)));
|
.pipe(map((apps) => apps.filter((app) => app.organizationId === orgId)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset the critical apps list
|
// Reset the critical apps list
|
||||||
setAppsInListForOrg(apps: PasswordHealthReportApplicationsResponse[]) {
|
setAppsInListForOrg(apps: PasswordHealthReportApplicationsResponse[]) {
|
||||||
this.criticalAppsList.next(apps);
|
this.criticalAppsListSubject$.next(apps);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the selected critical apps for a given organization
|
// Save the selected critical apps for a given organization
|
||||||
async setCriticalApps(orgId: OrganizationId, selectedUrls: string[]) {
|
async setCriticalApps(orgId: OrganizationId, selectedUrls: string[]) {
|
||||||
if (orgId != this.orgId.value) {
|
if (orgId != this.organizationId.value) {
|
||||||
throw new Error("Organization ID mismatch");
|
throw new Error("Organization ID mismatch");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +103,7 @@ export class CriticalAppsService {
|
|||||||
// only save records that are not already in the database
|
// only save records that are not already in the database
|
||||||
const newEntries = await this.filterNewEntries(orgId as OrganizationId, selectedUrls);
|
const newEntries = await this.filterNewEntries(orgId as OrganizationId, selectedUrls);
|
||||||
const criticalAppsRequests = await this.encryptNewEntries(
|
const criticalAppsRequests = await this.encryptNewEntries(
|
||||||
this.orgId.value as OrganizationId,
|
this.organizationId.value as OrganizationId,
|
||||||
orgKey,
|
orgKey,
|
||||||
newEntries,
|
newEntries,
|
||||||
);
|
);
|
||||||
@@ -89,7 +113,7 @@ export class CriticalAppsService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// add the new entries to the criticalAppsList
|
// add the new entries to the criticalAppsList
|
||||||
const updatedList = [...this.criticalAppsList.value];
|
const updatedList = [...this.criticalAppsListSubject$.value];
|
||||||
for (const responseItem of dbResponse) {
|
for (const responseItem of dbResponse) {
|
||||||
const decryptedUrl = await this.encryptService.decryptString(
|
const decryptedUrl = await this.encryptService.decryptString(
|
||||||
new EncString(responseItem.uri),
|
new EncString(responseItem.uri),
|
||||||
@@ -103,26 +127,17 @@ export class CriticalAppsService {
|
|||||||
} as PasswordHealthReportApplicationsResponse);
|
} as PasswordHealthReportApplicationsResponse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.criticalAppsList.next(updatedList);
|
this.criticalAppsListSubject$.next(updatedList);
|
||||||
}
|
|
||||||
|
|
||||||
// Get the critical apps for a given organization
|
|
||||||
setOrganizationId(orgId: OrganizationId, userId: UserId) {
|
|
||||||
this.orgKey$ = this.keyService.orgKeys$(userId).pipe(
|
|
||||||
filter((OrgKeys) => !!OrgKeys),
|
|
||||||
map((organizationKeysById) => organizationKeysById[orgId as OrganizationId]),
|
|
||||||
);
|
|
||||||
this.orgId.next(orgId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drop a critical app for a given organization
|
// Drop a critical app for a given organization
|
||||||
// Only one app may be dropped at a time
|
// Only one app may be dropped at a time
|
||||||
async dropCriticalApp(orgId: OrganizationId, selectedUrl: string) {
|
async dropCriticalApp(orgId: OrganizationId, selectedUrl: string) {
|
||||||
if (orgId != this.orgId.value) {
|
if (orgId != this.organizationId.value) {
|
||||||
throw new Error("Organization ID mismatch");
|
throw new Error("Organization ID mismatch");
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = this.criticalAppsList.value.find(
|
const app = this.criticalAppsListSubject$.value.find(
|
||||||
(f) => f.organizationId === orgId && f.uri === selectedUrl,
|
(f) => f.organizationId === orgId && f.uri === selectedUrl,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -135,7 +150,9 @@ export class CriticalAppsService {
|
|||||||
passwordHealthReportApplicationIds: [app.id],
|
passwordHealthReportApplicationIds: [app.id],
|
||||||
});
|
});
|
||||||
|
|
||||||
this.criticalAppsList.next(this.criticalAppsList.value.filter((f) => f.uri !== selectedUrl));
|
this.criticalAppsListSubject$.next(
|
||||||
|
this.criticalAppsListSubject$.value.filter((f) => f.uri !== selectedUrl),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private retrieveCriticalApps(
|
private retrieveCriticalApps(
|
||||||
@@ -170,7 +187,7 @@ export class CriticalAppsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async filterNewEntries(orgId: OrganizationId, selectedUrls: string[]): Promise<string[]> {
|
private async filterNewEntries(orgId: OrganizationId, selectedUrls: string[]): Promise<string[]> {
|
||||||
return await firstValueFrom(this.criticalAppsList).then((criticalApps) => {
|
return await firstValueFrom(this.criticalAppsListSubject$).then((criticalApps) => {
|
||||||
const criticalAppsUri = criticalApps
|
const criticalAppsUri = criticalApps
|
||||||
.filter((f) => f.organizationId === orgId)
|
.filter((f) => f.organizationId === orgId)
|
||||||
.map((f) => f.uri);
|
.map((f) => f.uri);
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
import { BehaviorSubject } from "rxjs";
|
import { BehaviorSubject, firstValueFrom, Observable, of } from "rxjs";
|
||||||
import { finalize } from "rxjs/operators";
|
import { finalize, switchMap, withLatestFrom } from "rxjs/operators";
|
||||||
|
|
||||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
import {
|
||||||
|
getOrganizationById,
|
||||||
|
OrganizationService,
|
||||||
|
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
|
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AppAtRiskMembersDialogParams,
|
AppAtRiskMembersDialogParams,
|
||||||
@@ -9,14 +15,35 @@ import {
|
|||||||
AtRiskMemberDetail,
|
AtRiskMemberDetail,
|
||||||
DrawerType,
|
DrawerType,
|
||||||
ApplicationHealthReportDetail,
|
ApplicationHealthReportDetail,
|
||||||
|
ApplicationHealthReportDetailEnriched,
|
||||||
} from "../models/report-models";
|
} from "../models/report-models";
|
||||||
|
|
||||||
|
import { CriticalAppsService } from "./critical-apps.service";
|
||||||
import { RiskInsightsReportService } from "./risk-insights-report.service";
|
import { RiskInsightsReportService } from "./risk-insights-report.service";
|
||||||
export class RiskInsightsDataService {
|
export class RiskInsightsDataService {
|
||||||
private applicationsSubject = new BehaviorSubject<ApplicationHealthReportDetail[] | null>(null);
|
// -------------------------- Context state --------------------------
|
||||||
|
// Current user viewing risk insights
|
||||||
|
private userIdSubject = new BehaviorSubject<UserId | null>(null);
|
||||||
|
userId$ = this.userIdSubject.asObservable();
|
||||||
|
|
||||||
|
// Organization the user is currently viewing
|
||||||
|
private organizationDetailsSubject = new BehaviorSubject<{
|
||||||
|
organizationId: OrganizationId;
|
||||||
|
organizationName: string;
|
||||||
|
} | null>(null);
|
||||||
|
organizationDetails$ = this.organizationDetailsSubject.asObservable();
|
||||||
|
|
||||||
|
// -------------------------- Data ------------------------------------
|
||||||
|
private applicationsSubject = new BehaviorSubject<ApplicationHealthReportDetail[] | null>(null);
|
||||||
applications$ = this.applicationsSubject.asObservable();
|
applications$ = this.applicationsSubject.asObservable();
|
||||||
|
|
||||||
|
private dataLastUpdatedSubject = new BehaviorSubject<Date | null>(null);
|
||||||
|
dataLastUpdated$ = this.dataLastUpdatedSubject.asObservable();
|
||||||
|
|
||||||
|
criticalApps$ = this.criticalAppsService.criticalAppsList$;
|
||||||
|
|
||||||
|
// --------------------------- UI State ------------------------------------
|
||||||
|
|
||||||
private isLoadingSubject = new BehaviorSubject<boolean>(false);
|
private isLoadingSubject = new BehaviorSubject<boolean>(false);
|
||||||
isLoading$ = this.isLoadingSubject.asObservable();
|
isLoading$ = this.isLoadingSubject.asObservable();
|
||||||
|
|
||||||
@@ -26,9 +53,6 @@ export class RiskInsightsDataService {
|
|||||||
private errorSubject = new BehaviorSubject<string | null>(null);
|
private errorSubject = new BehaviorSubject<string | null>(null);
|
||||||
error$ = this.errorSubject.asObservable();
|
error$ = this.errorSubject.asObservable();
|
||||||
|
|
||||||
private dataLastUpdatedSubject = new BehaviorSubject<Date | null>(null);
|
|
||||||
dataLastUpdated$ = this.dataLastUpdatedSubject.asObservable();
|
|
||||||
|
|
||||||
openDrawer = false;
|
openDrawer = false;
|
||||||
drawerInvokerId: string = "";
|
drawerInvokerId: string = "";
|
||||||
activeDrawerType: DrawerType = DrawerType.None;
|
activeDrawerType: DrawerType = DrawerType.None;
|
||||||
@@ -36,7 +60,51 @@ export class RiskInsightsDataService {
|
|||||||
appAtRiskMembers: AppAtRiskMembersDialogParams | null = null;
|
appAtRiskMembers: AppAtRiskMembersDialogParams | null = null;
|
||||||
atRiskAppDetails: AtRiskApplicationDetail[] | null = null;
|
atRiskAppDetails: AtRiskApplicationDetail[] | null = null;
|
||||||
|
|
||||||
constructor(private reportService: RiskInsightsReportService) {}
|
constructor(
|
||||||
|
private accountService: AccountService,
|
||||||
|
private criticalAppsService: CriticalAppsService,
|
||||||
|
private organizationService: OrganizationService,
|
||||||
|
private reportService: RiskInsightsReportService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
// [FIXME] PM-25612 - Call Initialization in RiskInsightsComponent instead of child components
|
||||||
|
async initializeForOrganization(organizationId: OrganizationId) {
|
||||||
|
// Fetch current user
|
||||||
|
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
if (userId) {
|
||||||
|
this.userIdSubject.next(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// [FIXME] getOrganizationById is now deprecated - update when we can
|
||||||
|
// Fetch organization details
|
||||||
|
const org = await firstValueFrom(
|
||||||
|
this.organizationService.organizations$(userId).pipe(getOrganizationById(organizationId)),
|
||||||
|
);
|
||||||
|
if (org) {
|
||||||
|
this.organizationDetailsSubject.next({
|
||||||
|
organizationId: organizationId,
|
||||||
|
organizationName: org.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load critical applications for organization
|
||||||
|
await this.criticalAppsService.loadOrganizationContext(organizationId, userId);
|
||||||
|
|
||||||
|
// TODO: PM-25613
|
||||||
|
// // Load existing report
|
||||||
|
|
||||||
|
// this.fetchLastReport(organizationId, userId);
|
||||||
|
|
||||||
|
// // Setup new report generation
|
||||||
|
// this._runApplicationsReport().subscribe({
|
||||||
|
// next: (result) => {
|
||||||
|
// this.isRunningReportSubject.next(false);
|
||||||
|
// },
|
||||||
|
// error: () => {
|
||||||
|
// this.errorSubject.next("Failed to save report");
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches the applications report and updates the applicationsSubject.
|
* Fetches the applications report and updates the applicationsSubject.
|
||||||
@@ -72,6 +140,44 @@ export class RiskInsightsDataService {
|
|||||||
this.fetchApplicationsReport(organizationId, true);
|
this.fetchApplicationsReport(organizationId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------------------------- Enrichment methods -------------------------------
|
||||||
|
/**
|
||||||
|
* Takes the basic application health report details and enriches them to include
|
||||||
|
* critical app status and associated ciphers.
|
||||||
|
*
|
||||||
|
* @param applications The list of application health report details to enrich
|
||||||
|
* @returns The enriched application health report details with critical app status and ciphers
|
||||||
|
*/
|
||||||
|
enrichReportData$(
|
||||||
|
applications: ApplicationHealthReportDetail[],
|
||||||
|
): Observable<ApplicationHealthReportDetailEnriched[]> {
|
||||||
|
return of(applications).pipe(
|
||||||
|
withLatestFrom(this.organizationDetails$, this.criticalApps$),
|
||||||
|
switchMap(async ([apps, orgDetails, criticalApps]) => {
|
||||||
|
if (!orgDetails) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get ciphers for application
|
||||||
|
const cipherMap = await this.reportService.getApplicationCipherMap(
|
||||||
|
apps,
|
||||||
|
orgDetails.organizationId,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Find critical apps
|
||||||
|
const criticalApplicationNames = new Set(criticalApps.map((ca) => ca.uri));
|
||||||
|
|
||||||
|
// Return enriched application data
|
||||||
|
return apps.map((app) => ({
|
||||||
|
...app,
|
||||||
|
ciphers: cipherMap.get(app.applicationName) || [],
|
||||||
|
isMarkedAsCritical: criticalApplicationNames.has(app.applicationName),
|
||||||
|
})) as ApplicationHealthReportDetailEnriched[];
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------- Drawer management methods -------------------------------
|
||||||
isActiveDrawerType = (drawerType: DrawerType): boolean => {
|
isActiveDrawerType = (drawerType: DrawerType): boolean => {
|
||||||
return this.activeDrawerType === drawerType;
|
return this.activeDrawerType === drawerType;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -59,15 +59,6 @@ import { RiskInsightsApiService } from "./risk-insights-api.service";
|
|||||||
import { RiskInsightsEncryptionService } from "./risk-insights-encryption.service";
|
import { RiskInsightsEncryptionService } from "./risk-insights-encryption.service";
|
||||||
|
|
||||||
export class RiskInsightsReportService {
|
export class RiskInsightsReportService {
|
||||||
constructor(
|
|
||||||
private passwordStrengthService: PasswordStrengthServiceAbstraction,
|
|
||||||
private auditService: AuditService,
|
|
||||||
private cipherService: CipherService,
|
|
||||||
private memberCipherDetailsApiService: MemberCipherDetailsApiService,
|
|
||||||
private riskInsightsApiService: RiskInsightsApiService,
|
|
||||||
private riskInsightsEncryptionService: RiskInsightsEncryptionService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
private riskInsightsReportSubject = new BehaviorSubject<ApplicationHealthReportDetail[]>([]);
|
private riskInsightsReportSubject = new BehaviorSubject<ApplicationHealthReportDetail[]>([]);
|
||||||
riskInsightsReport$ = this.riskInsightsReportSubject.asObservable();
|
riskInsightsReport$ = this.riskInsightsReportSubject.asObservable();
|
||||||
|
|
||||||
@@ -84,6 +75,27 @@ export class RiskInsightsReportService {
|
|||||||
});
|
});
|
||||||
riskInsightsSummary$ = this.riskInsightsSummarySubject.asObservable();
|
riskInsightsSummary$ = this.riskInsightsSummarySubject.asObservable();
|
||||||
|
|
||||||
|
// [FIXME] CipherData
|
||||||
|
// Cipher data
|
||||||
|
// private _ciphersSubject = new BehaviorSubject<CipherView[] | null>(null);
|
||||||
|
// _ciphers$ = this._ciphersSubject.asObservable();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private passwordStrengthService: PasswordStrengthServiceAbstraction,
|
||||||
|
private auditService: AuditService,
|
||||||
|
private cipherService: CipherService,
|
||||||
|
private memberCipherDetailsApiService: MemberCipherDetailsApiService,
|
||||||
|
private riskInsightsApiService: RiskInsightsApiService,
|
||||||
|
private riskInsightsEncryptionService: RiskInsightsEncryptionService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
// [FIXME] CipherData
|
||||||
|
// async loadCiphersForOrganization(organizationId: OrganizationId): Promise<void> {
|
||||||
|
// await this.cipherService.getAllFromApiForOrganization(organizationId).then((ciphers) => {
|
||||||
|
// this._ciphersSubject.next(ciphers);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Report data from raw cipher health data.
|
* Report data from raw cipher health data.
|
||||||
* Can be used in the Raw Data diagnostic tab (just exclude the members in the view)
|
* Can be used in the Raw Data diagnostic tab (just exclude the members in the view)
|
||||||
@@ -559,6 +571,31 @@ export class RiskInsightsReportService {
|
|||||||
return applicationMap;
|
return applicationMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param applications The list of application health report details to map ciphers to
|
||||||
|
* @param organizationId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async getApplicationCipherMap(
|
||||||
|
applications: ApplicationHealthReportDetail[],
|
||||||
|
organizationId: OrganizationId,
|
||||||
|
): Promise<Map<string, CipherView[]>> {
|
||||||
|
// [FIXME] CipherData
|
||||||
|
// This call is made multiple times. We can optimize this
|
||||||
|
// by loading the ciphers once via a load method to avoid multiple API calls
|
||||||
|
// for the same organization
|
||||||
|
const allCiphers = await this.cipherService.getAllFromApiForOrganization(organizationId);
|
||||||
|
const cipherMap = new Map<string, CipherView[]>();
|
||||||
|
|
||||||
|
applications.forEach((app) => {
|
||||||
|
const filteredCiphers = allCiphers.filter((c) => app.cipherIds.includes(c.id));
|
||||||
|
cipherMap.set(app.applicationName, filteredCiphers);
|
||||||
|
});
|
||||||
|
return cipherMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------- Aggregation methods ---------------------------
|
||||||
/**
|
/**
|
||||||
* Loop through the flattened cipher to uri data. If the item exists it's values need to be updated with the new item.
|
* Loop through the flattened cipher to uri data. If the item exists it's values need to be updated with the new item.
|
||||||
* If the item is new, create and add the object with the flattened details
|
* If the item is new, create and add the object with the flattened details
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import {
|
|||||||
import { RiskInsightsEncryptionService } from "@bitwarden/bit-common/dirt/reports/risk-insights/services/risk-insights-encryption.service";
|
import { RiskInsightsEncryptionService } from "@bitwarden/bit-common/dirt/reports/risk-insights/services/risk-insights-encryption.service";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||||
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
|
import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { KeyGenerationService } from "@bitwarden/common/key-management/crypto";
|
import { KeyGenerationService } from "@bitwarden/common/key-management/crypto";
|
||||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||||
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength/password-strength.service.abstraction";
|
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength/password-strength.service.abstraction";
|
||||||
@@ -36,10 +38,15 @@ import { RiskInsightsComponent } from "./risk-insights.component";
|
|||||||
MemberCipherDetailsApiService,
|
MemberCipherDetailsApiService,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
safeProvider({
|
||||||
provide: RiskInsightsDataService,
|
provide: RiskInsightsDataService,
|
||||||
deps: [RiskInsightsReportService],
|
deps: [
|
||||||
},
|
AccountServiceAbstraction,
|
||||||
|
CriticalAppsService,
|
||||||
|
OrganizationService,
|
||||||
|
RiskInsightsReportService,
|
||||||
|
],
|
||||||
|
}),
|
||||||
{
|
{
|
||||||
provide: RiskInsightsEncryptionService,
|
provide: RiskInsightsEncryptionService,
|
||||||
useClass: RiskInsightsEncryptionService,
|
useClass: RiskInsightsEncryptionService,
|
||||||
|
|||||||
@@ -66,40 +66,42 @@ export class CriticalApplicationsComponent implements OnInit {
|
|||||||
"organizationId",
|
"organizationId",
|
||||||
) as OrganizationId;
|
) as OrganizationId;
|
||||||
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||||
this.criticalAppsService.setOrganizationId(this.organizationId as OrganizationId, userId);
|
this.criticalAppsService.loadOrganizationContext(this.organizationId as OrganizationId, userId);
|
||||||
// this.criticalAppsService.setOrganizationId(this.organizationId as OrganizationId);
|
|
||||||
combineLatest([
|
if (this.organizationId) {
|
||||||
this.dataService.applications$,
|
combineLatest([
|
||||||
this.criticalAppsService.getAppsListForOrg(this.organizationId as OrganizationId),
|
this.dataService.applications$,
|
||||||
])
|
this.criticalAppsService.getAppsListForOrg(this.organizationId as OrganizationId),
|
||||||
.pipe(
|
])
|
||||||
takeUntilDestroyed(this.destroyRef),
|
.pipe(
|
||||||
map(([applications, criticalApps]) => {
|
takeUntilDestroyed(this.destroyRef),
|
||||||
const criticalUrls = criticalApps.map((ca) => ca.uri);
|
map(([applications, criticalApps]) => {
|
||||||
const data = applications?.map((app) => ({
|
const criticalUrls = criticalApps.map((ca) => ca.uri);
|
||||||
...app,
|
const data = applications?.map((app) => ({
|
||||||
isMarkedAsCritical: criticalUrls.includes(app.applicationName),
|
...app,
|
||||||
})) as LEGACY_ApplicationHealthReportDetailWithCriticalFlag[];
|
isMarkedAsCritical: criticalUrls.includes(app.applicationName),
|
||||||
return data?.filter((app) => app.isMarkedAsCritical);
|
})) as LEGACY_ApplicationHealthReportDetailWithCriticalFlag[];
|
||||||
}),
|
return data?.filter((app) => app.isMarkedAsCritical);
|
||||||
switchMap(async (data) => {
|
}),
|
||||||
if (data) {
|
switchMap(async (data) => {
|
||||||
const dataWithCiphers = await this.reportService.identifyCiphers(
|
if (data) {
|
||||||
data,
|
const dataWithCiphers = await this.reportService.identifyCiphers(
|
||||||
this.organizationId,
|
data,
|
||||||
);
|
this.organizationId,
|
||||||
return dataWithCiphers;
|
);
|
||||||
|
return dataWithCiphers;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.subscribe((applications) => {
|
||||||
|
if (applications) {
|
||||||
|
this.dataSource.data = applications;
|
||||||
|
this.applicationSummary = this.reportService.generateApplicationsSummary(applications);
|
||||||
|
this.enableRequestPasswordChange = this.applicationSummary.totalAtRiskMemberCount > 0;
|
||||||
}
|
}
|
||||||
return null;
|
});
|
||||||
}),
|
}
|
||||||
)
|
|
||||||
.subscribe((applications) => {
|
|
||||||
if (applications) {
|
|
||||||
this.dataSource.data = applications;
|
|
||||||
this.applicationSummary = this.reportService.generateApplicationsSummary(applications);
|
|
||||||
this.enableRequestPasswordChange = this.applicationSummary.totalAtRiskMemberCount > 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
goToAllAppsTab = async () => {
|
goToAllAppsTab = async () => {
|
||||||
|
|||||||
@@ -127,7 +127,10 @@ export class RiskInsightsComponent implements OnInit {
|
|||||||
this.appsCount = applications.length;
|
this.appsCount = applications.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.criticalAppsService.setOrganizationId(this.organizationId as OrganizationId, userId);
|
this.criticalAppsService.loadOrganizationContext(
|
||||||
|
this.organizationId as OrganizationId,
|
||||||
|
userId,
|
||||||
|
);
|
||||||
this.criticalApps$ = this.criticalAppsService.getAppsListForOrg(
|
this.criticalApps$ = this.criticalAppsService.getAppsListForOrg(
|
||||||
this.organizationId as OrganizationId,
|
this.organizationId as OrganizationId,
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user