mirror of
https://github.com/bitwarden/browser
synced 2026-02-12 06:23:38 +00:00
PM-20132 do not filter out members
This commit is contained in:
@@ -167,4 +167,19 @@ export enum DrawerType {
|
||||
OrgAtRiskApps = 3,
|
||||
}
|
||||
|
||||
export interface CipherHealthWithMemberDetails {
|
||||
ciphers: CipherHealthReportDetail[];
|
||||
members: MemberDetailsFlat[];
|
||||
}
|
||||
|
||||
export interface CipherHealthReportUriDetailWithMemberDetails {
|
||||
ciphers: CipherHealthReportUriDetail[];
|
||||
members: MemberDetailsFlat[];
|
||||
}
|
||||
|
||||
export interface HealthReportUriDetailWithMemberDetails {
|
||||
healthReport: ApplicationHealthReportDetail[];
|
||||
members: MemberDetailsFlat[];
|
||||
}
|
||||
|
||||
export type PasswordHealthReportApplicationId = Opaque<string, "PasswordHealthReportApplicationId">;
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
AtRiskApplicationDetail,
|
||||
AtRiskMemberDetail,
|
||||
DrawerType,
|
||||
HealthReportUriDetailWithMemberDetails,
|
||||
MemberDetailsFlat,
|
||||
} from "../models/password-health";
|
||||
|
||||
import { RiskInsightsReportService } from "./risk-insights-report.service";
|
||||
@@ -27,6 +29,9 @@ export class RiskInsightsDataService {
|
||||
private dataLastUpdatedSubject = new BehaviorSubject<Date | null>(null);
|
||||
dataLastUpdated$ = this.dataLastUpdatedSubject.asObservable();
|
||||
|
||||
private memberDetailsSubject = new BehaviorSubject<MemberDetailsFlat[]>([]);
|
||||
memberDetails$ = this.memberDetailsSubject.asObservable();
|
||||
|
||||
openDrawer = false;
|
||||
drawerInvokerId: string = "";
|
||||
activeDrawerType: DrawerType = DrawerType.None;
|
||||
@@ -56,8 +61,9 @@ export class RiskInsightsDataService {
|
||||
}),
|
||||
)
|
||||
.subscribe({
|
||||
next: (reports: ApplicationHealthReportDetail[]) => {
|
||||
this.applicationsSubject.next(reports);
|
||||
next: (reports: HealthReportUriDetailWithMemberDetails) => {
|
||||
this.applicationsSubject.next(reports.healthReport);
|
||||
this.memberDetailsSubject.next(reports.members);
|
||||
this.errorSubject.next(null);
|
||||
},
|
||||
error: () => {
|
||||
|
||||
@@ -43,9 +43,11 @@ describe("RiskInsightsReportService", () => {
|
||||
it("should generate the raw data report correctly", async () => {
|
||||
const result = await firstValueFrom(service.generateRawDataReport$("orgId"));
|
||||
|
||||
expect(result).toHaveLength(6);
|
||||
expect(result.ciphers).toHaveLength(6);
|
||||
|
||||
let testCaseResults = result.filter((x) => x.id === "cbea34a8-bde4-46ad-9d19-b05001228ab1");
|
||||
let testCaseResults = result.ciphers.filter(
|
||||
(x) => x.id === "cbea34a8-bde4-46ad-9d19-b05001228ab1",
|
||||
);
|
||||
expect(testCaseResults).toHaveLength(1);
|
||||
let testCase = testCaseResults[0];
|
||||
expect(testCase).toBeTruthy();
|
||||
@@ -55,7 +57,7 @@ describe("RiskInsightsReportService", () => {
|
||||
expect(testCase.exposedPasswordDetail).toBeTruthy();
|
||||
expect(testCase.reusedPasswordCount).toEqual(2);
|
||||
|
||||
testCaseResults = result.filter((x) => x.id === "cbea34a8-bde4-46ad-9d19-b05001227tt1");
|
||||
testCaseResults = result.ciphers.filter((x) => x.id === "cbea34a8-bde4-46ad-9d19-b05001227tt1");
|
||||
expect(testCaseResults).toHaveLength(1);
|
||||
testCase = testCaseResults[0];
|
||||
expect(testCase).toBeTruthy();
|
||||
@@ -69,14 +71,16 @@ describe("RiskInsightsReportService", () => {
|
||||
it("should generate the raw data + uri report correctly", async () => {
|
||||
const result = await firstValueFrom(service.generateRawDataUriReport$("orgId"));
|
||||
|
||||
expect(result).toHaveLength(11);
|
||||
expect(result.ciphers).toHaveLength(11);
|
||||
|
||||
// Two ciphers that have google.com as their uri. There should be 2 results
|
||||
const googleResults = result.filter((x) => x.trimmedUri === "google.com");
|
||||
const googleResults = result.ciphers.filter((x) => x.trimmedUri === "google.com");
|
||||
expect(googleResults).toHaveLength(2);
|
||||
|
||||
// There is an invalid uri and it should not be trimmed
|
||||
const invalidUriResults = result.filter((x) => x.trimmedUri === "this_is-not|a-valid-uri123@+");
|
||||
const invalidUriResults = result.ciphers.filter(
|
||||
(x) => x.trimmedUri === "this_is-not|a-valid-uri123@+",
|
||||
);
|
||||
expect(invalidUriResults).toHaveLength(1);
|
||||
|
||||
// Verify the details for one of the googles matches the password health info
|
||||
@@ -92,13 +96,13 @@ describe("RiskInsightsReportService", () => {
|
||||
it("should generate applications health report data correctly", async () => {
|
||||
const result = await firstValueFrom(service.generateApplicationsReport$("orgId"));
|
||||
|
||||
expect(result).toHaveLength(8);
|
||||
expect(result.healthReport).toHaveLength(8);
|
||||
|
||||
// Two ciphers have google.com associated with them. The first cipher
|
||||
// has 2 members and the second has 4. However, the 2 members in the first
|
||||
// cipher are also associated with the second. The total amount of members
|
||||
// should be 4 not 6
|
||||
const googleTestResults = result.filter((x) => x.applicationName === "google.com");
|
||||
const googleTestResults = result.healthReport.filter((x) => x.applicationName === "google.com");
|
||||
expect(googleTestResults).toHaveLength(1);
|
||||
const googleTest = googleTestResults[0];
|
||||
expect(googleTest.memberCount).toEqual(4);
|
||||
@@ -111,7 +115,9 @@ describe("RiskInsightsReportService", () => {
|
||||
expect(googleTest.atRiskPasswordCount).toEqual(2);
|
||||
|
||||
// There are 2 ciphers associated with 101domain.com
|
||||
const domain101TestResults = result.filter((x) => x.applicationName === "101domain.com");
|
||||
const domain101TestResults = result.healthReport.filter(
|
||||
(x) => x.applicationName === "101domain.com",
|
||||
);
|
||||
expect(domain101TestResults).toHaveLength(1);
|
||||
const domain101Test = domain101TestResults[0];
|
||||
expect(domain101Test.passwordCount).toEqual(2);
|
||||
@@ -132,7 +138,10 @@ describe("RiskInsightsReportService", () => {
|
||||
|
||||
it("should generate applications summary data correctly", async () => {
|
||||
const reportResult = await firstValueFrom(service.generateApplicationsReport$("orgId"));
|
||||
const reportSummary = service.generateApplicationsSummary(reportResult);
|
||||
const reportSummary = service.generateApplicationsSummary(
|
||||
reportResult.healthReport,
|
||||
reportResult.members,
|
||||
);
|
||||
|
||||
expect(reportSummary.totalMemberCount).toEqual(7);
|
||||
expect(reportSummary.totalAtRiskMemberCount).toEqual(6);
|
||||
|
||||
@@ -21,6 +21,9 @@ import {
|
||||
WeakPasswordDetail,
|
||||
WeakPasswordScore,
|
||||
ApplicationHealthReportDetailWithCriticalFlagAndCipher,
|
||||
CipherHealthWithMemberDetails,
|
||||
CipherHealthReportUriDetailWithMemberDetails,
|
||||
HealthReportUriDetailWithMemberDetails,
|
||||
} from "../models/password-health";
|
||||
|
||||
import { MemberCipherDetailsApiService } from "./member-cipher-details-api.service";
|
||||
@@ -40,7 +43,7 @@ export class RiskInsightsReportService {
|
||||
* @param organizationId
|
||||
* @returns Cipher health report data with members and trimmed uris
|
||||
*/
|
||||
generateRawDataReport$(organizationId: string): Observable<CipherHealthReportDetail[]> {
|
||||
generateRawDataReport$(organizationId: string): Observable<CipherHealthWithMemberDetails> {
|
||||
const allCiphers$ = from(this.cipherService.getAllFromApiForOrganization(organizationId));
|
||||
const memberCiphers$ = from(
|
||||
this.memberCipherDetailsApiService.getMemberCipherDetails(organizationId),
|
||||
@@ -55,7 +58,14 @@ export class RiskInsightsReportService {
|
||||
);
|
||||
return [allCiphers, details] as const;
|
||||
}),
|
||||
concatMap(([ciphers, flattenedDetails]) => this.getCipherDetails(ciphers, flattenedDetails)),
|
||||
concatMap(async ([ciphers, flattenedDetails]) => {
|
||||
const cipherDetails = await this.getCipherDetails(ciphers, flattenedDetails);
|
||||
const memberDetails = this.getUniqueMembers(flattenedDetails);
|
||||
return {
|
||||
ciphers: cipherDetails,
|
||||
members: memberDetails,
|
||||
};
|
||||
}),
|
||||
first(),
|
||||
);
|
||||
|
||||
@@ -68,10 +78,18 @@ export class RiskInsightsReportService {
|
||||
* @param organizationId Id of the organization
|
||||
* @returns Cipher health report data flattened to the uris
|
||||
*/
|
||||
generateRawDataUriReport$(organizationId: string): Observable<CipherHealthReportUriDetail[]> {
|
||||
generateRawDataUriReport$(
|
||||
organizationId: string,
|
||||
): Observable<CipherHealthReportUriDetailWithMemberDetails> {
|
||||
const cipherHealthDetails$ = this.generateRawDataReport$(organizationId);
|
||||
const results$ = cipherHealthDetails$.pipe(
|
||||
map((healthDetails) => this.getCipherUriDetails(healthDetails)),
|
||||
map((healthDetails) => {
|
||||
const cipherHealthUriDetail = this.getCipherUriDetails(healthDetails.ciphers);
|
||||
return {
|
||||
ciphers: cipherHealthUriDetail,
|
||||
members: healthDetails.members,
|
||||
};
|
||||
}),
|
||||
first(),
|
||||
);
|
||||
|
||||
@@ -84,10 +102,18 @@ export class RiskInsightsReportService {
|
||||
* @param organizationId Id of the organization
|
||||
* @returns The all applications health report data
|
||||
*/
|
||||
generateApplicationsReport$(organizationId: string): Observable<ApplicationHealthReportDetail[]> {
|
||||
generateApplicationsReport$(
|
||||
organizationId: string,
|
||||
): Observable<HealthReportUriDetailWithMemberDetails> {
|
||||
const cipherHealthUriReport$ = this.generateRawDataUriReport$(organizationId);
|
||||
const results$ = cipherHealthUriReport$.pipe(
|
||||
map((uriDetails) => this.getApplicationHealthReport(uriDetails)),
|
||||
map((uriDetails) => {
|
||||
const appHealthReportDetail = this.getApplicationHealthReport(uriDetails.ciphers);
|
||||
return {
|
||||
healthReport: appHealthReportDetail,
|
||||
members: uriDetails.members,
|
||||
};
|
||||
}),
|
||||
first(),
|
||||
);
|
||||
|
||||
@@ -150,9 +176,11 @@ export class RiskInsightsReportService {
|
||||
*/
|
||||
generateApplicationsSummary(
|
||||
reports: ApplicationHealthReportDetail[],
|
||||
memberDetails: MemberDetailsFlat[],
|
||||
): ApplicationHealthReportSummary {
|
||||
const totalMembers = reports.flatMap((x) => x.memberDetails);
|
||||
const uniqueMembers = this.getUniqueMembers(totalMembers);
|
||||
// const totalMembers = reports.flatMap((x) => x.memberDetails);
|
||||
// const uniqueMembers = this.getUniqueMembers(totalMembers);
|
||||
const uniqueMembers = this.getUniqueMembers(memberDetails);
|
||||
|
||||
const atRiskMembers = reports.flatMap((x) => x.atRiskMemberDetails);
|
||||
const uniqueAtRiskMembers = this.getUniqueMembers(atRiskMembers);
|
||||
|
||||
@@ -86,12 +86,13 @@ export class AllApplicationsComponent implements OnInit {
|
||||
|
||||
combineLatest([
|
||||
this.dataService.applications$,
|
||||
this.dataService.memberDetails$,
|
||||
this.criticalAppsService.getAppsListForOrg(organizationId),
|
||||
organization$,
|
||||
])
|
||||
.pipe(
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
map(([applications, criticalApps, organization]) => {
|
||||
map(([applications, memberDetails, criticalApps, organization]) => {
|
||||
if (applications && applications.length === 0 && criticalApps && criticalApps) {
|
||||
const criticalUrls = criticalApps.map((ca) => ca.uri);
|
||||
const data = applications?.map((app) => ({
|
||||
@@ -101,9 +102,9 @@ export class AllApplicationsComponent implements OnInit {
|
||||
return { data, organization };
|
||||
}
|
||||
|
||||
return { data: applications, organization };
|
||||
return { data: applications, organization, memberDetails };
|
||||
}),
|
||||
switchMap(async ({ data, organization }) => {
|
||||
switchMap(async ({ data, organization, memberDetails }) => {
|
||||
if (data && organization) {
|
||||
const dataWithCiphers = await this.reportService.identifyCiphers(
|
||||
data,
|
||||
@@ -113,16 +114,20 @@ export class AllApplicationsComponent implements OnInit {
|
||||
return {
|
||||
data: dataWithCiphers,
|
||||
organization,
|
||||
memberDetails,
|
||||
};
|
||||
}
|
||||
|
||||
return { data: [], organization };
|
||||
return { data: [], organization, memberDetails: [] };
|
||||
}),
|
||||
)
|
||||
.subscribe(({ data, organization }) => {
|
||||
.subscribe(({ data, organization, memberDetails }) => {
|
||||
if (data) {
|
||||
this.dataSource.data = data;
|
||||
this.applicationSummary = this.reportService.generateApplicationsSummary(data);
|
||||
this.applicationSummary = this.reportService.generateApplicationsSummary(
|
||||
data,
|
||||
memberDetails,
|
||||
);
|
||||
}
|
||||
if (organization) {
|
||||
this.organization = organization;
|
||||
|
||||
@@ -75,33 +75,38 @@ export class CriticalApplicationsComponent implements OnInit {
|
||||
|
||||
combineLatest([
|
||||
this.dataService.applications$,
|
||||
this.dataService.memberDetails$,
|
||||
this.criticalAppsService.getAppsListForOrg(this.organizationId),
|
||||
])
|
||||
.pipe(
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
map(([applications, criticalApps]) => {
|
||||
map(([applications, memberDetails, criticalApps]) => {
|
||||
const criticalUrls = criticalApps.map((ca) => ca.uri);
|
||||
const data = applications?.map((app) => ({
|
||||
...app,
|
||||
isMarkedAsCritical: criticalUrls.includes(app.applicationName),
|
||||
})) as ApplicationHealthReportDetailWithCriticalFlag[];
|
||||
return data?.filter((app) => app.isMarkedAsCritical);
|
||||
const appsMarkedAsCritical = data?.filter((app) => app.isMarkedAsCritical);
|
||||
return { data: appsMarkedAsCritical, memberDetails };
|
||||
}),
|
||||
switchMap(async (data) => {
|
||||
switchMap(async ({ data, memberDetails }) => {
|
||||
if (data) {
|
||||
const dataWithCiphers = await this.reportService.identifyCiphers(
|
||||
data,
|
||||
this.organizationId,
|
||||
);
|
||||
return dataWithCiphers;
|
||||
return { applications: dataWithCiphers, memberDetails };
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
)
|
||||
.subscribe((applications) => {
|
||||
.subscribe(({ applications, memberDetails }) => {
|
||||
if (applications) {
|
||||
this.dataSource.data = applications;
|
||||
this.applicationSummary = this.reportService.generateApplicationsSummary(applications);
|
||||
this.applicationSummary = this.reportService.generateApplicationsSummary(
|
||||
applications,
|
||||
memberDetails,
|
||||
);
|
||||
this.enableRequestPasswordChange = this.applicationSummary.totalAtRiskMemberCount > 0;
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user