From ffbd0cf4fb397835b5f1a66a8d8cd545a6e898da Mon Sep 17 00:00:00 2001 From: Tom Date: Mon, 9 Dec 2024 11:51:19 -0500 Subject: [PATCH] Test cases update --- .../risk-insights/models/password-health.ts | 1 - .../risk-insights/services/ciphers.mock.ts | 78 +++++++++++------ .../member-cipher-details-api.service.spec.ts | 6 ++ .../services/risk-insights-aggregate.spec.ts | 0 .../risk-insights-report.service.spec.ts | 84 ++++++++++++++++++- .../services/risk-insights-report.service.ts | 26 ++---- 6 files changed, 150 insertions(+), 45 deletions(-) delete mode 100644 bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-aggregate.spec.ts diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts index 73ff0d0be38..c5676787eb0 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts @@ -22,7 +22,6 @@ export type ApplicationHealthReportDetail = { passwordCount: number; atRiskPasswordCount: number; memberCount: number; - atRiskMemberCount: number; memberDetails: MemberDetailsFlat[]; atRiskMemberDetails: MemberDetailsFlat[]; diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/ciphers.mock.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/ciphers.mock.ts index e7693e46a32..ca5cdc35b8a 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/ciphers.mock.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/ciphers.mock.ts @@ -1,10 +1,18 @@ +import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view"; + +const createLoginUriView = (uri: string): LoginUriView => { + const view = new LoginUriView(); + view.uri = uri; + return view; +}; + export const mockCiphers: any[] = [ { initializerKey: 1, id: "cbea34a8-bde4-46ad-9d19-b05001228ab1", organizationId: null, folderId: null, - name: "Cannot Be Edited", + name: "Weak Password Cipher", notes: null, isDeleted: false, type: 1, @@ -14,10 +22,11 @@ export const mockCiphers: any[] = [ password: "123", hasUris: true, uris: [ - { uri: "www.google.com" }, - { uri: "accounts.google.com" }, - { uri: "https://www.google.com" }, - { uri: "https://www.google.com/login" }, + createLoginUriView("101domain.com"), + createLoginUriView("www.google.com"), + createLoginUriView("accounts.google.com"), + createLoginUriView("https://www.google.com"), + createLoginUriView("https://www.google.com/login"), ], }, edit: false, @@ -31,23 +40,18 @@ export const mockCiphers: any[] = [ }, { initializerKey: 1, - id: "cbea34a8-bde4-46ad-9d19-b05001228ab2", + id: "cbea34a8-bde4-46ad-9d19-b05001228cd3", organizationId: null, folderId: null, - name: "Can Be Edited id ending 2", + name: "Strong Password Cipher", notes: null, - isDeleted: false, type: 1, favorite: false, organizationUseTotp: false, login: { - password: "123", + password: "Password!123", hasUris: true, - uris: [ - { - uri: "http://nothing.com", - }, - ], + uris: [createLoginUriView("http://example.com")], }, edit: true, viewPassword: true, @@ -60,22 +64,18 @@ export const mockCiphers: any[] = [ }, { initializerKey: 1, - id: "cbea34a8-bde4-46ad-9d19-b05001228cd3", + id: "cbea34a8-bde4-46ad-9d19-b05001228ab2", organizationId: null, folderId: null, - name: "Can Be Edited id ending 3", + name: "Strong password Cipher", notes: null, type: 1, favorite: false, organizationUseTotp: false, login: { - password: "123", hasUris: true, - uris: [ - { - uri: "http://example.com", - }, - ], + password: "Password!1234", + uris: [createLoginUriView("101domain.com")], }, edit: true, viewPassword: true, @@ -91,14 +91,15 @@ export const mockCiphers: any[] = [ id: "cbea34a8-bde4-46ad-9d19-b05001228xy4", organizationId: null, folderId: null, - name: "Can Be Edited id ending 4", + name: "Strong password Cipher", notes: null, type: 1, favorite: false, organizationUseTotp: false, login: { hasUris: true, - uris: [{ uri: "101domain.com" }], + password: "Password!123", + uris: [createLoginUriView("example.com")], }, edit: true, viewPassword: true, @@ -114,14 +115,39 @@ export const mockCiphers: any[] = [ id: "cbea34a8-bde4-46ad-9d19-b05001227nm5", organizationId: null, folderId: null, - name: "Can Be Edited id ending 5", + name: "Exposed password Cipher", notes: null, type: 1, favorite: false, organizationUseTotp: false, login: { hasUris: true, - uris: [{ uri: "123formbuilder.com" }], + password: "123", + uris: [createLoginUriView("123formbuilder.com"), createLoginUriView("www.google.com")], + }, + edit: true, + viewPassword: true, + collectionIds: [], + revisionDate: "2023-08-03T17:40:59.793Z", + creationDate: "2023-08-03T17:40:59.793Z", + deletedDate: null, + reprompt: 0, + localData: null, + }, + { + initializerKey: 1, + id: "cbea34a8-bde4-46ad-9d19-b05001227tt1", + organizationId: null, + folderId: null, + name: "Secure Co Login", + notes: null, + type: 1, + favorite: false, + organizationUseTotp: false, + login: { + hasUris: true, + password: "4gRyhhOX2Og2p0", + uris: [createLoginUriView("SecureCo.com")], }, edit: true, viewPassword: true, diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/member-cipher-details-api.service.spec.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/member-cipher-details-api.service.spec.ts index 872a4cdff55..69fa56db735 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/member-cipher-details-api.service.spec.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/member-cipher-details-api.service.spec.ts @@ -69,6 +69,12 @@ export const mockMemberCipherDetails: any = [ "cbea34a8-bde4-46ad-9d19-b05001228xy4", ], }, + { + userName: "Mister Secure", + email: "mister.secure@secureco.com", + usesKeyConnector: true, + cipherIds: ["cbea34a8-bde4-46ad-9d19-b05001227tt1"], + }, ]; describe("Member Cipher Details API Service", () => { diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-aggregate.spec.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-aggregate.spec.ts deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.spec.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.spec.ts index b322e316652..c70f7112a11 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.spec.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.spec.ts @@ -52,6 +52,88 @@ describe("RiskInsightsReportService", () => { it("should generate the raw data report correctly", async () => { const result = await service.generateRawDataReport("orgId"); - expect(result).toHaveLength(4); + expect(result).toHaveLength(6); + + let testCase = result.filter((x) => x.id === "cbea34a8-bde4-46ad-9d19-b05001228ab1")[0]; + expect(testCase).toBeTruthy(); + expect(testCase.cipherMembers).toHaveLength(2); + expect(testCase.trimmedUris).toHaveLength(3); + expect(testCase.weakPasswordDetail).toBeTruthy(); + expect(testCase.exposedPasswordDetail).toBeTruthy(); + expect(testCase.reusedPasswordCount).toEqual(2); + + testCase = result.filter((x) => x.id === "cbea34a8-bde4-46ad-9d19-b05001227tt1")[0]; + expect(testCase).toBeTruthy(); + expect(testCase.cipherMembers).toHaveLength(1); + expect(testCase.trimmedUris).toHaveLength(1); + expect(testCase.weakPasswordDetail).toBeFalsy(); + expect(testCase.exposedPasswordDetail).toBeFalsy(); + expect(testCase.reusedPasswordCount).toEqual(1); + }); + + it("should generate the raw data + uri report correctly", async () => { + const result = await service.generateRawDataUriReport("orgId"); + + expect(result).toHaveLength(9); + + // Two ciphers that have google.com as their uri. There should be 2 results + const googleResults = result.filter((x) => x.trimmedUri === "google.com"); + expect(googleResults).toHaveLength(2); + + // Verify the details for one of the googles matches the password health info + // expected + const firstGoogle = googleResults.filter( + (x) => x.cipherId === "cbea34a8-bde4-46ad-9d19-b05001228ab1" && x.trimmedUri === "google.com", + )[0]; + expect(firstGoogle.weakPasswordDetail).toBeTruthy(); + expect(firstGoogle.exposedPasswordDetail).toBeTruthy(); + expect(firstGoogle.reusedPasswordCount).toEqual(2); + }); + + it("should generate applications health report data correctly", async () => { + const result = await service.generateApplicationsReport("orgId"); + + expect(result).toHaveLength(6); + + // 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 googleTest = result.filter((x) => x.applicationName === "google.com")[0]; + expect(googleTest.memberCount).toEqual(4); + + // Both ciphers have at risk passwords + expect(googleTest.passwordCount).toEqual(2); + + // All members are at risk since both ciphers are at risk + expect(googleTest.atRiskMemberDetails).toHaveLength(4); + expect(googleTest.atRiskPasswordCount).toEqual(2); + + // There are 2 ciphers associated with 101domain.com + const doman101Test = result.filter((x) => x.applicationName === "101domain.com")[0]; + expect(doman101Test.passwordCount).toEqual(2); + + // The first cipher is at risk. The second cipher is not at risk + expect(doman101Test.atRiskPasswordCount).toEqual(1); + + // The first cipher has 2 members. The second cipher the second + // cipher has 4. One of the members in the first cipher is associated + // with the second. So there should be 5 members total. + expect(doman101Test.memberCount).toEqual(5); + + // The first cipher is at risk. The total at risk members is 2 and + // at risk password count is 1. + expect(doman101Test.atRiskMemberDetails).toHaveLength(2); + expect(doman101Test.atRiskPasswordCount).toEqual(1); + }); + + it("should generate applications summary data correctly", async () => { + const reportResult = await service.generateApplicationsReport("orgId"); + const reportSummary = service.generateApplicationsSummary(reportResult); + + expect(reportSummary.totalMemberCount).toEqual(7); + expect(reportSummary.totalAtRiskMemberCount).toEqual(6); + expect(reportSummary.totalApplicationCount).toEqual(6); + expect(reportSummary.totalAtRiskApplicationCount).toEqual(5); }); }); diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.ts index 076e0b38fac..fb92ceef0f7 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.ts @@ -22,8 +22,6 @@ import { MemberCipherDetailsApiService } from "./member-cipher-details-api.servi @Injectable() export class RiskInsightsReportService { - passwordUseMap = new Map(); - constructor( private passwordStrengthService: PasswordStrengthServiceAbstraction, private auditService: AuditService, @@ -109,13 +107,18 @@ export class RiskInsightsReportService { memberDetails: MemberDetailsFlat[], ): Promise { const cipherHealthReports: CipherHealthReportDetail[] = []; - + const passwordUseMap = new Map(); for (const cipher of ciphers) { if (this.validateCipher(cipher)) { const weakPassword = this.findWeakPassword(cipher); // Looping over all ciphers needs to happen first to determine reused passwords over all ciphers. // Store in the set and evaluate later - this.findReusedPassword(cipher); + passwordUseMap.has(cipher.login.password) + ? passwordUseMap.set( + cipher.login.password, + (passwordUseMap.get(cipher.login.password) || 0) + 1, + ) + : passwordUseMap.set(cipher.login.password, 1); const exposedPassword = await this.findExposedPassword(cipher); // Get the cipher members @@ -137,7 +140,7 @@ export class RiskInsightsReportService { // loop for reused passwords cipherHealthReports.forEach((detail) => { - detail.reusedPasswordCount = this.passwordUseMap.get(detail.id) ?? 0; + detail.reusedPasswordCount = passwordUseMap.get(detail.login.password) ?? 0; }); return cipherHealthReports; } @@ -169,7 +172,7 @@ export class RiskInsightsReportService { const index = appReports.findIndex((item) => item.applicationName === uri.trimmedUri); let atRisk: boolean = false; - if (uri.exposedPasswordDetail || uri.weakPasswordDetail || uri.reusedPasswordCount > 0) { + if (uri.exposedPasswordDetail || uri.weakPasswordDetail || uri.reusedPasswordCount > 1) { atRisk = true; } @@ -182,17 +185,6 @@ export class RiskInsightsReportService { return appReports; } - private findReusedPassword(cipher: CipherView) { - if (this.passwordUseMap.has(cipher.login.password)) { - this.passwordUseMap.set( - cipher.login.password, - (this.passwordUseMap.get(cipher.login.password) || 0) + 1, - ); - } else { - this.passwordUseMap.set(cipher.login.password, 1); - } - } - private async findExposedPassword(cipher: CipherView): Promise { const exposedCount = await this.auditService.passwordLeaked(cipher.login.password); if (exposedCount > 0) {