Skip to content

Commit

Permalink
[PM-16104] [PM-15929] Org at risk members click on the card (#12732)
Browse files Browse the repository at this point in the history
* Org at risk members click on the card

* Fixing at risk member counts

* At risk member text modification

* Changing ok button to close
  • Loading branch information
ttalty authored Jan 13, 2025
1 parent 8062475 commit 52b6bfe
Show file tree
Hide file tree
Showing 9 changed files with 185 additions and 2 deletions.
21 changes: 21 additions & 0 deletions apps/web/src/locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,27 @@
"atRiskMembers": {
"message": "At-risk members"
},
"atRiskMembersWithCount": {
"message": "At-risk members ($COUNT$)",
"placeholders": {
"count": {
"content": "$1",
"example": "3"
}
}
},
"atRiskMembersDescription": {
"message": "These members are logging into applications with weak, exposed, or reused passwords."
},
"atRiskMembersDescriptionWithApp": {
"message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.",
"placeholders": {
"appname": {
"content": "$1",
"example": "Salesforce"
}
}
},
"totalMembers": {
"message": "Total members"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,13 @@ export type MemberDetailsFlat = {
email: string;
cipherId: string;
};

/**
* Member email with the number of at risk passwords
* At risk member detail that contains the email
* and the count of at risk ciphers
*/
export type AtRiskMemberDetail = {
email: string;
atRiskPasswordCount: number;
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import {
ApplicationHealthReportDetail,
ApplicationHealthReportSummary,
AtRiskMemberDetail,
CipherHealthReportDetail,
CipherHealthReportUriDetail,
ExposedPasswordDetail,
Expand Down Expand Up @@ -89,6 +90,30 @@ export class RiskInsightsReportService {
return results$;
}

/**
* Generates a list of members with at-risk passwords along with the number of at-risk passwords.
*/
generateAtRiskMemberList(
cipherHealthReportDetails: ApplicationHealthReportDetail[],
): AtRiskMemberDetail[] {
const memberRiskMap = new Map<string, number>();

cipherHealthReportDetails.forEach((app) => {
app.atRiskMemberDetails.forEach((member) => {
if (memberRiskMap.has(member.email)) {
memberRiskMap.set(member.email, memberRiskMap.get(member.email) + 1);
} else {
memberRiskMap.set(member.email, 1);
}
});
});

return Array.from(memberRiskMap.entries()).map(([email, atRiskPasswordCount]) => ({
email,
atRiskPasswordCount,
}));
}

/**
* Gets the summary from the application health report. Returns total members and applications as well
* as the total at risk members and at risk applications
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ <h2 class="tw-font-semibold mt-4">
<h2 class="tw-mb-6" bitTypography="h2">{{ "allApplications" | i18n }}</h2>
<div class="tw-flex tw-gap-6">
<tools-card
class="tw-flex-1"
class="tw-flex-1 tw-cursor-pointer"
[title]="'atRiskMembers' | i18n"
[value]="applicationSummary.totalAtRiskMemberCount"
[maxValue]="applicationSummary.totalMemberCount"
(click)="showOrgAtRiskMembers()"
>
</tools-card>
<tools-card
Expand Down Expand Up @@ -82,7 +83,7 @@ <h2 class="tw-mb-6" bitTypography="h2">{{ "allApplications" | i18n }}</h2>
(change)="onCheckboxChange(r.id, $event)"
/>
</td>
<td bitCell>
<td class="tw-cursor-pointer" (click)="showAppAtRiskMembers(r.applicationName)" bitCell>
<span>{{ r.applicationName }}</span>
</td>
<td bitCell>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import {
DialogService,
Icons,
NoItemsModule,
SearchModule,
Expand All @@ -30,6 +31,8 @@ import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.mod
import { SharedModule } from "@bitwarden/web-vault/app/shared";
import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module";

import { openAppAtRiskMembersDialog } from "./app-at-risk-members-dialog.component";
import { OrgAtRiskMembersDialogComponent } from "./org-at-risk-members-dialog.component";
import { ApplicationsLoadingComponent } from "./risk-insights-loading.component";

@Component({
Expand Down Expand Up @@ -99,6 +102,7 @@ export class AllApplicationsComponent implements OnInit, OnDestroy {
protected dataService: RiskInsightsDataService,
protected organizationService: OrganizationService,
protected reportService: RiskInsightsReportService,
protected dialogService: DialogService,
) {
this.searchControl.valueChanges
.pipe(debounceTime(200), takeUntilDestroyed())
Expand Down Expand Up @@ -135,6 +139,21 @@ export class AllApplicationsComponent implements OnInit, OnDestroy {
return item.applicationName;
}

showAppAtRiskMembers = async (applicationName: string) => {
openAppAtRiskMembersDialog(this.dialogService, {
members:
this.dataSource.data.find((app) => app.applicationName === applicationName)
?.atRiskMemberDetails ?? [],
applicationName,
});
};

showOrgAtRiskMembers = async () => {
this.dialogService.open(OrgAtRiskMembersDialogComponent, {
data: this.reportService.generateAtRiskMemberList(this.dataSource.data),
});
};

onCheckboxChange(id: number, event: Event) {
const isChecked = (event.target as HTMLInputElement).checked;
if (isChecked) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<bit-dialog>
<span bitDialogTitle>{{ applicationName }}</span>
<ng-container bitDialogContent>
<div class="tw-flex tw-flex-col tw-gap-2">
<span bitDialogTitle>{{ "atRiskMembersWithCount" | i18n: members.length }} </span>
<span class="tw-text-muted">{{
"atRiskMembersDescriptionWithApp" | i18n: applicationName
}}</span>
<div class="tw-mt-1">
<ng-container *ngFor="let member of members">
<div>{{ member.email }}</div>
</ng-container>
</div>
</div>
</ng-container>
<ng-container bitDialogFooter>
<button bitButton bitDialogClose buttonType="secondary" type="button">
{{ "close" | i18n }}
</button>
</ng-container>
</bit-dialog>
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { DIALOG_DATA } from "@angular/cdk/dialog";
import { CommonModule } from "@angular/common";
import { Component, Inject } from "@angular/core";

import { JslibModule } from "@bitwarden/angular/jslib.module";
import { MemberDetailsFlat } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health";
import { ButtonModule, DialogModule, DialogService } from "@bitwarden/components";

type AppAtRiskMembersDialogParams = {
members: MemberDetailsFlat[];
applicationName: string;
};

export const openAppAtRiskMembersDialog = (
dialogService: DialogService,
dialogConfig: AppAtRiskMembersDialogParams,
) =>
dialogService.open<boolean, AppAtRiskMembersDialogParams>(AppAtRiskMembersDialogComponent, {
data: dialogConfig,
});

@Component({
standalone: true,
templateUrl: "./app-at-risk-members-dialog.component.html",
imports: [ButtonModule, CommonModule, JslibModule, DialogModule],
})
export class AppAtRiskMembersDialogComponent {
protected members: MemberDetailsFlat[];
protected applicationName: string;

constructor(@Inject(DIALOG_DATA) private params: AppAtRiskMembersDialogParams) {
this.members = params.members;
this.applicationName = params.applicationName;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<bit-dialog>
<ng-container bitDialogTitle>
<span bitDialogTitle>{{ "atRiskMembersWithCount" | i18n: atRiskMembers.length }} </span>
</ng-container>
<ng-container bitDialogContent>
<div class="tw-flex tw-flex-col tw-gap-2">
<span bitTypography="body2" class="tw-text-muted">{{
"atRiskMembersDescription" | i18n
}}</span>
<div class="tw-flex tw-justify-between tw-mt-2 tw-text-muted">
<div bitTypography="body2" class="tw-font-bold">{{ "email" | i18n }}</div>
<div bitTypography="body2" class="tw-font-bold">{{ "atRiskPasswords" | i18n }}</div>
</div>
<ng-container *ngFor="let member of atRiskMembers">
<div class="tw-flex tw-justify-between tw-mt-2">
<div>{{ member.email }}</div>
<div>{{ member.atRiskPasswordCount }}</div>
</div>
</ng-container>
</div>
</ng-container>
<ng-container bitDialogFooter>
<button bitButton bitDialogClose buttonType="secondary" type="button">
{{ "close" | i18n }}
</button>
</ng-container>
</bit-dialog>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { DIALOG_DATA } from "@angular/cdk/dialog";
import { CommonModule } from "@angular/common";
import { Component, Inject } from "@angular/core";

import { JslibModule } from "@bitwarden/angular/jslib.module";
import { AtRiskMemberDetail } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health";
import { ButtonModule, DialogModule, DialogService, TypographyModule } from "@bitwarden/components";

export const openOrgAtRiskMembersDialog = (
dialogService: DialogService,
dialogConfig: AtRiskMemberDetail[],
) =>
dialogService.open<boolean, AtRiskMemberDetail[]>(OrgAtRiskMembersDialogComponent, {
data: dialogConfig,
});

@Component({
standalone: true,
templateUrl: "./org-at-risk-members-dialog.component.html",
imports: [ButtonModule, CommonModule, DialogModule, JslibModule, TypographyModule],
})
export class OrgAtRiskMembersDialogComponent {
constructor(@Inject(DIALOG_DATA) protected atRiskMembers: AtRiskMemberDetail[]) {}
}

0 comments on commit 52b6bfe

Please sign in to comment.