mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 05:13:29 +00:00
[PM-25606] move data mapping methods to helper file (#16400)
* move data mapping methods to helper file * revert to original naming
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
export * from "./risk-insights-data-mappers";
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
|
|
||||||
|
import {
|
||||||
|
MemberDetailsFlat,
|
||||||
|
CipherHealthReportDetail,
|
||||||
|
CipherHealthReportUriDetail,
|
||||||
|
ApplicationHealthReportDetail,
|
||||||
|
} from "../models/password-health";
|
||||||
|
import { MemberCipherDetailsResponse } from "../response/member-cipher-details.response";
|
||||||
|
|
||||||
|
export function flattenMemberDetails(
|
||||||
|
memberCiphers: MemberCipherDetailsResponse[],
|
||||||
|
): MemberDetailsFlat[] {
|
||||||
|
return memberCiphers.flatMap((member) =>
|
||||||
|
member.cipherIds.map((cipherId) => ({
|
||||||
|
userGuid: member.userGuid,
|
||||||
|
userName: member.userName,
|
||||||
|
email: member.email,
|
||||||
|
cipherId,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Trim the cipher uris down to get the password health application.
|
||||||
|
* The uri should only exist once after being trimmed. No duplication.
|
||||||
|
* Example:
|
||||||
|
* - Untrimmed Uris: https://gmail.com, gmail.com/login
|
||||||
|
* - Both would trim to gmail.com
|
||||||
|
* - The cipher trimmed uri list should only return on instance in the list
|
||||||
|
* @param cipher
|
||||||
|
* @returns distinct list of trimmed cipher uris
|
||||||
|
*/
|
||||||
|
export function getTrimmedCipherUris(cipher: CipherView): string[] {
|
||||||
|
const uris = cipher.login?.uris ?? [];
|
||||||
|
|
||||||
|
const uniqueDomains = new Set<string>();
|
||||||
|
|
||||||
|
uris.forEach((u: { uri: string }) => {
|
||||||
|
const domain = Utils.getDomain(u.uri) ?? u.uri;
|
||||||
|
uniqueDomains.add(domain);
|
||||||
|
});
|
||||||
|
return Array.from(uniqueDomains);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a deduplicated array of members by email
|
||||||
|
export function getUniqueMembers(orgMembers: MemberDetailsFlat[]): MemberDetailsFlat[] {
|
||||||
|
const existingEmails = new Set<string>();
|
||||||
|
return orgMembers.filter((member) => {
|
||||||
|
if (existingEmails.has(member.email)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
existingEmails.add(member.email);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a flattened member details object
|
||||||
|
* @param userGuid User GUID
|
||||||
|
* @param userName User name
|
||||||
|
* @param email User email
|
||||||
|
* @param cipherId Cipher ID
|
||||||
|
* @returns Flattened member details
|
||||||
|
*/
|
||||||
|
export function getMemberDetailsFlat(
|
||||||
|
userGuid: string,
|
||||||
|
userName: string,
|
||||||
|
email: string,
|
||||||
|
cipherId: string,
|
||||||
|
): MemberDetailsFlat {
|
||||||
|
return {
|
||||||
|
userGuid: userGuid,
|
||||||
|
userName: userName,
|
||||||
|
email: email,
|
||||||
|
cipherId: cipherId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a flattened cipher details object for URI reporting
|
||||||
|
* @param detail Cipher health report detail
|
||||||
|
* @param uri Trimmed URI
|
||||||
|
* @returns Flattened cipher health details to URI
|
||||||
|
*/
|
||||||
|
export function getFlattenedCipherDetails(
|
||||||
|
detail: CipherHealthReportDetail,
|
||||||
|
uri: string,
|
||||||
|
): CipherHealthReportUriDetail {
|
||||||
|
return {
|
||||||
|
cipherId: detail.id,
|
||||||
|
reusedPasswordCount: detail.reusedPasswordCount,
|
||||||
|
weakPasswordDetail: detail.weakPasswordDetail,
|
||||||
|
exposedPasswordDetail: detail.exposedPasswordDetail,
|
||||||
|
cipherMembers: detail.cipherMembers,
|
||||||
|
trimmedUri: uri,
|
||||||
|
cipher: detail as CipherView,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the new application health report detail object with the details from the cipher health report uri detail object
|
||||||
|
* update or create the at risk values if the item is at risk.
|
||||||
|
* @param newUriDetail New cipher uri detail
|
||||||
|
* @param isAtRisk If the cipher has a weak, exposed, or reused password it is at risk
|
||||||
|
* @param existingUriDetail The previously processed Uri item
|
||||||
|
* @returns The new or updated application health report detail
|
||||||
|
*/
|
||||||
|
export function getApplicationReportDetail(
|
||||||
|
newUriDetail: CipherHealthReportUriDetail,
|
||||||
|
isAtRisk: boolean,
|
||||||
|
existingUriDetail?: ApplicationHealthReportDetail,
|
||||||
|
): ApplicationHealthReportDetail {
|
||||||
|
const reportDetail = {
|
||||||
|
applicationName: existingUriDetail
|
||||||
|
? existingUriDetail.applicationName
|
||||||
|
: newUriDetail.trimmedUri,
|
||||||
|
passwordCount: existingUriDetail ? existingUriDetail.passwordCount + 1 : 1,
|
||||||
|
memberDetails: existingUriDetail
|
||||||
|
? getUniqueMembers(existingUriDetail.memberDetails.concat(newUriDetail.cipherMembers))
|
||||||
|
: newUriDetail.cipherMembers,
|
||||||
|
atRiskMemberDetails: existingUriDetail ? existingUriDetail.atRiskMemberDetails : [],
|
||||||
|
atRiskPasswordCount: existingUriDetail ? existingUriDetail.atRiskPasswordCount : 0,
|
||||||
|
atRiskCipherIds: existingUriDetail ? existingUriDetail.atRiskCipherIds : [],
|
||||||
|
atRiskMemberCount: existingUriDetail ? existingUriDetail.atRiskMemberDetails.length : 0,
|
||||||
|
cipherIds: existingUriDetail
|
||||||
|
? existingUriDetail.cipherIds.concat(newUriDetail.cipherId)
|
||||||
|
: [newUriDetail.cipherId],
|
||||||
|
} as ApplicationHealthReportDetail;
|
||||||
|
|
||||||
|
if (isAtRisk) {
|
||||||
|
reportDetail.atRiskPasswordCount = reportDetail.atRiskPasswordCount + 1;
|
||||||
|
reportDetail.atRiskCipherIds.push(newUriDetail.cipherId);
|
||||||
|
|
||||||
|
reportDetail.atRiskMemberDetails = getUniqueMembers(
|
||||||
|
reportDetail.atRiskMemberDetails.concat(newUriDetail.cipherMembers),
|
||||||
|
);
|
||||||
|
reportDetail.atRiskMemberCount = reportDetail.atRiskMemberDetails.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
reportDetail.memberCount = reportDetail.memberDetails.length;
|
||||||
|
|
||||||
|
return reportDetail;
|
||||||
|
}
|
||||||
@@ -22,6 +22,13 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi
|
|||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
|
|
||||||
|
import {
|
||||||
|
getApplicationReportDetail,
|
||||||
|
getFlattenedCipherDetails,
|
||||||
|
getMemberDetailsFlat,
|
||||||
|
getTrimmedCipherUris,
|
||||||
|
getUniqueMembers,
|
||||||
|
} from "../helpers/risk-insights-data-mappers";
|
||||||
import {
|
import {
|
||||||
ApplicationHealthReportDetail,
|
ApplicationHealthReportDetail,
|
||||||
ApplicationHealthReportSummary,
|
ApplicationHealthReportSummary,
|
||||||
@@ -78,9 +85,7 @@ export class RiskInsightsReportService {
|
|||||||
const results$ = zip(allCiphers$, memberCiphers$).pipe(
|
const results$ = zip(allCiphers$, memberCiphers$).pipe(
|
||||||
map(([allCiphers, memberCiphers]) => {
|
map(([allCiphers, memberCiphers]) => {
|
||||||
const details: MemberDetailsFlat[] = memberCiphers.flatMap((dtl) =>
|
const details: MemberDetailsFlat[] = memberCiphers.flatMap((dtl) =>
|
||||||
dtl.cipherIds.map((c) =>
|
dtl.cipherIds.map((c) => getMemberDetailsFlat(dtl.userGuid, dtl.userName, dtl.email, c)),
|
||||||
this.getMemberDetailsFlat(dtl.userGuid, dtl.userName, dtl.email, c),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
return [allCiphers, details] as const;
|
return [allCiphers, details] as const;
|
||||||
}),
|
}),
|
||||||
@@ -185,10 +190,10 @@ export class RiskInsightsReportService {
|
|||||||
reports: ApplicationHealthReportDetail[],
|
reports: ApplicationHealthReportDetail[],
|
||||||
): ApplicationHealthReportSummary {
|
): ApplicationHealthReportSummary {
|
||||||
const totalMembers = reports.flatMap((x) => x.memberDetails);
|
const totalMembers = reports.flatMap((x) => x.memberDetails);
|
||||||
const uniqueMembers = this.getUniqueMembers(totalMembers);
|
const uniqueMembers = getUniqueMembers(totalMembers);
|
||||||
|
|
||||||
const atRiskMembers = reports.flatMap((x) => x.atRiskMemberDetails);
|
const atRiskMembers = reports.flatMap((x) => x.atRiskMemberDetails);
|
||||||
const uniqueAtRiskMembers = this.getUniqueMembers(atRiskMembers);
|
const uniqueAtRiskMembers = getUniqueMembers(atRiskMembers);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
totalMemberCount: uniqueMembers.length,
|
totalMemberCount: uniqueMembers.length,
|
||||||
@@ -317,7 +322,7 @@ export class RiskInsightsReportService {
|
|||||||
const cipherMembers = memberDetails.filter((x) => x.cipherId === cipher.id);
|
const cipherMembers = memberDetails.filter((x) => x.cipherId === cipher.id);
|
||||||
|
|
||||||
// Trim uris to host name and create the cipher health report
|
// Trim uris to host name and create the cipher health report
|
||||||
const cipherTrimmedUris = this.getTrimmedCipherUris(cipher);
|
const cipherTrimmedUris = getTrimmedCipherUris(cipher);
|
||||||
const cipherHealth = {
|
const cipherHealth = {
|
||||||
...cipher,
|
...cipher,
|
||||||
weakPasswordDetail: weakPassword,
|
weakPasswordDetail: weakPassword,
|
||||||
@@ -346,7 +351,7 @@ export class RiskInsightsReportService {
|
|||||||
cipherHealthReport: CipherHealthReportDetail[],
|
cipherHealthReport: CipherHealthReportDetail[],
|
||||||
): CipherHealthReportUriDetail[] {
|
): CipherHealthReportUriDetail[] {
|
||||||
return cipherHealthReport.flatMap((rpt) =>
|
return cipherHealthReport.flatMap((rpt) =>
|
||||||
rpt.trimmedUris.map((u) => this.getFlattenedCipherDetails(rpt, u)),
|
rpt.trimmedUris.map((u) => getFlattenedCipherDetails(rpt, u)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,9 +374,9 @@ export class RiskInsightsReportService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
appReports.push(this.getApplicationReportDetail(uri, atRisk));
|
appReports.push(getApplicationReportDetail(uri, atRisk));
|
||||||
} else {
|
} else {
|
||||||
appReports[index] = this.getApplicationReportDetail(uri, atRisk, appReports[index]);
|
appReports[index] = getApplicationReportDetail(uri, atRisk, appReports[index]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return appReports;
|
return appReports;
|
||||||
@@ -452,120 +457,6 @@ export class RiskInsightsReportService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create the new application health report detail object with the details from the cipher health report uri detail object
|
|
||||||
* update or create the at risk values if the item is at risk.
|
|
||||||
* @param newUriDetail New cipher uri detail
|
|
||||||
* @param isAtRisk If the cipher has a weak, exposed, or reused password it is at risk
|
|
||||||
* @param existingUriDetail The previously processed Uri item
|
|
||||||
* @returns The new or updated application health report detail
|
|
||||||
*/
|
|
||||||
private getApplicationReportDetail(
|
|
||||||
newUriDetail: CipherHealthReportUriDetail,
|
|
||||||
isAtRisk: boolean,
|
|
||||||
existingUriDetail?: ApplicationHealthReportDetail,
|
|
||||||
): ApplicationHealthReportDetail {
|
|
||||||
const reportDetail = {
|
|
||||||
applicationName: existingUriDetail
|
|
||||||
? existingUriDetail.applicationName
|
|
||||||
: newUriDetail.trimmedUri,
|
|
||||||
passwordCount: existingUriDetail ? existingUriDetail.passwordCount + 1 : 1,
|
|
||||||
memberDetails: existingUriDetail
|
|
||||||
? this.getUniqueMembers(existingUriDetail.memberDetails.concat(newUriDetail.cipherMembers))
|
|
||||||
: newUriDetail.cipherMembers,
|
|
||||||
atRiskMemberDetails: existingUriDetail ? existingUriDetail.atRiskMemberDetails : [],
|
|
||||||
atRiskPasswordCount: existingUriDetail ? existingUriDetail.atRiskPasswordCount : 0,
|
|
||||||
atRiskCipherIds: existingUriDetail ? existingUriDetail.atRiskCipherIds : [],
|
|
||||||
atRiskMemberCount: existingUriDetail ? existingUriDetail.atRiskMemberDetails.length : 0,
|
|
||||||
cipherIds: existingUriDetail
|
|
||||||
? existingUriDetail.cipherIds.concat(newUriDetail.cipherId)
|
|
||||||
: [newUriDetail.cipherId],
|
|
||||||
} as ApplicationHealthReportDetail;
|
|
||||||
|
|
||||||
if (isAtRisk) {
|
|
||||||
reportDetail.atRiskPasswordCount = reportDetail.atRiskPasswordCount + 1;
|
|
||||||
reportDetail.atRiskCipherIds.push(newUriDetail.cipherId);
|
|
||||||
|
|
||||||
reportDetail.atRiskMemberDetails = this.getUniqueMembers(
|
|
||||||
reportDetail.atRiskMemberDetails.concat(newUriDetail.cipherMembers),
|
|
||||||
);
|
|
||||||
reportDetail.atRiskMemberCount = reportDetail.atRiskMemberDetails.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
reportDetail.memberCount = reportDetail.memberDetails.length;
|
|
||||||
|
|
||||||
return reportDetail;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a distinct array of members from a combined list. Input list may contain
|
|
||||||
* duplicate members.
|
|
||||||
* @param orgMembers Input list of members
|
|
||||||
* @returns Distinct array of members
|
|
||||||
*/
|
|
||||||
private getUniqueMembers(orgMembers: MemberDetailsFlat[]): MemberDetailsFlat[] {
|
|
||||||
const existingEmails = new Set<string>();
|
|
||||||
const distinctUsers = orgMembers.filter((member) => {
|
|
||||||
if (existingEmails.has(member.email)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
existingEmails.add(member.email);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
return distinctUsers;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getFlattenedCipherDetails(
|
|
||||||
detail: CipherHealthReportDetail,
|
|
||||||
uri: string,
|
|
||||||
): CipherHealthReportUriDetail {
|
|
||||||
return {
|
|
||||||
cipherId: detail.id,
|
|
||||||
reusedPasswordCount: detail.reusedPasswordCount,
|
|
||||||
weakPasswordDetail: detail.weakPasswordDetail,
|
|
||||||
exposedPasswordDetail: detail.exposedPasswordDetail,
|
|
||||||
cipherMembers: detail.cipherMembers,
|
|
||||||
trimmedUri: uri,
|
|
||||||
cipher: detail as CipherView,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private getMemberDetailsFlat(
|
|
||||||
userGuid: string,
|
|
||||||
userName: string,
|
|
||||||
email: string,
|
|
||||||
cipherId: string,
|
|
||||||
): MemberDetailsFlat {
|
|
||||||
return {
|
|
||||||
userGuid: userGuid,
|
|
||||||
userName: userName,
|
|
||||||
email: email,
|
|
||||||
cipherId: cipherId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Trim the cipher uris down to get the password health application.
|
|
||||||
* The uri should only exist once after being trimmed. No duplication.
|
|
||||||
* Example:
|
|
||||||
* - Untrimmed Uris: https://gmail.com, gmail.com/login
|
|
||||||
* - Both would trim to gmail.com
|
|
||||||
* - The cipher trimmed uri list should only return on instance in the list
|
|
||||||
* @param cipher
|
|
||||||
* @returns distinct list of trimmed cipher uris
|
|
||||||
*/
|
|
||||||
private getTrimmedCipherUris(cipher: CipherView): string[] {
|
|
||||||
const cipherUris: string[] = [];
|
|
||||||
const uris = cipher.login?.uris ?? [];
|
|
||||||
uris.map((u: { uri: string }) => {
|
|
||||||
const uri = Utils.getDomain(u.uri) ?? u.uri;
|
|
||||||
if (!cipherUris.includes(uri)) {
|
|
||||||
cipherUris.push(uri);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return cipherUris;
|
|
||||||
}
|
|
||||||
|
|
||||||
private isUserNameNotEmpty(c: CipherView): boolean {
|
private isUserNameNotEmpty(c: CipherView): boolean {
|
||||||
return !Utils.isNullOrWhitespace(c.login.username);
|
return !Utils.isNullOrWhitespace(c.login.username);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user