-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[PM-13452] - add password health raw data component (#11519)
* add raw data component * fix tests * simplify logic. fix tests * revert change to default config service * remove cipher report dep. fix tests. * revert changes to mock data and specs * remove mock data * use orgId param * fix test
- Loading branch information
1 parent
1f330b0
commit d70d2cb
Showing
6 changed files
with
408 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
57 changes: 57 additions & 0 deletions
57
apps/web/src/app/tools/access-intelligence/password-health.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
<bit-container> | ||
<p>{{ "passwordsReportDesc" | i18n }}</p> | ||
<div *ngIf="loading"> | ||
<i | ||
class="bwi bwi-spinner bwi-spin tw-text-muted" | ||
title="{{ 'loading' | i18n }}" | ||
aria-hidden="true" | ||
></i> | ||
<span class="tw-sr-only">{{ "loading" | i18n }}</span> | ||
</div> | ||
<div class="tw-mt-4" *ngIf="!loading"> | ||
<bit-table [dataSource]="dataSource"> | ||
<ng-container header> | ||
<tr bitRow> | ||
<th bitCell></th> | ||
<th bitCell bitSortable="name">{{ "name" | i18n }}</th> | ||
<th bitCell class="tw-text-right">{{ "weakness" | i18n }}</th> | ||
<th bitCell class="tw-text-right">{{ "timesReused" | i18n }}</th> | ||
<th bitCell class="tw-text-right">{{ "timesExposed" | i18n }}</th> | ||
</tr> | ||
</ng-container> | ||
<ng-template body let-rows$> | ||
<tr bitRow *ngFor="let r of rows$ | async"> | ||
<td bitCell> | ||
<app-vault-icon [cipher]="r"></app-vault-icon> | ||
</td> | ||
<td bitCell> | ||
<ng-container> | ||
<span>{{ r.name }}</span> | ||
</ng-container> | ||
<br /> | ||
<small>{{ r.subTitle }}</small> | ||
</td> | ||
<td bitCell class="tw-text-right"> | ||
<span | ||
bitBadge | ||
*ngIf="passwordStrengthMap.has(r.id)" | ||
[variant]="passwordStrengthMap.get(r.id)[1]" | ||
> | ||
{{ passwordStrengthMap.get(r.id)[0] | i18n }} | ||
</span> | ||
</td> | ||
<td bitCell class="tw-text-right"> | ||
<span bitBadge *ngIf="passwordUseMap.has(r.login.password)" variant="warning"> | ||
{{ "reusedXTimes" | i18n: passwordUseMap.get(r.login.password) }} | ||
</span> | ||
</td> | ||
<td bitCell class="tw-text-right"> | ||
<span bitBadge *ngIf="exposedPasswordMap.has(r.id)" variant="warning"> | ||
{{ "exposedXTimes" | i18n: exposedPasswordMap.get(r.id) }} | ||
</span> | ||
</td> | ||
</tr> | ||
</ng-template> | ||
</bit-table> | ||
</div> | ||
</bit-container> |
114 changes: 114 additions & 0 deletions
114
apps/web/src/app/tools/access-intelligence/password-health.component.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
import { ComponentFixture, TestBed } from "@angular/core/testing"; | ||
import { ActivatedRoute, convertToParamMap } from "@angular/router"; | ||
import { MockProxy, mock } from "jest-mock-extended"; | ||
import { of } from "rxjs"; | ||
|
||
import { AuditService } from "@bitwarden/common/abstractions/audit.service"; | ||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; | ||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; | ||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; | ||
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; | ||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; | ||
import { TableModule } from "@bitwarden/components"; | ||
import { TableBodyDirective } from "@bitwarden/components/src/table/table.component"; | ||
|
||
import { LooseComponentsModule } from "../../shared"; | ||
import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module"; | ||
// eslint-disable-next-line no-restricted-imports | ||
import { cipherData } from "../reports/pages/reports-ciphers.mock"; | ||
|
||
import { PasswordHealthComponent } from "./password-health.component"; | ||
|
||
describe("PasswordHealthComponent", () => { | ||
let component: PasswordHealthComponent; | ||
let fixture: ComponentFixture<PasswordHealthComponent>; | ||
let passwordStrengthService: MockProxy<PasswordStrengthServiceAbstraction>; | ||
let organizationService: MockProxy<OrganizationService>; | ||
let cipherServiceMock: MockProxy<CipherService>; | ||
let auditServiceMock: MockProxy<AuditService>; | ||
const activeRouteParams = convertToParamMap({ organizationId: "orgId" }); | ||
|
||
beforeEach(async () => { | ||
passwordStrengthService = mock<PasswordStrengthServiceAbstraction>(); | ||
auditServiceMock = mock<AuditService>(); | ||
organizationService = mock<OrganizationService>({ | ||
get: jest.fn().mockResolvedValue({ id: "orgId" } as Organization), | ||
}); | ||
cipherServiceMock = mock<CipherService>({ | ||
getAllFromApiForOrganization: jest.fn().mockResolvedValue(cipherData), | ||
}); | ||
|
||
await TestBed.configureTestingModule({ | ||
imports: [PasswordHealthComponent, PipesModule, TableModule, LooseComponentsModule], | ||
declarations: [TableBodyDirective], | ||
providers: [ | ||
{ provide: CipherService, useValue: cipherServiceMock }, | ||
{ provide: PasswordStrengthServiceAbstraction, useValue: passwordStrengthService }, | ||
{ provide: OrganizationService, useValue: organizationService }, | ||
{ provide: I18nService, useValue: mock<I18nService>() }, | ||
{ provide: AuditService, useValue: auditServiceMock }, | ||
{ | ||
provide: ActivatedRoute, | ||
useValue: { | ||
paramMap: of(activeRouteParams), | ||
url: of([]), | ||
}, | ||
}, | ||
], | ||
}).compileComponents(); | ||
}); | ||
|
||
beforeEach(() => { | ||
fixture = TestBed.createComponent(PasswordHealthComponent); | ||
component = fixture.componentInstance; | ||
|
||
fixture.detectChanges(); | ||
}); | ||
|
||
it("should initialize component", () => { | ||
expect(component).toBeTruthy(); | ||
}); | ||
|
||
it("should populate reportCiphers with ciphers that have password issues", async () => { | ||
passwordStrengthService.getPasswordStrength.mockReturnValue({ score: 1 } as any); | ||
|
||
auditServiceMock.passwordLeaked.mockResolvedValue(5); | ||
|
||
await component.setCiphers(); | ||
|
||
const cipherIds = component.reportCiphers.map((c) => c.id); | ||
|
||
expect(cipherIds).toEqual([ | ||
"cbea34a8-bde4-46ad-9d19-b05001228ab1", | ||
"cbea34a8-bde4-46ad-9d19-b05001228ab2", | ||
"cbea34a8-bde4-46ad-9d19-b05001228cd3", | ||
]); | ||
expect(component.reportCiphers.length).toEqual(3); | ||
}); | ||
|
||
it("should correctly populate passwordStrengthMap", async () => { | ||
passwordStrengthService.getPasswordStrength.mockImplementation((password) => { | ||
let score = 0; | ||
if (password === "123") { | ||
score = 1; | ||
} else { | ||
score = 4; | ||
} | ||
return { score } as any; | ||
}); | ||
|
||
auditServiceMock.passwordLeaked.mockResolvedValue(0); | ||
|
||
await component.setCiphers(); | ||
|
||
expect(component.passwordStrengthMap.size).toBeGreaterThan(0); | ||
expect(component.passwordStrengthMap.get("cbea34a8-bde4-46ad-9d19-b05001228ab2")).toEqual([ | ||
"veryWeak", | ||
"danger", | ||
]); | ||
expect(component.passwordStrengthMap.get("cbea34a8-bde4-46ad-9d19-b05001228cd3")).toEqual([ | ||
"veryWeak", | ||
"danger", | ||
]); | ||
}); | ||
}); |
Oops, something went wrong.